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 ;
}
const view = await render(
} />,
);
try {
const button = view.container.querySelector("button")!;
expect(textOf(button.getAttribute("aria-labelledby"))).toEqual(["Transcription provider"]);
expect(view.container.querySelector("label")!.htmlFor).toBe(button.id);
} finally {
await view.unmount();
}
});
test("the label only points at a control that took its id", async () => {
function NamedOnly() {
const field = useSettingRowField();
return ;
}
const view = await render(
} />
}
/>
,
);
try {
for (const label of view.container.querySelectorAll("label")) {
expect(label.hasAttribute("for")).toBe(false);
}
expect(
view.container.querySelector('[role="radiogroup"]')!.getAttribute("aria-labelledby"),
).toBe(view.container.querySelectorAll("label")[1]!.id);
} finally {
await view.unmount();
}
});
test("control left keeps one text column for rows without a leading switch", () => {
const html = renderToStaticMarkup(
}
/>,
);
expect(html).toContain("*:col-start-2");
});
test("the skeleton is hidden from assistive tech", () => {
const html = renderToStaticMarkup();
expect(html).toContain('aria-hidden="true"');
});
});
describe("SettingNavRow", () => {
test("the whole row is one button named by its label and value", async () => {
let opened = 0;
const view = await render(
(opened += 1)}
/>,
);
try {
const buttons = view.container.querySelectorAll("button");
expect(buttons).toHaveLength(1);
const row = buttons[0]!;
expect(textOf(row.getAttribute("aria-labelledby"))).toEqual(["Allowed models", "3 models"]);
expect(textOf(row.getAttribute("aria-describedby"))).toEqual(["The models people can pick."]);
await act(async () => row.click());
expect(opened).toBe(1);
} finally {
await view.unmount();
}
});
test("disabled shows the value without a chevron or a button", () => {
const html = renderToStaticMarkup(
,
);
expect(html).not.toContain("