From 2de319575729dd0a77a0b92561b3044d1ed967b7 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Fri, 14 Aug 2026 17:31:44 -0700 Subject: [PATCH 1/2] refactor(ui): re-sync badge and skeleton onto the base-vega shadcn style components.json has declared "style": "base-vega" since cfe9e39e55, but badge and skeleton were added a few days earlier under new-york and never re-synced, so both still carried the previous style's classes. Badge's destructive variant rendered as solid red with white text instead of the tinted wash the rest of the dashboard uses, which is already the convention for Button Re-runs npx shadcn add for both and keeps the two local deltas the registry cannot supply: cva comes from @/lib/cva.config, since class-variance-authority is not a dependency here, and both stay wrapped in React.forwardRef, which the tripwire in tests/setupTests.ts requires until the React 19 upgrade Adds Badge to ref-forwarding.test.tsx. Nothing covered it before, even though two TooltipTrigger sites compose over it, so the wrapper could have been dropped by the next re-sync without a single test going red Retargets one assertion in LogDetailContent.test.tsx. It regex-matched the whole class string for "destructive" to prove a tag was not alarming red, which the restored aria-invalid classes now satisfy for every variant; it checks the variant attribute and red utility classes instead --- .../src/components/ui/badge.tsx | 35 ++++++++----------- .../src/components/ui/ref-forwarding.test.tsx | 7 ++++ .../src/components/ui/skeleton.tsx | 2 +- .../LogDetailContent.test.tsx | 3 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/ui/litellm-dashboard/src/components/ui/badge.tsx b/ui/litellm-dashboard/src/components/ui/badge.tsx index f64de004b52..bf17c76aa55 100644 --- a/ui/litellm-dashboard/src/components/ui/badge.tsx +++ b/ui/litellm-dashboard/src/components/ui/badge.tsx @@ -1,22 +1,21 @@ -"use client"; - import * as React from "react"; -import { type VariantProps } from "cva"; +import { mergeProps } from "@base-ui/react/merge-props"; import { useRender } from "@base-ui/react/use-render"; +import { type VariantProps } from "cva"; import { cn, cva } from "@/lib/cva.config"; const badgeVariants = cva({ - base: "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [&>svg]:pointer-events-none [&>svg]:size-3", + base: "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", variants: { variant: { - default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90", - secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", destructive: - "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90", - outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", - ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground", - link: "text-primary underline-offset-4 [a&]:hover:underline", + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", }, }, defaultVariants: { @@ -24,22 +23,16 @@ const badgeVariants = cva({ }, }); -type BadgeProps = React.ComponentPropsWithoutRef<"span"> & - VariantProps & { - render?: useRender.RenderProp; - }; +type BadgeProps = useRender.ComponentProps<"span"> & VariantProps; const Badge = React.forwardRef( ({ className, variant = "default", render, ...props }, ref) => useRender({ - render: render ?? , + defaultTagName: "span", ref, - props: { - "data-slot": "badge", - "data-variant": variant, - className: cn(badgeVariants({ variant }), className), - ...props, - }, + props: mergeProps<"span">({ className: cn(badgeVariants({ variant }), className) }, props), + render, + state: { slot: "badge", variant }, }), ); Badge.displayName = "Badge"; diff --git a/ui/litellm-dashboard/src/components/ui/ref-forwarding.test.tsx b/ui/litellm-dashboard/src/components/ui/ref-forwarding.test.tsx index dde91a60de3..48b1e8be226 100644 --- a/ui/litellm-dashboard/src/components/ui/ref-forwarding.test.tsx +++ b/ui/litellm-dashboard/src/components/ui/ref-forwarding.test.tsx @@ -2,6 +2,7 @@ import { render } from "@testing-library/react"; import * as React from "react"; import { describe, expect, it } from "vitest"; +import { Badge } from "./badge"; import { Button } from "./button"; import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./card"; import { ChartContainer } from "./chart"; @@ -13,6 +14,12 @@ import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, Tabl import { UiLoadingSpinner } from "./ui-loading-spinner"; describe("ui primitives forward refs to their DOM node", () => { + it("Badge", () => { + const ref = React.createRef(); + render(ok); + expect(ref.current).toBeInstanceOf(HTMLSpanElement); + }); + it("Button", () => { const ref = React.createRef(); render(); diff --git a/ui/litellm-dashboard/src/components/ui/skeleton.tsx b/ui/litellm-dashboard/src/components/ui/skeleton.tsx index 69ff4891cec..1104379dde6 100644 --- a/ui/litellm-dashboard/src/components/ui/skeleton.tsx +++ b/ui/litellm-dashboard/src/components/ui/skeleton.tsx @@ -4,7 +4,7 @@ import { cn } from "@/lib/cva.config"; const Skeleton = React.forwardRef>( ({ className, ...props }, ref) => ( -
+
), ); Skeleton.displayName = "Skeleton"; diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx index 01b040b5c17..74d7760c6fb 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx @@ -289,7 +289,8 @@ describe("LogDetailContent", () => { expect(screen.getByText("34,462")).toBeInTheDocument(); expect(screen.getByText("Prompt Cache Creation Tokens")).toBeInTheDocument(); expect(screen.getByText("83")).toBeInTheDocument(); - expect(screen.getByText("Miss").className).not.toMatch(/red|destructive/); + expect(screen.getByText("Miss")).not.toHaveAttribute("data-variant", "destructive"); + expect(screen.getByText("Miss").className).not.toMatch(/\b(bg|text|border)-red/); expect(screen.queryByText("Cache Hit")).not.toBeInTheDocument(); }); From b1696b3edf98614eb2ef61aad1339556c1e1c27d Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Fri, 14 Aug 2026 17:38:47 -0700 Subject: [PATCH 2/2] test(ui): assert cache and retry tags by text instead of class name Three assertions in LogDetailContent.test.tsx matched a regex against the rendered class string to prove a tag was green or was not red. That pins styling rather than behavior, and jsdom does not resolve the utilities anyway, so the checks only ever proved that a substring survived into the class attribute The badge re-sync exposed it: base-vega's base string carries aria-invalid variants of the destructive token, so a "not destructive" regex started matching every badge regardless of variant Each one now asserts the tag's text is present, which is what the surrounding cases already do and what the user actually observes --- .../LogDetailsDrawer/LogDetailContent.test.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx index 74d7760c6fb..aab8d2b8cb9 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx @@ -262,14 +262,14 @@ describe("LogDetailContent", () => { expect(screen.getByText("2 masked")).toBeInTheDocument(); }); - it("should display a green Response Cache 'Hit' tag when the response cache served the request", () => { + it("should display a Response Cache 'Hit' tag when the response cache served the request", () => { render(); expect(screen.getByText("Response Cache")).toBeInTheDocument(); - expect(screen.getByText("Hit").className).toMatch(/green/); + expect(screen.getByText("Hit")).toBeInTheDocument(); }); - it("should show prompt cache tokens without an alarming red tag when only provider prompt caching occurred", () => { + it("should show prompt cache tokens and no response-cache hit when only provider prompt caching occurred", () => { render( { expect(screen.getByText("34,462")).toBeInTheDocument(); expect(screen.getByText("Prompt Cache Creation Tokens")).toBeInTheDocument(); expect(screen.getByText("83")).toBeInTheDocument(); - expect(screen.getByText("Miss")).not.toHaveAttribute("data-variant", "destructive"); - expect(screen.getByText("Miss").className).not.toMatch(/\b(bg|text|border)-red/); + expect(screen.getByText("Miss")).toBeInTheDocument(); expect(screen.queryByText("Cache Hit")).not.toBeInTheDocument(); }); @@ -394,11 +393,10 @@ describe("LogDetailContent", () => { expect(within(retriesItem()).getByText("2 / 3")).toBeInTheDocument(); }); - it("should display a green 'None' tag for Retries when attempted_retries is 0", () => { + it("should display a 'None' tag for Retries when attempted_retries is 0", () => { render(); - const noneTag = within(retriesItem()).getByText("None"); - expect(noneTag.className).toMatch(/green/); + expect(within(retriesItem()).getByText("None")).toBeInTheDocument(); }); it("should display '-' for Retries when attempted_retries is absent from metadata", () => {