import { readFileSync } from "node:fs"; import { join } from "node:path"; import { HERO_TAIL_SCREENS } from "./HeroField"; /* * O mergulho do hero é uma conta que vive em dois lugares: o CSS (altura do hero e margem de Projetos) e o * HeroField (de onde o progresso --hero-p sai). Se um mudar sem o outro, o progresso acaba antes ou depois de * Projetos começar a subir e volta o tempo morto (ou a tela vazia) no fim do mergulho. */ const css = readFileSync(join(__dirname, "../../globals.css"), "utf8"); function diveBlock() { const start = css.indexOf(".hero[data-dive] {"); expect(start).toBeGreaterThan(-1); return css.slice(start, start + 6000); } describe("hero dive (globals.css <-> HeroField)", () => { it("the hero is HERO_TAIL_SCREENS screens taller than the dive", () => { const height = /height: calc\((\d+)svh \+ var\(--hero-dive\)\)/.exec( diveBlock(), ); expect(height).not.toBeNull(); expect(Number(height?.[1]) / 100).toBe(HERO_TAIL_SCREENS); }); it("Projects rises over the stage by the tail minus the pinned stage (one screen)", () => { const margin = /margin-top: -(\d+)svh/.exec(diveBlock()); expect(margin).not.toBeNull(); expect(Number(margin?.[1]) / 100).toBeCloseTo(HERO_TAIL_SCREENS - 1); }); it("the last caption stays on stage until Projects covers it (its end is past the end of the dive)", () => { const last = /li:nth-child\(3\) \{\s*--from: ([\d.]+);\s*--to: ([\d.]+);/.exec( diveBlock(), ); expect(last).not.toBeNull(); expect(Number(last?.[2])).toBeGreaterThan(1); }); it("each caption starts before the previous one is gone far enough that the gap between them is short", () => { const ranges = [ ...diveBlock().matchAll( /li:nth-child\((\d)\) \{\s*--from: ([\d.]+);\s*--to: ([\d.]+);/g, ), ].map((match) => [Number(match[2]), Number(match[3])]); expect(ranges).toHaveLength(3); for (let i = 1; i < ranges.length; i++) { // sem texto entre uma legenda e a seguinte: no máximo 0,1 de progresso (~100 px de rolagem) expect(ranges[i][0] - ranges[i - 1][1]).toBeLessThanOrEqual(0.1); } }); });