import { render, screen, waitFor } from "@testing-library/react"; import ScrollStory from "./ScrollStory"; // gsap é carregado via import() dinâmico dentro do effect; o contrato testado // aqui é markup do reel (cenas/HUD/alvos), não a timeline — que é verificada // no browser via probe CDP. Mock leve mantém a suíte em jsdom. jest.mock("gsap", () => { const chain = { set: jest.fn(), fromTo: jest.fn(), to: jest.fn() }; Object.assign(chain, { set: jest.fn(() => chain), fromTo: jest.fn(() => chain), to: jest.fn(() => chain), }); return { gsap: { registerPlugin: jest.fn(), set: jest.fn(), timeline: jest.fn(() => chain), ticker: { add: jest.fn(), remove: jest.fn(), lagSmoothing: jest.fn() }, utils: { toArray: jest.fn(() => []) }, context: jest.fn((fn: () => void) => { fn(); return { revert: jest.fn() }; }), }, }; }); jest.mock("gsap/ScrollTrigger", () => ({ ScrollTrigger: { getAll: jest.fn(() => []) }, })); const props = { projectsCount: 5, experiencesCount: 6, skillsCount: 18 }; function mockMatchMedia(matches: boolean) { Object.defineProperty(window, "matchMedia", { writable: true, value: jest.fn().mockImplementation((query: string) => ({ matches, media: query, onchange: null, addListener: jest.fn(), removeListener: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), dispatchEvent: jest.fn(), })), }); } describe("ScrollStory", () => { afterEach(() => { jest.restoreAllMocks(); mockMatchMedia(false); }); it("renderiza fallback empilhado com conteúdo real com reduced motion", () => { mockMatchMedia(true); const { container } = render(); // sem palco scrub: capítulos legíveis no fluxo, números reais do portfólio expect(container.querySelector("[data-story]")).not.toBeInTheDocument(); expect(container).toHaveTextContent("01 — Visão geral"); expect(container).toHaveTextContent("04 — Próximo ciclo"); expect(container).toHaveTextContent("5"); expect(screen.getAllByText(/sistemas ativos no portfólio/)).toHaveLength(1); }); it("renderiza o reel com 4 cenas, HUD e índice quando o engine liga", async () => { const { container } = render(); await waitFor( () => expect(container.querySelector("[data-story]")).toBeInTheDocument(), { timeout: 10000 }, ); expect(container.querySelectorAll("[data-story-scene]")).toHaveLength(4); expect(container.querySelector("[data-story-index]")).toHaveTextContent("01"); expect(container.querySelector("[data-story-bar]")).toBeInTheDocument(); // count-up é alimentado pela timeline: markup carrega o alvo real const num = container.querySelector("[data-story-num]"); expect(num?.getAttribute("data-story-num-to")).toBe("5"); }, 20000); });