perf(movimento): sem tempo morto, vídeos regravados e 3D sem React Three Fiber
Legenda do mergulho até Projetos cobrir a cena; transição de página sem blur e com fundo escuro; foco no conteúdo ao trocar de página; listas com marcador e dois pesos de título; vídeos da SEV e da SC Plus regravados sem quadros brancos; scrub a 12-15 fps (-30%); vitrine com uma parada de Tab e setas, contador no celular e CSS sob demanda; âncoras num módulo só (fragment-nav); FramePulse com setInterval; CSS morto removido.
This commit is contained in:
1 parent
f8545cd442
commit
6ac87824f3
68 files changed
+5735
-3300
No files matched your search
@@ -0,0 +1,300 @@
|
||||
import { fireEvent, screen } from "@testing-library/react";
|
||||
import {
|
||||
FRAGMENT_NAV_BOOT_SCRIPT,
|
||||
focusFragment,
|
||||
isPlainPrimaryClick,
|
||||
onFragmentClick,
|
||||
samePageHashTarget,
|
||||
setScrollRestoration,
|
||||
} from "./fragment-nav";
|
||||
|
||||
/*
|
||||
* Âncoras da home num lugar só: a leitura do clique (quem é um link de âncora da mesma página), o único ouvinte
|
||||
* de clique e o único dono de history.scrollRestoration.
|
||||
*/
|
||||
|
||||
describe("isPlainPrimaryClick", () => {
|
||||
const click = (init: MouseEventInit = {}) =>
|
||||
new MouseEvent("click", { cancelable: true, button: 0, ...init });
|
||||
|
||||
it("accepts a plain click of the main button", () => {
|
||||
expect(isPlainPrimaryClick(click())).toBe(true);
|
||||
});
|
||||
|
||||
it.each(["metaKey", "ctrlKey", "shiftKey", "altKey"] as const)(
|
||||
"refuses a click with %s held (new tab, selection)",
|
||||
(modifier) => {
|
||||
expect(isPlainPrimaryClick(click({ [modifier]: true }))).toBe(false);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([1, 2])("refuses mouse button %i", (button) => {
|
||||
expect(isPlainPrimaryClick(click({ button }))).toBe(false);
|
||||
});
|
||||
|
||||
it("refuses a click somebody else already cancelled", () => {
|
||||
const event = click();
|
||||
event.preventDefault();
|
||||
|
||||
expect(isPlainPrimaryClick(event)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("samePageHashTarget", () => {
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
function clickOn(markup: string, selector: string) {
|
||||
document.body.innerHTML = markup;
|
||||
const target = document.querySelector(selector) as Element;
|
||||
const event = new MouseEvent("click", { bubbles: true });
|
||||
Object.defineProperty(event, "target", { value: target });
|
||||
return event;
|
||||
}
|
||||
|
||||
it("finds the element a same-page #link points to", () => {
|
||||
const event = clickOn(
|
||||
`<a href="/#alvo">ir</a><section id="alvo"></section>`,
|
||||
"a",
|
||||
);
|
||||
|
||||
const hit = samePageHashTarget(event);
|
||||
|
||||
expect(hit?.hash).toBe("#alvo");
|
||||
expect(hit?.target.id).toBe("alvo");
|
||||
});
|
||||
|
||||
it("works from inside the link (the text, an icon)", () => {
|
||||
const event = clickOn(
|
||||
`<a href="/#alvo"><span id="inside">ir</span></a><section id="alvo"></section>`,
|
||||
"#inside",
|
||||
);
|
||||
|
||||
expect(samePageHashTarget(event)?.target.id).toBe("alvo");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["another site", `<a href="https://example.com/#alvo">x</a>`],
|
||||
["another page of this site", `<a href="/outra#alvo">x</a>`],
|
||||
["no anchor", `<a href="/">x</a>`],
|
||||
["an empty anchor", `<a href="/#">x</a>`],
|
||||
["a new tab", `<a href="/#alvo" target="_blank">x</a>`],
|
||||
["an id that does not exist", `<a href="/#nada">x</a>`],
|
||||
["a malformed percent-encoding", `<a href="/#%E0%A4%A">x</a>`],
|
||||
["no link at all", `<p>x</p>`],
|
||||
])("returns null for %s", (_label, markup) => {
|
||||
const event = clickOn(
|
||||
`${markup}<section id="alvo"></section>`,
|
||||
markup.startsWith("<a") ? "a" : "p",
|
||||
);
|
||||
|
||||
expect(samePageHashTarget(event)).toBeNull();
|
||||
});
|
||||
|
||||
it("handles target=_self like a plain link", () => {
|
||||
const event = clickOn(
|
||||
`<a href="/#alvo" target="_self">x</a><section id="alvo"></section>`,
|
||||
"a",
|
||||
);
|
||||
|
||||
expect(samePageHashTarget(event)?.target.id).toBe("alvo");
|
||||
});
|
||||
|
||||
it("ignores a click whose target is not an element", () => {
|
||||
const event = new MouseEvent("click", { bubbles: true });
|
||||
Object.defineProperty(event, "target", { value: document });
|
||||
|
||||
expect(samePageHashTarget(event)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("samePageHashTarget (rules shared with the arrival code)", () => {
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = "";
|
||||
window.history.replaceState(null, "", "/");
|
||||
});
|
||||
|
||||
function clickOn(markup: string) {
|
||||
document.body.innerHTML = `${markup}<section id="alvo"></section>`;
|
||||
const target = document.querySelector("a") as Element;
|
||||
const event = new MouseEvent("click", { bubbles: true });
|
||||
Object.defineProperty(event, "target", { value: target });
|
||||
return event;
|
||||
}
|
||||
|
||||
it("refuses a link that downloads a file", () => {
|
||||
expect(
|
||||
samePageHashTarget(clickOn(`<a href="/#alvo" download>x</a>`)),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("refuses a link to the same path with another query string", () => {
|
||||
window.history.replaceState(null, "", "/?a=1");
|
||||
expect(
|
||||
samePageHashTarget(clickOn(`<a href="/?a=2#alvo">x</a>`)),
|
||||
).toBeNull();
|
||||
expect(
|
||||
samePageHashTarget(clickOn(`<a href="/?a=1#alvo">x</a>`))?.hash,
|
||||
).toBe("#alvo");
|
||||
});
|
||||
});
|
||||
|
||||
describe("onFragmentClick", () => {
|
||||
let add: jest.SpyInstance;
|
||||
let remove: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
add = jest.spyOn(document, "addEventListener");
|
||||
remove = jest.spyOn(document, "removeEventListener");
|
||||
document.body.innerHTML = `<a href="/#alvo">ir</a><a href="/outra">fora</a><section id="alvo"></section>`;
|
||||
window.history.replaceState(null, "", "/");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
const clicks = () => add.mock.calls.filter(([type]) => type === "click");
|
||||
const unclicks = () => remove.mock.calls.filter(([type]) => type === "click");
|
||||
const link = (name: string) => screen.getByRole("link", { name });
|
||||
|
||||
it("installs ONE document click listener no matter how many subscribe, and removes it with the last", () => {
|
||||
const first = jest.fn();
|
||||
const second = jest.fn();
|
||||
const stopFirst = onFragmentClick(first);
|
||||
const stopSecond = onFragmentClick(second);
|
||||
expect(clicks()).toHaveLength(1);
|
||||
expect(clicks()[0][2]).toBe(true);
|
||||
|
||||
fireEvent.click(link("ir"));
|
||||
expect(first).toHaveBeenCalledTimes(1);
|
||||
expect(second).toHaveBeenCalledTimes(1);
|
||||
|
||||
stopFirst();
|
||||
expect(unclicks()).toHaveLength(0);
|
||||
fireEvent.click(link("ir"));
|
||||
expect(first).toHaveBeenCalledTimes(1);
|
||||
expect(second).toHaveBeenCalledTimes(2);
|
||||
|
||||
stopSecond();
|
||||
expect(unclicks()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("hands every subscriber the same interpreted hit, in subscription order", () => {
|
||||
const order: string[] = [];
|
||||
const stops = [
|
||||
onFragmentClick((hit) => order.push(`a:${hit.hash}:${hit.target.id}`)),
|
||||
onFragmentClick((hit) => order.push(`b:${hit.hash}`)),
|
||||
];
|
||||
fireEvent.click(link("ir"));
|
||||
expect(order).toEqual(["a:#alvo:alvo", "b:#alvo"]);
|
||||
for (const stop of stops) stop();
|
||||
});
|
||||
|
||||
it("ignores links to other pages, modified clicks and clicks somebody already cancelled", () => {
|
||||
const handler = jest.fn();
|
||||
const stop = onFragmentClick(handler);
|
||||
fireEvent.click(link("fora"));
|
||||
fireEvent.click(link("ir"), { ctrlKey: true });
|
||||
fireEvent.click(link("ir"), { button: 1 });
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
stop();
|
||||
});
|
||||
|
||||
it("lets a subscriber cancel the navigation (preventDefault) without the others being skipped", () => {
|
||||
const second = jest.fn();
|
||||
const stops = [
|
||||
onFragmentClick((_hit, event) => event.preventDefault()),
|
||||
onFragmentClick(second),
|
||||
];
|
||||
expect(fireEvent.click(link("ir"))).toBe(false);
|
||||
expect(second).toHaveBeenCalledTimes(1);
|
||||
for (const stop of stops) stop();
|
||||
});
|
||||
|
||||
it("tolerates a subscriber that leaves while the click is being delivered", () => {
|
||||
const calls: string[] = [];
|
||||
const stopFirst = onFragmentClick(() => {
|
||||
calls.push("first");
|
||||
stopFirst();
|
||||
});
|
||||
const stopSecond = onFragmentClick(() => calls.push("second"));
|
||||
fireEvent.click(link("ir"));
|
||||
expect(calls).toEqual(["first", "second"]);
|
||||
stopSecond();
|
||||
});
|
||||
});
|
||||
|
||||
describe("focusFragment", () => {
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
it("focuses the destination without scrolling and gives it a tabindex only when it has none", () => {
|
||||
document.body.innerHTML = `<section id="a"></section><section id="b" tabindex="0"></section>`;
|
||||
const a = document.getElementById("a") as HTMLElement;
|
||||
const b = document.getElementById("b") as HTMLElement;
|
||||
const focus = jest.spyOn(a, "focus");
|
||||
|
||||
focusFragment(a);
|
||||
focusFragment(b);
|
||||
|
||||
expect(a).toHaveAttribute("tabindex", "-1");
|
||||
expect(b).toHaveAttribute("tabindex", "0");
|
||||
expect(focus).toHaveBeenCalledWith({ preventScroll: true });
|
||||
expect(b).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
describe("history.scrollRestoration ownership", () => {
|
||||
const run = () => new Function(FRAGMENT_NAV_BOOT_SCRIPT)();
|
||||
// o jsdom não tem performance.getEntriesByType: o teste o define e o tira no fim
|
||||
const navigationType = (type: string) =>
|
||||
Object.defineProperty(performance, "getEntriesByType", {
|
||||
configurable: true,
|
||||
value: () => [{ type }],
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Reflect.deleteProperty(performance, "getEntriesByType");
|
||||
window.history.scrollRestoration = "auto";
|
||||
window.history.replaceState(null, "", "/");
|
||||
});
|
||||
|
||||
it("setScrollRestoration is the runtime writer", () => {
|
||||
setScrollRestoration("manual");
|
||||
expect(window.history.scrollRestoration).toBe("manual");
|
||||
setScrollRestoration("auto");
|
||||
expect(window.history.scrollRestoration).toBe("auto");
|
||||
});
|
||||
|
||||
it("the boot script (run before hydration) goes manual on a fresh arrival at / and back to auto on pagehide", () => {
|
||||
navigationType("navigate");
|
||||
run();
|
||||
expect(window.history.scrollRestoration).toBe("manual");
|
||||
window.dispatchEvent(new Event("pagehide"));
|
||||
expect(window.history.scrollRestoration).toBe("auto");
|
||||
});
|
||||
|
||||
it("the boot script leaves a history traversal to the browser", () => {
|
||||
navigationType("back_forward");
|
||||
run();
|
||||
expect(window.history.scrollRestoration).toBe("auto");
|
||||
});
|
||||
|
||||
it("the boot script drops the stale #fragment on a reload", () => {
|
||||
window.history.replaceState(null, "", "/#contato");
|
||||
navigationType("reload");
|
||||
run();
|
||||
expect(window.location.hash).toBe("");
|
||||
});
|
||||
|
||||
it("the boot script does nothing outside the home", () => {
|
||||
window.history.replaceState(null, "", "/cases");
|
||||
navigationType("navigate");
|
||||
run();
|
||||
expect(window.history.scrollRestoration).toBe("auto");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user