import { afterAll, afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import type { Root } from "react-dom/client";
// Register the DOM before React DOM and Radix load, so Radix uses real layout effects.
GlobalRegistrator.register();
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT =
true;
const { act } = await import("react");
const { createRoot } = await import("react-dom/client");
const { ChoiceCard, ChoiceCards, ChoiceCardsSkeleton, selectableSurface } =
await import("./choice-cards");
const { Field } = await import("./field");
const { SectionCardContext, SectionFrameReset } = await import("./section-variant");
afterAll(() => GlobalRegistrator.unregister());
let container: HTMLDivElement;
let root: Root;
beforeEach(() => {
container = document.createElement("div");
document.body.append(container);
root = createRoot(container);
});
afterEach(async () => {
await act(async () => root.unmount());
container.remove();
});
function InviteRole(props: {
onValueChange?: (value: string) => void;
error?: string;
variant?: "ring" | "radio" | "list";
}) {
return (
);
}
describe("ChoiceCards", () => {
test("is a labelled radio group whose cards are named by their title", async () => {
await act(async () => root.render());
const group = container.querySelector('[role="radiogroup"]')!;
const label = document.getElementById(group.getAttribute("aria-labelledby")!);
expect(label?.textContent).toBe("Organization role");
const radios = [...container.querySelectorAll('[role="radio"]')];
expect(radios).toHaveLength(3);
const member = radios[2]!;
expect(document.getElementById(member.getAttribute("aria-labelledby")!)?.textContent).toBe(
"Member",
);
expect(member.getAttribute("aria-checked")).toBe("true");
});
test("a locked option is disabled and describes why", async () => {
await act(async () => root.render());
const owner = container.querySelector('[role="radio"]')!;
expect(owner.disabled).toBe(true);
const described = owner
.getAttribute("aria-describedby")!
.split(" ")
.map((id) => document.getElementById(id)?.textContent)
.join(" ");
expect(described).toContain("Only an owner can invite another owner.");
});
test("inside a Field group, the field's label names the group and its error describes it", async () => {
await act(async () =>
root.render(
,
),
);
const group = container.querySelector('[role="radiogroup"]')!;
expect(document.getElementById(group.getAttribute("aria-labelledby")!)?.textContent).toBe(
"Who can use it?",
);
expect(document.getElementById(group.getAttribute("aria-describedby")!)?.textContent).toBe(
"Choose who can use this connection.",
);
expect(group.getAttribute("aria-invalid")).toBe("true");
});
test("clicking a card selects it and reports the value", async () => {
const change = mock();
await act(async () => root.render());
const admin = container.querySelectorAll('[role="radio"]')[1]!;
await act(async () => admin.click());
expect(change).toHaveBeenCalledWith("admin");
expect(admin.getAttribute("aria-checked")).toBe("true");
});
test("an error marks the group invalid and is announced with it", async () => {
await act(async () => root.render());
const group = container.querySelector('[role="radiogroup"]')!;
expect(group.getAttribute("aria-invalid")).toBe("true");
const describedBy = group.getAttribute("aria-describedby")!;
expect(document.getElementById(describedBy)?.textContent).toBe("Choose a role.");
});
test("only the ring version shows a check; radio and list show a radio dot", async () => {
await act(async () => root.render());
expect(container.querySelectorAll(".lucide-check")).toHaveLength(1);
await act(async () => root.render());
expect(container.querySelectorAll(".lucide-check")).toHaveLength(0);
expect(container.querySelector('[data-variant="list"]')).not.toBeNull();
});
test("inside a settings card the options are flat radio rows, never cards in a card", async () => {
await act(async () =>
root.render(
,
),
);
const group = container.querySelector('[role="radiogroup"]')!;
expect(group.getAttribute("data-variant")).toBe("list");
expect(group.hasAttribute("data-rows")).toBe(true);
expect(container.querySelectorAll(".lucide-check")).toHaveLength(0);
for (const card of container.querySelectorAll('[data-slot="choice-card"]')) {
expect(card.className).not.toMatch(/\bborder\b|rounded-/);
}
});
test("a dialog opened from a card starts fresh and keeps its own cards", async () => {
await act(async () =>
root.render(
,
),
);
const group = container.querySelector('[role="radiogroup"]')!;
expect(group.getAttribute("data-variant")).toBe("ring");
expect(group.hasAttribute("data-rows")).toBe(false);
});
test("the skeleton keeps the question visible and says it is loading", async () => {
await act(async () => root.render());
const status = container.querySelector('[role="status"]')!;
expect(status.textContent).toContain("Organization role");
expect(status.textContent).toContain("Loading options");
});
});
describe("selectableSurface", () => {
test("adds the brand border and fill only when selected", () => {
expect(selectableSurface(false)).not.toContain("border-brand");
expect(selectableSurface(true)).toContain("border-brand");
expect(selectableSurface(true)).toContain("bg-brand/5");
});
});