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 ( <> ); } 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(); 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(); 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(); 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(); fireEvent.click(screen.getByRole("button", { name: "c" })); rerender(); expect(tabIndexes()).toEqual(["-1", "-1", "0"]); }); it("leaves Tab, Enter, space and any arrow with a modifier alone (no preventDefault)", () => { render(); 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(); 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(