import { act, fireEvent, render, screen, within } from "@testing-library/react"; import { getFeaturedProjects } from "@/lib/portfolio-data"; import { ProjectList } from "./ProjectList"; import { buildShowcaseItems, ShowcaseProvider } from "./ShowcaseProvider"; /** * Testes de caminhos-base de ProjectList (e do ListItem dentro dela): sem aparelho (servidor), celular x desktop, * longe x perto da janela (pôster), com e sem subtítulo, rótulo "pendente" e o botão de ampliar. */ jest.mock("./ProjectLightbox", () => ({ ProjectLightbox: ({ index }: { index: number }) => (
aberto:{index}
), })); type Callback = IntersectionObserverCallback; /** Cada observador criado, com o elemento que observa. */ const observers: Array<{ callback: Callback; target?: Element }> = []; const Original = global.IntersectionObserver; const projects = getFeaturedProjects(null); const items = buildShowcaseItems(projects); beforeEach(() => { observers.length = 0; global.IntersectionObserver = class { private readonly record: { callback: Callback; target?: Element }; constructor(callback: Callback) { this.record = { callback }; observers.push(this.record); } observe(target: Element) { this.record.target = target; } unobserve() {} disconnect() {} takeRecords() { return []; } } as unknown as typeof IntersectionObserver; }); afterEach(() => { global.IntersectionObserver = Original; }); function renderList(props: Partial[0]> = {}) { return render( , ); } function bringNear(target: Element) { act(() => { for (const observer of observers) if (observer.target === target) observer.callback( [{ isIntersecting: true } as IntersectionObserverEntry], {} as IntersectionObserver, ); }); } function bringAllNear(container: HTMLElement) { for (const item of container.querySelectorAll("li.ds-reel-item")) bringNear(item); } describe("ProjectList", () => { it("path: device unknown (server / before mount) -> text and links only, no media and no expand button", () => { const { container } = renderList({ device: null }); expect(container.querySelectorAll("li")).toHaveLength(items.length); expect(container.querySelector("[data-device]")).toBeNull(); expect(container.querySelector(".ds-reel-item-media")).toBeNull(); expect(screen.queryByRole("button", { name: /Ampliar vídeo/ })).toBeNull(); expect(screen.getAllByRole("link", { name: /Ver case/ })).toHaveLength( items.length, ); }); it("path: desktop -> browser frame, poster only once the item is near the window", () => { const { container } = renderList({ device: "desktop" }); expect(container.querySelectorAll(".ds-bframe")).toHaveLength(items.length); expect(container.querySelector(".ds-phone")).toBeNull(); expect(container.querySelector("img")).toBeNull(); bringAllNear(container); const images = [...container.querySelectorAll("img")]; expect(images).toHaveLength(items.length); expect(images[0]).toHaveAttribute( "alt", `Captura de tela de ${items[0].project.title}`, ); expect(decodeURIComponent(images[0].getAttribute("src") ?? "")).toContain( "desktop.webp", ); expect(images[0]).toHaveAttribute("sizes", "50vw"); }); it("path: mobile -> phone frame with the phone poster and the phone sizes", () => { const { container } = renderList({ device: "mobile" }); bringAllNear(container); expect(container.querySelectorAll(".ds-phone")).toHaveLength(items.length); expect(container.querySelector(".ds-bframe")).toBeNull(); const image = container.querySelector("img") as HTMLImageElement; expect(decodeURIComponent(image.getAttribute("src") ?? "")).toContain( "mobile.webp", ); expect(image).toHaveAttribute("sizes", "(max-width: 767px) 70vw"); }); it("path: an item that is not near yet keeps its frame but no image", () => { const { container } = renderList({ device: "desktop" }); bringNear(container.querySelector("li.ds-reel-item") as Element); expect(container.querySelectorAll("img")).toHaveLength(1); expect(container.querySelectorAll(".ds-bframe")).toHaveLength(items.length); }); it("path: project with a subtitle -> shown; without -> no subtitle paragraph", () => { const withSubtitle = items.map((item, index) => ({ ...item, project: { ...item.project, subtitle: index === 0 ? "Loja virtual" : "" }, })); const { container } = render( , ); const subs = container.querySelectorAll(".ds-reel-sub"); expect(subs).toHaveLength(1); expect(subs[0]).toHaveTextContent("Loja virtual"); }); it("path: pending -> the section is marked so CSS can hide it; not pending -> unmarked", () => { const pending = renderList({ pending: true, device: null }); expect(pending.container.querySelector("section")).toHaveAttribute( "data-pending", ); pending.unmount(); const settled = renderList({ pending: false }); expect(settled.container.querySelector("section")).not.toHaveAttribute( "data-pending", ); }); it("path: pressing 'Ampliar vídeo' on an item -> opens the enlarged window on that project", () => { renderList({ device: "desktop" }); const second = screen .getByRole("heading", { level: 3, name: items[1].project.title }) .closest("li") as HTMLElement; fireEvent.click( within(second).getByRole("button", { name: /Ampliar vídeo/ }), ); expect(screen.getByRole("dialog", { name: "ampliado" })).toHaveTextContent( "aberto:1", ); }); });