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"; import type { SelectOption } from "./select-menu"; // Register the DOM before React DOM and Radix load: Radix picks a no-op layout // effect when it is imported without a document, and the popover never mounts. 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 { renderToStaticMarkup } = await import("react-dom/server"); const { ComboboxSelect, MenuSelect, NativeSelect, filterSelectOptions, nativeOptionText, nextEnabledIndex, typeaheadIndex, } = await import("./select-menu"); const { Field } = await import("./field"); afterAll(() => GlobalRegistrator.unregister()); const models: SelectOption[] = [ { value: "codex:gpt-6-astra", label: "GPT-6 Astra", meta: "Codex plan", description: "The most capable model for long, careful work.", }, { value: "codex:gpt-6-luna", label: "GPT-6 Luna", meta: "Codex plan", description: "Fast and light, for quick questions and edits.", }, { value: "codex:gpt-6-sol", label: "GPT-6 Sol", meta: "Codex plan", description: "Balanced speed and depth for everyday work.", }, { value: "credits:gpt-6-astra", label: "GPT-6 Astra", meta: "Opengeni credits", disabled: true, disabledReason: "No credit balance. An owner can add credits in Billing.", }, ]; describe("option helpers", () => { test("search matches every word across label, meta, description and keywords", () => { expect(filterSelectOptions(models, "astra credits").map((option) => option.value)).toEqual([ "credits:gpt-6-astra", ]); expect(filterSelectOptions(models, " QUICK ").map((option) => option.label)).toEqual([ "GPT-6 Luna", ]); expect( filterSelectOptions( [{ value: "Europe/Oslo", label: "Oslo time", keywords: ["Europe/Oslo"] }], "europe", ), ).toHaveLength(1); expect(filterSelectOptions(models, "")).toHaveLength(4); }); test("arrow movement skips unavailable options and wraps", () => { expect(nextEnabledIndex(models, 2, 1)).toBe(0); expect(nextEnabledIndex(models, 0, -1)).toBe(2); expect(nextEnabledIndex([{ value: "x", label: "X", disabled: true }], 0, 1)).toBe(-1); }); test("typeahead cycles on a repeated letter and matches a typed prefix", () => { const roles: SelectOption[] = [ { value: "viewer", label: "Viewer" }, { value: "member", label: "Member" }, { value: "workspace_admin", label: "Workspace admin" }, { value: "maintainer", label: "Maintainer" }, ]; expect(typeaheadIndex(roles, "m", 1)).toBe(3); expect(typeaheadIndex(roles, "mm", 3)).toBe(1); expect(typeaheadIndex(roles, "wor", 0)).toBe(2); expect(typeaheadIndex(models, "gpt-6 astra", 3)).toBe(0); }); test("native options keep the payer and say when they are unavailable", () => { expect(nativeOptionText(models[2]!)).toBe("GPT-6 Sol · Codex plan"); expect(nativeOptionText(models[3]!)).toBe("GPT-6 Astra · Opengeni credits - unavailable"); }); }); describe("native select", () => { test("shows the chosen option's description and the disabled reason", () => { const html = renderToStaticMarkup( , ); expect(html).toContain("Balanced speed and depth for everyday work."); expect(html).toContain("Only workspace admins can change the default model."); expect(html).toContain("GPT-6 Astra · Opengeni credits - unavailable"); }); test("inside a Field it takes the field's label, error and invalid state", () => { for (const Select of [NativeSelect, MenuSelect]) { const html = renderToStaticMarkup(