import { act, fireEvent, render, screen } from "@testing-library/react"; import { renderToString } from "react-dom/server"; import { MOBILE_QUERY } from "@/lib/device"; import { getFeaturedProjects } from "@/lib/portfolio-data"; import { ProjectMedia } from "./ProjectMedia"; import { ShowcaseProvider } from "./ShowcaseProvider"; let reducedMotion = false; jest.mock("@/app/hooks/useReducedMotion", () => ({ useReducedMotion: () => reducedMotion, })); jest.mock("./ProjectLightbox", () => ({ ProjectLightbox: () =>
, })); const projects = getFeaturedProjects(null); const nutriscan = projects.find((project) => project.id === "nutriscan"); if (!nutriscan) throw new Error("projeto ausente"); type Callback = IntersectionObserverCallback; const callbacks: Callback[] = []; const Original = global.IntersectionObserver; const originalMatchMedia = window.matchMedia; function mockPhone() { window.matchMedia = jest.fn().mockImplementation((query: string) => ({ matches: query === MOBILE_QUERY, media: query, addEventListener() {}, removeEventListener() {}, })); } beforeEach(() => { jest.useFakeTimers(); reducedMotion = false; callbacks.length = 0; global.IntersectionObserver = class { constructor(callback: Callback) { callbacks.push(callback); } observe() {} unobserve() {} disconnect() {} takeRecords() { return []; } } as unknown as typeof IntersectionObserver; jest.spyOn(HTMLMediaElement.prototype, "play").mockResolvedValue(); jest.spyOn(HTMLMediaElement.prototype, "pause").mockImplementation(() => {}); jest.spyOn(HTMLMediaElement.prototype, "load").mockImplementation(() => {}); }); afterEach(() => { jest.useRealTimers(); jest.restoreAllMocks(); global.IntersectionObserver = Original; window.matchMedia = originalMatchMedia; Object.defineProperty(navigator, "connection", { configurable: true, value: undefined, }); }); function renderCard() { const view = render(
} />
, ); const frame = view.container.querySelector(".ds-frame") as HTMLElement; frame.getBoundingClientRect = () => ({ top: 300, height: 300, bottom: 600, left: 0, right: 400, width: 400, x: 0, y: 300, }) as DOMRect; return { ...view, frame }; } function show(frame: HTMLElement) { act(() => { for (const callback of callbacks) callback( [ { target: frame, intersectionRatio: 1, isIntersecting: true, } as unknown as IntersectionObserverEntry, ], {} as IntersectionObserver, ); jest.advanceTimersByTime(40); }); } describe("ProjectMedia", () => { it("frames the video with the project's address and a button that enlarges it", () => { renderCard(); expect(screen.getByText("nutriscan.evandro.dev.br")).toBeInTheDocument(); const expand = screen.getByRole("button", { name: "Ampliar o vídeo de NutriScan", }); expect(expand).toHaveAttribute("aria-haspopup", "dialog"); fireEvent.click(expand); expect( screen.getByRole("dialog", { name: "ampliado" }), ).toBeInTheDocument(); }); it("plays only while the card is the active one", () => { const { frame, container } = renderCard(); const video = container.querySelector("video") as HTMLVideoElement; expect(container.querySelector(".ds-frame")).toHaveAttribute( "data-active", "false", ); expect(video.querySelectorAll("source")).toHaveLength(0); show(frame); expect(container.querySelector(".ds-frame")).toHaveAttribute( "data-active", "true", ); expect(video.querySelectorAll("source")).toHaveLength(2); }); it("lets the visitor pause the videos that play on their own, and resume them", () => { const { frame, container } = renderCard(); show(frame); expect(container.querySelector(".ds-frame")).toHaveAttribute( "data-active", "true", ); fireEvent.click( screen.getByRole("button", { name: "Pausar os vídeos dos projetos" }), ); act(() => { jest.advanceTimersByTime(40); }); expect(container.querySelector(".ds-frame")).toHaveAttribute( "data-active", "false", ); fireEvent.click( screen.getByRole("button", { name: "Retomar os vídeos dos projetos" }), ); act(() => { jest.advanceTimersByTime(40); }); expect(container.querySelector(".ds-frame")).toHaveAttribute( "data-active", "true", ); }); it("has no pause button when nothing plays on its own (reduced motion)", () => { reducedMotion = true; renderCard(); expect( screen.queryByRole("button", { name: /vídeos dos projetos/ }), ).toBeNull(); }); it("sends no media at all in the server HTML: the device is only known in the browser", () => { const html = renderToString( } /> , ); expect(html).toContain("ds-frame-screen"); expect(html).not.toContain(" { const { frame, container } = renderCard(); const screenElement = container.querySelector( ".ds-frame-screen", ) as HTMLElement; const video = container.querySelector("video") as HTMLVideoElement; expect(screenElement.style.backgroundImage).toBe(""); expect(video).not.toHaveAttribute("poster"); expect(video).toHaveAttribute("preload", "metadata"); show(frame); expect(screenElement.style.backgroundImage).toContain( "/videos/nutriscan/desktop.webp", ); expect(video).toHaveAttribute("poster", "/videos/nutriscan/desktop.webp"); }); it("uses the phone version of the poster and the video below 768 px", () => { mockPhone(); const { frame, container } = renderCard(); show(frame); const screenElement = container.querySelector( ".ds-frame-screen", ) as HTMLElement; expect(screenElement.style.backgroundImage).toContain( "/videos/nutriscan/mobile.webp", ); const sources = [...container.querySelectorAll("video source")].map( (source) => source.getAttribute("src"), ); expect(sources).toEqual([ "/videos/nutriscan/mobile.webm", "/videos/nutriscan/mobile.mp4", ]); }); it("keeps the poster and never attaches a video when the data saver is on", () => { Object.defineProperty(navigator, "connection", { configurable: true, value: { saveData: true }, }); const { frame, container } = renderCard(); show(frame); expect(container.querySelector("video")).toBeNull(); expect(container.querySelector("source")).toBeNull(); expect( screen.getByRole("img", { name: "Captura de tela de NutriScan" }), ).toBeInTheDocument(); }); it("renders nothing for a project without a recorded video", () => { const { container } = render( , ); expect(container.querySelector(".ds-frame")).toBeNull(); }); it("does nothing, without crashing, when used outside the provider", () => { const { container } = render( } />, ); fireEvent.click( screen.getByRole("button", { name: "Ampliar o vídeo de NutriScan" }), ); expect(container.querySelector(".ds-frame")).not.toBeNull(); }); });