import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { CustomModelDropdown } from "../CustomModelDropdown";
import type { ModelInfo } from "../../api";
const MOCK_MODELS: ModelInfo[] = [
{ provider: "anthropic", id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", reasoning: true, contextWindow: 200000 },
{ provider: "anthropic", id: "claude-opus-4", name: "Claude Opus 4", reasoning: true, contextWindow: 200000 },
{ provider: "openai", id: "gpt-4o", name: "GPT-4o", reasoning: false, contextWindow: 128000 },
{ provider: "ollama", id: "llama3", name: "Llama 3", reasoning: false, contextWindow: 4096 },
{ provider: "kimi", id: "moonshot-v1-8k", name: "Moonshot V1 8K", reasoning: false, contextWindow: 8192 },
{ provider: "moonshot", id: "moonshot-v1-32k", name: "Moonshot V1 32K", reasoning: false, contextWindow: 32768 },
{ provider: "deepseek", id: "deepseek-chat", name: "DeepSeek Chat", reasoning: false, contextWindow: 64000 },
];
const defaultProps = {
models: MOCK_MODELS,
value: "",
onChange: vi.fn(),
label: "Test Model",
id: "test-model",
};
describe("CustomModelDropdown ProviderIcon Integration", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders actual OpenAI SVG icon in trigger when OpenAI model is selected", () => {
render();
// Verify the actual OpenAI SVG icon is rendered in the trigger
const openaiIcon = screen.getByTestId("openai-icon");
expect(openaiIcon).toBeInTheDocument();
expect(openaiIcon).toHaveAttribute("aria-label", "OpenAI");
// Verify the SVG has the correct fill color (var(--provider-openai) - OpenAI green)
const paths = openaiIcon.querySelectorAll("path");
expect(paths.length).toBeGreaterThan(0);
expect(paths[0]).toHaveAttribute("fill", "var(--provider-openai)");
});
it("renders actual OpenAI SVG icon in dropdown group header", async () => {
const user = userEvent.setup();
render();
await user.click(screen.getByLabelText("Test Model"));
// When dropdown is open, there should be OpenAI icons in the group header
// Since no model is selected, the trigger won't have an icon, but the group header will
const openaiIcons = screen.getAllByTestId("openai-icon");
expect(openaiIcons.length).toBeGreaterThanOrEqual(1);
// Verify at least one has the correct aria-label
const openaiIcon = screen.getByLabelText("OpenAI");
expect(openaiIcon).toBeInTheDocument();
// Verify the icon has the correct color
const paths = openaiIcon.querySelectorAll("path");
expect(paths.length).toBeGreaterThan(0);
expect(paths[0]).toHaveAttribute("fill", "var(--provider-openai)");
});
it("renders actual provider icons for all providers in dropdown", async () => {
const user = userEvent.setup();
render();
await user.click(screen.getByLabelText("Test Model"));
// Verify all provider icons are rendered with proper SVG elements
const anthropicIcon = screen.getByTestId("anthropic-icon");
expect(anthropicIcon).toBeInTheDocument();
expect(anthropicIcon).toHaveAttribute("aria-label", "Anthropic");
expect(anthropicIcon.querySelector("path")).toHaveAttribute("fill", "var(--provider-anthropic)");
const openaiIcon = screen.getByTestId("openai-icon");
expect(openaiIcon).toBeInTheDocument();
expect(openaiIcon).toHaveAttribute("aria-label", "OpenAI");
expect(openaiIcon.querySelector("path")).toHaveAttribute("fill", "var(--provider-openai)");
const ollamaIcon = screen.getByTestId("ollama-icon");
expect(ollamaIcon).toBeInTheDocument();
expect(ollamaIcon).toHaveAttribute("aria-label", "Ollama");
// Ollama icon is intentionally theme-aware (token-based) rather than hardcoded white.
expect(ollamaIcon.querySelector("path")).toHaveAttribute("fill", "var(--text)");
});
it("renders both trigger icon and group header icons when dropdown is open with OpenAI selected", async () => {
const user = userEvent.setup();
render();
// Initially, trigger should show the OpenAI icon
expect(screen.getByTestId("openai-icon")).toBeInTheDocument();
// Open dropdown
await user.click(screen.getByLabelText("Test Model"));
// Now there should be two OpenAI icons: one in trigger, one in group header
const openaiIcons = screen.getAllByTestId("openai-icon");
expect(openaiIcons).toHaveLength(2);
// Both should have correct attributes
openaiIcons.forEach((icon) => {
expect(icon).toHaveAttribute("aria-label", "OpenAI");
expect(icon.querySelector("path")).toHaveAttribute("fill", "var(--provider-openai)");
});
});
it("renders icons with correct sizes (16px for sm size)", async () => {
const user = userEvent.setup();
render();
const openaiIcon = screen.getByTestId("openai-icon");
expect(openaiIcon).toHaveAttribute("width", "16");
expect(openaiIcon).toHaveAttribute("height", "16");
// Open dropdown and verify group header icon also has correct size
await user.click(screen.getByLabelText("Test Model"));
const openaiIcons = screen.getAllByTestId("openai-icon");
openaiIcons.forEach((icon) => {
expect(icon).toHaveAttribute("width", "16");
expect(icon).toHaveAttribute("height", "16");
});
});
it("renders Kimi brand icon for kimi provider model in dropdown", async () => {
const user = userEvent.setup();
render();
await user.click(screen.getByLabelText("Test Model"));
// Verify at least one Kimi icon is rendered in dropdown
const kimiIcons = screen.getAllByTestId("kimi-icon");
expect(kimiIcons.length).toBeGreaterThanOrEqual(1);
// Verify the first one has correct attributes
const kimiIcon = kimiIcons[0];
expect(kimiIcon).toHaveAttribute("aria-label", "Kimi");
expect(kimiIcon.querySelector("path")).toHaveAttribute("fill", "var(--provider-kimi)");
});
it("uses explicit icon/text layout hooks for favorited model rows", async () => {
const user = userEvent.setup();
render();
await user.click(screen.getByLabelText("Test Model"));
const pinnedRow = screen
.getByText("GPT-4o")
.closest(".model-combobox-option--favorite");
expect(pinnedRow).toBeInTheDocument();
const mainLayout = pinnedRow?.querySelector(".model-combobox-option-main");
expect(mainLayout).toBeInTheDocument();
const iconSlot = mainLayout?.querySelector(".model-combobox-option-icon");
expect(iconSlot).toBeInTheDocument();
expect(iconSlot?.querySelector("[data-testid='openai-icon']")).toBeInTheDocument();
const textSlot = mainLayout?.querySelector(".model-combobox-option-text");
expect(textSlot).toBeInTheDocument();
expect(textSlot).toHaveTextContent("GPT-4o");
});
it("renders Kimi brand icon for moonshot provider model in dropdown (alias)", async () => {
const user = userEvent.setup();
render();
await user.click(screen.getByLabelText("Test Model"));
// Both kimi and moonshot providers should show the same Kimi icon
const kimiIcons = screen.getAllByTestId("kimi-icon");
expect(kimiIcons.length).toBeGreaterThanOrEqual(1);
});
it("renders Kimi icon in trigger when kimi model is selected", () => {
render();
const kimiIcon = screen.getByTestId("kimi-icon");
expect(kimiIcon).toBeInTheDocument();
expect(kimiIcon).toHaveAttribute("aria-label", "Kimi");
expect(kimiIcon.querySelector("path")).toHaveAttribute("fill", "var(--provider-kimi)");
});
it("renders Kimi icon in trigger when moonshot model is selected (alias)", () => {
render();
const kimiIcon = screen.getByTestId("kimi-icon");
expect(kimiIcon).toBeInTheDocument();
expect(kimiIcon).toHaveAttribute("aria-label", "Kimi");
expect(kimiIcon.querySelector("path")).toHaveAttribute("fill", "var(--provider-kimi)");
});
it("renders DeepSeek icon in trigger and dropdown group header", async () => {
const user = userEvent.setup();
render();
expect(screen.getByTestId("deepseek-icon")).toBeInTheDocument();
await user.click(screen.getByLabelText("Test Model"));
const deepseekIcons = screen.getAllByTestId("deepseek-icon");
expect(deepseekIcons.length).toBeGreaterThanOrEqual(2);
deepseekIcons.forEach((icon) => {
expect(icon).toHaveAttribute("aria-label", "DeepSeek");
expect(icon.querySelector("path")).toHaveAttribute("fill", "var(--provider-deepseek)");
});
});
});