mirror of
https://github.com/MeshCore-Beacon/beacon-web.git
synced 2026-09-03 04:03:51 +00:00
- footer read 'v0.1' forever; it's injected from package.json at build time now - region picker shows 'Failed to load' instead of an eternal Loading when /iatas errors - selecting text in a modal and releasing over the backdrop no longer closes it (close requires the press to start on the backdrop) - Escape in a dropdown inside the mobile filter sheet closes just the dropdown, not the whole sheet - Shift+Tab right after a modal opens wraps to the last control instead of escaping the focus trap - formatCount rolls 999,999 over to 1M instead of printing 1000k - TABS list moved to lib/constants so AppShell only exports components
83 lines
3.3 KiB
TypeScript
83 lines
3.3 KiB
TypeScript
import { useRef, type ReactNode } from "react";
|
|
import { describe, it, expect } from "vitest";
|
|
import { render, screen, fireEvent } from "@testing-library/react";
|
|
import { useFocusTrap } from "../../src/hooks/useFocusTrap";
|
|
|
|
function TrapBox({ children }: { children: ReactNode }) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
useFocusTrap(ref);
|
|
return (
|
|
<div ref={ref} tabIndex={-1} data-testid="dialog">
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Harness({ trapped }: { trapped: boolean }) {
|
|
return (
|
|
<div>
|
|
<button data-testid="outside">outside</button>
|
|
{trapped && (
|
|
<TrapBox>
|
|
<button data-testid="first">first</button>
|
|
<button data-testid="last">last</button>
|
|
</TrapBox>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
describe("useFocusTrap", () => {
|
|
it("focuses the container itself on mount (so the dialog name is announced before its controls)", () => {
|
|
render(<Harness trapped={true} />);
|
|
expect(document.activeElement).toBe(screen.getByTestId("dialog"));
|
|
});
|
|
|
|
it("wraps focus to the first element when Tab is pressed on the last", () => {
|
|
render(<Harness trapped={true} />);
|
|
screen.getByTestId("last").focus();
|
|
fireEvent.keyDown(screen.getByTestId("last"), { key: "Tab" });
|
|
expect(document.activeElement).toBe(screen.getByTestId("first"));
|
|
});
|
|
|
|
it("wraps focus to the last element when Shift+Tab is pressed on the first", () => {
|
|
render(<Harness trapped={true} />);
|
|
screen.getByTestId("first").focus();
|
|
fireEvent.keyDown(screen.getByTestId("first"), { key: "Tab", shiftKey: true });
|
|
expect(document.activeElement).toBe(screen.getByTestId("last"));
|
|
});
|
|
|
|
it("wraps Shift+Tab to the last element while the container itself is focused", () => {
|
|
// regression: right after open, focus sits on the container — Shift+Tab escaped behind the overlay
|
|
render(<Harness trapped={true} />);
|
|
expect(document.activeElement).toBe(screen.getByTestId("dialog"));
|
|
fireEvent.keyDown(screen.getByTestId("dialog"), { key: "Tab", shiftKey: true });
|
|
expect(document.activeElement).toBe(screen.getByTestId("last"));
|
|
});
|
|
|
|
it("does not yank focus to <body> on unmount when nothing was focused on open", () => {
|
|
// Opened from a click that left focus on <body> (e.g. a Safari/Firefox button or a non-focusable
|
|
// row). On close we must not call body.focus() and blur whatever the user moved to next.
|
|
const { rerender } = render(<Harness trapped={false} />);
|
|
expect(document.activeElement).toBe(document.body);
|
|
|
|
rerender(<Harness trapped={true} />); // trap focuses the container; restoreTo is <body>
|
|
screen.getByTestId("outside").focus(); // focus subsequently moves elsewhere
|
|
|
|
rerender(<Harness trapped={false} />); // unmount must leave focus on "outside", not reset to body
|
|
expect(document.activeElement).toBe(screen.getByTestId("outside"));
|
|
});
|
|
|
|
it("restores focus to the previously focused element on unmount", () => {
|
|
const { rerender } = render(<Harness trapped={false} />);
|
|
screen.getByTestId("outside").focus();
|
|
expect(document.activeElement).toBe(screen.getByTestId("outside"));
|
|
|
|
rerender(<Harness trapped={true} />);
|
|
expect(document.activeElement).toBe(screen.getByTestId("dialog"));
|
|
|
|
rerender(<Harness trapped={false} />);
|
|
expect(document.activeElement).toBe(screen.getByTestId("outside"));
|
|
});
|
|
});
|