perf(movimento): uma transição, 3D leve e vídeo certo por aparelho
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.
This commit is contained in:
1 parent
8d07b8c6bb
commit
c9a0b80656
48 files changed
+7210
-1241
No files matched your search
@@ -1,4 +1,6 @@
|
||||
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";
|
||||
@@ -18,6 +20,16 @@ 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();
|
||||
@@ -43,6 +55,11 @@ afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
jest.restoreAllMocks();
|
||||
global.IntersectionObserver = Original;
|
||||
window.matchMedia = originalMatchMedia;
|
||||
Object.defineProperty(navigator, "connection", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
function renderCard() {
|
||||
@@ -156,6 +173,69 @@ describe("ProjectMedia", () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("sends no media at all in the server HTML: the device is only known in the browser", () => {
|
||||
const html = renderToString(
|
||||
<ShowcaseProvider projects={projects}>
|
||||
<ProjectMedia project={nutriscan as NonNullable<typeof nutriscan>} />
|
||||
</ShowcaseProvider>,
|
||||
);
|
||||
expect(html).toContain("ds-frame-screen");
|
||||
expect(html).not.toContain("<video");
|
||||
expect(html).not.toContain("/videos/");
|
||||
});
|
||||
|
||||
it("asks for the poster only once the cover is within about a screen of the window", () => {
|
||||
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(
|
||||
<ShowcaseProvider projects={projects}>
|
||||
|
||||
Reference in new issue
Block a user