Foco cai no destino ao chegar de outra página; âncoras ignoram ?tipo=; movimento reduzido salta em todos os cliques; cronômetro da janela ampliada; degradê no topo de Projetos e legendas do mergulho com intervalos testados; vitrine acima da barra de cookies e com 2 paradas por projeto; reel-list.css no global; 3D montado em tarefas curtas, limitado a ~30 qps e estático com WebGL por software; pôsteres da vitrine de 330 para 158 KB; SC Plus regravado; scripts sem funções acima de complexidade 10.
161 lines
5.5 KiB
TypeScript
161 lines
5.5 KiB
TypeScript
import { act, fireEvent, render, screen, within } from "@testing-library/react";
|
|
import { getFeaturedProjects } from "@/lib/portfolio-data";
|
|
import { getShowcaseConfig, mobileFractionAt } from "@/lib/project-showcase";
|
|
import { ShowcaseProvider, useShowcaseOpen } from "./ShowcaseProvider";
|
|
|
|
/**
|
|
* Caminhos-base de readMetadata (CC 3) e do efeito de `readyState` no ProjectLightbox (complemento de
|
|
* ProjectLightbox.test.tsx): o vídeo que já chega com os metadados (cache, preload) não dispara `loadedmetadata`
|
|
* depois de montado, então o efeito lê a duração e aplica o ponto pendente da troca de aparelho.
|
|
* O que o teste principal já cobre: pronto na abertura sem ponto pendente; troca de aparelho com o evento.
|
|
* Aqui: troca de aparelho com o vídeo novo JÁ pronto (o ponto pendente é aplicado sem evento algum), troca de
|
|
* projeto com o vídeo novo já pronto (sem ponto pendente, o vídeo começa do zero), e vídeo ainda sem metadados.
|
|
*/
|
|
|
|
jest.mock("./ProjectLightboxLazy", () => ({
|
|
ProjectLightboxLazy: jest.requireActual("./ProjectLightbox").ProjectLightbox,
|
|
}));
|
|
jest.mock("@/app/hooks/useReducedMotion", () => ({
|
|
useReducedMotion: () => false,
|
|
}));
|
|
|
|
const DURATION = 30;
|
|
const lavacao = getShowcaseConfig("lavacao-047");
|
|
const projects = getFeaturedProjects(null);
|
|
/** O ponto de cada <video> (jsdom não tem reprodução). */
|
|
const positions = new WeakMap<object, number>();
|
|
let readyState = 0;
|
|
|
|
function Card({ id }: { id: string }) {
|
|
const openProject = useShowcaseOpen();
|
|
return (
|
|
<button type="button" onClick={() => openProject(id)}>
|
|
ampliar {id}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
function open(id = "lavacao-047") {
|
|
render(
|
|
<ShowcaseProvider projects={projects}>
|
|
<Card id="sev-modaintima" />
|
|
<Card id="lavacao-047" />
|
|
<Card id="nutriscan" />
|
|
<Card id="as-servicos" />
|
|
</ShowcaseProvider>,
|
|
);
|
|
fireEvent.click(screen.getByRole("button", { name: `ampliar ${id}` }));
|
|
return screen.getByRole("dialog");
|
|
}
|
|
|
|
const video = (dialog: HTMLElement) =>
|
|
dialog.querySelector("video") as HTMLVideoElement;
|
|
|
|
beforeAll(() => {
|
|
window.matchMedia = jest.fn().mockImplementation((query: string) => ({
|
|
matches: false,
|
|
media: query,
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
}));
|
|
HTMLDialogElement.prototype.showModal = function showModal(
|
|
this: HTMLDialogElement,
|
|
) {
|
|
this.setAttribute("open", "");
|
|
};
|
|
HTMLDialogElement.prototype.close = function close(this: HTMLDialogElement) {
|
|
this.removeAttribute("open");
|
|
this.dispatchEvent(new Event("close"));
|
|
};
|
|
});
|
|
|
|
beforeEach(() => {
|
|
readyState = 0;
|
|
jest
|
|
.spyOn(HTMLMediaElement.prototype, "readyState", "get")
|
|
.mockImplementation(() => readyState);
|
|
jest
|
|
.spyOn(HTMLMediaElement.prototype, "duration", "get")
|
|
.mockImplementation(() => DURATION);
|
|
jest
|
|
.spyOn(HTMLMediaElement.prototype, "currentTime", "get")
|
|
.mockImplementation(function currentTime(this: HTMLMediaElement) {
|
|
return positions.get(this) ?? 0;
|
|
});
|
|
jest
|
|
.spyOn(HTMLMediaElement.prototype, "currentTime", "set")
|
|
.mockImplementation(function currentTime(
|
|
this: HTMLMediaElement,
|
|
value: number,
|
|
) {
|
|
positions.set(this, value);
|
|
});
|
|
jest
|
|
.spyOn(HTMLMediaElement.prototype, "play")
|
|
.mockImplementation(() => Promise.resolve());
|
|
jest.spyOn(HTMLMediaElement.prototype, "pause").mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
document.documentElement.classList.remove("ds-lb-open");
|
|
});
|
|
|
|
describe("ProjectLightbox: vídeo que já chega com os metadados", () => {
|
|
it("path: switching device and the new video is already ready -> the pending point is applied without any loadedmetadata event", () => {
|
|
if (!lavacao) throw new Error("config ausente");
|
|
const dialog = open();
|
|
const desktop = video(dialog);
|
|
positions.set(desktop, DURATION * 0.5);
|
|
|
|
readyState = 1;
|
|
fireEvent.click(within(dialog).getByRole("button", { name: "Celular" }));
|
|
|
|
const phone = video(dialog);
|
|
expect(phone).not.toBe(desktop);
|
|
expect(positions.get(phone)).toBeCloseTo(
|
|
mobileFractionAt(lavacao, 0.5) * DURATION,
|
|
5,
|
|
);
|
|
expect(dialog.querySelector(".ds-lb-time")?.textContent).toMatch(
|
|
/ \/ 0:30$/,
|
|
);
|
|
});
|
|
|
|
it("path: switching device, the new video is NOT ready yet -> nothing is applied until loadedmetadata, then the point is applied once", () => {
|
|
if (!lavacao) throw new Error("config ausente");
|
|
const dialog = open();
|
|
positions.set(video(dialog), DURATION * 0.5);
|
|
|
|
fireEvent.click(within(dialog).getByRole("button", { name: "Celular" }));
|
|
const phone = video(dialog);
|
|
expect(positions.has(phone)).toBe(false);
|
|
|
|
act(() => {
|
|
phone.dispatchEvent(new Event("loadedmetadata"));
|
|
});
|
|
const applied = positions.get(phone);
|
|
expect(applied).toBeCloseTo(mobileFractionAt(lavacao, 0.5) * DURATION, 5);
|
|
|
|
// um segundo loadedmetadata (o vídeo recarrega) não puxa o ponto de volta: o pendente já foi consumido
|
|
positions.set(phone, 7);
|
|
act(() => {
|
|
phone.dispatchEvent(new Event("loadedmetadata"));
|
|
});
|
|
expect(positions.get(phone)).toBe(7);
|
|
});
|
|
|
|
it("path: moving to another project whose video is already ready -> it starts from zero, with the duration shown", () => {
|
|
const dialog = open("lavacao-047");
|
|
positions.set(video(dialog), DURATION * 0.5);
|
|
|
|
readyState = 1;
|
|
fireEvent.click(within(dialog).getByRole("button", { name: /próximo/i }));
|
|
|
|
expect(positions.has(video(dialog))).toBe(false);
|
|
expect(dialog.querySelector(".ds-lb-time")?.textContent).toMatch(
|
|
/^0:00 \/ 0:30$/,
|
|
);
|
|
});
|
|
});
|