import { type ModelSlug, type ProviderKind } from "@t3tools/contracts"; import { page } from "vitest/browser"; import { afterEach, describe, expect, it, vi } from "vitest"; import { render } from "vitest-browser-react"; import { ProviderModelPicker } from "./ProviderModelPicker"; const MODEL_OPTIONS_BY_PROVIDER = { claudeAgent: [ { slug: "claude-opus-4-6", name: "Claude Opus 4.6" }, { slug: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, { slug: "claude-haiku-4-5", name: "Claude Haiku 4.5" }, ], codex: [ { slug: "gpt-5-codex", name: "GPT-5 Codex" }, { slug: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, ], } as const satisfies Record>; async function mountPicker(props: { provider: ProviderKind; model: ModelSlug; lockedProvider: ProviderKind | null; triggerVariant?: "ghost" | "outline"; }) { const host = document.createElement("div"); document.body.append(host); const onProviderModelChange = vi.fn(); const screen = await render( , { container: host }, ); return { onProviderModelChange, cleanup: async () => { await screen.unmount(); host.remove(); }, }; } describe("ProviderModelPicker", () => { afterEach(() => { document.body.innerHTML = ""; }); it("shows provider submenus when provider switching is allowed", async () => { const mounted = await mountPicker({ provider: "claudeAgent", model: "claude-opus-4-6", lockedProvider: null, }); try { await page.getByRole("button").click(); await vi.waitFor(() => { const text = document.body.textContent ?? ""; expect(text).toContain("Codex"); expect(text).toContain("Claude"); expect(text).not.toContain("Claude Sonnet 4.6"); }); } finally { await mounted.cleanup(); } }); it("opens provider submenus with a visible gap from the parent menu", async () => { const mounted = await mountPicker({ provider: "claudeAgent", model: "claude-opus-4-6", lockedProvider: null, }); try { await page.getByRole("button").click(); const providerTrigger = page.getByRole("menuitem", { name: "Codex" }); await providerTrigger.hover(); await vi.waitFor(() => { expect(document.body.textContent ?? "").toContain("GPT-5 Codex"); }); const providerTriggerElement = Array.from( document.querySelectorAll('[role="menuitem"]'), ).find((element) => element.textContent?.includes("Codex")); if (!providerTriggerElement) { throw new Error("Expected the Codex provider trigger to be mounted."); } const providerTriggerRect = providerTriggerElement.getBoundingClientRect(); const modelElement = Array.from( document.querySelectorAll('[role="menuitemradio"]'), ).find((element) => element.textContent?.includes("GPT-5 Codex")); if (!modelElement) { throw new Error("Expected the submenu model option to be mounted."); } const submenuPopup = modelElement.closest('[data-slot="menu-sub-content"]'); if (!(submenuPopup instanceof HTMLElement)) { throw new Error("Expected submenu popup to be mounted."); } const submenuRect = submenuPopup.getBoundingClientRect(); expect(submenuRect.left).toBeGreaterThanOrEqual(providerTriggerRect.right); expect(submenuRect.left - providerTriggerRect.right).toBeGreaterThanOrEqual(2); } finally { await mounted.cleanup(); } }); it("shows models directly when the provider is locked mid-thread", async () => { const mounted = await mountPicker({ provider: "claudeAgent", model: "claude-opus-4-6", lockedProvider: "claudeAgent", }); try { await page.getByRole("button").click(); await vi.waitFor(() => { const text = document.body.textContent ?? ""; expect(text).toContain("Claude Sonnet 4.6"); expect(text).toContain("Claude Haiku 4.5"); expect(text).not.toContain("Codex"); }); } finally { await mounted.cleanup(); } }); it("dispatches the canonical slug when a model is selected", async () => { const mounted = await mountPicker({ provider: "claudeAgent", model: "claude-opus-4-6", lockedProvider: "claudeAgent", }); try { await page.getByRole("button").click(); await page.getByRole("menuitemradio", { name: "Claude Sonnet 4.6" }).click(); expect(mounted.onProviderModelChange).toHaveBeenCalledWith( "claudeAgent", "claude-sonnet-4-6", ); } finally { await mounted.cleanup(); } }); it("accepts outline trigger styling", async () => { const mounted = await mountPicker({ provider: "codex", model: "gpt-5-codex", lockedProvider: null, triggerVariant: "outline", }); try { const button = document.querySelector("button"); if (!(button instanceof HTMLButtonElement)) { throw new Error("Expected picker trigger button to be rendered."); } expect(button.className).toContain("border-input"); expect(button.className).toContain("bg-popover"); } finally { await mounted.cleanup(); } }); });