import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { GlobalRegistrator } from "@happy-dom/global-registrator"; import { act, type ReactNode } from "react"; import { createRoot } from "react-dom/client"; import { SettingRow } from "./setting-row"; import { Switch } from "./switch"; import { TooltipProvider } from "./tooltip"; beforeAll(() => { GlobalRegistrator.register(); ( globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean } ).IS_REACT_ACT_ENVIRONMENT = true; }); afterAll(() => GlobalRegistrator.unregister()); async function render(node: ReactNode) { const container = document.createElement("div"); document.body.append(container); const root = createRoot(container); await act(async () => root.render({node})); return { container, rerender: (next: ReactNode) => act(async () => root.render({next})), unmount: async () => { await act(async () => root.unmount()); container.remove(); }, }; } function control(container: HTMLElement) { const element = container.querySelector('[role="switch"]'); if (!element) throw new Error("switch not rendered"); return element; } describe("Switch", () => { test("is a labelled switch that reports the next value", async () => { const changes: boolean[] = []; const view = await render( changes.push(next)} />, ); try { const element = control(view.container); expect(element.getAttribute("aria-checked")).toBe("false"); expect(element.getAttribute("aria-label")).toBe("Voice input"); await act(async () => element.click()); expect(changes).toEqual([true]); } finally { await view.unmount(); } }); test("uncontrolled switches keep their own state", async () => { const view = await render(); try { const element = control(view.container); await act(async () => element.click()); expect(element.getAttribute("aria-checked")).toBe("true"); await act(async () => element.click()); expect(element.getAttribute("aria-checked")).toBe("false"); } finally { await view.unmount(); } }); test("while saving it stays focusable, reports busy and ignores clicks", async () => { const changes: boolean[] = []; const view = await render( changes.push(next)} />, ); try { const element = control(view.container); expect(element.disabled).toBe(false); expect(element.getAttribute("aria-busy")).toBe("true"); element.focus(); await act(async () => element.click()); expect(changes).toEqual([]); expect(element.getAttribute("aria-checked")).toBe("true"); expect(document.activeElement).toBe(element); expect(view.container.querySelector("svg")).not.toBeNull(); } finally { await view.unmount(); } }); test("a disabled reason keeps it focusable and describes why", async () => { const changes: boolean[] = []; const reason = "Video models are paid through AI Gateway."; const view = await render( changes.push(next)} />, ); try { const element = control(view.container); expect(element.disabled).toBe(false); expect(element.getAttribute("aria-disabled")).toBe("true"); await act(async () => element.click()); expect(changes).toEqual([]); expect(element.getAttribute("aria-checked")).toBe("false"); const describedBy = element.getAttribute("aria-describedby")?.split(" ") ?? []; const texts = describedBy.map((id) => document.getElementById(id)?.textContent); expect(texts).toContain(reason); } finally { await view.unmount(); } }); test("without a reason it is natively disabled", async () => { const view = await render(); try { expect(control(view.container).disabled).toBe(true); } finally { await view.unmount(); } }); test("state text shows the current state and never becomes the name", async () => { const view = await render( } />, ); try { const element = control(view.container); const words = view.container.querySelector('[data-slot="switch-with-state"] [aria-hidden]'); expect(words?.querySelector(".invisible")?.textContent).toBe("Off"); const labelId = element.getAttribute("aria-labelledby"); expect(labelId ? document.getElementById(labelId)?.textContent : null).toBe( "Use for new work", ); } finally { await view.unmount(); } }); });