Passagens com fade e 32 px, cena anterior termina em opacidade 0, sem visibility nem clip-path. 3D do hero como fundo ambiente, sob demanda e desligado em toque, aparelho fraco, saveData e movimento reduzido. Lenis fora do toque e salto direto em âncoras distantes. Vitrine só de clientes, até 3 capítulos, fixa só no desktop e carrossel no celular; vídeo só do projeto em cena com preload metadata. Rótulos mono a partir de 12 px e limão só no contato.
194 lines
6.1 KiB
TypeScript
194 lines
6.1 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import { getShowcaseAssets } from "@/lib/project-showcase";
|
|
import { ProjectVideo } from "./ProjectVideo";
|
|
|
|
/**
|
|
* Testes de caminhos-base de ProjectVideo (complemento de ProjectVideo.test.tsx): modo só pôster
|
|
* (movimento reduzido ou "economizar dados") com e sem pôster pedido, inativo x ativo, vídeo sem fontes,
|
|
* falha do play() e a combinação de ativo com modo só pôster.
|
|
*/
|
|
|
|
let reducedMotion = false;
|
|
jest.mock("@/app/hooks/useReducedMotion", () => ({
|
|
useReducedMotion: () => reducedMotion,
|
|
}));
|
|
|
|
const video = getShowcaseAssets("nutriscan", "desktop");
|
|
|
|
function setSaveData(value: boolean | undefined) {
|
|
Object.defineProperty(navigator, "connection", {
|
|
configurable: true,
|
|
value: value === undefined ? undefined : { saveData: value },
|
|
});
|
|
}
|
|
|
|
beforeEach(() => {
|
|
reducedMotion = false;
|
|
setSaveData(undefined);
|
|
jest.spyOn(HTMLMediaElement.prototype, "play").mockResolvedValue();
|
|
jest.spyOn(HTMLMediaElement.prototype, "pause").mockImplementation(() => {});
|
|
jest.spyOn(HTMLMediaElement.prototype, "load").mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
setSaveData(undefined);
|
|
});
|
|
|
|
describe("ProjectVideo: modo só pôster", () => {
|
|
it.each([
|
|
[
|
|
"reduced motion",
|
|
() => {
|
|
reducedMotion = true;
|
|
},
|
|
],
|
|
["the data saver", () => setSaveData(true)],
|
|
])(
|
|
"path: %s and the poster is not wanted yet -> renders nothing at all",
|
|
(_l, arrange) => {
|
|
arrange();
|
|
|
|
const { container } = render(
|
|
<ProjectVideo video={video} title="Demo" active showPoster={false} />,
|
|
);
|
|
|
|
expect(container).toBeEmptyDOMElement();
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
[
|
|
"reduced motion",
|
|
() => {
|
|
reducedMotion = true;
|
|
},
|
|
],
|
|
["the data saver", () => setSaveData(true)],
|
|
])(
|
|
"path: %s and the poster is wanted -> only the image, with the project name in its alt",
|
|
(_l, arrange) => {
|
|
arrange();
|
|
|
|
const { container } = render(
|
|
<ProjectVideo video={video} title="Demo" active={false} />,
|
|
);
|
|
|
|
expect(container.querySelector("video")).toBeNull();
|
|
const image = screen.getByRole("img", {
|
|
name: "Captura de tela de Demo",
|
|
});
|
|
expect(image).toHaveAttribute("width", String(video.width));
|
|
expect(image).toHaveAttribute("height", String(video.height));
|
|
},
|
|
);
|
|
|
|
it("path: data saver off and motion allowed -> the video element, never the image", () => {
|
|
setSaveData(false);
|
|
|
|
const { container } = render(
|
|
<ProjectVideo video={video} title="Demo" active={false} />,
|
|
);
|
|
|
|
expect(container.querySelector("video")).not.toBeNull();
|
|
expect(screen.queryByRole("img")).toBeNull();
|
|
});
|
|
|
|
it("path: active card but poster-only mode -> nothing is attached and nothing plays", () => {
|
|
reducedMotion = true;
|
|
|
|
render(<ProjectVideo video={video} title="Demo" active />);
|
|
|
|
expect(HTMLMediaElement.prototype.play).not.toHaveBeenCalled();
|
|
expect(HTMLMediaElement.prototype.load).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("ProjectVideo: vídeo", () => {
|
|
it("path: far card (poster not wanted) -> the video has no poster attribute", () => {
|
|
const { container } = render(
|
|
<ProjectVideo
|
|
video={video}
|
|
title="Demo"
|
|
active={false}
|
|
showPoster={false}
|
|
/>,
|
|
);
|
|
|
|
expect(container.querySelector("video")).not.toHaveAttribute("poster");
|
|
});
|
|
|
|
it("path: inactive and never activated -> no source, no load, no play, no pause", () => {
|
|
render(<ProjectVideo video={video} title="Demo" active={false} />);
|
|
|
|
expect(HTMLMediaElement.prototype.load).not.toHaveBeenCalled();
|
|
expect(HTMLMediaElement.prototype.play).not.toHaveBeenCalled();
|
|
expect(HTMLMediaElement.prototype.pause).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("path: becomes active -> muted is forced on the element before play()", () => {
|
|
const { container, rerender } = render(
|
|
<ProjectVideo video={video} title="Demo" active={false} />,
|
|
);
|
|
const element = container.querySelector("video") as HTMLVideoElement;
|
|
element.muted = false;
|
|
let mutedAtPlay: boolean | undefined;
|
|
jest.mocked(HTMLMediaElement.prototype.play).mockImplementation(function (
|
|
this: HTMLMediaElement,
|
|
) {
|
|
mutedAtPlay = this.muted;
|
|
return Promise.resolve();
|
|
});
|
|
|
|
rerender(<ProjectVideo video={video} title="Demo" active />);
|
|
|
|
expect(mutedAtPlay).toBe(true);
|
|
});
|
|
|
|
it("path: play() is rejected by the browser -> swallowed, the video stays mounted", async () => {
|
|
jest
|
|
.mocked(HTMLMediaElement.prototype.play)
|
|
.mockRejectedValue(new DOMException("blocked", "NotAllowedError"));
|
|
|
|
const { container } = render(
|
|
<ProjectVideo video={video} title="Demo" active />,
|
|
);
|
|
await Promise.resolve();
|
|
|
|
expect(container.querySelector("video")).not.toBeNull();
|
|
});
|
|
|
|
it("path: no sources in the assets -> becomes active without calling load()", () => {
|
|
const empty = { ...video, sources: [] };
|
|
|
|
const { container } = render(
|
|
<ProjectVideo video={empty} title="Demo" active />,
|
|
);
|
|
|
|
expect(container.querySelectorAll("source")).toHaveLength(0);
|
|
expect(HTMLMediaElement.prototype.load).not.toHaveBeenCalled();
|
|
expect(HTMLMediaElement.prototype.play).toHaveBeenCalled();
|
|
});
|
|
|
|
it("path: active -> inactive -> active again -> plays again without reloading the sources", () => {
|
|
const { container, rerender } = render(
|
|
<ProjectVideo video={video} title="Demo" active />,
|
|
);
|
|
const loadsAfterFirst = jest.mocked(HTMLMediaElement.prototype.load).mock
|
|
.calls.length;
|
|
const playsAfterFirst = jest.mocked(HTMLMediaElement.prototype.play).mock
|
|
.calls.length;
|
|
|
|
rerender(<ProjectVideo video={video} title="Demo" active={false} />);
|
|
rerender(<ProjectVideo video={video} title="Demo" active />);
|
|
|
|
expect(jest.mocked(HTMLMediaElement.prototype.load).mock.calls.length).toBe(
|
|
loadsAfterFirst,
|
|
);
|
|
expect(jest.mocked(HTMLMediaElement.prototype.play).mock.calls.length).toBe(
|
|
playsAfterFirst + 1,
|
|
);
|
|
expect(container.querySelectorAll("source")).toHaveLength(2);
|
|
});
|
|
});
|