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(); 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(); }); });