From 4405837aaa0ba67147c5f796a85a587c6da584da Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 11 Mar 2026 15:46:14 -0700 Subject: [PATCH] Add unit tests for 5 untested UI components Co-Authored-By: Claude Opus 4.6 --- .../src/components/atoms/Tooltip.test.tsx | 46 ++++++++++++ .../DefaultProxyAdminTag.test.tsx | 27 +++++++ .../guardrails/guardrail_garden_card.test.tsx | 72 +++++++++++++++++++ .../shared/CreatedKeyDisplay.test.tsx | 71 ++++++++++++++++++ .../components/shared/chart_loader.test.tsx | 27 +++++++ 5 files changed, 243 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/atoms/Tooltip.test.tsx create mode 100644 ui/litellm-dashboard/src/components/common_components/DefaultProxyAdminTag.test.tsx create mode 100644 ui/litellm-dashboard/src/components/guardrails/guardrail_garden_card.test.tsx create mode 100644 ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx create mode 100644 ui/litellm-dashboard/src/components/shared/chart_loader.test.tsx diff --git a/ui/litellm-dashboard/src/components/atoms/Tooltip.test.tsx b/ui/litellm-dashboard/src/components/atoms/Tooltip.test.tsx new file mode 100644 index 0000000000..8242a4506f --- /dev/null +++ b/ui/litellm-dashboard/src/components/atoms/Tooltip.test.tsx @@ -0,0 +1,46 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { Tooltip } from "./Tooltip"; + +vi.mock("@ant-design/icons", () => ({ + QuestionCircleOutlined: (props: any) => , +})); + +describe("Tooltip", () => { + it("should render", () => { + render(); + expect(screen.getByTestId("question-icon")).toBeInTheDocument(); + }); + + it("should render children instead of the default icon when provided", () => { + render(); + expect(screen.getByRole("button", { name: /info/i })).toBeInTheDocument(); + expect(screen.queryByTestId("question-icon")).not.toBeInTheDocument(); + }); + + it("should show tooltip content on mouse enter", async () => { + const user = userEvent.setup(); + render(); + + await user.hover(screen.getByTestId("question-icon")); + + expect(screen.getByText("Help text")).toBeInTheDocument(); + }); + + it("should hide tooltip content on mouse leave", async () => { + const user = userEvent.setup(); + render(); + + await user.hover(screen.getByTestId("question-icon")); + expect(screen.getByText("Help text")).toBeInTheDocument(); + + await user.unhover(screen.getByTestId("question-icon")); + expect(screen.queryByText("Help text")).not.toBeInTheDocument(); + }); + + it("should not show tooltip content before hovering", () => { + render(); + expect(screen.queryByText("Help text")).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/common_components/DefaultProxyAdminTag.test.tsx b/ui/litellm-dashboard/src/components/common_components/DefaultProxyAdminTag.test.tsx new file mode 100644 index 0000000000..e330bb2e1b --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/DefaultProxyAdminTag.test.tsx @@ -0,0 +1,27 @@ +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/react"; +import DefaultProxyAdminTag from "./DefaultProxyAdminTag"; + +describe("DefaultProxyAdminTag", () => { + it("should render", () => { + render(); + expect(screen.getByText("some-user")).toBeInTheDocument(); + }); + + it("should render a blue tag when userId is default_user_id", () => { + render(); + expect(screen.getByText("Default Proxy Admin")).toBeInTheDocument(); + }); + + it("should render plain text when userId is a regular value", () => { + render(); + expect(screen.getByText("alice@example.com")).toBeInTheDocument(); + expect(screen.queryByText("Default Proxy Admin")).not.toBeInTheDocument(); + }); + + it("should render empty text when userId is null", () => { + const { container } = render(); + expect(screen.queryByText("Default Proxy Admin")).not.toBeInTheDocument(); + expect(container.textContent).toBe(""); + }); +}); diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_garden_card.test.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_garden_card.test.tsx new file mode 100644 index 0000000000..0fa5d2ffcd --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_garden_card.test.tsx @@ -0,0 +1,72 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen, fireEvent, act } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import GuardrailCard from "./guardrail_garden_card"; +import type { GuardrailCardInfo } from "./guardrail_garden_data"; + +vi.mock("@ant-design/icons", () => ({ + CheckCircleFilled: ({ style, ...props }: any) => , +})); + +const baseCard: GuardrailCardInfo = { + id: "test-guard", + name: "Test Guardrail", + description: "A guardrail for testing purposes", + category: "litellm", + logo: "/logos/test.svg", + tags: ["safety"], +}; + +describe("GuardrailCard", () => { + it("should render", () => { + render(); + expect(screen.getByText("Test Guardrail")).toBeInTheDocument(); + }); + + it("should display the card description", () => { + render(); + expect(screen.getByText("A guardrail for testing purposes")).toBeInTheDocument(); + }); + + it("should call onClick when the card is clicked", async () => { + const onClick = vi.fn(); + const user = userEvent.setup(); + render(); + + await user.click(screen.getByText("Test Guardrail")); + + expect(onClick).toHaveBeenCalled(); + }); + + it("should show eval badge when card has eval data", () => { + const cardWithEval: GuardrailCardInfo = { + ...baseCard, + eval: { f1: 95, precision: 92, recall: 98, testCases: 500, latency: "10ms" }, + }; + render(); + expect(screen.getByText(/F1: 95%/)).toBeInTheDocument(); + expect(screen.getByText(/500 test cases/)).toBeInTheDocument(); + }); + + it("should not show eval badge when card has no eval data", () => { + render(); + expect(screen.queryByText(/F1:/)).not.toBeInTheDocument(); + }); + + it("should show fallback initial when logo fails to load", () => { + render(); + const img = screen.getByRole("presentation"); + + act(() => { + fireEvent.error(img); + }); + + expect(screen.getByText("T")).toBeInTheDocument(); + }); + + it("should show fallback initial when logo src is empty", () => { + const cardNoLogo: GuardrailCardInfo = { ...baseCard, logo: "" }; + render(); + expect(screen.getByText("T")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx new file mode 100644 index 0000000000..013e222c6b --- /dev/null +++ b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx @@ -0,0 +1,71 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { render, screen, act } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import CreatedKeyDisplay from "./CreatedKeyDisplay"; + +vi.mock("antd", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + message: { success: vi.fn() }, + }; +}); + +import { message } from "antd"; + +describe("CreatedKeyDisplay", () => { + beforeEach(() => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should render", () => { + render(); + expect(screen.getByText("sk-test-123")).toBeInTheDocument(); + }); + + it("should display the security warning", () => { + render(); + expect(screen.getByText(/you will not be able to view it again/i)).toBeInTheDocument(); + }); + + it("should show the copy button with initial label", () => { + render(); + expect(screen.getByRole("button", { name: /copy virtual key/i })).toBeInTheDocument(); + }); + + it("should change button text to Copied after clicking copy", async () => { + const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); + render(); + + await user.click(screen.getByRole("button", { name: /copy virtual key/i })); + + expect(screen.getByRole("button", { name: /copied/i })).toBeInTheDocument(); + }); + + it("should show a success message when the key is copied", async () => { + const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); + render(); + + await user.click(screen.getByRole("button", { name: /copy virtual key/i })); + + expect(message.success).toHaveBeenCalledWith("Key copied to clipboard"); + }); + + it("should revert button text back after 2 seconds", async () => { + const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); + render(); + + await user.click(screen.getByRole("button", { name: /copy virtual key/i })); + expect(screen.getByRole("button", { name: /copied/i })).toBeInTheDocument(); + + act(() => { + vi.advanceTimersByTime(2000); + }); + + expect(screen.getByRole("button", { name: /copy virtual key/i })).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/shared/chart_loader.test.tsx b/ui/litellm-dashboard/src/components/shared/chart_loader.test.tsx new file mode 100644 index 0000000000..8c924931a2 --- /dev/null +++ b/ui/litellm-dashboard/src/components/shared/chart_loader.test.tsx @@ -0,0 +1,27 @@ +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { ChartLoader } from "./chart_loader"; + +describe("ChartLoader", () => { + it("should render", () => { + render(); + expect(screen.getByText("Loading chart data...")).toBeInTheDocument(); + }); + + it("should show loading text by default", () => { + render(); + expect(screen.getByText("Fetching your data")).toBeInTheDocument(); + }); + + it("should show date-changing text when isDateChanging is true", () => { + render(); + expect(screen.getByText("Processing date selection...")).toBeInTheDocument(); + expect(screen.getByText("This will only take a moment")).toBeInTheDocument(); + }); + + it("should not show default loading text when isDateChanging is true", () => { + render(); + expect(screen.queryByText("Loading chart data...")).not.toBeInTheDocument(); + expect(screen.queryByText("Fetching your data")).not.toBeInTheDocument(); + }); +});