import { act, render, screen } from "@testing-library/react"; import { DrawLine } from "./animations/DrawLine"; import { LazyRevealImage } from "./animations/LazyRevealImage"; import { Reveal } from "./Reveal"; import { Section } from "./Section"; /** * Entradas por rolagem sem biblioteca de animação: o componente só marca data-shown (ou data-loaded) quando o * IntersectionObserver avisa e o CSS (globals.css) faz a transição. Aqui vale o contrato com o CSS: as classes, * as variáveis e o momento em que as marcas aparecem. */ type Observer = { callback: IntersectionObserverCallback; observed: Element[]; }; let observers: Observer[] = []; const original = global.IntersectionObserver; beforeEach(() => { observers = []; Object.defineProperty(window, "innerHeight", { configurable: true, value: 800, }); global.IntersectionObserver = class { private readonly record: Observer; constructor(callback: IntersectionObserverCallback) { this.record = { callback, observed: [] }; observers.push(this.record); } observe(target: Element) { this.record.observed.push(target); } unobserve() {} disconnect() {} takeRecords() { return []; } } as unknown as typeof IntersectionObserver; }); afterEach(() => { global.IntersectionObserver = original; }); /** Avisa que `target` entrou inteiro na janela. */ function reveal(target: Element) { act(() => { for (const observer of observers) if (observer.observed.includes(target)) observer.callback( [ { isIntersecting: true, intersectionRatio: 1, intersectionRect: { height: 400 }, target, } as unknown as IntersectionObserverEntry, ], {} as IntersectionObserver, ); }); } describe("Reveal", () => { it("renders its children and waits for the viewport to mark them as shown", () => { const { container } = render(

Conteúdo

, ); const wrapper = container.firstElementChild as HTMLElement; expect(screen.getByText("Conteúdo")).toBeInTheDocument(); expect(wrapper).toHaveClass("ds-reveal"); expect(wrapper).not.toHaveAttribute("data-shown"); reveal(wrapper); expect(wrapper).toHaveAttribute("data-shown"); }); it("hands the CSS its offset, duration and delay as custom properties", () => { const { container } = render( x , ); const wrapper = container.firstElementChild as HTMLElement; expect(wrapper.style.getPropertyValue("--reveal-y")).toBe("40px"); expect(wrapper.style.getPropertyValue("--reveal-duration")).toBe("0.9s"); expect(wrapper.style.getPropertyValue("--reveal-delay")).toBe("0.12s"); expect(wrapper).toHaveClass("ds-reveal", "extra"); }); it("uses no inline opacity or transform: the hidden state lives in the CSS, so no script means visible content", () => { const { container } = render(x); const wrapper = container.firstElementChild as HTMLElement; expect(wrapper.style.opacity).toBe(""); expect(wrapper.style.transform).toBe(""); expect(wrapper.style.clipPath).toBe(""); }); }); describe("DrawLine", () => { it("draws once the line is mostly on screen, and takes its delay from the prop", () => { const { container } = render(); const line = container.firstElementChild as HTMLElement; expect(line).toHaveClass("ds-draw-line", "x"); expect(line.style.getPropertyValue("--reveal-delay")).toBe("0.16s"); expect(line).not.toHaveAttribute("data-shown"); reveal(line); expect(line).toHaveAttribute("data-shown"); }); }); describe("LazyRevealImage", () => { it("shows the picture (data-loaded) only after it has loaded", async () => { const { container } = render( , ); const wrapper = container.firstElementChild as HTMLElement; const image = screen.getByAltText("Foto"); expect(wrapper).toHaveClass("ds-lazy-reveal", "h-40"); expect(wrapper).not.toHaveAttribute("data-loaded"); // o next/image só repassa o onLoad depois de decodificar a imagem (uma microtarefa) await act(async () => { image.dispatchEvent(new Event("load")); }); expect(wrapper).toHaveAttribute("data-loaded"); }); it("does not wait forever for an image that was already complete before the page hydrated", async () => { const complete = jest .spyOn(HTMLImageElement.prototype, "complete", "get") .mockReturnValue(true); const width = jest .spyOn(HTMLImageElement.prototype, "naturalWidth", "get") .mockReturnValue(640); let view: ReturnType | undefined; await act(async () => { view = render(); }); expect(view?.container.firstElementChild).toHaveAttribute("data-loaded"); complete.mockRestore(); width.mockRestore(); }); }); describe("Section", () => { it("renders the heading, the description and the content, and marks only the header as it enters", () => { const { container } = render(
lateral} >

Corpo

, ); const header = container.querySelector(".ds-section-header") as HTMLElement; expect(container.querySelector("section#sobre")).toBeInTheDocument(); expect( screen.getByRole("heading", { level: 2, name: "Quem faz" }), ).toBeInTheDocument(); expect(screen.getByText("Texto de apoio")).toHaveClass( "ds-section-description", "ds-reveal", ); expect(screen.getByText("Sobre")).toHaveClass("ds-eyebrow", "ds-type"); // um
, não um