Show Xiaomi branding for direct provider names and MiMo model labels. - Add an accessible, tokenized Xiaomi provider mark - Infer boundary-safe MiMo labels through the shared provider icon key - Reuse shared inference in usage cards and cover Xiaomi mappings - Add a patch changeset for Xiaomi provider branding Files changed: .changeset/fn-8500-xiaomi-provider-icon.md | 7 +++ packages/dashboard/app/components/ProviderIcon.tsx | 24 +++++++++ .../dashboard/app/components/UsageIndicator.tsx | 58 +++------------------- .../app/components/__tests__/ProviderIcon.test.tsx | 48 ++++++++++++++++++ .../components/__tests__/UsageIndicator.test.tsx | 17 +++++++ packages/dashboard/app/styles.css | 1 + .../app/utils/__tests__/providerIconKey.test.ts | 9 ++++ packages/dashboard/app/utils/providerIconKey.ts | 8 +++ 8 files changed, 120 insertions(+), 52 deletions(-) Fusion-Task-Id: FN-8500 Fusion-Task-Lineage: 1784a180-cf41-4666-a59e-10aa24f47b72 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
872 lines
37 KiB
TypeScript
872 lines
37 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { ProviderIcon } from "../ProviderIcon";
|
|
import {
|
|
STATIC_API_KEY_PROVIDER_CATALOG,
|
|
STATIC_OAUTH_PROVIDER_CATALOG,
|
|
} from "../../../src/routes/auth-provider-catalog";
|
|
|
|
// These synthetic/runtime IDs have no static auth-catalog source. Keep their aliases
|
|
// explicit so adding a base integration without its runtime spellings fails closed.
|
|
const SYNTHETIC_FIRST_CLASS_PROVIDER_IDS = [
|
|
"anthropic-subscription",
|
|
"claude-cli",
|
|
"pi-claude-cli",
|
|
"droid-cli",
|
|
"cursor-cli",
|
|
"grok-cli",
|
|
"omp-cli",
|
|
"llama-cpp",
|
|
"llama-server",
|
|
"hermes",
|
|
"hermes-agent",
|
|
"hermesagent",
|
|
"openclaw",
|
|
"open-claw",
|
|
"paperclip",
|
|
"paperclipai",
|
|
"paperclip-ai",
|
|
"omp",
|
|
"oh-my-pi",
|
|
"xiaomi",
|
|
] as const;
|
|
|
|
const FIRST_CLASS_PROVIDER_IDS = [...new Set([
|
|
...STATIC_OAUTH_PROVIDER_CATALOG.map(({ id }) => id),
|
|
...STATIC_API_KEY_PROVIDER_CATALOG.map(({ id }) => id),
|
|
...SYNTHETIC_FIRST_CLASS_PROVIDER_IDS,
|
|
])];
|
|
|
|
/** The fallback's structural signature is Lucide's actual `lucide-cpu` SVG class, never a brand sticker. */
|
|
function hasLucideCpuSignature(svg: SVGElement): boolean {
|
|
return svg.classList.contains("lucide-cpu");
|
|
}
|
|
|
|
function isNonCpuMark(svg: SVGElement): boolean {
|
|
return !hasLucideCpuSignature(svg);
|
|
}
|
|
|
|
describe("ProviderIcon", () => {
|
|
it("renders OpenAI brand icon for openai-codex provider", () => {
|
|
render(<ProviderIcon provider="openai-codex" />);
|
|
expect(screen.getByTestId("openai-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("OpenAI Codex")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies provider-specific color for openai-codex", () => {
|
|
render(<ProviderIcon provider="openai-codex" />);
|
|
const icon = screen.getByTestId("openai-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-openai)" });
|
|
});
|
|
|
|
it("passes correct color to SVG fill for openai-codex", () => {
|
|
render(<ProviderIcon provider="openai-codex" />);
|
|
const svg = screen.getByTestId("openai-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-openai)");
|
|
});
|
|
|
|
it("renders Anthropic brand icon for anthropic provider", () => {
|
|
render(<ProviderIcon provider="anthropic" />);
|
|
expect(screen.getByTestId("anthropic-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Anthropic")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders claude-cli icon with tokenized contrast stroke", () => {
|
|
render(<ProviderIcon provider="claude-cli" />);
|
|
const svg = screen.getByTestId("claude-cli-icon");
|
|
expect(svg).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Anthropic — via Claude CLI")).toBeInTheDocument();
|
|
const badgeGlyph = svg.querySelector('path[stroke]');
|
|
expect(badgeGlyph).toHaveAttribute("stroke", "var(--provider-icon-contrast)");
|
|
});
|
|
|
|
it("renders pi-claude-cli with Claude CLI icon, label, and provider color", () => {
|
|
render(<ProviderIcon provider="pi-claude-cli" />);
|
|
const svg = screen.getByTestId("claude-cli-icon");
|
|
expect(svg).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Anthropic — via Claude CLI")).toBeInTheDocument();
|
|
const wrapper = svg.parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "pi-claude-cli");
|
|
expect(wrapper).toHaveStyle({ color: "var(--provider-anthropic)" });
|
|
});
|
|
|
|
it("renders droid-cli icon with tokenized contrast stroke", () => {
|
|
render(<ProviderIcon provider="droid-cli" />);
|
|
const svg = screen.getByTestId("droid-cli-icon");
|
|
expect(svg).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Factory AI — via Droid CLI")).toBeInTheDocument();
|
|
const badgeGlyph = svg.querySelector('path[stroke="var(--provider-icon-contrast)"]');
|
|
expect(badgeGlyph).toBeInTheDocument();
|
|
expect(svg.parentElement).toHaveStyle({ color: "var(--provider-openai)" });
|
|
});
|
|
|
|
it("renders cursor-cli icon with provider token color and official brand mark", () => {
|
|
render(<ProviderIcon provider="cursor-cli" />);
|
|
const svg = screen.getByTestId("cursor-cli-icon");
|
|
expect(svg).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Cursor — via Cursor CLI")).toBeInTheDocument();
|
|
expect(svg.querySelector('path[data-cursor-brand-mark="cube"]')).toHaveAttribute("fill", "var(--provider-cursor-cli)");
|
|
expect(svg.querySelector('path[data-cursor-brand-mark="arrow"]')).toHaveAttribute("fill", "var(--provider-icon-contrast)");
|
|
expect(svg.parentElement).toHaveStyle({ color: "var(--provider-cursor-cli)" });
|
|
});
|
|
|
|
it("infers cursor provider strings to the cursor-cli brand icon", () => {
|
|
render(<ProviderIcon provider="cursor" />);
|
|
expect(screen.getByTestId("cursor-cli-icon")).toBeInTheDocument();
|
|
expect(document.querySelector('[data-provider="cursor"] svg:not([data-testid])')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("renders the OMP brand icon, auth label, and official tokenized gradient for omp-cli", () => {
|
|
render(<ProviderIcon provider="omp-cli" />);
|
|
const svg = screen.getByTestId("omp-icon");
|
|
const gradient = svg.querySelector("linearGradient");
|
|
const path = svg.querySelector("path");
|
|
|
|
expect(svg).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Oh My Pi — via omp ACP")).toBeInTheDocument();
|
|
expect(svg.parentElement).toHaveAttribute("data-provider", "omp-cli");
|
|
expect(svg.parentElement).toHaveStyle({ color: "var(--provider-omp)" });
|
|
expect(path).toHaveAttribute("fill", `url(#${gradient?.id})`);
|
|
expect(gradient).toHaveAttribute("x1", "0");
|
|
expect(gradient).toHaveAttribute("y1", "0");
|
|
expect(gradient).toHaveAttribute("x2", "1");
|
|
expect(gradient).toHaveAttribute("y2", "1");
|
|
expect(Array.from(gradient?.querySelectorAll("stop") ?? []).map((stop) => stop.getAttribute("stop-color"))).toEqual([
|
|
"var(--provider-omp-gradient-start)",
|
|
"var(--provider-omp-gradient-mid)",
|
|
"var(--provider-omp-gradient-end)",
|
|
]);
|
|
});
|
|
|
|
it("assigns unique gradient ids to simultaneous OMP icons", () => {
|
|
const { container } = render(
|
|
<>
|
|
<ProviderIcon provider="omp-cli" />
|
|
<ProviderIcon provider="omp" />
|
|
</>,
|
|
);
|
|
const gradients = Array.from(container.querySelectorAll("[data-testid='omp-icon'] linearGradient"));
|
|
const paths = Array.from(container.querySelectorAll("[data-testid='omp-icon'] path"));
|
|
|
|
expect(gradients).toHaveLength(2);
|
|
expect(new Set(gradients.map((gradient) => gradient.id)).size).toBe(2);
|
|
expect(paths.map((path) => path.getAttribute("fill"))).toEqual(gradients.map((gradient) => `url(#${gradient.id})`));
|
|
});
|
|
|
|
it.each(["omp", "oh-my-pi"])("renders the OMP brand icon for the %s alias", (provider) => {
|
|
render(<ProviderIcon provider={provider} />);
|
|
const svg = screen.getByTestId("omp-icon");
|
|
expect(svg.parentElement).toHaveAttribute("data-provider", provider);
|
|
expect(svg.parentElement).toHaveStyle({ color: "var(--provider-omp)" });
|
|
});
|
|
|
|
it("infers OMP model-provider strings to the OMP brand icon", () => {
|
|
render(<ProviderIcon provider="omp/claude-sonnet-4" />);
|
|
expect(screen.getByTestId("omp-icon")).toBeInTheDocument();
|
|
expect(document.querySelector('[data-provider="omp/claude-sonnet-4"] svg:not([data-testid])')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("renders grok-cli icon reusing the xAI brand mark", () => {
|
|
render(<ProviderIcon provider="grok-cli" />);
|
|
const svg = screen.getByTestId("xai-icon");
|
|
expect(svg).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Grok — via Grok CLI")).toBeInTheDocument();
|
|
expect(svg.parentElement).toHaveStyle({ color: "var(--text)" });
|
|
});
|
|
|
|
it("normalizes PI-Claude-CLI provider name to lowercase alias", () => {
|
|
render(<ProviderIcon provider="PI-Claude-CLI" />);
|
|
const svg = screen.getByTestId("claude-cli-icon");
|
|
expect(svg).toBeInTheDocument();
|
|
expect(svg.parentElement).toHaveAttribute("data-provider", "pi-claude-cli");
|
|
});
|
|
|
|
it("renders OpenAI brand icon for openai provider", () => {
|
|
render(<ProviderIcon provider="openai" />);
|
|
expect(screen.getByTestId("openai-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("OpenAI")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Gemini brand icon for google provider", () => {
|
|
render(<ProviderIcon provider="google" />);
|
|
expect(screen.getByTestId("gemini-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Google Gemini")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Gemini brand icon for gemini provider", () => {
|
|
render(<ProviderIcon provider="gemini" />);
|
|
expect(screen.getByTestId("gemini-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Google Gemini")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Gemini brand icon for google-antigravity provider", () => {
|
|
render(<ProviderIcon provider="google-antigravity" />);
|
|
expect(screen.getByTestId("gemini-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Google Gemini")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Ollama brand icon for ollama provider", () => {
|
|
render(<ProviderIcon provider="ollama" />);
|
|
expect(screen.getByTestId("ollama-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Ollama")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders llama.cpp aliases as the intentional non-Lucide-Cpu mark", () => {
|
|
const { rerender } = render(<ProviderIcon provider="llama-cpp" />);
|
|
expect(isNonCpuMark(screen.getByTestId("llama-cpp-icon"))).toBe(true);
|
|
rerender(<ProviderIcon provider="llama-server" />);
|
|
expect(isNonCpuMark(screen.getByTestId("llama-cpp-icon"))).toBe(true);
|
|
});
|
|
|
|
it("ratchets every catalog-derived and enumerated first-class ID to an accessible non-Cpu mark", () => {
|
|
for (const provider of FIRST_CLASS_PROVIDER_IDS) {
|
|
const { container, unmount } = render(<ProviderIcon provider={provider} />);
|
|
const wrapper = container.querySelector<HTMLElement>(`.provider-icon[data-provider="${provider}"]`);
|
|
const svg = wrapper?.querySelector<SVGElement>("svg");
|
|
|
|
expect(wrapper, provider).toBeTruthy();
|
|
expect(svg, provider).toHaveAttribute("data-testid");
|
|
expect(svg, provider).toHaveAccessibleName(/\S+/);
|
|
expect(isNonCpuMark(svg as SVGElement), provider).toBe(true);
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("rejects a Lucide Cpu even when a brand testid or configured sticker is attached", () => {
|
|
const cpuWithStickers = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
cpuWithStickers.setAttribute("class", "lucide lucide-cpu");
|
|
cpuWithStickers.setAttribute("data-testid", "brave-icon");
|
|
cpuWithStickers.setAttribute("data-configured-icon", "true");
|
|
|
|
expect(isNonCpuMark(cpuWithStickers)).toBe(false);
|
|
});
|
|
|
|
it("renders Cpu icon as fallback for unknown providers", () => {
|
|
render(<ProviderIcon provider="unknown" />);
|
|
expect(screen.queryByTestId("xai-icon")).not.toBeInTheDocument();
|
|
// Cpu icon from lucide-react renders as an svg without our custom data-testid
|
|
const icon = screen.getByText((_, element) => {
|
|
return element?.tagName.toLowerCase() === "svg" &&
|
|
element?.parentElement?.getAttribute("data-provider") === "unknown";
|
|
});
|
|
expect(icon).toBeInTheDocument();
|
|
expect(hasLucideCpuSignature(icon as SVGElement)).toBe(true);
|
|
});
|
|
|
|
it("renders Cpu icon as fallback for empty provider", () => {
|
|
render(<ProviderIcon provider="" />);
|
|
expect(screen.queryByTestId("xai-icon")).not.toBeInTheDocument();
|
|
const icon = screen.getByText((_, element) => {
|
|
return element?.tagName.toLowerCase() === "svg" &&
|
|
element?.parentElement?.getAttribute("data-provider") === "";
|
|
});
|
|
expect(icon).toBeInTheDocument();
|
|
});
|
|
|
|
it("normalizes provider name to lowercase", () => {
|
|
render(<ProviderIcon provider="Anthropic" />);
|
|
expect(screen.getByTestId("anthropic-icon")).toBeInTheDocument();
|
|
const wrapper = screen.getByTestId("anthropic-icon").parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "anthropic");
|
|
});
|
|
|
|
it("normalizes Google-Antigravity (capitalized) to google-antigravity", () => {
|
|
render(<ProviderIcon provider="Google-Antigravity" />);
|
|
expect(screen.getByTestId("gemini-icon")).toBeInTheDocument();
|
|
const wrapper = screen.getByTestId("gemini-icon").parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "google-antigravity");
|
|
});
|
|
|
|
it("applies provider-specific color for anthropic", () => {
|
|
render(<ProviderIcon provider="anthropic" />);
|
|
const icon = screen.getByTestId("anthropic-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-anthropic)" });
|
|
});
|
|
|
|
it("applies provider-specific color for openai", () => {
|
|
render(<ProviderIcon provider="openai" />);
|
|
const icon = screen.getByTestId("openai-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-openai)" });
|
|
});
|
|
|
|
it("applies provider-specific color for google", () => {
|
|
render(<ProviderIcon provider="google" />);
|
|
const icon = screen.getByTestId("gemini-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-gemini)" });
|
|
});
|
|
|
|
it("applies provider-specific color for google-antigravity", () => {
|
|
render(<ProviderIcon provider="google-antigravity" />);
|
|
const icon = screen.getByTestId("gemini-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-gemini)" });
|
|
});
|
|
|
|
it("applies theme-safe color for ollama", () => {
|
|
render(<ProviderIcon provider="ollama" />);
|
|
const icon = screen.getByTestId("ollama-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--text)" });
|
|
});
|
|
|
|
it("applies default color for unknown providers", () => {
|
|
render(<ProviderIcon provider="unknown" />);
|
|
const icon = document.querySelector('[data-provider="unknown"]');
|
|
expect(icon).toHaveStyle({ color: "var(--text-muted)" });
|
|
});
|
|
|
|
it("sets data-provider attribute with normalized provider name", () => {
|
|
render(<ProviderIcon provider="OpenAI" />);
|
|
const icon = screen.getByTestId("openai-icon").parentElement;
|
|
expect(icon).toHaveAttribute("data-provider", "openai");
|
|
});
|
|
|
|
it("uses sm size (16px) by default", () => {
|
|
render(<ProviderIcon provider="anthropic" />);
|
|
const icon = screen.getByTestId("anthropic-icon");
|
|
expect(icon).toHaveAttribute("width", "16");
|
|
expect(icon).toHaveAttribute("height", "16");
|
|
});
|
|
|
|
it("uses sm size when explicitly specified", () => {
|
|
render(<ProviderIcon provider="anthropic" size="sm" />);
|
|
const icon = screen.getByTestId("anthropic-icon");
|
|
expect(icon).toHaveAttribute("width", "16");
|
|
expect(icon).toHaveAttribute("height", "16");
|
|
});
|
|
|
|
it("uses md size (20px) when specified", () => {
|
|
render(<ProviderIcon provider="anthropic" size="md" />);
|
|
const icon = screen.getByTestId("anthropic-icon");
|
|
expect(icon).toHaveAttribute("width", "20");
|
|
expect(icon).toHaveAttribute("height", "20");
|
|
});
|
|
|
|
it("uses lg size (24px) when specified", () => {
|
|
render(<ProviderIcon provider="anthropic" size="lg" />);
|
|
const icon = screen.getByTestId("anthropic-icon");
|
|
expect(icon).toHaveAttribute("width", "24");
|
|
expect(icon).toHaveAttribute("height", "24");
|
|
});
|
|
|
|
it("renders with className provider-icon", () => {
|
|
render(<ProviderIcon provider="anthropic" />);
|
|
const icon = screen.getByTestId("anthropic-icon").parentElement;
|
|
expect(icon).toHaveClass("provider-icon");
|
|
});
|
|
|
|
it("passes correct color to SVG fill for anthropic", () => {
|
|
render(<ProviderIcon provider="anthropic" />);
|
|
const svg = screen.getByTestId("anthropic-icon");
|
|
// The SVG should have the color in its path fill
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
// First path should have the provider color
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-anthropic)");
|
|
});
|
|
|
|
it("passes correct color to SVG fill for openai", () => {
|
|
render(<ProviderIcon provider="openai" />);
|
|
const svg = screen.getByTestId("openai-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-openai)");
|
|
});
|
|
|
|
it("passes correct color to SVG fill for gemini", () => {
|
|
render(<ProviderIcon provider="gemini" />);
|
|
const svg = screen.getByTestId("gemini-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-gemini)");
|
|
});
|
|
|
|
it("passes correct color to SVG fill for google-antigravity", () => {
|
|
render(<ProviderIcon provider="google-antigravity" />);
|
|
const svg = screen.getByTestId("gemini-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-gemini)");
|
|
});
|
|
|
|
it("passes theme-safe color to SVG fill for ollama", () => {
|
|
render(<ProviderIcon provider="ollama" />);
|
|
const svg = screen.getByTestId("ollama-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--text)");
|
|
});
|
|
|
|
it("renders MiniMax brand icon for minimax provider", () => {
|
|
render(<ProviderIcon provider="minimax" />);
|
|
expect(screen.getByTestId("minimax-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("MiniMax")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies provider-specific color for minimax", () => {
|
|
render(<ProviderIcon provider="minimax" />);
|
|
const icon = screen.getByTestId("minimax-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-minimax)" });
|
|
});
|
|
|
|
it("passes correct color to SVG fill for minimax", () => {
|
|
render(<ProviderIcon provider="minimax" />);
|
|
const svg = screen.getByTestId("minimax-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-minimax)");
|
|
});
|
|
|
|
it("normalizes Minimax (capitalized) to minimax", () => {
|
|
render(<ProviderIcon provider="Minimax" />);
|
|
expect(screen.getByTestId("minimax-icon")).toBeInTheDocument();
|
|
const wrapper = screen.getByTestId("minimax-icon").parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "minimax");
|
|
});
|
|
|
|
it.each([
|
|
["xiaomi", "xiaomi"],
|
|
["XIAOMI", "xiaomi"],
|
|
])("renders an accessible tokenized Xiaomi mark for %s", (provider, expectedDataProvider) => {
|
|
render(<ProviderIcon provider={provider} size="md" />);
|
|
const svg = screen.getByTestId("xiaomi-icon");
|
|
const wrapper = svg.parentElement;
|
|
|
|
expect(svg).toHaveAccessibleName("Xiaomi");
|
|
expect(isNonCpuMark(svg)).toBe(true);
|
|
expect(wrapper).toHaveAttribute("data-provider", expectedDataProvider);
|
|
expect(wrapper).toHaveStyle({ color: "var(--provider-xiaomi)" });
|
|
expect(svg.querySelector("path")).toHaveAttribute("fill", "var(--provider-xiaomi)");
|
|
expect(svg).toHaveAttribute("width", "20");
|
|
expect(svg).toHaveAttribute("height", "20");
|
|
});
|
|
|
|
it("renders independent Xiaomi SVGs for duplicate provider entries", () => {
|
|
const { container } = render(
|
|
<>
|
|
<ProviderIcon provider="xiaomi" size="sm" />
|
|
<ProviderIcon provider="xiaomi" size="lg" />
|
|
</>,
|
|
);
|
|
const icons = container.querySelectorAll("[data-testid='xiaomi-icon']");
|
|
|
|
expect(icons).toHaveLength(2);
|
|
expect(Array.from(icons).map((icon) => icon.getAttribute("width"))).toEqual(["16", "24"]);
|
|
expect(Array.from(icons).every((icon) => isNonCpuMark(icon as SVGElement))).toBe(true);
|
|
});
|
|
|
|
it("renders Z.ai brand icon for zai provider", () => {
|
|
render(<ProviderIcon provider="zai" />);
|
|
expect(screen.getByTestId("zai-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Z.ai")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies provider-specific color for zai", () => {
|
|
render(<ProviderIcon provider="zai" />);
|
|
const icon = screen.getByTestId("zai-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-zai)" });
|
|
});
|
|
|
|
it("passes correct color to SVG fill for zai", () => {
|
|
render(<ProviderIcon provider="zai" />);
|
|
const svg = screen.getByTestId("zai-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-zai)");
|
|
});
|
|
|
|
it("normalizes Zai (capitalized) to zai", () => {
|
|
render(<ProviderIcon provider="Zai" />);
|
|
expect(screen.getByTestId("zai-icon")).toBeInTheDocument();
|
|
const wrapper = screen.getByTestId("zai-icon").parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "zai");
|
|
});
|
|
|
|
it("renders Kimi brand icon for kimi provider", () => {
|
|
render(<ProviderIcon provider="kimi" />);
|
|
expect(screen.getByTestId("kimi-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Kimi")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies provider-specific color for kimi", () => {
|
|
render(<ProviderIcon provider="kimi" />);
|
|
const icon = screen.getByTestId("kimi-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-kimi)" });
|
|
});
|
|
|
|
it("passes correct color to SVG fill for kimi", () => {
|
|
render(<ProviderIcon provider="kimi" />);
|
|
const svg = screen.getByTestId("kimi-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-kimi)");
|
|
});
|
|
|
|
it("normalizes Kimi (capitalized) to kimi", () => {
|
|
render(<ProviderIcon provider="Kimi" />);
|
|
expect(screen.getByTestId("kimi-icon")).toBeInTheDocument();
|
|
const wrapper = screen.getByTestId("kimi-icon").parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "kimi");
|
|
});
|
|
|
|
it("renders Kimi brand icon for moonshot provider (alias)", () => {
|
|
render(<ProviderIcon provider="moonshot" />);
|
|
expect(screen.getByTestId("kimi-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Kimi")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies provider-specific color for moonshot (alias)", () => {
|
|
render(<ProviderIcon provider="moonshot" />);
|
|
const icon = screen.getByTestId("kimi-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-kimi)" });
|
|
});
|
|
|
|
it("passes correct color to SVG fill for moonshot (alias)", () => {
|
|
render(<ProviderIcon provider="moonshot" />);
|
|
const svg = screen.getByTestId("kimi-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-kimi)");
|
|
});
|
|
|
|
it("normalizes Moonshot (capitalized) to moonshot", () => {
|
|
render(<ProviderIcon provider="Moonshot" />);
|
|
expect(screen.getByTestId("kimi-icon")).toBeInTheDocument();
|
|
const wrapper = screen.getByTestId("kimi-icon").parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "moonshot");
|
|
});
|
|
|
|
it("renders OpenRouter brand icon for openrouter provider", () => {
|
|
render(<ProviderIcon provider="openrouter" />);
|
|
expect(screen.getByTestId("openrouter-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("OpenRouter")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders GitHub brand icon for github provider", () => {
|
|
render(<ProviderIcon provider="github" />);
|
|
expect(screen.getByTestId("github-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("GitHub")).toBeInTheDocument();
|
|
});
|
|
|
|
it("reuses GitHub icon for github-copilot alias", () => {
|
|
render(<ProviderIcon provider="github-copilot" />);
|
|
expect(screen.getByTestId("github-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("GitHub Copilot")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Kimi brand icon for kimi-coding provider (alias)", () => {
|
|
render(<ProviderIcon provider="kimi-coding" />);
|
|
expect(screen.getByTestId("kimi-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Kimi")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies provider-specific color for kimi-coding (alias)", () => {
|
|
render(<ProviderIcon provider="kimi-coding" />);
|
|
const icon = screen.getByTestId("kimi-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-kimi)" });
|
|
});
|
|
|
|
it("passes correct color to SVG fill for kimi-coding (alias)", () => {
|
|
render(<ProviderIcon provider="kimi-coding" />);
|
|
const svg = screen.getByTestId("kimi-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-kimi)");
|
|
});
|
|
|
|
// xAI provider tests
|
|
it.each(["xai", "grok", "grok-cli"])("renders xAI brand icon for exact %s provider key", (provider) => {
|
|
render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("xai-icon")).toBeInTheDocument();
|
|
});
|
|
|
|
it.each([
|
|
["grok-4.5", "grok-4.5"],
|
|
["grok-4-fast", "grok-4-fast"],
|
|
["grok-cli/grok-4-fast", "grok-cli/grok-4-fast"],
|
|
["xai-grok-4", "xai-grok-4"],
|
|
["Grok-4.5", "grok-4.5"],
|
|
])("renders xAI brand icon for inferred Grok model/provider string %s", (provider, expectedDataProvider) => {
|
|
render(<ProviderIcon provider={provider} />);
|
|
const icon = screen.getByTestId("xai-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--text)" });
|
|
expect(icon).toHaveAttribute("data-provider", expectedDataProvider);
|
|
});
|
|
|
|
it.each(["xiaomi/MiMo-V2-Flash", "MiMo-V2-Flash"])('infers Xiaomi icon from the boundary-safe MiMo model label %s', (provider) => {
|
|
render(<ProviderIcon provider={provider} />);
|
|
const svg = screen.getByTestId("xiaomi-icon");
|
|
|
|
expect(svg).toHaveAccessibleName("Xiaomi");
|
|
expect(isNonCpuMark(svg)).toBe(true);
|
|
});
|
|
|
|
it("preserves the CPU fallback for Xiaomi near-match custom names", () => {
|
|
const { container } = render(<ProviderIcon provider="mimosa-v1" />);
|
|
const svg = container.querySelector<SVGElement>('[data-provider="mimosa-v1"] svg');
|
|
|
|
expect(svg).toBeTruthy();
|
|
expect(hasLucideCpuSignature(svg as SVGElement)).toBe(true);
|
|
});
|
|
|
|
it("infers non-Grok provider icons from model-shaped ids", () => {
|
|
render(<ProviderIcon provider="gpt-5.5" />);
|
|
const icon = screen.getByTestId("openai-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-openai)" });
|
|
expect(icon).toHaveAttribute("data-provider", "gpt-5.5");
|
|
});
|
|
|
|
it("renders xAI brand icon for xai provider", () => {
|
|
render(<ProviderIcon provider="xai" />);
|
|
expect(screen.getByTestId("xai-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("xAI")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies theme-safe color for xai", () => {
|
|
render(<ProviderIcon provider="xai" />);
|
|
const icon = screen.getByTestId("xai-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--text)" });
|
|
});
|
|
|
|
it("passes correct color to SVG fill for xai", () => {
|
|
render(<ProviderIcon provider="xai" />);
|
|
const svg = screen.getByTestId("xai-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--text)");
|
|
});
|
|
|
|
it("normalizes Xai (capitalized) to xai", () => {
|
|
render(<ProviderIcon provider="Xai" />);
|
|
expect(screen.getByTestId("xai-icon")).toBeInTheDocument();
|
|
const wrapper = screen.getByTestId("xai-icon").parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "xai");
|
|
});
|
|
|
|
it("renders xAI brand icon for grok provider (alias)", () => {
|
|
render(<ProviderIcon provider="grok" />);
|
|
expect(screen.getByTestId("xai-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("xAI")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies theme-safe color for grok (alias)", () => {
|
|
render(<ProviderIcon provider="grok" />);
|
|
const icon = screen.getByTestId("xai-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--text)" });
|
|
});
|
|
|
|
// Opencode provider tests
|
|
it("renders Opencode brand icon for opencode provider", () => {
|
|
render(<ProviderIcon provider="opencode" />);
|
|
expect(screen.getByTestId("opencode-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Opencode")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies provider-specific color for opencode", () => {
|
|
render(<ProviderIcon provider="opencode" />);
|
|
const icon = screen.getByTestId("opencode-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-opencode)" });
|
|
});
|
|
|
|
it("passes correct color to SVG fill for opencode", () => {
|
|
render(<ProviderIcon provider="opencode" />);
|
|
const svg = screen.getByTestId("opencode-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-opencode)");
|
|
});
|
|
|
|
it("normalizes Opencode (capitalized) to opencode", () => {
|
|
render(<ProviderIcon provider="Opencode" />);
|
|
expect(screen.getByTestId("opencode-icon")).toBeInTheDocument();
|
|
const wrapper = screen.getByTestId("opencode-icon").parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "opencode");
|
|
});
|
|
|
|
// Regression test: verify the Kimi icon is the crescent moon shape, not the old "K" placeholder
|
|
it("renders crescent moon icon geometry (not the old K placeholder)", () => {
|
|
render(<ProviderIcon provider="kimi" />);
|
|
const svg = screen.getByTestId("kimi-icon");
|
|
const path = svg.querySelector("path");
|
|
expect(path).toBeInTheDocument();
|
|
|
|
// The old "K" placeholder path was: "M5.5 5.5h8v2.5h-5v2h3.5v2.5h-3.5v6.5h-3z"
|
|
// The new crescent moon path should contain "a9 9" (circle arc) not "h8" (horizontal line)
|
|
const pathD = path?.getAttribute("d") || "";
|
|
expect(pathD).not.toBe("M5.5 5.5h8v2.5h-5v2h3.5v2.5h-3.5v6.5h-3z");
|
|
// Verify the new crescent moon geometry: contains circle arc notation "a9 9"
|
|
expect(pathD).toContain("a9 9");
|
|
});
|
|
|
|
it("renders Bedrock brand icon for bedrock provider", () => {
|
|
render(<ProviderIcon provider="bedrock" />);
|
|
expect(screen.getByTestId("bedrock-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Amazon Bedrock")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies provider-specific color for bedrock", () => {
|
|
render(<ProviderIcon provider="bedrock" />);
|
|
const icon = screen.getByTestId("bedrock-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-bedrock)" });
|
|
});
|
|
|
|
it("passes correct color to SVG fill for bedrock", () => {
|
|
render(<ProviderIcon provider="bedrock" />);
|
|
const svg = screen.getByTestId("bedrock-icon");
|
|
const paths = svg.querySelectorAll("path");
|
|
expect(paths.length).toBeGreaterThan(0);
|
|
expect(paths[0]).toHaveAttribute("fill", "var(--provider-bedrock)");
|
|
});
|
|
|
|
it("normalizes Bedrock (capitalized) to bedrock", () => {
|
|
render(<ProviderIcon provider="Bedrock" />);
|
|
expect(screen.getByTestId("bedrock-icon")).toBeInTheDocument();
|
|
const wrapper = screen.getByTestId("bedrock-icon").parentElement;
|
|
expect(wrapper).toHaveAttribute("data-provider", "bedrock");
|
|
});
|
|
|
|
it("renders Bedrock brand icon for amazon-bedrock alias", () => {
|
|
render(<ProviderIcon provider="amazon-bedrock" />);
|
|
expect(screen.getByTestId("bedrock-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Amazon Bedrock")).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies provider-specific color for amazon-bedrock alias", () => {
|
|
render(<ProviderIcon provider="amazon-bedrock" />);
|
|
const icon = screen.getByTestId("bedrock-icon").parentElement;
|
|
expect(icon).toHaveStyle({ color: "var(--provider-bedrock)" });
|
|
});
|
|
|
|
// ── New providers (Qwen, LM Studio, Hugging Face, Mistral, Azure, Fireworks)
|
|
// and additional Gemini-mapped Google product aliases.
|
|
|
|
it("renders Qwen icon for qwen and its aliases", () => {
|
|
for (const provider of ["qwen", "qwen-ai", "qwen-coder", "alibaba", "tongyi"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("qwen-icon")).toBeInTheDocument();
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("renders LM Studio icon for both lmstudio and lm-studio", () => {
|
|
for (const provider of ["lmstudio", "lm-studio"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("lmstudio-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("LM Studio")).toBeInTheDocument();
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("renders Hugging Face icon for huggingface, hugging-face, and hf", () => {
|
|
for (const provider of ["huggingface", "hugging-face", "hf"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("huggingface-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Hugging Face")).toBeInTheDocument();
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("renders Mistral icon for mistral and mistral-ai", () => {
|
|
for (const provider of ["mistral", "mistral-ai"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("mistral-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Mistral AI")).toBeInTheDocument();
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("renders Azure icon for azure and azure-openai with provider-azure color", () => {
|
|
for (const provider of ["azure", "azure-openai"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("azure-icon")).toBeInTheDocument();
|
|
const wrap = screen.getByTestId("azure-icon").parentElement;
|
|
expect(wrap).toHaveStyle({ color: "var(--provider-azure)" });
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("renders Fireworks icon for fireworks, fireworks-ai, and fireworksai", () => {
|
|
for (const provider of ["fireworks", "fireworks-ai", "fireworksai"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("fireworks-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Fireworks AI")).toBeInTheDocument();
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("maps Google Vertex / Cloud Code / Antigravity all to the Gemini icon", () => {
|
|
for (const provider of ["google-vertex", "vertex", "google-cloud-code", "cloud-code", "google-antigravity", "antigravity"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("gemini-icon")).toBeInTheDocument();
|
|
const wrap = screen.getByTestId("gemini-icon").parentElement;
|
|
expect(wrap).toHaveStyle({ color: "var(--provider-gemini)" });
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
// ── pi-ai catalog gap fills (Cerebras, Groq, Vercel) and aliases for
|
|
// existing icons (minimax-cn, azure-openai-responses, google-gemini-cli,
|
|
// opencode-go).
|
|
|
|
it("renders Cerebras icon for cerebras provider", () => {
|
|
render(<ProviderIcon provider="cerebras" />);
|
|
expect(screen.getByTestId("cerebras-icon")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Groq icon for groq provider", () => {
|
|
render(<ProviderIcon provider="groq" />);
|
|
expect(screen.getByTestId("groq-icon")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Vercel icon for vercel and vercel-ai-gateway", () => {
|
|
for (const provider of ["vercel", "vercel-ai-gateway"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("vercel-icon")).toBeInTheDocument();
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("aliases minimax-cn to the MiniMax icon", () => {
|
|
render(<ProviderIcon provider="minimax-cn" />);
|
|
expect(screen.getByTestId("minimax-icon")).toBeInTheDocument();
|
|
});
|
|
|
|
it("aliases azure-openai-responses to the Azure icon", () => {
|
|
render(<ProviderIcon provider="azure-openai-responses" />);
|
|
expect(screen.getByTestId("azure-icon")).toBeInTheDocument();
|
|
});
|
|
|
|
it("aliases google-gemini-cli and google-generative-ai to the Gemini icon", () => {
|
|
for (const provider of ["google-gemini-cli", "google-generative-ai"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("gemini-icon")).toBeInTheDocument();
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("aliases opencode-go to the Opencode icon", () => {
|
|
render(<ProviderIcon provider="opencode-go" />);
|
|
expect(screen.getByTestId("opencode-icon")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders DeepSeek icon for deepseek aliases", () => {
|
|
for (const provider of ["deepseek", "deepseek-ai", "deep-seek"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("deepseek-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("DeepSeek")).toBeInTheDocument();
|
|
expect(screen.getByTestId("deepseek-icon").parentElement).toHaveStyle({ color: "var(--provider-deepseek)" });
|
|
unmount();
|
|
}
|
|
});
|
|
|
|
it("renders Cloudflare icon for cloudflare aliases", () => {
|
|
for (const provider of ["cloudflare", "cloudflared"]) {
|
|
const { unmount } = render(<ProviderIcon provider={provider} />);
|
|
expect(screen.getByTestId("cloudflare-icon")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Cloudflare")).toBeInTheDocument();
|
|
expect(screen.getByTestId("cloudflare-icon").parentElement).toHaveStyle({ color: "var(--provider-cloudflare)" });
|
|
unmount();
|
|
}
|
|
});
|
|
});
|