mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-22 10:25:58 +00:00
Add unit tests for 5 previously untested UI components
Tests for HelpLink, ScoreChart, AgentCard, ToolPoliciesView, and CostBreakdownViewer (33 tests total). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
6549b4583f
commit
1b0c4bdbb7
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { renderWithProviders } from "../../../tests/test-utils";
|
||||
import { ScoreChart } from "./ScoreChart";
|
||||
|
||||
vi.mock("@tremor/react", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@tremor/react")>();
|
||||
return {
|
||||
...actual,
|
||||
BarChart: ({ data, categories }: { data: any[]; categories: string[] }) => (
|
||||
<div data-testid="bar-chart">
|
||||
{data.map((d, i) => (
|
||||
<span key={i}>
|
||||
{d.date}: {categories.map((c) => `${c}=${d[c]}`).join(", ")}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
describe("ScoreChart", () => {
|
||||
it("should render the title", () => {
|
||||
renderWithProviders(<ScoreChart />);
|
||||
|
||||
expect(screen.getByText("Request Outcomes Over Time")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show empty state when no data is provided", () => {
|
||||
renderWithProviders(<ScoreChart />);
|
||||
|
||||
expect(screen.getByText("No chart data for this period")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show empty state when data is an empty array", () => {
|
||||
renderWithProviders(<ScoreChart data={[]} />);
|
||||
|
||||
expect(screen.getByText("No chart data for this period")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the chart when data is provided", () => {
|
||||
const data = [
|
||||
{ date: "2026-03-01", passed: 10, blocked: 2 },
|
||||
{ date: "2026-03-02", passed: 15, blocked: 1 },
|
||||
];
|
||||
|
||||
renderWithProviders(<ScoreChart data={data} />);
|
||||
|
||||
expect(screen.queryByText("No chart data for this period")).not.toBeInTheDocument();
|
||||
expect(screen.getByText(/2026-03-01/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/2026-03-02/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../../tests/test-utils";
|
||||
import { HelpLink, HelpIcon, DocsMenu } from "./HelpLink";
|
||||
|
||||
describe("HelpLink", () => {
|
||||
it("should render with default children and open in new tab", () => {
|
||||
renderWithProviders(<HelpLink href="https://docs.example.com" />);
|
||||
|
||||
const link = screen.getByRole("link", { name: /learn more/i });
|
||||
expect(link).toHaveAttribute("href", "https://docs.example.com");
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
expect(link).toHaveAttribute("rel", "noopener noreferrer");
|
||||
});
|
||||
|
||||
it("should render custom children text", () => {
|
||||
renderWithProviders(
|
||||
<HelpLink href="https://docs.example.com">Custom docs link</HelpLink>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Custom docs link")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should include a screen-reader-only label for accessibility", () => {
|
||||
renderWithProviders(<HelpLink href="https://docs.example.com" />);
|
||||
|
||||
expect(screen.getByText("(opens in a new tab)")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HelpIcon", () => {
|
||||
it("should render a help button with accessible label", () => {
|
||||
renderWithProviders(<HelpIcon content="Some help text" />);
|
||||
|
||||
expect(screen.getByRole("button", { name: /help information/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show tooltip content on hover", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<HelpIcon content="Tooltip help text" />);
|
||||
|
||||
await user.hover(screen.getByRole("button", { name: /help information/i }));
|
||||
|
||||
expect(screen.getByText("Tooltip help text")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show learn more link when learnMoreHref is provided", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(
|
||||
<HelpIcon
|
||||
content="Help text"
|
||||
learnMoreHref="https://docs.example.com"
|
||||
learnMoreText="Read docs"
|
||||
/>
|
||||
);
|
||||
|
||||
await user.hover(screen.getByRole("button", { name: /help information/i }));
|
||||
|
||||
const link = screen.getByRole("link", { name: /read docs/i });
|
||||
expect(link).toHaveAttribute("href", "https://docs.example.com");
|
||||
});
|
||||
|
||||
it("should not show learn more link when learnMoreHref is not provided", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<HelpIcon content="Help text" />);
|
||||
|
||||
await user.hover(screen.getByRole("button", { name: /help information/i }));
|
||||
|
||||
expect(screen.queryByRole("link")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("DocsMenu", () => {
|
||||
const items = [
|
||||
{ label: "Custom pricing", href: "https://docs.example.com/pricing" },
|
||||
{ label: "Cost tracking", href: "https://docs.example.com/cost" },
|
||||
];
|
||||
|
||||
it("should render the menu button with default text", () => {
|
||||
renderWithProviders(<DocsMenu items={items} />);
|
||||
|
||||
expect(screen.getByRole("button", { name: /docs/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show menu items when button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<DocsMenu items={items} />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /docs/i }));
|
||||
|
||||
expect(screen.getByText("Custom pricing")).toBeInTheDocument();
|
||||
expect(screen.getByText("Cost tracking")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should close the menu when an item is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<DocsMenu items={items} />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /docs/i }));
|
||||
await user.click(screen.getByText("Custom pricing"));
|
||||
|
||||
expect(screen.queryByText("Cost tracking")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should set aria-expanded correctly based on menu state", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<DocsMenu items={items} />);
|
||||
|
||||
const button = screen.getByRole("button", { name: /docs/i });
|
||||
expect(button).toHaveAttribute("aria-expanded", "false");
|
||||
|
||||
await user.click(button);
|
||||
expect(button).toHaveAttribute("aria-expanded", "true");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../../tests/test-utils";
|
||||
import ToolPoliciesView from "./ToolPoliciesView";
|
||||
|
||||
vi.mock("@/components/ToolDetail", () => ({
|
||||
ToolDetail: ({ toolName, onBack }: { toolName: string; onBack: () => void }) => (
|
||||
<div>
|
||||
<span>Detail: {toolName}</span>
|
||||
<button onClick={onBack}>Back</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/ToolPolicies", () => ({
|
||||
ToolPolicies: ({ onSelectTool }: { onSelectTool: (name: string) => void }) => (
|
||||
<div>
|
||||
<span>Tool Policies Overview</span>
|
||||
<button onClick={() => onSelectTool("my-tool")}>Select Tool</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("ToolPoliciesView", () => {
|
||||
it("should render the overview by default", () => {
|
||||
renderWithProviders(<ToolPoliciesView accessToken="token" userRole="Admin" />);
|
||||
|
||||
expect(screen.getByText("Tool Policies Overview")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should navigate to tool detail when a tool is selected", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<ToolPoliciesView accessToken="token" userRole="Admin" />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /select tool/i }));
|
||||
|
||||
expect(screen.getByText("Detail: my-tool")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Tool Policies Overview")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should navigate back to overview when back is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<ToolPoliciesView accessToken="token" userRole="Admin" />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /select tool/i }));
|
||||
await user.click(screen.getByRole("button", { name: /back/i }));
|
||||
|
||||
expect(screen.getByText("Tool Policies Overview")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Detail: my-tool")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../../../tests/test-utils";
|
||||
import AgentCard from "./agent_card";
|
||||
import type { Agent } from "./types";
|
||||
|
||||
const baseAgent: Agent = {
|
||||
agent_id: "agent-123",
|
||||
agent_name: "Test Agent",
|
||||
litellm_params: { model: "gpt-4" },
|
||||
agent_card_params: {
|
||||
description: "A test agent for unit testing",
|
||||
url: "https://agent.example.com",
|
||||
},
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
agent: baseAgent,
|
||||
onAgentClick: vi.fn(),
|
||||
accessToken: "token-123",
|
||||
isAdmin: false,
|
||||
onAgentUpdated: vi.fn(),
|
||||
};
|
||||
|
||||
describe("AgentCard", () => {
|
||||
it("should render the agent name and description", () => {
|
||||
renderWithProviders(<AgentCard {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Test Agent")).toBeInTheDocument();
|
||||
expect(screen.getByText("A test agent for unit testing")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show 'No description' when agent has no description", () => {
|
||||
const agent = { ...baseAgent, agent_card_params: {} };
|
||||
renderWithProviders(<AgentCard {...defaultProps} agent={agent} />);
|
||||
|
||||
expect(screen.getByText("No description")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show the agent URL when provided", () => {
|
||||
renderWithProviders(<AgentCard {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("https://agent.example.com")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show 'Needs Setup' badge when agent has no key", () => {
|
||||
renderWithProviders(<AgentCard {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Needs Setup")).toBeInTheDocument();
|
||||
expect(screen.getByText("No key assigned")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show 'Active' badge and key info when agent has a key", () => {
|
||||
const keyInfo = { has_key: true, key_alias: "my-key" };
|
||||
renderWithProviders(<AgentCard {...defaultProps} keyInfo={keyInfo} />);
|
||||
|
||||
expect(screen.getByText("Active")).toBeInTheDocument();
|
||||
expect(screen.getByText("my-key")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onAgentClick when card is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onAgentClick = vi.fn();
|
||||
renderWithProviders(<AgentCard {...defaultProps} onAgentClick={onAgentClick} />);
|
||||
|
||||
await user.click(screen.getByText("Test Agent"));
|
||||
|
||||
expect(onAgentClick).toHaveBeenCalledWith("agent-123");
|
||||
});
|
||||
|
||||
it("should show delete button only for admins", () => {
|
||||
const onDeleteClick = vi.fn();
|
||||
const { unmount } = renderWithProviders(
|
||||
<AgentCard {...defaultProps} isAdmin={false} onDeleteClick={onDeleteClick} />
|
||||
);
|
||||
expect(screen.queryByRole("button", { name: /delete/i })).not.toBeInTheDocument();
|
||||
|
||||
unmount();
|
||||
|
||||
renderWithProviders(
|
||||
<AgentCard {...defaultProps} isAdmin={true} onDeleteClick={onDeleteClick} />
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /delete/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onDeleteClick with agent id and name when delete is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onDeleteClick = vi.fn();
|
||||
renderWithProviders(
|
||||
<AgentCard {...defaultProps} isAdmin={true} onDeleteClick={onDeleteClick} />
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /delete/i }));
|
||||
|
||||
expect(onDeleteClick).toHaveBeenCalledWith("agent-123", "Test Agent");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
import React from "react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../../../tests/test-utils";
|
||||
import { CostBreakdownViewer, CostBreakdown } from "./CostBreakdownViewer";
|
||||
|
||||
describe("CostBreakdownViewer", () => {
|
||||
it("should render nothing when there is no meaningful data", () => {
|
||||
const { container } = renderWithProviders(
|
||||
<CostBreakdownViewer costBreakdown={null} totalSpend={0} />
|
||||
);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it("should render nothing when costBreakdown is undefined", () => {
|
||||
const { container } = renderWithProviders(
|
||||
<CostBreakdownViewer costBreakdown={undefined} totalSpend={0} />
|
||||
);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it("should render the collapse header with heading and total", () => {
|
||||
const breakdown: CostBreakdown = {
|
||||
input_cost: 0.001,
|
||||
output_cost: 0.002,
|
||||
total_cost: 0.003,
|
||||
};
|
||||
|
||||
renderWithProviders(
|
||||
<CostBreakdownViewer costBreakdown={breakdown} totalSpend={0.003} />
|
||||
);
|
||||
|
||||
expect(screen.getByRole("heading", { name: "Cost Breakdown" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show input and output costs when the panel is expanded", async () => {
|
||||
const user = userEvent.setup();
|
||||
const breakdown: CostBreakdown = {
|
||||
input_cost: 0.001,
|
||||
output_cost: 0.002,
|
||||
};
|
||||
|
||||
renderWithProviders(
|
||||
<CostBreakdownViewer
|
||||
costBreakdown={breakdown}
|
||||
totalSpend={0.003}
|
||||
promptTokens={500}
|
||||
completionTokens={200}
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("heading", { name: "Cost Breakdown" }));
|
||||
|
||||
expect(screen.getByText("Input Cost:")).toBeVisible();
|
||||
expect(screen.getByText("Output Cost:")).toBeVisible();
|
||||
expect(screen.getByText(/500 prompt tokens/)).toBeVisible();
|
||||
expect(screen.getByText(/200 completion tokens/)).toBeVisible();
|
||||
});
|
||||
|
||||
it("should show '(Cached)' in the header when cacheHit is true", () => {
|
||||
const breakdown: CostBreakdown = {
|
||||
input_cost: 0.001,
|
||||
output_cost: 0.002,
|
||||
total_cost: 0.003,
|
||||
};
|
||||
|
||||
renderWithProviders(
|
||||
<CostBreakdownViewer
|
||||
costBreakdown={breakdown}
|
||||
totalSpend={0}
|
||||
cacheHit="true"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/\(Cached\)/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show discount label with percentage when panel is expanded", async () => {
|
||||
const user = userEvent.setup();
|
||||
const breakdown: CostBreakdown = {
|
||||
input_cost: 0.01,
|
||||
output_cost: 0.02,
|
||||
discount_percent: 0.1,
|
||||
discount_amount: 0.003,
|
||||
};
|
||||
|
||||
renderWithProviders(
|
||||
<CostBreakdownViewer costBreakdown={breakdown} totalSpend={0.027} />
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("heading", { name: "Cost Breakdown" }));
|
||||
|
||||
expect(screen.getByText(/Discount \(10\.00%\)/)).toBeVisible();
|
||||
});
|
||||
|
||||
it("should show margin label with percentage when panel is expanded", async () => {
|
||||
const user = userEvent.setup();
|
||||
const breakdown: CostBreakdown = {
|
||||
input_cost: 0.01,
|
||||
output_cost: 0.02,
|
||||
margin_percent: 0.15,
|
||||
margin_total_amount: 0.005,
|
||||
};
|
||||
|
||||
renderWithProviders(
|
||||
<CostBreakdownViewer costBreakdown={breakdown} totalSpend={0.035} />
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("heading", { name: "Cost Breakdown" }));
|
||||
|
||||
expect(screen.getByText(/Margin \(15\.00%\)/)).toBeVisible();
|
||||
expect(screen.getByText("Final Calculated Cost:")).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user