Passagens com fade e 32 px, cena anterior termina em opacidade 0, sem visibility nem clip-path. 3D do hero como fundo ambiente, sob demanda e desligado em toque, aparelho fraco, saveData e movimento reduzido. Lenis fora do toque e salto direto em âncoras distantes. Vitrine só de clientes, até 3 capítulos, fixa só no desktop e carrossel no celular; vídeo só do projeto em cena com preload metadata. Rótulos mono a partir de 12 px e limão só no contato.
140 lines
4.2 KiB
TypeScript
140 lines
4.2 KiB
TypeScript
import { act, render } from "@testing-library/react";
|
|
import FilmGrain from "./FilmGrain";
|
|
|
|
/**
|
|
* Testes de caminhos-base de FilmGrain (complemento de CinematicEffects.test.tsx): cada condição do retângulo
|
|
* de uma tela de projeto (altura, fim acima do topo da janela, início abaixo da janela), várias telas, quadros
|
|
* agrupados e a limpeza ao desmontar.
|
|
*/
|
|
|
|
const INNER_HEIGHT = 800;
|
|
|
|
function addScreen(
|
|
className: string,
|
|
rect: { top: number; bottom: number; height: number },
|
|
) {
|
|
const element = document.createElement("div");
|
|
element.className = className;
|
|
element.getBoundingClientRect = () => rect as DOMRect;
|
|
document.body.append(element);
|
|
return element;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
Object.defineProperty(window, "innerHeight", {
|
|
configurable: true,
|
|
value: INNER_HEIGHT,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
jest.restoreAllMocks();
|
|
document.body.innerHTML = "";
|
|
});
|
|
|
|
function renderGrain() {
|
|
const view = render(<FilmGrain />);
|
|
return { ...view, grain: view.container.firstElementChild as HTMLElement };
|
|
}
|
|
|
|
describe("FilmGrain: is a project screen in view?", () => {
|
|
it("path: a visible screen -> the grain steps aside", () => {
|
|
addScreen("ds-phone", { top: 100, bottom: 700, height: 600 });
|
|
|
|
const { grain } = renderGrain();
|
|
|
|
expect(grain).toHaveAttribute("data-over-projects");
|
|
});
|
|
|
|
it.each([
|
|
["has no height (hidden by CSS)", { top: 100, bottom: 100, height: 0 }],
|
|
["ended exactly at the top edge", { top: -600, bottom: 0, height: 600 }],
|
|
["ended above the window", { top: -900, bottom: -300, height: 600 }],
|
|
[
|
|
"starts exactly at the bottom edge",
|
|
{ top: INNER_HEIGHT, bottom: 1400, height: 600 },
|
|
],
|
|
["starts below the window", { top: 2000, bottom: 2600, height: 600 }],
|
|
])("path: a screen that %s -> does not count", (_label, rect) => {
|
|
addScreen("ds-bframe", rect);
|
|
|
|
const { grain } = renderGrain();
|
|
|
|
expect(grain).not.toHaveAttribute("data-over-projects");
|
|
});
|
|
|
|
it("path: just inside the window by one pixel at either edge -> counts", () => {
|
|
addScreen("ds-frame", { top: -599, bottom: 1, height: 600 });
|
|
const top = renderGrain();
|
|
expect(top.grain).toHaveAttribute("data-over-projects");
|
|
top.unmount();
|
|
document.body.innerHTML = "";
|
|
|
|
addScreen("ds-frame", { top: INNER_HEIGHT - 1, bottom: 1400, height: 600 });
|
|
const bottom = renderGrain();
|
|
expect(bottom.grain).toHaveAttribute("data-over-projects");
|
|
});
|
|
|
|
it("path: one screen off the window and another in view -> counts (any is enough)", () => {
|
|
addScreen("ds-reel", { top: 5000, bottom: 5600, height: 600 });
|
|
addScreen("ds-phone", { top: 100, bottom: 700, height: 600 });
|
|
|
|
const { grain } = renderGrain();
|
|
|
|
expect(grain).toHaveAttribute("data-over-projects");
|
|
});
|
|
|
|
it("path: no project screen on the page -> the grain stays", () => {
|
|
const { grain } = renderGrain();
|
|
|
|
expect(grain).not.toHaveAttribute("data-over-projects");
|
|
});
|
|
});
|
|
|
|
describe("FilmGrain: frames and cleanup", () => {
|
|
it("path: many scroll events inside one frame -> the page is measured once", () => {
|
|
const screenEl = addScreen("ds-reel", {
|
|
top: 5000,
|
|
bottom: 5600,
|
|
height: 600,
|
|
});
|
|
const measure = jest.spyOn(screenEl, "getBoundingClientRect");
|
|
renderGrain();
|
|
measure.mockClear();
|
|
|
|
act(() => {
|
|
window.dispatchEvent(new Event("scroll"));
|
|
window.dispatchEvent(new Event("scroll"));
|
|
window.dispatchEvent(new Event("resize"));
|
|
jest.advanceTimersByTime(40);
|
|
});
|
|
|
|
expect(measure).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("path: unmount -> listeners removed and no measurement afterwards", () => {
|
|
const screenEl = addScreen("ds-reel", {
|
|
top: 5000,
|
|
bottom: 5600,
|
|
height: 600,
|
|
});
|
|
const measure = jest.spyOn(screenEl, "getBoundingClientRect");
|
|
const { unmount } = renderGrain();
|
|
act(() => {
|
|
window.dispatchEvent(new Event("scroll"));
|
|
});
|
|
|
|
unmount();
|
|
measure.mockClear();
|
|
act(() => {
|
|
window.dispatchEvent(new Event("scroll"));
|
|
window.dispatchEvent(new Event("resize"));
|
|
jest.advanceTimersByTime(100);
|
|
});
|
|
|
|
expect(measure).not.toHaveBeenCalled();
|
|
});
|
|
});
|