/**
* @vitest-environment happy-dom
*/
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";
import type {
ModelBreakdownItem,
ModelBreakdownLabels,
} from "@/components/analytics/model-breakdown-column";
import {
ModelBreakdownColumn,
ModelBreakdownRow,
} from "@/components/analytics/model-breakdown-column";
// -- mocks --
vi.mock("next-intl", () => ({
useTranslations: () => (key: string) => `t:${key}`,
}));
vi.mock("@/components/ui/dialog", () => ({
Dialog: ({ children }: { children: React.ReactNode }) => (
{children}
),
DialogContent: ({ children }: { children: React.ReactNode }) => (
{children}
),
DialogHeader: ({ children }: { children: React.ReactNode }) => (
{children}
),
DialogTitle: ({ children }: { children: React.ReactNode }) => (
{children}
),
}));
vi.mock("@/components/ui/separator", () => ({
Separator: () =>
,
}));
vi.mock("@/lib/utils/currency", async () => {
const actual =
await vi.importActual("@/lib/utils/currency");
return {
...actual,
formatCurrency: (value: number) => `$${value.toFixed(2)}`,
};
});
// -- helpers --
function makeItem(overrides: Partial = {}): ModelBreakdownItem {
return {
model: "claude-opus-4",
requests: 150,
cost: 3.5,
inputTokens: 10000,
outputTokens: 5000,
cacheCreationTokens: 2000,
cacheReadTokens: 8000,
...overrides,
};
}
const customLabels: ModelBreakdownLabels = {
unknownModel: "Custom Unknown",
modal: {
requests: "Custom Requests",
cost: "Custom Cost",
inputTokens: "Custom Input",
outputTokens: "Custom Output",
cacheCreationTokens: "Custom Cache Write",
cacheReadTokens: "Custom Cache Read",
totalTokens: "Custom Total Tokens",
costPercentage: "Custom Cost %",
cacheHitRate: "Custom Cache Hit",
cacheTokens: "Custom Cache Tokens",
performanceHigh: "Custom High",
performanceMedium: "Custom Medium",
performanceLow: "Custom Low",
},
};
function renderText(element: React.ReactElement): string {
const markup = renderToStaticMarkup(element);
const container = document.createElement("div");
// Safe: content comes from our own renderToStaticMarkup, not user input
container.textContent = "";
const template = document.createElement("template");
template.innerHTML = markup;
container.appendChild(template.content.cloneNode(true));
return container.textContent ?? "";
}
// -- tests --
describe("ModelBreakdownColumn", () => {
it("renders model name for each page item", () => {
const items = [makeItem({ model: "gpt-4.1" }), makeItem({ model: "claude-sonnet-4" })];
const text = renderText(
);
expect(text).toContain("gpt-4.1");
expect(text).toContain("claude-sonnet-4");
});
it("renders unknownModel label for null model", () => {
const items = [makeItem({ model: null })];
const text = renderText(
);
// Falls back to useTranslations which returns "t:unknownModel"
expect(text).toContain("t:unknownModel");
});
it("renders request count and token amounts", () => {
const items = [
makeItem({
requests: 42,
inputTokens: 1500,
outputTokens: 500,
cacheCreationTokens: 200,
cacheReadTokens: 300,
}),
];
const text = renderText(
);
// Request count
expect(text).toContain("42");
// Total tokens = 1500 + 500 + 200 + 300 = 2500 -> "2.5K"
expect(text).toContain("2.5K");
});
it("passes correct props to ModelBreakdownRow", () => {
const item = makeItem({ model: "test-model", cost: 5.0, requests: 99 });
const text = renderText(
);
// Model name
expect(text).toContain("test-model");
// Request count
expect(text).toContain("99");
// Cost formatted
expect(text).toContain("$5.00");
// Cost percentage = (5/10)*100 = 50.0
expect(text).toContain("50.0%");
});
it("uses custom labels when provided", () => {
const items = [makeItem({ model: null })];
const text = renderText(
);
// Custom unknown model label instead of "t:unknownModel"
expect(text).toContain("Custom Unknown");
expect(text).not.toContain("t:unknownModel");
// Custom modal labels appear in the dialog content
expect(text).toContain("Custom Requests");
expect(text).toContain("Custom Cost");
expect(text).toContain("Custom Total Tokens");
expect(text).toContain("Custom Cache Tokens");
expect(text).toContain("Custom Cache Hit");
});
});
describe("ModelBreakdownRow", () => {
it("renders model name and metrics in the row", () => {
const text = renderText(
);
expect(text).toContain("claude-opus-4");
expect(text).toContain("150");
expect(text).toContain("$3.50");
});
it("computes cache hit rate correctly", () => {
// totalInputTokens = 10000 + 2000 + 8000 = 20000
// cacheHitRate = (8000 / 20000) * 100 = 40.0
const text = renderText(
);
expect(text).toContain("40.0%");
});
it("shows zero cache hit rate when no input tokens", () => {
const text = renderText(
);
expect(text).toContain("0.0%");
});
it("uses translation fallback when no labels provided", () => {
const text = renderText(
);
// unknownModel via translation mock
expect(text).toContain("t:unknownModel");
// modal labels via translation mock
expect(text).toContain("t:modal.requests");
expect(text).toContain("t:modal.cacheWrite");
expect(text).toContain("t:modal.cacheRead");
});
it("uses custom labels when provided", () => {
const text = renderText(
);
expect(text).toContain("Custom Unknown");
expect(text).toContain("Custom Requests");
expect(text).toContain("Custom Cache Write");
expect(text).toContain("Custom Cache Read");
expect(text).toContain("Custom Cache Tokens");
expect(text).not.toContain("t:unknownModel");
});
});