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,144 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { useState } from "react";
|
||||
import { useRovingTabindex } from "./useRovingTabindex";
|
||||
|
||||
function Group({ hidden = false }: { hidden?: boolean }) {
|
||||
const [current, setCurrent] = useState(0);
|
||||
const { groupProps, itemProps } = useRovingTabindex(`p:${current}`, (key) =>
|
||||
key.startsWith("p:"),
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<button type="button">antes</button>
|
||||
<nav aria-label="grupo" {...groupProps}>
|
||||
{["a", "b", "c"].map((name, index) => (
|
||||
<button
|
||||
key={name}
|
||||
type="button"
|
||||
onClick={() => setCurrent(index)}
|
||||
{...itemProps(`p:${index}`)}
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
))}
|
||||
{hidden && (
|
||||
<button type="button" {...itemProps("p:9")} hidden>
|
||||
escondido
|
||||
</button>
|
||||
)}
|
||||
</nav>
|
||||
<button type="button">depois</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const tabIndexes = () =>
|
||||
["a", "b", "c"].map((name) =>
|
||||
screen.getByRole("button", { name }).getAttribute("tabindex"),
|
||||
);
|
||||
|
||||
describe("useRovingTabindex", () => {
|
||||
it("makes the group ONE tab stop: only the current item has tabindex 0", () => {
|
||||
render(<Group />);
|
||||
expect(tabIndexes()).toEqual(["0", "-1", "-1"]);
|
||||
// fora do grupo, os botões comuns seguem como estão
|
||||
expect(screen.getByRole("button", { name: "antes" })).not.toHaveAttribute(
|
||||
"tabindex",
|
||||
);
|
||||
});
|
||||
|
||||
it("moves the focus with the arrows (and Home/End), stopping at the ends", () => {
|
||||
render(<Group />);
|
||||
const [a, b, c] = ["a", "b", "c"].map((name) =>
|
||||
screen.getByRole("button", { name }),
|
||||
);
|
||||
a.focus();
|
||||
fireEvent.keyDown(a, { key: "ArrowDown" });
|
||||
expect(b).toHaveFocus();
|
||||
fireEvent.keyDown(b, { key: "ArrowRight" });
|
||||
expect(c).toHaveFocus();
|
||||
fireEvent.keyDown(c, { key: "ArrowDown" });
|
||||
expect(c).toHaveFocus();
|
||||
fireEvent.keyDown(c, { key: "ArrowUp" });
|
||||
expect(b).toHaveFocus();
|
||||
fireEvent.keyDown(b, { key: "ArrowLeft" });
|
||||
expect(a).toHaveFocus();
|
||||
fireEvent.keyDown(a, { key: "ArrowUp" });
|
||||
expect(a).toHaveFocus();
|
||||
fireEvent.keyDown(a, { key: "End" });
|
||||
expect(c).toHaveFocus();
|
||||
fireEvent.keyDown(c, { key: "Home" });
|
||||
expect(a).toHaveFocus();
|
||||
});
|
||||
|
||||
it("keeps the tab stop where the focus last was, so Shift+Tab and Tab come back to the same item", () => {
|
||||
render(<Group />);
|
||||
const b = screen.getByRole("button", { name: "b" });
|
||||
screen.getByRole("button", { name: "a" }).focus();
|
||||
fireEvent.keyDown(screen.getByRole("button", { name: "a" }), {
|
||||
key: "ArrowDown",
|
||||
});
|
||||
expect(b).toHaveFocus();
|
||||
expect(tabIndexes()).toEqual(["-1", "0", "-1"]);
|
||||
});
|
||||
|
||||
it("follows the current item while nothing has been focused yet", () => {
|
||||
const { rerender } = render(<Group />);
|
||||
fireEvent.click(screen.getByRole("button", { name: "c" }));
|
||||
rerender(<Group />);
|
||||
expect(tabIndexes()).toEqual(["-1", "-1", "0"]);
|
||||
});
|
||||
|
||||
it("leaves Tab, Enter, space and any arrow with a modifier alone (no preventDefault)", () => {
|
||||
render(<Group />);
|
||||
const a = screen.getByRole("button", { name: "a" });
|
||||
a.focus();
|
||||
for (const init of [
|
||||
{ key: "Tab" },
|
||||
{ key: "Enter" },
|
||||
{ key: " " },
|
||||
{ key: "ArrowDown", altKey: true },
|
||||
{ key: "ArrowDown", ctrlKey: true },
|
||||
{ key: "ArrowDown", metaKey: true },
|
||||
{ key: "ArrowDown", shiftKey: true },
|
||||
])
|
||||
expect(fireEvent.keyDown(a, init)).toBe(true);
|
||||
expect(a).toHaveFocus();
|
||||
});
|
||||
|
||||
it("cancels the page scroll of an arrow that moved the focus", () => {
|
||||
render(<Group />);
|
||||
const a = screen.getByRole("button", { name: "a" });
|
||||
a.focus();
|
||||
expect(fireEvent.keyDown(a, { key: "ArrowDown" })).toBe(false);
|
||||
expect(
|
||||
fireEvent.keyDown(screen.getByRole("button", { name: "b" }), {
|
||||
key: "Home",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("skips items that CSS hides (they cannot take the focus)", () => {
|
||||
const original = HTMLElement.prototype.checkVisibility;
|
||||
HTMLElement.prototype.checkVisibility = function check(this: HTMLElement) {
|
||||
return !this.hidden;
|
||||
};
|
||||
try {
|
||||
render(<Group hidden />);
|
||||
const c = screen.getByRole("button", { name: "c" });
|
||||
c.focus();
|
||||
fireEvent.keyDown(c, { key: "ArrowDown" });
|
||||
expect(c).toHaveFocus();
|
||||
fireEvent.keyDown(c, { key: "End" });
|
||||
expect(c).toHaveFocus();
|
||||
} finally {
|
||||
HTMLElement.prototype.checkVisibility = original;
|
||||
}
|
||||
});
|
||||
|
||||
it("ignores keys pressed on something that is not one of the items", () => {
|
||||
render(<Group />);
|
||||
const nav = screen.getByRole("navigation", { name: "grupo" });
|
||||
expect(fireEvent.keyDown(nav, { key: "ArrowDown" })).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user