Files
portifolio-evandrodevbr/app/components/cinematic/hero-dive.test.tsx
T
Evandro Fonseca Junior 204ebd5fa5 fix(navegacao): foco em âncora de outra página, vitrine acessível e 3D mais leve
Foco cai no destino ao chegar de outra página; âncoras ignoram ?tipo=; movimento reduzido salta em todos os cliques; cronômetro da janela ampliada; degradê no topo de Projetos e legendas do mergulho com intervalos testados; vitrine acima da barra de cookies e com 2 paradas por projeto; reel-list.css no global; 3D montado em tarefas curtas, limitado a ~30 qps e estático com WebGL por software; pôsteres da vitrine de 330 para 158 KB; SC Plus regravado; scripts sem funções acima de complexidade 10.
2026-10-14 19:14:48 -03:00

111 lines
3.7 KiB
TypeScript

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("./HeroFieldScene", () => ({
__esModule: true,
default: () => null,
prepareField: () => {},
}));
const win = window as unknown as Record<string, unknown>;
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(
<section className="hero" style={style}>
<HeroField />
</section>,
);
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<number>((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);
});
});