perf(movimento): vitrine e 3D mais leves, WebGL por software lembrado na sessão e suporte ao WebKit
This commit is contained in:
1 parent
9fd1570426
commit
8903f3c57e
28 files changed
+854
-406
No files matched your search
@@ -1,10 +1,11 @@
|
||||
import { act, render, screen } from "@testing-library/react";
|
||||
import { rememberSoftwareWebGL } from "@/lib/device";
|
||||
import HeroField, { DIVE_QUERY } from "./HeroField";
|
||||
|
||||
/**
|
||||
* Testes de caminhos-base de HeroField (complemento de HeroField.test.tsx): como a espera pelo WebGL acaba
|
||||
* (primeira interação, requestIdleCallback com folga ou sem folga, temporizador), os cancelamentos, as falhas
|
||||
* (contexto WebGL2, pacote que não carrega), o fundo sem a seção `.hero`, as variáveis de rolagem e a
|
||||
* (cena que não abre o contexto, pacote que não carrega), o WebGL por software lembrado na sessão, o fundo sem a seção `.hero`, as variáveis de rolagem e a
|
||||
* visibilidade (observador, aba oculta, fim do mergulho) e a limpeza ao desmontar.
|
||||
*/
|
||||
|
||||
@@ -306,25 +307,13 @@ describe("HeroField: quando o WebGL monta", () => {
|
||||
});
|
||||
|
||||
describe("HeroField: falhas voltam ao hero simples", () => {
|
||||
it("path: WebGL2 constructor exists but creating the context throws -> static background, no dive", async () => {
|
||||
setContext(() => {
|
||||
throw new Error("sem GPU");
|
||||
});
|
||||
|
||||
it("path: the scene fails to open its WebGL context (it reports an error) -> static background, no dive, no scene", async () => {
|
||||
render(<Hero />);
|
||||
await advance(1200);
|
||||
await finishLoading();
|
||||
expect(section()).toHaveAttribute("data-dive");
|
||||
|
||||
expect(screen.queryByTestId("scene")).toBeNull();
|
||||
expect(section()).not.toHaveAttribute("data-dive");
|
||||
});
|
||||
|
||||
it("path: WebGL2 constructor exists but the context is null -> static background, no dive", async () => {
|
||||
setContext(() => null);
|
||||
|
||||
render(<Hero />);
|
||||
await advance(1200);
|
||||
await finishLoading();
|
||||
act(() => mockScenes.at(-1)?.onError());
|
||||
|
||||
expect(screen.queryByTestId("scene")).toBeNull();
|
||||
expect(section()).not.toHaveAttribute("data-dive");
|
||||
@@ -342,28 +331,16 @@ describe("HeroField: falhas voltam ao hero simples", () => {
|
||||
expect(section()).not.toHaveAttribute("data-dive");
|
||||
});
|
||||
|
||||
it("path: the probe context is released right away (WEBGL2 lose_context) before the real scene mounts", async () => {
|
||||
const loseContext = jest.fn();
|
||||
setContext(() => ({
|
||||
getExtension: (name: string) =>
|
||||
name === "WEBGL_lose_context" ? { loseContext } : null,
|
||||
}));
|
||||
|
||||
render(<Hero />);
|
||||
await advance(1200);
|
||||
await finishLoading();
|
||||
|
||||
expect(loseContext).toHaveBeenCalledTimes(1);
|
||||
expect(screen.getByTestId("scene")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("path: the lose-context extension is missing -> the probe still succeeds", async () => {
|
||||
setContext(() => ({ getExtension: () => null }));
|
||||
it("path: HeroField never touches a canvas context, on hydration or when idle (a test context cost a 150 to 500 ms task)", async () => {
|
||||
const probe = jest.fn(() => ({ getExtension: () => null }));
|
||||
setContext(probe);
|
||||
|
||||
render(<Hero />);
|
||||
expect(probe).not.toHaveBeenCalled();
|
||||
await advance(1200);
|
||||
await finishLoading();
|
||||
|
||||
expect(probe).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId("scene")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -601,61 +578,48 @@ describe("HeroField: desmontagem em pontos que faltavam", () => {
|
||||
});
|
||||
});
|
||||
|
||||
/** Um contexto WebGL de teste que conta ao navegador o nome do renderizador (WEBGL_debug_renderer_info). */
|
||||
function contextRenderedBy(renderer: unknown, loseContext = jest.fn()) {
|
||||
const RENDERER = 37446;
|
||||
return {
|
||||
getExtension: (name: string) => {
|
||||
if (name === "WEBGL_debug_renderer_info")
|
||||
return { UNMASKED_RENDERER_WEBGL: RENDERER };
|
||||
return name === "WEBGL_lose_context" ? { loseContext } : null;
|
||||
},
|
||||
getParameter: (parameter: number) =>
|
||||
parameter === RENDERER ? renderer : undefined,
|
||||
};
|
||||
}
|
||||
describe("HeroField: WebGL por software (o veredito vem da cena e fica na sessão)", () => {
|
||||
beforeEach(() => sessionStorage.clear());
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
describe("HeroField: WebGL2 por software (supportsWebGL2 -> isSoftwareWebGL)", () => {
|
||||
it.each([
|
||||
[
|
||||
"SwiftShader (Chrome sem GPU)",
|
||||
"ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)",
|
||||
],
|
||||
["llvmpipe (Mesa)", "llvmpipe (LLVM 15.0.7, 256 bits)"],
|
||||
[
|
||||
"Microsoft Basic Render Driver",
|
||||
"ANGLE (Microsoft, Microsoft Basic Render Driver Direct3D11 vs_5_0 ps_5_0)",
|
||||
],
|
||||
])(
|
||||
"path: %s -> the plain 100svh hero from hydration: no dive, no scroll listeners, no observer, no scene, and the probe context is still released",
|
||||
async (_name, renderer) => {
|
||||
const loseContext = jest.fn();
|
||||
setContext(() => contextRenderedBy(renderer, loseContext));
|
||||
const addListener = jest.spyOn(window, "addEventListener");
|
||||
it("path: software WebGL already found in this session -> the plain 100svh hero from hydration: no dive, no scroll listeners, no observer, no scene", async () => {
|
||||
rememberSoftwareWebGL();
|
||||
const addListener = jest.spyOn(window, "addEventListener");
|
||||
|
||||
render(<Hero />);
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event("pointermove"));
|
||||
});
|
||||
await advance(3000);
|
||||
await finishLoading();
|
||||
render(<Hero />);
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event("pointermove"));
|
||||
});
|
||||
await advance(3000);
|
||||
await finishLoading();
|
||||
|
||||
expect(section()).not.toHaveAttribute("data-dive");
|
||||
expect(screen.queryByTestId("scene")).toBeNull();
|
||||
expect(mockScenes).toHaveLength(0);
|
||||
expect(mockPrepare).not.toHaveBeenCalled();
|
||||
expect(observers).toHaveLength(0);
|
||||
expect(addListener.mock.calls.map(([type]) => type)).not.toContain(
|
||||
"scroll",
|
||||
);
|
||||
expect(loseContext).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("path: a hardware renderer -> the scene mounts and the dive starts", async () => {
|
||||
setContext(() =>
|
||||
contextRenderedBy("ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11)"),
|
||||
expect(section()).not.toHaveAttribute("data-dive");
|
||||
expect(screen.queryByTestId("scene")).toBeNull();
|
||||
expect(mockScenes).toHaveLength(0);
|
||||
expect(mockPrepare).not.toHaveBeenCalled();
|
||||
expect(observers).toHaveLength(0);
|
||||
expect(addListener.mock.calls.map(([type]) => type)).not.toContain(
|
||||
"scroll",
|
||||
);
|
||||
});
|
||||
|
||||
it("path: nothing known about this device yet -> the dive starts at hydration and the scene mounts", async () => {
|
||||
render(<Hero />);
|
||||
expect(section()).toHaveAttribute("data-dive");
|
||||
|
||||
await advance(1200);
|
||||
await finishLoading();
|
||||
|
||||
expect(screen.getByTestId("scene")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("path: sessionStorage blocked (privacy mode) -> counts as unknown, the scene still mounts", async () => {
|
||||
jest.spyOn(Storage.prototype, "getItem").mockImplementation(() => {
|
||||
throw new Error("SecurityError");
|
||||
});
|
||||
|
||||
render(<Hero />);
|
||||
await advance(1200);
|
||||
@@ -665,24 +629,16 @@ describe("HeroField: WebGL2 por software (supportsWebGL2 -> isSoftwareWebGL)", (
|
||||
expect(section()).toHaveAttribute("data-dive");
|
||||
});
|
||||
|
||||
it("path: no renderer info extension (Firefox, Safari) -> cannot tell, so the scene mounts", async () => {
|
||||
setContext(() => ({ getExtension: () => null }));
|
||||
|
||||
it("path: the scene finds software WebGL once it has opened its context (onError) -> back to the 100svh hero, no scene", async () => {
|
||||
render(<Hero />);
|
||||
await advance(1200);
|
||||
await finishLoading();
|
||||
expect(section()).toHaveAttribute("data-dive");
|
||||
|
||||
expect(screen.getByTestId("scene")).toBeInTheDocument();
|
||||
});
|
||||
act(() => mockScenes.at(-1)?.onError());
|
||||
|
||||
it("path: the renderer name is not a string -> cannot tell, so the scene mounts", async () => {
|
||||
setContext(() => contextRenderedBy(null));
|
||||
|
||||
render(<Hero />);
|
||||
await advance(1200);
|
||||
await finishLoading();
|
||||
|
||||
expect(screen.getByTestId("scene")).toBeInTheDocument();
|
||||
expect(section()).not.toHaveAttribute("data-dive");
|
||||
expect(screen.queryByTestId("scene")).toBeNull();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
Reference in new issue
Block a user