/* * Portions of this file are based on code from react-spectrum. * Apache License Version 2.0, Copyright 2020 Adobe. * * Credits to the React Spectrum team: * https://github.com/adobe/react-spectrum/blob/810579b671791f1593108f62cdc1893de3a220e3/packages/@react-aria/overlays/test/ariaHideOutside.test.js */ import { fireEvent, render, waitFor } from "@solidjs/testing-library"; import { createSignal, onMount } from "solid-js"; import { ariaHideOutside } from "./create-hide-outside"; describe("ariaHideOutside", () => { it("should hide everything except the provided element [button]", () => { const { getByRole, getAllByRole } = render(() => ( <> )); const checkboxes = getAllByRole("checkbox"); const button = getByRole("button"); const revert = ariaHideOutside([button]); expect(checkboxes[0]).toHaveAttribute("aria-hidden", "true"); expect(checkboxes[1]).toHaveAttribute("aria-hidden", "true"); expect(button).not.toHaveAttribute("aria-hidden"); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getByRole("button")).not.toThrow(); revert(); expect(checkboxes[0]).not.toHaveAttribute("aria-hidden"); expect(checkboxes[1]).not.toHaveAttribute("aria-hidden"); expect(button).not.toHaveAttribute("aria-hidden"); expect(() => getAllByRole("checkbox")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); }); it("should hide everything except multiple elements", () => { const { getByRole, getAllByRole, queryByRole, queryAllByRole } = render( () => ( <> ), ); const checkboxes = getAllByRole("checkbox"); const button = getByRole("button"); const revert = ariaHideOutside(checkboxes); expect(checkboxes[0]).not.toHaveAttribute("aria-hidden", "true"); expect(checkboxes[1]).not.toHaveAttribute("aria-hidden", "true"); expect(button).toHaveAttribute("aria-hidden"); expect(queryAllByRole("checkbox")).not.toBeNull(); expect(queryByRole("button")).toBeNull(); revert(); expect(checkboxes[0]).not.toHaveAttribute("aria-hidden"); expect(checkboxes[1]).not.toHaveAttribute("aria-hidden"); expect(button).not.toHaveAttribute("aria-hidden"); expect(() => getAllByRole("checkbox")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); }); it("should not traverse into an already hidden container", () => { const { getByRole, getAllByRole } = render(() => ( <>
)); const checkboxes = getAllByRole("checkbox"); const button = getByRole("button"); const revert = ariaHideOutside([button]); expect(checkboxes[0].parentElement).toHaveAttribute("aria-hidden", "true"); expect(checkboxes[1]).toHaveAttribute("aria-hidden", "true"); expect(button).not.toHaveAttribute("aria-hidden"); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getByRole("button")).not.toThrow(); revert(); expect(checkboxes[0].parentElement).not.toHaveAttribute("aria-hidden"); expect(checkboxes[1]).not.toHaveAttribute("aria-hidden"); expect(button).not.toHaveAttribute("aria-hidden"); expect(() => getAllByRole("checkbox")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); }); it("should not overwrite an existing aria-hidden prop", () => { const { getByRole, getAllByRole } = render(() => ( <> {/* biome-ignore lint/a11y/noAriaHiddenOnFocusable: test */} )); let checkboxes = getAllByRole("checkbox"); const button = getByRole("button"); const revert = ariaHideOutside([button]); expect(checkboxes).toHaveLength(1); expect(checkboxes[0]).toHaveAttribute("aria-hidden", "true"); expect(button).not.toHaveAttribute("aria-hidden"); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getByRole("button")).not.toThrow(); revert(); checkboxes = getAllByRole("checkbox"); expect(checkboxes).toHaveLength(1); expect(checkboxes[0]).not.toHaveAttribute("aria-hidden"); expect(button).not.toHaveAttribute("aria-hidden"); expect(() => getAllByRole("checkbox")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); }); it("should handle when a new element is added outside while active", async () => { const Test = () => { let toggleRef: any; let revertRef: any; const [show, setShow] = createSignal(false); let revert: () => void; onMount(() => { revert = ariaHideOutside([toggleRef, revertRef]); }); return ( <> {show() && } {show() && } ); }; const { getByTestId, getAllByRole } = render(() => ); const toggle = getByTestId("toggle"); const revert = getByTestId("revert"); expect(() => getAllByRole("checkbox")).toThrow(); // Toggle the show state fireEvent.click(toggle); await Promise.resolve(); // MutationObserver is async await waitFor(() => expect(() => getAllByRole("checkbox")).toThrow()); expect(() => getAllByRole("button")).not.toThrow(); // revert the 'ariaHideOutside' fireEvent.click(revert); await Promise.resolve(); expect(getAllByRole("checkbox")).toHaveLength(2); }); it("should handle when a new element is added to an already hidden container", async () => { const Test = () => { const [show, setShow] = createSignal(false); return ( <>
{show() && }
{show() && } ); }; const { getByRole, getAllByRole, getByTestId } = render(() => ); const button = getByRole("button"); const test = getByTestId("test"); expect(() => getAllByRole("checkbox")).toThrow(); const revert = ariaHideOutside([button]); expect(test).toHaveAttribute("aria-hidden"); // Toggle the show state fireEvent.click(button); await Promise.resolve(); // MutationObserver is async await waitFor(() => expect(() => getAllByRole("checkbox")).toThrow()); expect(() => getByRole("button")).not.toThrow(); const checkboxes = getAllByRole("checkbox", { hidden: true }); expect(test).toHaveAttribute("aria-hidden"); expect(checkboxes[0]).not.toHaveAttribute("aria-hidden"); expect(checkboxes[1]).toHaveAttribute("aria-hidden", "true"); revert(); expect(getAllByRole("checkbox")).toHaveLength(2); }); it("should handle when a new element is added inside a target element", async () => { const Test = () => { const [show, setShow] = createSignal(false); return ( <>
{show() && }
); }; const { getByRole, getAllByRole, getByTestId, queryByRole, queryAllByRole, } = render(() => ); const button = getByRole("button"); const test = getByTestId("test"); const revert = ariaHideOutside([test]); expect(() => getAllByRole("checkbox")).toThrow(); expect(queryByRole("radio")).toBeNull(); expect(queryByRole("button")).not.toBeNull(); expect(() => getByTestId("test")).not.toThrow(); // Toggle the show state fireEvent.click(button); await Promise.resolve(); // Wait for mutation observer tick await Promise.resolve(); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getByRole("radio")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); expect(() => getByTestId("test")).not.toThrow(); revert(); expect(() => getAllByRole("checkbox")).not.toThrow(); expect(() => getByRole("radio")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); expect(() => getByTestId("test")).not.toThrow(); }); it("work when called multiple times", () => { const { getByRole, getAllByRole, getByTestId } = render(() => ( <> )); const radios = getAllByRole("radio"); const button = getByRole("button"); const revert1 = ariaHideOutside([button, ...radios]); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getAllByRole("radio")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); const revert2 = ariaHideOutside([button]); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getAllByRole("radio")).toThrow(); expect(() => getByRole("button")).not.toThrow(); revert2(); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getAllByRole("radio")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); revert1(); expect(() => getAllByRole("checkbox")).not.toThrow(); expect(() => getAllByRole("radio")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); }); it("work when called multiple times and restored out of order", () => { const { getByRole, getAllByRole } = render(() => ( <> )); const radios = getAllByRole("radio"); const button = getByRole("button"); const revert1 = ariaHideOutside([button, ...radios]); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getAllByRole("radio")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); const revert2 = ariaHideOutside([button]); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getAllByRole("radio")).toThrow(); expect(() => getByRole("button")).not.toThrow(); revert1(); expect(() => getAllByRole("checkbox")).toThrow(); expect(() => getAllByRole("radio")).toThrow(); expect(() => getByRole("button")).not.toThrow(); revert2(); expect(() => getAllByRole("checkbox")).not.toThrow(); expect(() => getAllByRole("radio")).not.toThrow(); expect(() => getByRole("button")).not.toThrow(); }); it("should hide everything except the provided element [row]", () => { const { getAllByRole } = render(() => (
Cell 1
Cell 2
)); const cells = getAllByRole("gridcell"); const rows = getAllByRole("row"); const revert = ariaHideOutside([rows[1]]); // Applies aria-hidden to the row and cell despite recursive nature of aria-hidden // for https://bugs.webkit.org/show_bug.cgi?id=222623 expect(rows[0]).toHaveAttribute("aria-hidden", "true"); expect(cells[0]).toHaveAttribute("aria-hidden", "true"); expect(rows[1]).not.toHaveAttribute("aria-hidden", "true"); expect(cells[1]).not.toHaveAttribute("aria-hidden", "true"); revert(); expect(rows[0]).not.toHaveAttribute("aria-hidden", "true"); expect(cells[0]).not.toHaveAttribute("aria-hidden", "true"); expect(rows[1]).not.toHaveAttribute("aria-hidden", "true"); expect(cells[1]).not.toHaveAttribute("aria-hidden", "true"); }); });