Skip to content

Components: Component tests for the agentic component library #4216

Description

@JasonVMo

Summary

Establish and enforce one minimum coverage contract for every agentic component and primitive, close the current gaps, and document how generated components satisfy it. Existing tests and stories provide a strong base, but their depth is inconsistent and no repository gate currently prevents an under-tested component from shipping.

Observed. Coverage already exists and is colocated, so this is not a green-field
task: 18 components and 5 primitives each have a runtime test and a story, and 22 of
23 have a type test. What is missing is a contract: nothing states what coverage a
component must have, nothing enforces it, and the depth varies by a factor of five
between components. The canonical component, button, is itself the one file set with
no type test.

Goal

Define and enforce a single minimum coverage contract for every component and
primitive in packages/agentic/components, close the measured gaps against that
contract in the existing library, and publish authoring instructions so a newly
generated component arrives with the same coverage as button without an author
having to infer it.

Stage

Stage 1 (beta foundations). The readiness model and every missing-component task
depend on this contract existing.

Why it matters

Observed. .github/skills/agentic-component-authoring/references/tests-and-stories.md
describes what good coverage looks like in prose and points at button as the
canonical example, but it is a skill reference loaded by an agent, not a gate. A
component can be generated, exported, and published with three test cases and no
snapshot, and nothing in lint, build, or test objects.

Observed. Runtime case counts across the 18 components range from 3
(divider.test.tsx) to 15 (button.test.tsx). The spread is not explained by
component complexity alone: progress-bar has three variant axes and six cases;
spinner has one axis and four.

Observed. Only button, list-item, and skeleton have __snapshots__
directories. The other 15 components have no committed record of resolved output, so a
token or theme regression that changes every component's flattened style is invisible
to the package test run.

Inferred. Because readiness-model.md intends to publish a
readiness level per component, and because a readiness level is only meaningful if the
evidence behind it is uniform, an un-enforced coverage contract makes every readiness
claim self-reported.

Observed current state

Inventory

All counts observed from packages/agentic/components/src on
user/jasonvmo/furn-plan.

Component Runtime cases Type test Story Snapshots
accordion 10 accordion.types.test.tsx yes no
avatar 9 avatar.types.test.ts yes no
badge 6 badge.types.test.ts yes no
button 15 none yes yes
card 7 card.types.test.tsx yes no
checkbox 13 checkbox.types.test.ts yes no
divider 3 divider.types.test.ts yes no
input 9 input.types.test.tsx yes no
list-item 12 list-item.types.test.ts yes yes
listbox-item 9 listbox-item.types.test.ts yes no
menu-item 10 menu-item.types.test.ts yes no
progress-bar 6 progress-bar.types.test.ts yes no
radio 9 radio.types.test.ts yes no
skeleton 4 skeleton.types.test.ts yes yes
spinner 4 spinner.types.test.ts yes no
switch 12 switch.types.test.ts yes no
tab 10 tab.types.test.ts yes no
tag 9 tag.types.test.ts yes no

Observed. Primitives (checkbox-indicator, compound-item-layout, focus-visual,
icon, layout-stable-text) each have a runtime test, a type test, and a story, and
none has a SPEC.md.

Execution

Observed. jest.config.cjs
extends @fluentui-react-native/scripts/jest-config and sets testTimeout: 10000.
package.json declares furn.jestPlatform: "macos", so the package's unit tests
resolve React Native modules through the macOS preset only. There is no Windows or
Win32 unit-test pass for this package.

Observed. src/index.test.ts
is an export-surface guard: it references each use*_unstable, use*Styles_unstable,
and render*_unstable symbol. It is maintained by hand, so it can silently fall behind
src/index.ts.

Observed. On-device coverage is broader than the render smoke list.
apps/storybook/windows-tests/storybook-smoke.test.cjs
selects 14 story IDs: the 3 render cases data-driven from
scripts/smoke-stories.json
(components-button--default, primitives-icon--default,
primitives-callout--default), a keyboard-movement case on
components-button--overview, and 10 focus-regression cases covering tag,
accordion, tab, listbox-item, checkbox, menu-item, list-item, radio,
switch, and the interactive card. Counting Button Overview, that is 11 focus
assertions
, each checking HasKeyboardFocus after a click and that the app has not
terminated.

Observed. On-device touchpoints therefore exist for 11 of the 18 components plus
the icon primitive and Callout. avatar, badge, divider, input,
progress-bar, skeleton, and spinner have none, and neither do the
checkbox-indicator, compound-item-layout, focus-visual, and
layout-stable-text primitives.

Observed. That harness is Windows-only (testEnvironment: '@react-native-windows/automation' via
jest.windows.config.cjs), has no
macOS equivalent, and is invoked by no workflow.

Inferred. The on-device problem this task has to address is not absence of
coverage but its shape: real Windows focus coverage exists, is unenforced, single
platform, concentrated on one axis, and silent about seven components and four
primitives.

Scope

  1. Write the minimum coverage contract for a higher-order component and for a
    primitive, as a durable document under
    packages/agentic/components (or as a section in the existing authoring reference,
    if that is the preferred home).
  2. Decide, per requirement, whether it is required, required when the component has
    the relevant axis
    , or recommended, so the contract can be checked rather than
    argued.
  3. Close the concrete gaps in the existing library:
    • add button.types.test.ts;
    • add snapshot coverage for the components whose visual contract is
      state-dependent, or record why a component is exempt;
    • raise the low-coverage components to the contract, driven by their SPEC.md
      variant axes rather than by a target number;
    • raise primitive coverage from the axes in each colocated CONTRACT.md, its public
      types, and its renderer branches. Public primitives do not require an upstream
      SPEC.md.
  4. Add enforcement: a check that fails when a component directory is missing a
    required artifact, and a check that src/index.ts and src/index.test.ts agree.
  5. Write the "how to create component tests" instructions, including the table-driven
    pattern for finite axes, the self-driving versus externally driven axis
    distinction, and when a snapshot is the right tool instead of an assertion.
  6. Decide whether a second Jest platform pass (Windows) is in scope for beta, and
    record the decision either way.
  7. After the retain-or-retire decision in
    storybook-e2e.md, add the
    uncovered components and primitives through the portable story-test format.
    Only extend storybook-smoke.test.cjs when that decision explicitly retains
    it; otherwise preserve its existing focus regressions until the replacement
    demonstrates parity. State whether stable root testID values are required
    by the portable coverage contract.

Out of scope

  • Building the on-device end-to-end pipeline; that is
    research/infrastructure/tasks/storybook-e2e.md.
  • Adding a visual-regression service.
  • Changing component behavior to make it easier to test. A test that requires a
    behavior change should raise the behavior change as its own item.
  • Coverage for packages/components, packages/experimental, and packages/native.

Deliverables

  1. A written coverage contract, with each requirement marked required, conditional, or
    recommended, and with the condition stated for conditional requirements.
  2. button.types.test.ts covering the button slot acceptance and public prop
    contract, matching the depth of the other 17 type tests.
  3. Snapshot coverage decisions applied: added where the visual contract is
    state-dependent, and a recorded exemption where it is not.
  4. Raised runtime coverage for items below the contract, with each new component case
    traceable to a SPEC.md axis and each new primitive case traceable to its
    CONTRACT.md, public types, or renderer branches.
  5. An automated structural check that a component directory contains its required
    files, wired into the package's declared lint or test script.
  6. An automated check that every symbol exported from src/index.ts is exercised by
    src/index.test.ts, replacing the hand-maintained list.
  7. Authoring instructions for creating component tests, discoverable from
    packages/agentic/components/AGENTS.md.
  8. Portable on-device story tests for the components and primitives the current
    Windows harness does not touch, plus a stated position on whether testID
    conventions are contract-required for every component.

Acceptance criteria

  • A coverage contract document exists and is linked from
    packages/agentic/components/AGENTS.md.
  • Every requirement in the contract is either mechanically checkable or explicitly
    marked as a review-time judgement.
  • packages/agentic/components/src/components/button/button.types.test.ts exists
    and covers slot acceptance and mutually exclusive props.
  • Every one of the 18 components either has a __snapshots__ directory or a
    recorded exemption in the contract document.
  • No component's runtime test omits an axis declared in its SPEC.md variant
    table; where an axis is deliberately untested, the reason is recorded.
  • No primitive's runtime test omits a behavioral, prop, accessibility, or renderer
    axis declared by its CONTRACT.md and public types; where an axis is deliberately
    untested, the reason is recorded.
  • A structural coverage check fails when a component directory is missing a
    required file, and it runs as part of a declared package script.
  • The src/index.ts / src/index.test.ts agreement is machine-verified.
  • yarn workspace @fluentui-react-native/components format, lint, build, and
    test all pass after the changes.
  • The authoring instructions are sufficient for a new component to be generated
    with contract-satisfying coverage without reading an existing component's tests.
  • A decision on a Windows Jest pass is recorded, with its rationale.
  • Every component and primitive has a portable on-device story test or a
    recorded exemption, closing the current gap for avatar, badge,
    divider, input, progress-bar, skeleton, spinner, and the four
    uncovered primitives.
  • Existing Windows focus regressions remain covered until the portable
    replacement passes the same cases; no new work is committed to the legacy
    harness before its retain-or-retire decision.
  • The contract states whether a stable root testID is required for every
    component or only for the on-device validated subset.

Dependencies and ordering

  • Blocks readiness-model.md: the readiness levels assert
    coverage, so the coverage contract must exist first.
  • Blocks, softly, every task in
    missing-components/: each new component should be
    generated against the finished contract rather than retrofitted.
  • Independent of focus-zone.md and
    spec-ingestion-and-generation.md.
  • Consumes the Infrastructure workstream's on-device pipeline and its
    legacy-harness decision for the platform-validation half of the contract.

Risks and open decisions

  1. Snapshot churn. Snapshots over flattened resolved styles are sensitive to theme
    and token changes. Open decision: snapshot the resolved output of a small,
    named set of states per component (the button approach) rather than whole trees.
  2. Contract as ceiling. A mechanical contract can become the maximum rather than
    the minimum. Open decision: whether the structural check should also assert a
    floor on axis coverage, or leave depth to review.
  3. Second Jest platform cost. A Windows preset pass doubles unit-test runtime and
    may surface platform-fork type conflicts that
    AGENTS.md warns about. Open decision: whether beta
    requires it or whether on-device Windows coverage from the Storybook pipeline is
    sufficient.
  4. Primitives have no SPEC.md. Resolved in
    #4250: public
    primitives use a colocated lightweight CONTRACT.md, public types, and renderer
    branches as their coverage source instead of an upstream design-system spec.
  5. On-device coverage is single-platform and single-axis. The existing 11 focus
    assertions run only under @react-native-windows/automation and only check
    HasKeyboardFocus after a click. Open decision: whether extending them is this
    task's job or belongs entirely to
    storybook-e2e.md; the boundary
    should be agreed before either task starts, so the coverage is not written twice or
    not at all.

Evidence and references

Sources retrieved 2026-08-20.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions