import { act, render } from "@testing-library/react"; import type { CSSProperties } from "react"; import HeroField, { DIVE_QUERY } from "./HeroField"; /* * A altura do mergulho do hero vive num lugar só: `--hero-tail` (globals.css), que o CSS usa na altura do hero e na * margem de Projetos, e que o HeroField lê para saber onde o progresso (--hero-p) acaba: quando Projetos começa a * subir sobre o palco, `--hero-tail` telas antes do fim do hero. Estes testes conferem o comportamento (o progresso * segue a variável), não o texto do CSS. */ jest.mock("./BlocosScene", () => ({ __esModule: true, default: () => null, prepareBlocos: () => Promise.resolve(), })); const win = window as unknown as Record; const original = { matchMedia: window.matchMedia, webgl2: win.WebGL2RenderingContext, getContext: HTMLCanvasElement.prototype.getContext, innerHeight: Object.getOwnPropertyDescriptor(window, "innerHeight"), }; const SCREEN = 800; beforeEach(() => { window.matchMedia = jest.fn().mockImplementation((query: string) => ({ matches: query === DIVE_QUERY, media: query, addEventListener() {}, removeEventListener() {}, })); Object.defineProperty(navigator, "hardwareConcurrency", { configurable: true, value: 8, }); win.WebGL2RenderingContext = function WebGL2RenderingContext() {}; HTMLCanvasElement.prototype.getContext = (() => ({ getExtension: () => null, })) as unknown as typeof HTMLCanvasElement.prototype.getContext; Object.defineProperty(window, "innerHeight", { configurable: true, value: SCREEN, }); }); afterEach(() => { window.matchMedia = original.matchMedia; win.WebGL2RenderingContext = original.webgl2; HTMLCanvasElement.prototype.getContext = original.getContext; if (original.innerHeight) Object.defineProperty(window, "innerHeight", original.innerHeight); }); /** O hero com a altura (px) que o CSS lhe dá, rolado `scrolled` px; devolve o progresso do mergulho. */ function progressAt({ tail, height, scrolled, }: { tail?: number; height: number; scrolled: number; }) { const style = tail === undefined ? undefined : ({ "--hero-tail": tail } as CSSProperties); const { container, unmount } = render(
, ); const hero = container.querySelector(".hero") as HTMLElement; hero.getBoundingClientRect = () => ({ top: -scrolled, height, bottom: height - scrolled }) as DOMRect; act(() => { window.dispatchEvent(new Event("resize")); }); // o rAF do jsdom corre em ~16 ms return new Promise((resolve) => { setTimeout(() => { const progress = Number(hero.style.getPropertyValue("--hero-p")); unmount(); resolve(progress); }, 60); }); } describe("hero dive: --hero-tail is the one source of its height", () => { it("ends the dive where Projects starts to rise: --hero-tail screens before the end of the hero", async () => { // hero de 2800 px com 2 telas de cauda: o mergulho tem 2800 - 2 * 800 = 1200 px expect( await progressAt({ tail: 2, height: 2800, scrolled: 600 }), ).toBeCloseTo(0.5); expect(await progressAt({ tail: 2, height: 2800, scrolled: 1200 })).toBe(1); }); it("follows the variable: the same hero with a shorter tail has a longer dive", async () => { // 2800 - 1,5 * 800 = 1600 px de mergulho expect( await progressAt({ tail: 1.5, height: 2800, scrolled: 800 }), ).toBeCloseTo(0.5); }); it("treats a hero without --hero-tail as stage only (the dive is the rest of the height)", async () => { // 2000 - 1 * 800 = 1200 px de mergulho expect(await progressAt({ height: 2000, scrolled: 600 })).toBeCloseTo(0.5); }); });