// @vitest-environment jsdom import { afterEach, describe, expect, it } from "vitest"; import { act, cleanup, render } from "@testing-library/react"; import { useRef, useState } from "react"; import { useFocusKeeper } from "./useFocusKeeper"; afterEach(cleanup); const flush = () => act(() => new Promise((r) => setTimeout(r, 0))); function Rows({ nav = false }: { nav?: boolean }) { const ref = useRef(null); useFocusKeeper(ref); const [rows, setRows] = useState(["a", "b", "c"]); return (

Invites

{rows.map((r, i) => (
))}
); } const btn = (name: string) => [...document.querySelectorAll("button")].find((b) => b.textContent === name)!; describe("useFocusKeeper", () => { it("keeps the focus in the removed row's slot — the next row takes it", async () => { render(); btn("approve b").focus(); act(() => btn("approve b").click()); await flush(); expect(document.activeElement).toBe(btn("approve c")); }); it("falls back to what followed the list once the last row is gone", async () => { render(); btn("approve c").focus(); act(() => btn("approve c").click()); await flush(); expect(document.activeElement).toBe(btn("after the list")); }); it("leaves a keyboard list's rows to listNav", async () => { render(); btn("approve b").focus(); act(() => btn("approve b").click()); await flush(); expect(document.activeElement).toBe(document.body); }); });