From 89d503778026b5b1f48d3716b4af1103ce8c1e94 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Wed, 12 Aug 2026 21:48:51 -0700 Subject: [PATCH 1/3] test(ui): characterize TokenFlow and JsonViewer Both components are shared by the logs, guardrails-monitor and tool-policies routes and had no test. These assert on rendered text and roles only, so they hold against antd Typography and against its replacement. --- .../LogDetailsDrawer/JsonViewer.test.tsx | 28 +++++++++++++++++++ .../LogDetailsDrawer/TokenFlow.test.tsx | 23 +++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.test.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.test.tsx diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.test.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.test.tsx new file mode 100644 index 00000000000..055e400167b --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.test.tsx @@ -0,0 +1,28 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { JsonViewer } from "./JsonViewer"; + +describe("JsonViewer", () => { + it("should render a placeholder and no tree when the log entry carries no payload", () => { + render(); + + expect(screen.getByText("No data")).toBeInTheDocument(); + expect(screen.queryByRole("tree")).not.toBeInTheDocument(); + }); + + it("should render the payload as a tree exposing its keys", () => { + render(); + + expect(screen.getByRole("tree")).toBeInTheDocument(); + expect(screen.getByText(/model/)).toBeInTheDocument(); + expect(screen.getByText(/stream/)).toBeInTheDocument(); + expect(screen.queryByText("No data")).not.toBeInTheDocument(); + }); + + it("should treat an empty payload as data rather than showing the placeholder", () => { + render(); + + expect(screen.getByRole("tree")).toBeInTheDocument(); + expect(screen.queryByText("No data")).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.test.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.test.tsx new file mode 100644 index 00000000000..f1784bb4009 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.test.tsx @@ -0,0 +1,23 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { TokenFlow } from "./TokenFlow"; + +describe("TokenFlow", () => { + it("should render the total followed by its prompt and completion breakdown", () => { + render(); + + expect(screen.getByText("12 (9 prompt tokens + 3 completion tokens)")).toBeInTheDocument(); + }); + + it("should group large counts with thousands separators", () => { + render(); + + expect(screen.getByText("1,323,579 (1,234,567 prompt tokens + 89,012 completion tokens)")).toBeInTheDocument(); + }); + + it("should fall back to zero for counts the log entry does not carry", () => { + render(); + + expect(screen.getByText("12 (0 prompt tokens + 0 completion tokens)")).toBeInTheDocument(); + }); +}); From 214a476c3e196812447060c68e6c7ca54679aab0 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Wed, 12 Aug 2026 22:10:39 -0700 Subject: [PATCH 2/3] refactor(ui): migrate TokenFlow and JsonViewer to shadcn Drops the antd Typography import from both. TokenFlow renders inside an antd Descriptions.Item that already sets the colour, font size, line height and wrapping the Text wrapper restated, so a bare span is pixel-identical there. JsonViewer's placeholder moves onto the muted-foreground token. The characterisation tests from the previous commit are unchanged and stay green, which is what shows the markup swap did not move behaviour. --- ui/litellm-dashboard/eslint-suppressions.json | 10 ---------- .../view_logs/LogDetailsDrawer/JsonViewer.tsx | 5 +---- .../view_logs/LogDetailsDrawer/TokenFlow.tsx | 8 ++------ 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 8a86305b4cb..8b067f79af6 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -3873,11 +3873,6 @@ "count": 1 } }, - "src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx": { "no-nested-ternary": { "count": 3 @@ -3927,11 +3922,6 @@ "count": 1 } }, - "src/components/view_logs/LogDetailsDrawer/TokenFlow.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/view_logs/LogDetailsDrawer/TruncatedValue.tsx": { "no-restricted-imports": { "count": 1 diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx index 0a873a7e255..af99eee0d16 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx @@ -1,10 +1,7 @@ -import { Typography } from "antd"; import { JsonView, defaultStyles } from "react-json-view-lite"; import "react-json-view-lite/dist/index.css"; import { JSON_MAX_HEIGHT, COLOR_BG_LIGHT, SPACING_LARGE } from "./constants"; -const { Text } = Typography; - interface JsonViewerProps { data: any; mode: "formatted"; @@ -15,7 +12,7 @@ interface JsonViewerProps { * Uses an interactive tree component for easy navigation. */ export function JsonViewer({ data }: JsonViewerProps) { - if (!data) return No data; + if (!data) return No data; return (
+ {total.toLocaleString()} ({prompt.toLocaleString()} prompt tokens + {completion.toLocaleString()} completion tokens) - + ); } From c25f927f034803e4613955872bb48f8f6219c1e6 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Wed, 12 Aug 2026 22:23:06 -0700 Subject: [PATCH 3/3] test(ui): make the TokenFlow grouping assertion locale-independent The assertion hard-coded en-US separators while toLocaleString follows the host locale, so it failed under de_DE. Building the expected string the same way keeps it deterministic everywhere and still catches a dropped toLocaleString wherever the locale groups at all. --- .../view_logs/LogDetailsDrawer/TokenFlow.test.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.test.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.test.tsx index f1784bb4009..0e76b92da36 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.test.tsx @@ -2,6 +2,8 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; import { TokenFlow } from "./TokenFlow"; +const localised = (count: number) => count.toLocaleString(); + describe("TokenFlow", () => { it("should render the total followed by its prompt and completion breakdown", () => { render(); @@ -9,10 +11,14 @@ describe("TokenFlow", () => { expect(screen.getByText("12 (9 prompt tokens + 3 completion tokens)")).toBeInTheDocument(); }); - it("should group large counts with thousands separators", () => { + it("should group large counts the way the reader's locale does", () => { render(); - expect(screen.getByText("1,323,579 (1,234,567 prompt tokens + 89,012 completion tokens)")).toBeInTheDocument(); + expect( + screen.getByText( + `${localised(1323579)} (${localised(1234567)} prompt tokens + ${localised(89012)} completion tokens)`, + ), + ).toBeInTheDocument(); }); it("should fall back to zero for counts the log entry does not carry", () => {