<!-- GENERATED by scripts/build-llms.mjs from llms/agent-tools.md — do not edit this file. -->

# `lr-test-results`

- **Import** `import '@aceshooting/lyra-ui/components/lr-test-results.js';` (stable tag alias; registers the tag)
- **Class** `LyraTestResults`, also available unregistered from `@aceshooting/lyra-ui/components/agent-tools/test-results/test-results.class.js`
- **Family** `components/agent-tools/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 16 parts, 8 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-test-results`

A pass/fail suite summary with per-status counts, status filter toggles, and per-test rows whose
failures auto-expand by default and can host rich slotted detail (e.g. a diff or code block)
alongside the plain failure message.

**Properties:** `suites: readonly TestSuiteResult[] = []` (attribute: false) — `TestSuiteResult { id: string;
name: string; tests: readonly TestCaseResult[] }` and `TestCaseResult { id: string; name: string; status:
TestStatus; durationMs?: number; message?: string }`, with `TestStatus = 'passed' | 'failed' |
'skipped' | 'running'` (all three exported here). `statusFilter: readonly TestStatus[] =
[]` (attribute: false) — empty shows every status. `runId: string | null = null` (attribute
`run-id`) identifies the source run, and `runState: TestRunState = 'idle'` (attribute `run-state`,
reflected) exposes its lifecycle. `autoExpandFailures: boolean = true`
(attribute `auto-expand-failures`). A duration renders only when it is finite and non-negative;
invalid/negative values are omitted rather than reaching `Intl.NumberFormat`. Empty/blank suite and
test ids are omitted; retained suite ids, then test ids within each suite, use deterministic
first-wins identity. Foreign runtime statuses normalize
once to the localized neutral `skipped` state, so every accepted row contributes to one coherent
summary count and renders a label/glyph.

Summary counts cover the complete normalized input. At most 1,000 rows mount for the active filter;
manually expanded identities reserve positions first, failed rows next, then ordinary input-order
rows. A localized `[part="limit"]` note exposes truncation without losing the complete counts.

**Slots:** `detail-{encodedSuiteId}:{encodedTestId}` — collision-free suite-scoped rich detail for
a test. Derive the complete name with the exported
`testResultDetailSlotName(suiteId, testId)` helper. Well-formed ids use `encodeURIComponent`
segments; isolated UTF-16 surrogates, which that built-in rejects, use deterministic uppercase
`%uXXXX` code-unit escapes. It renders after the plain `message` once expanded (for example, suite
`unit` and test `same` use `slot="detail-unit:same"`). This is the only detail slot the component
reads, and exactly one is mounted per row. The legacy `detail-{suiteId}-{testId}` and
`detail-{testId}` spellings were removed in 9.0.0 — content assigned to either is never slotted and
never makes a row expandable; migrate by deriving the name with
`testResultDetailSlotName(suiteId, testId)`. Slot listeners remain mounted while detail is absent,
so appending matching slotted content after the component's first render immediately enables the
row's disclosure.

**Events:** `lr-test-select` (`detail: { suiteId: string; testId: string }`, a test row's name was
activated), `lr-filter-change` (`detail: { statuses: TestStatus[] }` — the complete next filter set; the
component updates its own `statusFilter` first, then emits),
and `lr-toggle` (`detail: { suiteId: string; testId: string; expanded: boolean }`, a row's failure
detail was expanded/collapsed). The suite-scoped identity shape is invariant even when `testId` is
globally unique. Manual expansion state is keyed by that same suite+test pair.
Each expand/collapse action's localized accessible name includes both suite and test names, so
repeated row controls remain distinguishable.

Passed, failed, and skipped rows use language-neutral decorative marks (`✓`, `×`, and `–`); the
adjacent localized status word carries the meaning. Running rows use the decorative spinner.

**CSS parts:** `base`, `summary` (the status-count strip), `count` (carries `data-status`), `filter`,
`filter-toggle` (carries `data-status`/`aria-pressed`), `suite`, `suite-header`, `test` (carries
`data-status`), `test-status`, `test-name`, `test-duration`, `test-expand-toggle`, `failure`
(hidden while collapsed), `failure-message`, `limit`, and `empty`.

**Themeable custom properties:** `--lr-test-results-filter-active-bg` (default
`var(--lr-color-brand-quiet)`), `--lr-test-results-filter-active-border` (default
`var(--lr-color-brand)`) and `--lr-test-results-filter-active-color` (default
`var(--lr-color-brand)`) — the background, border color and text color of a pressed (active) status
filter toggle. All three follow the state-scoped-property convention described under
`lr-span-waterfall`: inline `var()` fallbacks rather than `:host` declarations, so each can be set on
the element or on any ancestor. They exist because
`::part(filter-toggle)[aria-pressed='true']` is invalid CSS — Shadow Parts forbids an attribute
selector after `::part()` — so restyling the pressed state otherwise meant overriding the
library-wide brand tokens. Per-result status foregrounds are independently exposed through
`--lr-test-results-passed-color` (default `var(--lr-color-success)`),
`--lr-test-results-failed-color` (default `var(--lr-color-danger)`),
`--lr-test-results-skipped-color` (default `var(--lr-color-text-quiet)`), and
`--lr-test-results-running-color` (default `var(--lr-color-brand)`).
`--lr-test-results-spinner-size` (default `var(--lr-size-1em)`) controls the composed spinner's
diameter in a running test row without requiring an override of `lr-spinner`'s token.
