Skip to content

[Test] UI: Add unit tests for 10 untested components#23891

Merged
yuneng-jiang merged 1 commit intolitellm_yj_march_17_2026from
litellm_/mystifying-tereshkova
Mar 17, 2026
Merged

[Test] UI: Add unit tests for 10 untested components#23891
yuneng-jiang merged 1 commit intolitellm_yj_march_17_2026from
litellm_/mystifying-tereshkova

Conversation

@yuneng-jiang
Copy link
Copy Markdown
Contributor

Summary

10 UI component files had no test coverage. This PR adds Vitest + React Testing Library tests for each, totaling 67 new tests.

Testing

All 67 tests pass locally via npx vitest run.

Components tested:

  • HelpLink (17 tests) — HelpLink, HelpIcon, DocsMenu: rendering, a11y attributes, hover tooltips, menu toggle
  • DebugWarningBanner (5 tests) — conditional rendering based on useHealthReadiness hook
  • ExportFormatSelector (3 tests) — renders format label, CSV/JSON display
  • ExportTypeSelector (5 tests) — radio options with entity type interpolation, onChange
  • ExportSummary (5 tests) — date range display, filter count singular/plural
  • MetricCard (7 tests) — label/value rendering, optional icon/subtitle
  • PolicySelect (9 tests) — policyStyle helper, disabled state, option constants
  • ComplexityRouterConfig (5 tests) — tier labels, examples, score thresholds
  • RateLimitTypeFormItem (5 tests) — TPM/RPM labels, placeholder, onChange
  • AgentCardGrid (6 tests) — loading skeleton, admin vs non-admin empty states, click handler

Type

✅ Test

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 17, 2026 8:32pm

Request Review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 17, 2026

Greptile Summary

This PR adds 67 Vitest + React Testing Library unit tests across 10 previously untested UI components, providing meaningful first-coverage for HelpLink, DebugWarningBanner, ExportFormatSelector, ExportTypeSelector, ExportSummary, MetricCard, PolicySelect, ComplexityRouterConfig, RateLimitTypeFormItem, and AgentCardGrid. The tests follow the project's established patterns (mocked hooks, renderWithProviders, userEvent) and do not make any real network calls.

Key observations:

  • Most test files are well-structured and correctly reflect their component implementations
  • One misleading assertion in PolicySelect.test.tsx uses aria-expanded="false" to verify disabled state — this attribute is also false for non-disabled closed selects and will not catch a regression if disabled={saving} is removed (the Ant Design class check in the same test does catch it, but the aria assertion is a false safety signal)
  • policyStyle tests assert equality against hardcoded array indices (INPUT_POLICY_OPTIONS[1], INPUT_POLICY_OPTIONS[2]), making them brittle to reordering of the constant array
  • RateLimitTypeFormItem.test.tsx contains two duplicate test cases ("should render" and "should display TPM label for tpm type") that exercise identical code paths
  • Previously flagged issues (dynamic userEvent import in agent_card_grid.test.tsx, locale-sensitive date regex in ExportSummary.test.tsx, missing two-arg onChange coverage for PolicySelect, tooltip mouse accessibility gap in HelpLink.test.tsx) remain open

Confidence Score: 4/5

  • Safe to merge — test-only PR with no production code changes; minor test quality issues do not affect runtime behavior.
  • All 10 files are new or extended test files with no changes to production code. The majority of tests are correct and well-structured. The identified issues (misleading aria assertion, duplicate test cases, fragile index-based assertions) reduce test suite reliability and future maintainability but do not introduce regressions or fail any existing tests. Several issues from earlier review rounds also remain unresolved.
  • ui/litellm-dashboard/src/components/ToolPolicies/PolicySelect.test.tsx requires attention for the misleading aria-expanded disabled assertion and fragile index-based policyStyle assertions.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/components/ToolPolicies/PolicySelect.test.tsx New test file for PolicySelect; aria-expanded="false" in the disabled test does not verify disabled state (aria-disabled would be correct), and policyStyle assertions use fragile index-based comparisons rather than value-based matching.
ui/litellm-dashboard/src/components/common_components/RateLimitTypeFormItem.test.tsx New tests covering TPM/RPM labels, placeholder, and onChange; "should render" and "should display TPM label for tpm type" are exact duplicates providing no additional coverage.
ui/litellm-dashboard/src/components/agents/agent_card_grid.test.tsx New tests for AgentCardGrid with mock of AgentCard; correctly validates loading skeletons, empty states, and click handlers. Dynamic userEvent import inside the test body has been flagged in previous review threads.
ui/litellm-dashboard/src/components/EntityUsageExport/ExportSummary.test.tsx New tests for ExportSummary; locale-sensitive toLocaleDateString() usage in regex (already noted in previous review threads) could cause flaky behaviour across environments.
ui/litellm-dashboard/src/components/HelpLink.test.tsx Extended with 5 new tests covering href attribute, initial tooltip/menu hidden state, and learn-more link; all look correct and well-structured.
ui/litellm-dashboard/src/components/DebugWarningBanner.test.tsx New tests correctly mock useHealthReadiness hook and verify conditional rendering; all five tests are distinct and meaningful.
ui/litellm-dashboard/src/components/EntityUsageExport/ExportFormatSelector.test.tsx New tests for ExportFormatSelector covering label rendering and CSV/JSON display; straightforward and correct.
ui/litellm-dashboard/src/components/EntityUsageExport/ExportTypeSelector.test.tsx New tests validating entity type interpolation, radio selection, and onChange delegation; implementation matches ExportTypeSelector correctly.
ui/litellm-dashboard/src/components/GuardrailsMonitor/MetricCard.test.tsx New tests for MetricCard covering label, value, optional icon, and subtitle; all tests are distinct and appropriately scoped.
ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.test.tsx New tests covering tier labels, example queries, and score thresholds in ComplexityRouterConfig; well-structured and comprehensive.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    TU[Test Utilities\nrenderWithProviders / screen]

    subgraph New Test Files
        HLT[HelpLink.test.tsx\n+5 tests]
        DWB[DebugWarningBanner.test.tsx\n5 tests]
        EFS[ExportFormatSelector.test.tsx\n3 tests]
        ETS[ExportTypeSelector.test.tsx\n5 tests]
        ESM[ExportSummary.test.tsx\n5 tests]
        MC[MetricCard.test.tsx\n7 tests]
        PS[PolicySelect.test.tsx\n9 tests]
        CRC[ComplexityRouterConfig.test.tsx\n5 tests]
        RL[RateLimitTypeFormItem.test.tsx\n5 tests]
        ACG[agent_card_grid.test.tsx\n6 tests]
    end

    subgraph Mocked Dependencies
        UHR[useHealthReadiness hook]
        AC[agent_card component]
    end

    TU --> HLT & DWB & EFS & ETS & ESM & MC & PS & CRC & RL & ACG
    DWB -- vi.mock --> UHR
    ACG -- vi.mock --> AC

    PS -- ⚠️ aria-expanded assertion\ndoes not verify disabled --> PS
    PS -- ⚠️ index-based policyStyle\nassertions are fragile --> PS
    RL -- ⚠️ duplicate render /\nTPM label test cases --> RL
Loading

Last reviewed commit: e0e1ac6

Comment on lines +83 to +88
<AgentCardGrid {...defaultProps} onAgentClick={onAgentClick} />
);
const { default: userEvent } = await import("@testing-library/user-event");
const user = userEvent.setup();
await user.click(screen.getByTestId("agent-card-agent-1"));
expect(onAgentClick).toHaveBeenCalledWith("agent-1");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Inconsistent dynamic import of userEvent

userEvent is statically imported at the top of every other test file in this PR (e.g. ExportTypeSelector.test.tsx, HelpLink.test.tsx), but here it is dynamically imported inside the test body. This introduces unnecessary async overhead and is inconsistent with the established pattern. Move the import to the top of the file alongside the other imports.

Suggested change
<AgentCardGrid {...defaultProps} onAgentClick={onAgentClick} />
);
const { default: userEvent } = await import("@testing-library/user-event");
const user = userEvent.setup();
await user.click(screen.getByTestId("agent-card-agent-1"));
expect(onAgentClick).toHaveBeenCalledWith("agent-1");
it("should call onAgentClick when a card is clicked", async () => {
const onAgentClick = vi.fn();
renderWithProviders(
<AgentCardGrid {...defaultProps} onAgentClick={onAgentClick} />
);
const user = userEvent.setup();
await user.click(screen.getByTestId("agent-card-agent-1"));
expect(onAgentClick).toHaveBeenCalledWith("agent-1");
});

And add at the top of the file:

import userEvent from "@testing-library/user-event";

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +24 to +44
describe("PolicySelect", () => {
it("should render", () => {
renderWithProviders(
<PolicySelect
value="untrusted"
toolName="test-tool"
saving={false}
onChange={vi.fn()}
/>
);
expect(screen.getByText("untrusted")).toBeInTheDocument();
});

it("should show the current policy value", () => {
renderWithProviders(
<PolicySelect
value="trusted"
toolName="test-tool"
saving={false}
onChange={vi.fn()}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 PolicySelect.onChange two-argument signature is never exercised

The component's onChange prop has the signature (toolName: string, policy: string) => void — it is called as onChange(toolName, v) in the implementation. However, none of the tests verify that onChange is actually invoked with both arguments. This means a refactor that accidentally drops toolName from the call would go undetected. Consider adding a test like:

it("should call onChange with toolName and new policy value when selection changes", async () => {
  const user = userEvent.setup();
  const onChange = vi.fn();
  renderWithProviders(
    <PolicySelect
      value="untrusted"
      toolName="test-tool"
      saving={false}
      onChange={onChange}
    />
  );
  await user.click(screen.getByRole("combobox"));
  await user.click(screen.getByText("trusted"));
  expect(onChange).toHaveBeenCalledWith("test-tool", "trusted");
});

Comment on lines +19 to +27
const dateRange = { from, to };
renderWithProviders(
<ExportSummary dateRange={dateRange} selectedFilters={[]} />
);
expect(screen.getByText(new RegExp(from.toLocaleDateString()))).toBeInTheDocument();
expect(screen.getByText(new RegExp(to.toLocaleDateString()))).toBeInTheDocument();
});

it("should show filter count when filters are selected", () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Locale-sensitive toLocaleDateString() used in regex constructor

new RegExp(from.toLocaleDateString()) is fragile in two ways:

  1. In some locales (e.g. German "1.1.2024"), the . in the formatted string becomes a wildcard in the resulting regex, so the assertion matches more broadly than intended.
  2. If the test is run in a locale that formats the date differently than the CI environment, the regex could silently never fail.

Both issues can be avoided by using a fixed locale:

Suggested change
const dateRange = { from, to };
renderWithProviders(
<ExportSummary dateRange={dateRange} selectedFilters={[]} />
);
expect(screen.getByText(new RegExp(from.toLocaleDateString()))).toBeInTheDocument();
expect(screen.getByText(new RegExp(to.toLocaleDateString()))).toBeInTheDocument();
});
it("should show filter count when filters are selected", () => {
expect(screen.getByText(new RegExp(from.toLocaleDateString("en-US")))).toBeInTheDocument();
expect(screen.getByText(new RegExp(to.toLocaleDateString("en-US")))).toBeInTheDocument();

And create the dates with the same locale in mind, or alternatively use toBeInTheDocument with the exact rendered string.

Comment on lines +68 to +74
it("should use custom learn more text when provided", async () => {
const user = userEvent.setup();
renderWithProviders(
<HelpIcon
content="Help text"
learnMoreHref="https://docs.example.com"
learnMoreText="View docs"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Tooltip "Learn more" link is inaccessible to mouse users — test doesn't expose this

In HelpLink.tsx, the tooltip container has style={{ pointerEvents: "none" }}. The button's onMouseLeave fires as soon as the cursor moves away from the button (e.g. to reach the "Learn more" link), which immediately sets showTooltip = false and unmounts the tooltip. The link therefore cannot be clicked by a mouse user even though it is rendered.

The test only asserts the link text appears immediately after hover — it doesn't attempt to move focus to the link and click it — so the inaccessibility is not caught. Consider adding an interaction test, and fixing the component to keep the tooltip visible while the mouse is over it (e.g. include both the button and the tooltip in the onMouseLeave boundary).

Add Vitest + RTL tests for HelpLink, DebugWarningBanner, ExportFormatSelector,
ExportTypeSelector, ExportSummary, MetricCard, PolicySelect,
ComplexityRouterConfig, RateLimitTypeFormItem, and AgentCardGrid (67 tests total).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@yuneng-jiang yuneng-jiang force-pushed the litellm_/mystifying-tereshkova branch from 0ca92d6 to e0e1ac6 Compare March 17, 2026 20:30
@yuneng-jiang yuneng-jiang merged commit 251c279 into litellm_yj_march_17_2026 Mar 17, 2026
14 of 50 checks passed
Comment on lines +58 to +59
expect(screen.getByRole("combobox")).toHaveAttribute("aria-expanded", "false");
expect(screen.getByRole("combobox").closest(".ant-select")).toHaveClass("ant-select-disabled");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 aria-expanded does not verify disabled state

aria-expanded="false" simply means the dropdown is currently closed — which is also true for a non-disabled select that hasn't been opened. This assertion passes regardless of the saving prop and therefore doesn't contribute to verifying the disabled state. A non-disabled select would also satisfy this assertion, so it will not catch a regression where disabled={saving} is accidentally removed.

The correct ARIA attribute for disabled state on an Ant Design combobox is aria-disabled="true". Replace the first assertion:

Suggested change
expect(screen.getByRole("combobox")).toHaveAttribute("aria-expanded", "false");
expect(screen.getByRole("combobox").closest(".ant-select")).toHaveClass("ant-select-disabled");
expect(screen.getByRole("combobox")).toHaveAttribute("aria-disabled", "true");
expect(screen.getByRole("combobox").closest(".ant-select")).toHaveClass("ant-select-disabled");

Comment on lines +13 to +28
it("should render", () => {
renderWithProviders(
<Wrapper>
<RateLimitTypeFormItem type="tpm" name="tpm_type" />
</Wrapper>
);
expect(screen.getByText(/TPM Rate Limit Type/)).toBeInTheDocument();
});

it("should display TPM label for tpm type", () => {
renderWithProviders(
<Wrapper>
<RateLimitTypeFormItem type="tpm" name="tpm_type" />
</Wrapper>
);
expect(screen.getByText(/TPM Rate Limit Type/)).toBeInTheDocument();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicate test cases reduce suite value

"should render" (lines 13–20) and "should display TPM label for tpm type" (lines 22–29) are identical: both render <RateLimitTypeFormItem type="tpm" name="tpm_type" /> and assert screen.getByText(/TPM Rate Limit Type/). If the first test passes, the second must too, and vice versa — one of them provides no additional coverage. Consider merging them into a single meaningful test, or converting "should render" into a smoke test that verifies the component mounts without throwing.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +11 to +21
it("should return the matching option for a known policy", () => {
expect(policyStyle("trusted")).toEqual(INPUT_POLICY_OPTIONS[1]);
});

it("should return the matching option for blocked", () => {
expect(policyStyle("blocked")).toEqual(INPUT_POLICY_OPTIONS[2]);
});

it("should return the first option as fallback for unknown policy", () => {
expect(policyStyle("unknown")).toEqual(INPUT_POLICY_OPTIONS[0]);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 policyStyle tests rely on fragile index-based assertions

Both assertions reference positional indices into INPUT_POLICY_OPTIONS:

expect(policyStyle("trusted")).toEqual(INPUT_POLICY_OPTIONS[1]);
expect(policyStyle("blocked")).toEqual(INPUT_POLICY_OPTIONS[2]);

If the order of INPUT_POLICY_OPTIONS is ever changed (e.g., to alphabetical), policyStyle("trusted") will still return the object whose value === "trusted", but INPUT_POLICY_OPTIONS[1] may now be a different object — causing a spurious test failure even though the function behaves correctly. Assert on the return value's identity property instead:

Suggested change
it("should return the matching option for a known policy", () => {
expect(policyStyle("trusted")).toEqual(INPUT_POLICY_OPTIONS[1]);
});
it("should return the matching option for blocked", () => {
expect(policyStyle("blocked")).toEqual(INPUT_POLICY_OPTIONS[2]);
});
it("should return the first option as fallback for unknown policy", () => {
expect(policyStyle("unknown")).toEqual(INPUT_POLICY_OPTIONS[0]);
});
it("should return the matching option for a known policy", () => {
expect(policyStyle("trusted")).toMatchObject({ value: "trusted" });
});
it("should return the matching option for blocked", () => {
expect(policyStyle("blocked")).toMatchObject({ value: "blocked" });
});

@ishaan-berri ishaan-berri deleted the litellm_/mystifying-tereshkova branch March 26, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant