Merge pull request #24036 from BerriAI/litellm_/vigorous-beaver

[Test] UI: Add unit tests for 10 untested components
This commit is contained in:
yuneng-jiang
2026-03-18 15:05:42 -07:00
committed by GitHub
10 changed files with 511 additions and 1 deletions
@@ -0,0 +1,146 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { getAgentHubTableColumns, AgentHubData } from "./AgentHubTableColumns";
const mockAgent: AgentHubData = {
agent_id: "agent-1",
protocolVersion: "1.0",
name: "Test Agent",
description: "A test agent for unit testing",
url: "https://agent.example.com",
version: "2.0",
capabilities: { streaming: true, caching: false },
defaultInputModes: ["text"],
defaultOutputModes: ["text", "image"],
skills: [
{ id: "s1", name: "Skill One", description: "First skill" },
{ id: "s2", name: "Skill Two", description: "Second skill" },
{ id: "s3", name: "Skill Three", description: "Third skill" },
],
is_public: true,
};
function TestTable({
data,
publicPage = false,
showModal = vi.fn(),
copyToClipboard = vi.fn(),
}: {
data: AgentHubData[];
publicPage?: boolean;
showModal?: ReturnType<typeof vi.fn>;
copyToClipboard?: ReturnType<typeof vi.fn>;
}) {
const columns = getAgentHubTableColumns(showModal, copyToClipboard, publicPage);
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });
return (
<table>
<thead>
{table.getHeaderGroups().map((hg) => (
<tr key={hg.id}>
{hg.headers.map((h) => (
<th key={h.id}>{flexRender(h.column.columnDef.header, h.getContext())}</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
))}
</tbody>
</table>
);
}
describe("AgentHubTableColumns", () => {
it("should render", () => {
render(<TestTable data={[mockAgent]} />);
expect(screen.getByText("Test Agent")).toBeInTheDocument();
});
it("should display the agent description", () => {
render(<TestTable data={[mockAgent]} />);
// Description appears in both the description column and the mobile view within agent name column
expect(screen.getAllByText("A test agent for unit testing").length).toBeGreaterThanOrEqual(1);
});
it("should display the version with a 'v' prefix", () => {
render(<TestTable data={[mockAgent]} />);
expect(screen.getByText("v2.0")).toBeInTheDocument();
});
it("should display the protocol version", () => {
render(<TestTable data={[mockAgent]} />);
expect(screen.getByText("1.0")).toBeInTheDocument();
});
it("should show skill count with correct pluralization", () => {
render(<TestTable data={[mockAgent]} />);
expect(screen.getByText("3 skills")).toBeInTheDocument();
});
it("should show first two skills and '+1' for overflow", () => {
render(<TestTable data={[mockAgent]} />);
expect(screen.getByText("Skill One")).toBeInTheDocument();
expect(screen.getByText("Skill Two")).toBeInTheDocument();
expect(screen.getByText("+1")).toBeInTheDocument();
});
it("should show only true capabilities as badges", () => {
render(<TestTable data={[mockAgent]} />);
expect(screen.getByText("streaming")).toBeInTheDocument();
expect(screen.queryByText("caching")).not.toBeInTheDocument();
});
it("should display I/O modes", () => {
render(<TestTable data={[mockAgent]} />);
// "In:" and "Out:" are in <span> children; getByText with exact:false
// matches against the element's full textContent across child nodes
expect(screen.getByText((_, el) =>
el?.tagName === "P" && el.textContent === "In: text"
)).toBeInTheDocument();
expect(screen.getByText((_, el) =>
el?.tagName === "P" && el.textContent === "Out: text, image"
)).toBeInTheDocument();
});
it("should display 'Yes' badge for public agents", () => {
render(<TestTable data={[mockAgent]} />);
expect(screen.getByText("Yes")).toBeInTheDocument();
});
it("should display 'No' badge for non-public agents", () => {
const privateAgent = { ...mockAgent, is_public: false };
render(<TestTable data={[privateAgent]} />);
expect(screen.getByText("No")).toBeInTheDocument();
});
it("should display a Details button", () => {
render(<TestTable data={[mockAgent]} />);
expect(screen.getByRole("button", { name: /details|info/i })).toBeInTheDocument();
});
it("should show '-' when agent has no capabilities", () => {
const noCapAgent = { ...mockAgent, capabilities: {} };
render(<TestTable data={[noCapAgent]} />);
// The dash is rendered in the capabilities column
expect(screen.getByText("-")).toBeInTheDocument();
});
it("should show singular 'skill' for one skill", () => {
const oneSkillAgent = {
...mockAgent,
skills: [{ id: "s1", name: "Only Skill", description: "One" }],
};
render(<TestTable data={[oneSkillAgent]} />);
expect(screen.getByText("1 skill")).toBeInTheDocument();
});
});
@@ -194,7 +194,6 @@ export const getAgentHubTableColumns = (
return publicA - publicB;
},
cell: ({ row }) => {
console.log(`CHECKPOINT 1: ${JSON.stringify(row.original)}`);
const agent = row.original;
return agent.is_public === true ? (
@@ -0,0 +1,38 @@
import { renderWithProviders, screen } from "../../tests/test-utils";
import { vi } from "vitest";
import { DebugWarningBanner } from "./DebugWarningBanner";
const mockUseHealthReadiness = vi.fn();
vi.mock("@/app/(dashboard)/hooks/healthReadiness/useHealthReadiness", () => ({
useHealthReadiness: () => mockUseHealthReadiness(),
}));
describe("DebugWarningBanner", () => {
afterEach(() => {
vi.resetAllMocks();
});
it("should render", () => {
mockUseHealthReadiness.mockReturnValue({ data: { is_detailed_debug: true } });
renderWithProviders(<DebugWarningBanner />);
expect(screen.getByText(/Performance Warning/i)).toBeInTheDocument();
});
it("should render nothing when debug mode is disabled", () => {
mockUseHealthReadiness.mockReturnValue({ data: { is_detailed_debug: false } });
const { container } = renderWithProviders(<DebugWarningBanner />);
expect(container.firstChild).toBeNull();
});
it("should render nothing when health data is undefined", () => {
mockUseHealthReadiness.mockReturnValue({ data: undefined });
const { container } = renderWithProviders(<DebugWarningBanner />);
expect(container.firstChild).toBeNull();
});
it("should mention LITELLM_LOG=DEBUG in the description", () => {
mockUseHealthReadiness.mockReturnValue({ data: { is_detailed_debug: true } });
renderWithProviders(<DebugWarningBanner />);
expect(screen.getByText(/LITELLM_LOG=DEBUG/)).toBeInTheDocument();
});
});
@@ -0,0 +1,34 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import ExportFormatSelector from "./ExportFormatSelector";
describe("ExportFormatSelector", () => {
it("should render", () => {
render(<ExportFormatSelector value="csv" onChange={vi.fn()} />);
expect(screen.getByText("Format")).toBeInTheDocument();
});
it("should display the current value as csv", () => {
render(<ExportFormatSelector value="csv" onChange={vi.fn()} />);
expect(screen.getByText("CSV (Excel, Google Sheets)")).toBeInTheDocument();
});
it("should display the current value as json", () => {
render(<ExportFormatSelector value="json" onChange={vi.fn()} />);
expect(screen.getByText("JSON (includes metadata)")).toBeInTheDocument();
});
it("should call onChange when a different format is selected", async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<ExportFormatSelector value="csv" onChange={onChange} />);
// Open the Ant Design Select dropdown
await user.click(screen.getByText("CSV (Excel, Google Sheets)"));
// Select JSON option from the dropdown
const jsonOption = await screen.findByText("JSON (includes metadata)");
await user.click(jsonOption);
expect(onChange).toHaveBeenCalledWith("json", expect.anything());
});
});
@@ -0,0 +1,37 @@
import { render, screen } from "@testing-library/react";
import ExportSummary from "./ExportSummary";
describe("ExportSummary", () => {
const dateRange = {
from: new Date("2025-01-01"),
to: new Date("2025-01-31"),
};
it("should render", () => {
render(<ExportSummary dateRange={dateRange} selectedFilters={[]} />);
expect(screen.getByText(/2025/)).toBeInTheDocument();
});
it("should display formatted date range", () => {
render(<ExportSummary dateRange={dateRange} selectedFilters={[]} />);
// Pin locale to en-US so test is deterministic regardless of CI runner locale
const expectedFrom = dateRange.from!.toLocaleDateString("en-US");
const expectedTo = dateRange.to!.toLocaleDateString("en-US");
expect(screen.getByText(`${expectedFrom} - ${expectedTo}`)).toBeInTheDocument();
});
it("should show singular 'filter' for one filter", () => {
render(<ExportSummary dateRange={dateRange} selectedFilters={["team-a"]} />);
expect(screen.getByText(/1 filter$/)).toBeInTheDocument();
});
it("should show plural 'filters' for multiple filters", () => {
render(<ExportSummary dateRange={dateRange} selectedFilters={["team-a", "team-b"]} />);
expect(screen.getByText(/2 filters/)).toBeInTheDocument();
});
it("should not show filter text when no filters applied", () => {
render(<ExportSummary dateRange={dateRange} selectedFilters={[]} />);
expect(screen.queryByText(/filter/)).not.toBeInTheDocument();
});
});
@@ -0,0 +1,37 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import ExportTypeSelector from "./ExportTypeSelector";
describe("ExportTypeSelector", () => {
it("should render", () => {
render(<ExportTypeSelector value="daily" onChange={vi.fn()} entityType="team" />);
expect(screen.getByText("Export type")).toBeInTheDocument();
});
it("should render all three radio options", () => {
render(<ExportTypeSelector value="daily" onChange={vi.fn()} entityType="team" />);
expect(screen.getAllByRole("radio")).toHaveLength(3);
});
it("should interpolate entity type in labels", () => {
render(<ExportTypeSelector value="daily" onChange={vi.fn()} entityType="organization" />);
expect(screen.getByText(/Day-by-day breakdown by organization$/)).toBeInTheDocument();
expect(screen.getByText(/organization and key/)).toBeInTheDocument();
expect(screen.getByText(/organization and model/)).toBeInTheDocument();
});
it("should call onChange when a different option is selected", async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<ExportTypeSelector value="daily" onChange={onChange} entityType="team" />);
await user.click(screen.getByText(/by team and key/));
expect(onChange).toHaveBeenCalledWith("daily_with_keys");
});
it("should have the correct radio checked based on value prop", () => {
render(<ExportTypeSelector value="daily_with_models" onChange={vi.fn()} entityType="team" />);
const modelRadio = screen.getByRole("radio", { name: /by team and model/i });
expect(modelRadio).toBeChecked();
});
});
@@ -0,0 +1,73 @@
import { renderWithProviders, screen } from "../../../tests/test-utils";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import UsageExportHeader from "./UsageExportHeader";
import type { EntitySpendData } from "./types";
vi.mock("./EntityUsageExportModal", () => ({
default: ({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) =>
isOpen ? (
<div data-testid="export-modal">
<button onClick={onClose}>Close</button>
</div>
) : null,
}));
const defaultProps = {
dateValue: { from: new Date("2025-01-01"), to: new Date("2025-01-31") },
entityType: "team" as const,
spendData: {
results: [],
metadata: {
total_spend: 0,
total_api_requests: 0,
total_successful_requests: 0,
total_failed_requests: 0,
total_tokens: 0,
},
} satisfies EntitySpendData,
};
describe("UsageExportHeader", () => {
it("should render", () => {
renderWithProviders(<UsageExportHeader {...defaultProps} />);
expect(screen.getByRole("button", { name: /export data/i })).toBeInTheDocument();
});
it("should open the export modal when the export button is clicked", async () => {
const user = userEvent.setup();
renderWithProviders(<UsageExportHeader {...defaultProps} />);
await user.click(screen.getByRole("button", { name: /export data/i }));
expect(screen.getByTestId("export-modal")).toBeInTheDocument();
});
it("should close the export modal when onClose is called", async () => {
const user = userEvent.setup();
renderWithProviders(<UsageExportHeader {...defaultProps} />);
await user.click(screen.getByRole("button", { name: /export data/i }));
await user.click(screen.getByRole("button", { name: /close/i }));
expect(screen.queryByTestId("export-modal")).not.toBeInTheDocument();
});
it("should not show filter dropdown when showFilters is false", () => {
renderWithProviders(<UsageExportHeader {...defaultProps} showFilters={false} />);
expect(screen.queryByText(/filter/i)).not.toBeInTheDocument();
});
it("should show filter dropdown when showFilters is true and options provided", () => {
renderWithProviders(
<UsageExportHeader
{...defaultProps}
showFilters
filterLabel="Team"
filterPlaceholder="Select teams"
filterOptions={[
{ label: "Team A", value: "team-a" },
{ label: "Team B", value: "team-b" },
]}
onFiltersChange={vi.fn()}
/>,
);
expect(screen.getByText("Team")).toBeInTheDocument();
});
});
@@ -0,0 +1,98 @@
import { render, screen, act } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import { GuardrailConfig } from "./GuardrailConfig";
describe("GuardrailConfig", () => {
const defaultProps = {
guardrailName: "Content Safety",
guardrailType: "Content Safety",
provider: "bedrock",
};
afterEach(() => {
vi.useRealTimers();
});
it("should render", () => {
render(<GuardrailConfig {...defaultProps} />);
expect(screen.getByText("Parameters")).toBeInTheDocument();
});
it("should display the guardrail name in the parameters description", () => {
render(<GuardrailConfig {...defaultProps} />);
expect(screen.getByText(/Configure Content Safety behavior/)).toBeInTheDocument();
});
// Note: Version history entries are hardcoded placeholders in the component.
// These assertions will need updating when wired to real API data.
it("should show version history when 'View history' is clicked", async () => {
const user = userEvent.setup();
render(<GuardrailConfig {...defaultProps} />);
await user.click(screen.getByRole("button", { name: /view history/i }));
expect(screen.getByText("Initial configuration")).toBeInTheDocument();
expect(screen.getByText("Added custom categories list")).toBeInTheDocument();
});
it("should toggle version history text between View/Hide", async () => {
const user = userEvent.setup();
render(<GuardrailConfig {...defaultProps} />);
const button = screen.getByRole("button", { name: /view history/i });
await user.click(button);
expect(screen.getByRole("button", { name: /hide history/i })).toBeInTheDocument();
});
it("should show custom code textarea when custom code override is toggled on", async () => {
const user = userEvent.setup();
render(<GuardrailConfig {...defaultProps} />);
// Walk up from "Custom Code Override" heading to find the enclosing section,
// then locate the switch within it
const heading = screen.getByText("Custom Code Override");
let container = heading.parentElement;
let customCodeSwitch: Element | null = null;
while (container && !customCodeSwitch) {
customCodeSwitch = container.querySelector('[role="switch"]');
container = container.parentElement;
}
if (!customCodeSwitch) {
throw new Error("Could not find the Custom Code Override switch via DOM traversal");
}
await user.click(customCodeSwitch);
expect(screen.getByPlaceholderText(/async def evaluate/)).toBeInTheDocument();
});
it("should hide custom code textarea when custom code override is off", () => {
render(<GuardrailConfig {...defaultProps} />);
// There's an input for categories, but no textarea
expect(screen.queryByPlaceholderText(/async def evaluate/)).not.toBeInTheDocument();
});
it("should show the re-run button in idle state", () => {
render(<GuardrailConfig {...defaultProps} />);
expect(screen.getByRole("button", { name: /re-run on failing logs/i })).toBeInTheDocument();
});
it("should show loading state when re-run is clicked", async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
render(<GuardrailConfig {...defaultProps} />);
await user.click(screen.getByRole("button", { name: /re-run on failing logs/i }));
expect(screen.getByText(/Running on 10 samples/)).toBeInTheDocument();
});
it("should show success message after re-run completes", async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
render(<GuardrailConfig {...defaultProps} />);
await user.click(screen.getByRole("button", { name: /re-run on failing logs/i }));
await act(async () => { vi.advanceTimersByTime(2500); });
expect(screen.getByText(/7\/10 would now pass/)).toBeInTheDocument();
});
it("should display the Revert and Save buttons", () => {
render(<GuardrailConfig {...defaultProps} />);
expect(screen.getByRole("button", { name: /revert/i })).toBeInTheDocument();
// The component's hardcoded default version is "v3", so Save shows "v4"
expect(screen.getByRole("button", { name: /save as v\d+/i })).toBeInTheDocument();
});
});
@@ -0,0 +1,34 @@
import { render, screen } from "@testing-library/react";
import { MetricCard } from "./MetricCard";
describe("MetricCard", () => {
it("should render", () => {
render(<MetricCard label="Total Requests" value={1234} />);
expect(screen.getByText("Total Requests")).toBeInTheDocument();
});
it("should display the numeric value", () => {
render(<MetricCard label="Total Requests" value={1234} />);
expect(screen.getByText("1234")).toBeInTheDocument();
});
it("should display a string value", () => {
render(<MetricCard label="Pass Rate" value="95.2%" />);
expect(screen.getByText("95.2%")).toBeInTheDocument();
});
it("should display subtitle when provided", () => {
render(<MetricCard label="Blocked" value={42} subtitle="Last 7 days" />);
expect(screen.getByText("Last 7 days")).toBeInTheDocument();
});
it("should not display subtitle when not provided", () => {
render(<MetricCard label="Blocked" value={42} />);
expect(screen.queryByText(/days/)).not.toBeInTheDocument();
});
it("should display icon when provided", () => {
render(<MetricCard label="Status" value="OK" icon={<span data-testid="icon">!</span>} />);
expect(screen.getByTestId("icon")).toBeInTheDocument();
});
});
@@ -114,4 +114,18 @@ describe("DocsMenu", () => {
await user.click(button);
expect(button).toHaveAttribute("aria-expanded", "true");
});
it("should close menu when clicking outside", async () => {
const user = userEvent.setup();
renderWithProviders(
<div>
<DocsMenu items={items} />
<button>Outside</button>
</div>,
);
await user.click(screen.getByRole("button", { name: /docs/i }));
expect(screen.getByText("Custom pricing")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: /outside/i }));
expect(screen.queryByText("Custom pricing")).not.toBeInTheDocument();
});
});