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 { renderToStaticMarkup } from "react-dom/server"; import { SegmentedControl } from "./segmented-control"; import { SettingDangerRow, SettingNavRow, SettingRow, SettingRowGroup, SettingRowSkeleton, useSettingRowControl, useSettingRowField, } 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 textOf(ids: string | null) { return (ids ?? "") .split(" ") .filter(Boolean) .map((id) => document.getElementById(id)?.textContent); } describe("SettingRow", () => { test("names and describes its control, including hint and error", async () => { const view = await render( } />, ); try { const control = view.container.querySelector('[role="switch"]')!; expect(textOf(control.getAttribute("aria-labelledby"))).toEqual(["Video generation"]); expect(textOf(control.getAttribute("aria-describedby"))).toEqual([ "Let agents create short video clips in chats.", "Connect AI Gateway", "Couldn't save your change.", ]); const label = view.container.querySelector("label")!; expect(label.htmlFor).toBe(control.id); } finally { await view.unmount(); } }); test("clicking the label toggles the switch", async () => { const view = await render(} />); try { const control = view.container.querySelector('[role="switch"]')!; await act(async () => view.container.querySelector("label")!.click()); expect(control.getAttribute("aria-checked")).toBe("true"); } finally { await view.unmount(); } }); test("sub-rows render inside the parent and only when given", async () => { const row = (on: boolean) => ( }> {on ? : null} ); const view = await render(row(true)); try { const group = view.container.querySelector('[data-slot="setting-row-group"]')!; // Two top-level rows: the sub-row lives inside Voice input, so no divider splits them. expect(group.children).toHaveLength(2); const nested = view.container.querySelector('[data-slot="setting-row-children"]'); expect(nested?.textContent).toContain("Transcription provider"); expect(nested?.querySelector('[data-slot="setting-row"]')?.hasAttribute("data-nested")).toBe( true, ); await view.rerender(row(false)); expect(view.container.querySelector('[data-slot="setting-row-children"]')).toBeNull(); } finally { await view.unmount(); } }); test("control placement follows the variant", () => { const html = (variant: "control-right" | "control-left" | "stacked") => renderToStaticMarkup( } />, ); const order = (markup: string) => markup.indexOf("data-control") < markup.indexOf("Voice input") ? "control-first" : "text-first"; expect(order(html("control-left"))).toBe("control-first"); expect(order(html("control-right"))).toBe("text-first"); expect(order(html("stacked"))).toBe("text-first"); expect(html("stacked")).toContain("grid-cols-1"); }); test("custom controls can take the field ids", async () => { function CustomControl() { const field = useSettingRowControl(); return