AI Interface Components

Test Results Panel

A test results panel whose pass, fail and skip counts roll digit by digit over a smoothly growing ratio bar, with failed tests expanding to show an expected-versus-actual diff.

A results panel for an AI coding assistant after it runs tests, or for a CI summary page. The three count chips in the header — passed, failed, skipped — roll digit by digit next to the total duration; below them a segmented ratio bar grows smoothly on a spring, with still-running tests shown as stripes. The count chips double as filter buttons, and the selected backdrop slides to its new spot. The list is grouped by suite and collapsible; expanding a failure shows the error message and a line diff (- expected / + actual), along with "Rerun this" and "Copy error". The filter supports controlled and uncontrolled modes.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add test-results-panel.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

200
200
Active edited usageDefault props
<TestResultsPanel/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/test-results-panel.en.json

Or, once registries are configured in components.json, install it as @webberui/test-results-panel.

Usage

import { TestResultsPanel } from "@/components/ui/test-results-panel";

<TestResultsPanel
  running={false}
  suites={[
    {
      id: "cart",
      name: "cart-total.test.ts",
      tests: [
        { id: "c1", name: "subtotal is correct after adding items", status: "pass", duration: 12 },
        {
          id: "c2",
          name: "NT$100 off NT$1,000 applies to the total incl. shipping",
          status: "fail",
          duration: 18,
          error: { message: "discount not applied", expected: "950", actual: "1000" },
        },
        { id: "c3", name: "shows a notice when stock runs low", status: "skip" },
      ],
    },
  ]}
  onRerun={(testId) => console.log(testId ? `rerun ${testId}` : "rerun all")}
/>

Props

PropTypeDefaultDescription
suitesTestSuite[]—Test data grouped by suite
runningbooleanfalseWhether the whole run is in progress; when true the header shows a spinner and "Running…"
filter"all" | "fail" | "pass" | "skip"—Controlled filter; when omitted the component manages it internally (uncontrolled)
defaultFilter"all" | "fail" | "pass" | "skip""all"Initial filter in uncontrolled mode
onFilterChange(filter: TestResultsFilter) => void—Called when the filter changes
defaultExpandFailedbooleantrueAutomatically expand the first failed test when one appears
onRerun(testId?: string) => void—Rerun: no argument = rerun all; with testId = rerun just that test. Rerun buttons are hidden when omitted
stiffnessnumber200Spring stiffness for the rolling counts and ratio bar
dampingnumber26Spring damping for the rolling counts and ratio bar
durationnumber200Duration (ms) of expand/collapse and row enter/exit, also written to --wb-test-results-panel-duration
classNamestring—Passed through to the outermost container

TestSuite

FieldTypeDefaultDescription
idstring—Unique id
namestring—Display name (file path or description)
testsTestCase[]—Tests in this suite

TestCase

FieldTypeDefaultDescription
idstring—Unique id; onRerun passes this value back
namestring—Test name
status"pass" | "fail" | "skip" | "running"—Current status
durationnumber—Duration (ms); anything over 500ms is flagged as slow in amber
errorTestError—Error details when failed

TestError

FieldTypeDefaultDescription
messagestring—Error message (shown in red monospace)
expectedstring—Expected value; a line diff is shown when actual is also provided
actualstring—Actual value; a line diff is shown when expected is also provided

The pure function diffLines(expected, actual) is also exported by name. It returns { type: "same" | "removed" | "added"; text }[], so you can reuse the same line comparison elsewhere.

How it works

  • Rolling counts: each digit is an overflow-hidden column with 0–9 stacked vertically, and a spring (stiffness / damping) rolls it to the target digit; when digits are added, the ones digit keeps its own wheel.
  • Ratio bar: the widths of the passed / failed / skipped / running segments tween on the same spring, with the running part drawn as stripes.
  • Filtering: the count chips form an aria-pressed button group, and pressing the selected chip again returns to "all"; the selected backdrop slides via layoutId. Rows enter and exit with height and opacity when the filter changes.
  • Line diff: diffLines compares the two texts by longest common subsequence (LCS) with no package dependency; expected lines get a red background, actual lines green, and shared lines grey.
  • Auto-expand: with defaultExpandFailed on, the first failure to appear is expanded automatically; it only does so again after all failures have cleared and new ones appear, so rerunning a single test does not keep stealing expansion.
  • Any test whose status is still running also counts as running, so you do not need to toggle running when rerunning a single test.
  • The root sets --wb-test-results-panel-duration, which the colour transitions share, so you can override it globally.

Accessibility

  • The count chips are aria-pressed buttons whose aria-label includes the status and count; the rolling digits are hidden from assistive tech
  • When a run ends, an aria-live="polite" region announces "Passed 42, failed 3, skipped 1" once, without interrupting on every tick during the run
  • The ratio bar is aria-hidden because the information is already in the counts
  • Suite headers and failed rows are buttons with aria-expanded / aria-controls; ↑ / ↓ / Home / End move focus between them (roving tabindex), and Enter / Space expands or collapses
  • The − / + markers on each diff line are hidden from assistive tech and replaced by sr-only "Expected" / "Actual" labels; each row's status and slow tests also get sr-only text
  • When the user has reduce motion enabled, counts swap instantly, the ratio bar jumps into place, expansions appear immediately and status icons do not pop