mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-14 18:23:13 +00:00
Merge pull request #24036 from BerriAI/litellm_/vigorous-beaver
[Test] UI: Add unit tests for 10 untested components
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user