// @vitest-environment jsdom // // The parts of the shared dialog contract that are not already pinned through ui/Modal: the ONE // counted page scroll lock, and `focusScope` (the player opens focused on its card, not a button). import { useRef } from "react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { cleanup, render } from "@testing-library/react"; import { useDialogChrome } from "./dialogFocus"; afterEach(cleanup); function Dialog({ focusScope = false }: { focusScope?: boolean }) { const ref = useRef(null); useDialogChrome(ref, undefined, { focusScope }); return (
); } describe("useDialogChrome · the page scroll lock", () => { // Each dialog used to save and restore `overflow` itself, so the one mounted SECOND but unmounted // LAST put back the "hidden" it had saved — a page left locked with no dialog open. it("unlocks only when the last dialog closes, in whatever order they close", () => { document.body.style.overflow = ""; const player = render(); const modal = render(); expect(document.body.style.overflow).toBe("hidden"); player.unmount(); // the one underneath goes first expect(document.body.style.overflow).toBe("hidden"); // the other is still open modal.unmount(); expect(document.body.style.overflow).toBe(""); }); }); describe("useDialogChrome · initial focus", () => { it("lands on the first control by default", () => { // jsdom has no layout, so the visibility filter (offsetParent) would see nothing focusable. const spy = vi .spyOn(HTMLElement.prototype, "offsetParent", "get") .mockReturnValue(document.body); try { const { getByText } = render(); expect(document.activeElement).toBe(getByText("first")); } finally { spy.mockRestore(); } }); it("lands on the scope itself with focusScope", () => { // Same layout stub as above: without it jsdom sees no focusable child, so the scope would get // the focus with or without the option and this would pin nothing. const spy = vi .spyOn(HTMLElement.prototype, "offsetParent", "get") .mockReturnValue(document.body); try { const { getByTestId } = render(); expect(document.activeElement).toBe(getByTestId("scope")); } finally { spy.mockRestore(); } }); });