mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
[Test] UI - Logs: Add unit tests for 5 untested view_logs components
Add vitest tests for TypeBadges, ErrorViewer, ConfigInfoMessage, TimeCell, and TruncatedValue covering rendering, user interactions, and edge cases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6fe82d3886
commit
bead0b7d90
5 changed files with 242 additions and 0 deletions
|
|
@ -0,0 +1,41 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { ConfigInfoMessage } from "./ConfigInfoMessage";
|
||||
|
||||
describe("ConfigInfoMessage", () => {
|
||||
it("should render the info message when show is true", () => {
|
||||
render(<ConfigInfoMessage show={true} />);
|
||||
expect(screen.getByText("Request/Response Data Not Available")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render nothing when show is false", () => {
|
||||
const { container } = render(<ConfigInfoMessage show={false} />);
|
||||
expect(container.innerHTML).toBe("");
|
||||
});
|
||||
|
||||
it("should display the YAML config snippet", () => {
|
||||
render(<ConfigInfoMessage show={true} />);
|
||||
expect(screen.getByText(/store_prompts_in_spend_logs: true/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the settings button when onOpenSettings is provided", () => {
|
||||
render(<ConfigInfoMessage show={true} onOpenSettings={() => {}} />);
|
||||
expect(screen.getByText("open the settings")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not render the settings button when onOpenSettings is omitted", () => {
|
||||
render(<ConfigInfoMessage show={true} />);
|
||||
expect(screen.queryByText("open the settings")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onOpenSettings when the settings button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOpenSettings = vi.fn();
|
||||
|
||||
render(<ConfigInfoMessage show={true} onOpenSettings={onOpenSettings} />);
|
||||
await user.click(screen.getByText("open the settings"));
|
||||
|
||||
expect(onOpenSettings).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { ErrorViewer } from "./ErrorViewer";
|
||||
|
||||
const basicError = {
|
||||
error_class: "NotFoundError",
|
||||
error_message: "Model gpt-5 not found",
|
||||
};
|
||||
|
||||
const errorWithTraceback = {
|
||||
error_class: "AuthenticationError",
|
||||
error_message: "Invalid API key",
|
||||
traceback: `Traceback (most recent call last):
|
||||
File "/app/main.py", line 42, in handle_request
|
||||
result = await client.chat(model="gpt-4")
|
||||
File "/app/llms/openai.py", line 100, in chat
|
||||
response = self._make_request(payload)
|
||||
File "/app/llms/base.py", line 55, in _make_request
|
||||
raise AuthenticationError("Invalid API key")`,
|
||||
};
|
||||
|
||||
describe("ErrorViewer", () => {
|
||||
it("should render error type and message", () => {
|
||||
render(<ErrorViewer errorInfo={basicError} />);
|
||||
expect(screen.getByText("NotFoundError")).toBeInTheDocument();
|
||||
expect(screen.getByText("Model gpt-5 not found")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show 'Unknown Error' when error_class is missing", () => {
|
||||
render(<ErrorViewer errorInfo={{ error_message: "something broke" }} />);
|
||||
expect(screen.getByText("Unknown Error")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show 'Unknown error occurred' when error_message is missing", () => {
|
||||
render(<ErrorViewer errorInfo={{ error_class: "RuntimeError" }} />);
|
||||
expect(screen.getByText("Unknown error occurred")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render traceback frames when traceback is present", () => {
|
||||
render(<ErrorViewer errorInfo={errorWithTraceback} />);
|
||||
expect(screen.getByText("Traceback")).toBeInTheDocument();
|
||||
expect(screen.getByText("main.py")).toBeInTheDocument();
|
||||
expect(screen.getByText("openai.py")).toBeInTheDocument();
|
||||
expect(screen.getByText("base.py")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should expand a frame when clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ErrorViewer errorInfo={errorWithTraceback} />);
|
||||
|
||||
await user.click(screen.getByText("main.py"));
|
||||
|
||||
expect(
|
||||
screen.getByText("result = await client.chat(model=\"gpt-4\")")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should expand all frames when 'Expand All' is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ErrorViewer errorInfo={errorWithTraceback} />);
|
||||
|
||||
await user.click(screen.getByText("Expand All"));
|
||||
|
||||
expect(screen.getByText("Collapse All")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should copy traceback to clipboard when copy button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockWriteText = vi.fn().mockResolvedValue(undefined);
|
||||
Object.defineProperty(navigator, "clipboard", {
|
||||
value: { writeText: mockWriteText },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
render(<ErrorViewer errorInfo={errorWithTraceback} />);
|
||||
|
||||
await user.click(screen.getByTitle("Copy traceback"));
|
||||
expect(mockWriteText).toHaveBeenCalledWith(errorWithTraceback.traceback);
|
||||
});
|
||||
|
||||
it("should not render traceback section when traceback is absent", () => {
|
||||
render(<ErrorViewer errorInfo={basicError} />);
|
||||
expect(screen.queryByText("Traceback")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { TruncatedValue } from "./TruncatedValue";
|
||||
|
||||
describe("TruncatedValue", () => {
|
||||
it("should render the value text", () => {
|
||||
render(<TruncatedValue value="chatcmpl-abc123" />);
|
||||
expect(screen.getByText("chatcmpl-abc123")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render a dash when value is undefined", () => {
|
||||
render(<TruncatedValue />);
|
||||
expect(screen.getByText("-")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render a dash when value is empty string", () => {
|
||||
render(<TruncatedValue value="" />);
|
||||
expect(screen.getByText("-")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should apply the default maxWidth when not specified", () => {
|
||||
render(<TruncatedValue value="some-long-id-value" />);
|
||||
const el = screen.getByText("some-long-id-value");
|
||||
expect(el).toHaveStyle({ maxWidth: "180px" });
|
||||
});
|
||||
|
||||
it("should apply custom maxWidth when provided", () => {
|
||||
render(<TruncatedValue value="test-value" maxWidth={300} />);
|
||||
const el = screen.getByText("test-value");
|
||||
expect(el).toHaveStyle({ maxWidth: "300px" });
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { LlmBadge, McpBadge, AgentBadge } from "./TypeBadges";
|
||||
|
||||
describe("TypeBadges", () => {
|
||||
describe("LlmBadge", () => {
|
||||
it("should render with default 'LLM' text when no count is provided", () => {
|
||||
render(<LlmBadge />);
|
||||
expect(screen.getByText("LLM")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the count when provided", () => {
|
||||
render(<LlmBadge count={5} />);
|
||||
expect(screen.getByText("5")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render count of 0 instead of default text", () => {
|
||||
render(<LlmBadge count={0} />);
|
||||
expect(screen.getByText("0")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("McpBadge", () => {
|
||||
it("should render with default 'MCP' text when no count is provided", () => {
|
||||
render(<McpBadge />);
|
||||
expect(screen.getByText("MCP")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the count when provided", () => {
|
||||
render(<McpBadge count={3} />);
|
||||
expect(screen.getByText("3")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("AgentBadge", () => {
|
||||
it("should render with default 'Agent' text when no count is provided", () => {
|
||||
render(<AgentBadge />);
|
||||
expect(screen.getByText("Agent")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the count when provided", () => {
|
||||
render(<AgentBadge count={12} />);
|
||||
expect(screen.getByText("12")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { TimeCell, getTimeZone } from "./time_cell";
|
||||
|
||||
describe("TimeCell", () => {
|
||||
it("should render a formatted time string", () => {
|
||||
render(<TimeCell utcTime="2025-06-15T14:30:00Z" />);
|
||||
// The global toLocaleString mock in setupTests returns "YYYY-MM-DD HH:MM:SS"
|
||||
expect(screen.getByText(/2025/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render 'Error converting time' for invalid dates", () => {
|
||||
// toLocaleString on an Invalid Date returns "Invalid Date", not throwing,
|
||||
// but the component catches exceptions. Force an error by passing something
|
||||
// that causes Date constructor to produce NaN.
|
||||
render(<TimeCell utcTime="not-a-date" />);
|
||||
// The mock returns "NaN-NaN-NaN NaN:NaN:NaN" for invalid dates
|
||||
// The component has a try/catch that returns "Error converting time" on exception
|
||||
const el = screen.getByText(/NaN|Error/);
|
||||
expect(el).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render with monospace font", () => {
|
||||
render(<TimeCell utcTime="2025-06-15T14:30:00Z" />);
|
||||
const span = screen.getByText(/2025/);
|
||||
expect(span).toHaveStyle({ fontFamily: "monospace" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTimeZone", () => {
|
||||
it("should return a non-empty timezone string", () => {
|
||||
const tz = getTimeZone();
|
||||
expect(typeof tz).toBe("string");
|
||||
expect(tz.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue