import { readFileSync } from "node:fs"; import { join } from "node:path"; import { render, screen } from "@testing-library/react"; import type { PortfolioProjectView } from "@/lib/portfolio-data"; import CinematicHome from "./CinematicHome"; import { useCinematicScroll } from "./useCinematicScroll"; import { useHomeFragmentNavigation } from "./useHomeFragmentNavigation"; /** * Testes de caminhos-base de CinematicHome: a repasse das props opcionais, a ordem das cenas (sem rodapé próprio: ele * vem do layout) e a ligação dos hooks. * As cenas e os hooks são trocados por dublês que registram o que recebem. */ const mockHeroProps = jest.fn(); const mockProjectsProps = jest.fn(); const mockModalitiesProps = jest.fn(); const mockTestimonialsProps = jest.fn(); jest.mock("../Hero", () => ({ __esModule: true, default: (props: unknown) => { mockHeroProps(props); return
; }, })); jest.mock("../Projects", () => ({ __esModule: true, default: (props: unknown) => { mockProjectsProps(props); return
; }, })); jest.mock("../About", () => ({ __esModule: true, default: () =>
, })); jest.mock("../WorkModalities", () => ({ WorkModalities: (props: unknown) => { mockModalitiesProps(props); return
; }, })); jest.mock("../HomeFaq", () => ({ __esModule: true, default: () =>
, })); jest.mock("../Testimonials", () => ({ __esModule: true, default: (props: unknown) => { mockTestimonialsProps(props); return
; }, })); jest.mock("../Contact", () => ({ __esModule: true, default: () =>
, })); jest.mock("./useCinematicScroll", () => ({ useCinematicScroll: jest.fn() })); jest.mock("./useHomeFragmentNavigation", () => ({ useHomeFragmentNavigation: jest.fn(), })); const onLayoutReady = jest.fn(); function project(id: string, demoUrl: string | null): PortfolioProjectView { return { id, demoUrl } as unknown as PortfolioProjectView; } const lastHeroProps = () => mockHeroProps.mock.calls[mockHeroProps.mock.calls.length - 1][0]; beforeEach(() => { jest.clearAllMocks(); jest.mocked(useHomeFragmentNavigation).mockReturnValue(onLayoutReady); }); describe("CinematicHome: props e estrutura", () => { it("path: every optional prop given -> each scene receives its own data", () => { const projects = [project("lavacao-047", "https://a.example.com")]; const clients = [{ id: "lavacao-047", name: "Lavação 047" }]; const testimonials = [ { quote: "Ótimo.", name: "Ana", role: "Sócia", company: "Empresa" }, ]; render( , ); expect(lastHeroProps()).toMatchObject({ stats: { years: 5 }, clients }); expect(mockProjectsProps).toHaveBeenCalledWith({ projects }); expect(mockTestimonialsProps).toHaveBeenCalledWith({ items: testimonials }); // as ofertas não recebem mais os serviços do hub: a grade duplicada saiu da seção expect(mockModalitiesProps).toHaveBeenCalledWith({}); }); it("path: optional props omitted -> the scenes receive undefined and decide their own defaults", () => { render(); expect(lastHeroProps()).toMatchObject({ stats: undefined, clients: undefined, }); expect(mockTestimonialsProps).toHaveBeenCalledWith({ items: undefined }); expect(mockModalitiesProps).toHaveBeenCalledWith({}); }); it("path: scenes sit in order inside
(hero, projects, offers, about, FAQ, contact; no AI block); the home has no footer of its own (it comes from the layout)", () => { render(); const main = screen.getByRole("main"); expect( Array.from(main.children).map((child) => child.getAttribute("data-testid"), ), ).toEqual([ "scene-hero", "scene-projects", "scene-modalities", "scene-about", "scene-testimonials", "scene-faq", "scene-contact", ]); // o rodapé é do layout: a home não desenha um (seria o segundo da página) expect(screen.queryByRole("contentinfo")).toBeNull(); expect(document.querySelector("footer")).toBeNull(); }); it("path: mount -> the scroll hook gets the
ref and the layout-ready callback runs once", () => { render(); const ref = jest.mocked(useCinematicScroll).mock.calls[0][0]; expect(ref.current).toBe(screen.getByRole("main")); expect(onLayoutReady).toHaveBeenCalledTimes(1); }); }); describe("CinematicHome: componente de servidor", () => { const source = readFileSync(join(__dirname, "CinematicHome.tsx"), "utf8"); it("path: the file is a server component: no 'use client' and no React hooks, which live in the CinematicMain island", () => { expect(source).not.toMatch(/^["']use client["']/m); expect(source).not.toMatch( /\buse(?:State|Effect|LayoutEffect|Ref|Callback|Memo)\b/, ); expect(source).not.toMatch(/useCinematicScroll|useHomeFragmentNavigation/); expect(source).toContain('import CinematicMain from "./CinematicMain"'); }); it("path: the island is the only client file of the pair, and it wraps the scenes as children", () => { const island = readFileSync(join(__dirname, "CinematicMain.tsx"), "utf8"); expect(island).toMatch(/^"use client";/); expect(island).toMatch(/children/); expect(island).toContain("useCinematicScroll"); expect(island).toContain("useHomeFragmentNavigation"); }); });