From e5619c39a0b8e2cfbf44ea38b3f82aecd7811087 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 19 Feb 2026 11:33:20 -0800 Subject: [PATCH] [Fix] Update UI tests for GuardrailViewer rewrite and AllModelsTab QueryClient GuardrailViewer was rewritten from ant-design Collapse to a custom card layout. Tests now match the new component: updated header text, ms-based duration, expand-to-reveal provider details, and removed ant-collapse references. AllModelsTab tests failed because ModelSettingsModal now uses useMutation via useStoreModelInDB. Switched from bare render() to renderWithProviders() which wraps in QueryClientProvider. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/AllModelsTab.test.tsx | 18 ++-- .../GuardrailViewer/GuardrailViewer.test.tsx | 99 +++++++++++-------- 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx index 813a365d36..34c1c3ca4b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx @@ -1,5 +1,5 @@ import * as useAuthorizedModule from "@/app/(dashboard)/hooks/useAuthorized"; -import { render, screen, waitFor } from "@testing-library/react"; +import { renderWithProviders, screen, waitFor } from "../../../../../tests/test-utils"; import { beforeEach, describe, expect, it, vi } from "vitest"; import AllModelsTab from "./AllModelsTab"; @@ -116,7 +116,7 @@ describe("AllModelsTab", () => { mockUseModelCostMap.mockReturnValueOnce(createModelCostMapMock({})); - render(); + renderWithProviders(); expect(screen.getByText("Current Team:")).toBeInTheDocument(); }); @@ -172,7 +172,7 @@ describe("AllModelsTab", () => { mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); - render(); + renderWithProviders(); // Component shows API total_count (2), not filtered count // Since default is "personal" team and models don't have direct_access, they're filtered out @@ -233,7 +233,7 @@ describe("AllModelsTab", () => { mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); - render(); + renderWithProviders(); // Component shows API total_count (2), not filtered count // Since default is "personal" team and models don't have direct_access, they're filtered out @@ -280,7 +280,7 @@ describe("AllModelsTab", () => { mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); - render(); + renderWithProviders(); // Component shows API total_count (2), but only 1 model has direct_access await waitFor(() => { @@ -338,7 +338,7 @@ describe("AllModelsTab", () => { mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); - render(); + renderWithProviders(); await waitFor(() => { expect(screen.getByText("Config Model")).toBeInTheDocument(); @@ -380,7 +380,7 @@ describe("AllModelsTab", () => { mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); - render(); + renderWithProviders(); await waitFor(() => { expect(screen.getByText("Defined in config")).toBeInTheDocument(); @@ -426,7 +426,7 @@ describe("AllModelsTab", () => { return { data: page1Data, isLoading: false, error: null }; }); - render(); + renderWithProviders(); await waitFor(() => { // Component calculates: ((1-1)*50)+1 = 1, Math.min(1*50, 2) = 2 @@ -479,7 +479,7 @@ describe("AllModelsTab", () => { return { data: singlePageData, isLoading: false, error: null }; }); - render(); + renderWithProviders(); await waitFor(() => { expect(screen.getByText("Showing 1 - 1 of 1 results")).toBeInTheDocument(); diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx index 95120f6057..28991ebfa0 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx @@ -19,68 +19,62 @@ describe("GuardrailViewer", () => { vi.resetModules(); }); - it("shows header, status pill color, duration rounding, and time labels", () => { + it("shows header, status pill, and duration", () => { const data = makeGuardrailInformation({ duration: 1.23456, guardrail_status: "success" }); renderWithProviders(); - expect(screen.getByText("Guardrail Information")).toBeInTheDocument(); - // header status pill (success => green) - const statusBadges = screen.getAllByText("success"); - // there are two status locations: header chip and grid "Status" - expect(statusBadges.length).toBeGreaterThanOrEqual(1); - // Quick class assertion for at least one of them - expect(statusBadges[0].className).toMatch(/bg-green-100/); + expect(screen.getByText("Guardrails & Policy Compliance")).toBeInTheDocument(); + // header shows passed count + expect(screen.getByText(/1 Passed/)).toBeInTheDocument(); + // The PASSED badge in the evaluation card + expect(screen.getByText("PASSED")).toBeInTheDocument(); - // duration displays with 4 decimals - expect(screen.getByText(/1\.2346s/)).toBeInTheDocument(); - - // time labels exist - expect(screen.getByText("Start Time:")).toBeInTheDocument(); - expect(screen.getByText("End Time:")).toBeInTheDocument(); + // duration displays in ms format: Math.round(1.23456 * 1000) = 1235 + expect(screen.getByText("1235ms")).toBeInTheDocument(); }); - it("calculates and displays masked entity totals with pluralization", () => { + it("calculates and displays masked entity totals", async () => { + const user = userEvent.setup(); const data = makeGuardrailInformation({ masked_entity_count: { EMAIL_ADDRESS: 2, PHONE_NUMBER: 1 }, }); renderWithProviders(); - expect(screen.getByText("3 masked entities")).toBeInTheDocument(); - // summary chips for each entry + // In collapsed state, the match count badge is visible + expect(screen.getByText("3 matched")).toBeInTheDocument(); + + // Expand the evaluation card to see entity details + await user.click(screen.getByText("pii-rail")); + // summary chips for each entry inside expanded card expect(screen.getByText("EMAIL_ADDRESS: 2")).toBeInTheDocument(); expect(screen.getByText("PHONE_NUMBER: 1")).toBeInTheDocument(); }); - it("hides masked badge & summary when count is zero/empty", () => { + it("hides matched badge when count is zero/empty", () => { const data = makeGuardrailInformation({ masked_entity_count: {} }); renderWithProviders(); - expect(screen.queryByText(/masked entity/)).not.toBeInTheDocument(); - expect(screen.queryByText("Masked Entity Summary")).not.toBeInTheDocument(); + expect(screen.queryByText(/matched/)).not.toBeInTheDocument(); }); - it("toggles main section open/closed and chevron rotation class", async () => { + it("toggles evaluation card open/closed on click", async () => { const user = userEvent.setup(); - const data = makeGuardrailInformation(); - const { container } = renderWithProviders(); - - const header = screen.getByText("Guardrail Information").closest(".ant-collapse-header")!; - // Initially expanded (content is visible) - expect(screen.getByText("Masked Entity Summary")).toBeInTheDocument(); - - // Click to collapse - await user.click(header); - // Wait for collapse animation and content to be hidden - await waitFor(() => { - const contentBox = container.querySelector(".ant-collapse-content-box"); - expect(contentBox).not.toBeVisible(); + const data = makeGuardrailInformation({ + masked_entity_count: { EMAIL_ADDRESS: 2 }, }); + renderWithProviders(); - // Click to expand again - await user.click(header); - // Wait for expand animation + // Initially collapsed — masked entity details not visible + expect(screen.queryByText("EMAIL_ADDRESS: 2")).not.toBeInTheDocument(); + + // Click to expand + await user.click(screen.getByText("pii-rail")); + expect(screen.getByText("EMAIL_ADDRESS: 2")).toBeInTheDocument(); + + // Click again to collapse + await user.click(screen.getByText("pii-rail")); await waitFor(() => { - expect(screen.getByText("Masked Entity Summary")).toBeVisible(); + expect(screen.queryByText("EMAIL_ADDRESS: 2")).not.toBeInTheDocument(); }); }); @@ -97,6 +91,9 @@ describe("GuardrailViewer", () => { }); renderWithProviders(); + // Expand the card to see provider-specific content + const user = userEvent.setup(); + await user.click(screen.getByText("pii-rail")); expect(screen.getByTestId("presidio-mock")).toHaveTextContent("presidio 2"); }); @@ -112,6 +109,10 @@ describe("GuardrailViewer", () => { guardrail_response: [makeEntity()], }); renderWithProviders(); + + // Expand the card to see provider-specific content + const user = userEvent.setup(); + await user.click(screen.getByText("pii-rail")); expect(screen.getByTestId("presidio-mock")).toHaveTextContent("count:1"); }); @@ -127,22 +128,31 @@ describe("GuardrailViewer", () => { guardrail_response: makeBedrockResponse({ action: "GUARDRAIL_INTERVENED" }), }); renderWithProviders(); + + // Expand the card to see provider-specific content + const user = userEvent.setup(); + await user.click(screen.getByText("pii-rail")); expect(screen.getByTestId("bedrock-mock")).toHaveTextContent("GUARDRAIL_INTERVENED"); }); - it("unknown provider renders neither Presidio nor Bedrock details", () => { + it("unknown provider renders neither Presidio nor Bedrock details", async () => { + const user = userEvent.setup(); const data = makeGuardrailInformation({ guardrail_provider: "unknown", }); renderWithProviders(); - // Summary still present - expect(screen.getByText("Guardrail Information")).toBeInTheDocument(); - // No provider sections + // Header still present + expect(screen.getByText("Guardrails & Policy Compliance")).toBeInTheDocument(); + + // Expand the card + await user.click(screen.getByText("pii-rail")); + // No Presidio or Bedrock sections expect(screen.queryByText(/Detected Entities/)).not.toBeInTheDocument(); expect(screen.queryByText(/Raw Bedrock Guardrail Response/)).not.toBeInTheDocument(); }); - it("integration: renders with real Bedrock details without mocks", () => { + it("integration: renders with real Bedrock details without mocks", async () => { + const user = userEvent.setup(); const data = makeGuardrailInformation({ guardrail_provider: "bedrock", guardrail_response: makeBedrockResponse({ @@ -152,6 +162,9 @@ describe("GuardrailViewer", () => { }); renderWithProviders(); + // Expand the card to reveal Bedrock details + await user.click(screen.getByText("pii-rail")); + // Bedrock summary bits expect(screen.getByText("Outputs")).toBeInTheDocument(); expect(screen.getByText("ok")).toBeInTheDocument();