import { act, render, screen } from "@testing-library/react"; import { getShowcaseAssets } from "@/lib/project-showcase"; import { ProjectVideo } from "./ProjectVideo"; let reducedMotion = false; jest.mock("@/app/hooks/useReducedMotion", () => ({ useReducedMotion: () => reducedMotion, })); const video = getShowcaseAssets("nutriscan", "desktop"); beforeEach(() => { reducedMotion = false; jest.spyOn(HTMLMediaElement.prototype, "play").mockResolvedValue(); jest.spyOn(HTMLMediaElement.prototype, "pause").mockImplementation(() => {}); jest.spyOn(HTMLMediaElement.prototype, "load").mockImplementation(() => {}); }); afterEach(() => { jest.restoreAllMocks(); Object.defineProperty(navigator, "connection", { configurable: true, value: undefined, }); }); describe("ProjectVideo", () => { it("renders a muted looping inline video that downloads nothing until it is active", () => { const { container } = render( , ); const element = container.querySelector("video"); expect(element).not.toBeNull(); expect(element).toHaveAttribute("autoplay"); expect(element).toHaveAttribute("loop"); expect(element).toHaveAttribute("playsinline"); expect(element).toHaveAttribute("preload", "metadata"); expect(element).toHaveAttribute("poster", video.poster); expect(element?.muted).toBe(true); expect(element).toHaveAttribute("data-ready", "false"); expect(container.querySelectorAll("source")).toHaveLength(0); expect(HTMLMediaElement.prototype.play).not.toHaveBeenCalled(); }); it("attaches the sources and plays when it becomes the active card, then pauses when it stops being", () => { const { container, rerender } = render( , ); rerender(); const sources = [...container.querySelectorAll("source")]; expect(sources.map((source) => source.getAttribute("src"))).toEqual( video.sources.map((source) => source.src), ); expect(HTMLMediaElement.prototype.load).toHaveBeenCalled(); expect(HTMLMediaElement.prototype.play).toHaveBeenCalled(); rerender(); expect(HTMLMediaElement.prototype.pause).toHaveBeenCalled(); // sai de cena, mas o arquivo já baixado fica: voltar a tocar não baixa de novo expect(container.querySelectorAll("source")).toHaveLength(2); }); it("fades the video in once it is playing", () => { const { container } = render( , ); const element = container.querySelector("video") as HTMLVideoElement; act(() => { element.dispatchEvent(new Event("playing")); }); expect(element).toHaveAttribute("data-ready", "true"); }); it("does not even request the poster while the card is far from the screen", () => { const { container, rerender } = render( , ); expect(container.querySelector("video")).not.toHaveAttribute("poster"); rerender( , ); expect(container.querySelector("video")).toHaveAttribute( "poster", video.poster, ); }); it("swaps to the sources of the new device when the version changes", () => { const mobile = getShowcaseAssets("nutriscan", "mobile"); const { container, rerender } = render( , ); const loads = jest.mocked(HTMLMediaElement.prototype.load).mock.calls .length; rerender(); expect( [...container.querySelectorAll("source")].map((source) => source.getAttribute("src"), ), ).toEqual(mobile.sources.map((source) => source.src)); expect( jest.mocked(HTMLMediaElement.prototype.load).mock.calls.length, ).toBeGreaterThan(loads); }); it("shows only the poster, with no video element, when the data saver is on", () => { Object.defineProperty(navigator, "connection", { configurable: true, value: { saveData: true }, }); const { container } = render( , ); expect(container.querySelector("video")).toBeNull(); expect(container.querySelector("source")).toBeNull(); expect( screen.getByRole("img", { name: "Captura de tela de Demo" }), ).toBeInTheDocument(); expect(HTMLMediaElement.prototype.play).not.toHaveBeenCalled(); }); it("shows only the poster, with no video element, when the user prefers reduced motion", () => { reducedMotion = true; const { container } = render( , ); expect(container.querySelector("video")).toBeNull(); expect( screen.getByRole("img", { name: "Captura de tela de Demo" }), ).toBeInTheDocument(); expect(HTMLMediaElement.prototype.play).not.toHaveBeenCalled(); }); });