CONTRIBUTOR-DOCS / Contributor guides / 2nd gen testing
In this doc
2nd gen components use Storybook play functions as the primary test layer. Tests live in dedicated test story files so docs stories remain clean and test stories only appear in development Storybook.
- Keep docs stories in
2nd-gen/packages/swc/components/<component>/stories/<component>.stories.ts. - Add test stories in
2nd-gen/packages/swc/components/<component>/test/<component>.test.ts. - Reuse the base stories and metadata from the main stories file.
- Test stories run in development Storybook only and are excluded from
storybook:build.
Every test file starts with the copyright header, imports, and a meta export that spreads the main stories meta. The tags: ['!autodocs', 'dev'] ensure test stories are excluded from docs and only appear in dev Storybook.
import { html } from 'lit';
import { expect } from '@storybook/test';
import type { Meta, StoryObj as Story } from '@storybook/web-components';
import { Badge } from '@adobe/swc/badge';
import '@adobe/swc/badge';
import { getComponent, setupSwcWarningSpy } from '../../../utils/test-utils.js';
import { meta } from '../stories/badge.stories.js';
import { Overview, Sizes } from '../stories/badge.stories.js';
// This file defines dev-only test stories that reuse the main story metadata.
export default {
...meta,
title: 'Badge/Tests',
parameters: {
...meta.parameters,
docs: { disable: true, page: null },
},
tags: ['!autodocs', 'dev'],
} as Meta;Organize test stories into exactly 5 sections using visual separator comments. Every test file follows this order — skip sections that don't apply to the component.
| # | Section | What it covers |
|---|---|---|
| 1 | Defaults | Overview test verifying initial property values and rendering |
| 2 | Properties / Attributes | Mutations, reflection, attribute-property sync, sizes, static colors, ARIA attributes |
| 3 | Slots | Slotted content, anatomy, label fallbacks |
| 4 | Variants / States | Semantic, non-semantic, outline, subtle variants; indeterminate, disabled, progress values, state transitions |
| 5 | Dev mode warnings | DEBUG mode validation, unsupported attribute warnings |
// ──────────────────────────────────────────────────────────────
// TEST: Defaults
// ──────────────────────────────────────────────────────────────
export const OverviewTest: Story = { /* ... */ };
// ──────────────────────────────────────────────────────────────
// TEST: Properties / Attributes
// ──────────────────────────────────────────────────────────────
export const PropertyMutationTest: Story = { /* ... */ };
// ──────────────────────────────────────────────────────────────
// TEST: Slots
// ──────────────────────────────────────────────────────────────
export const AnatomyTest: Story = { /* ... */ };
// ──────────────────────────────────────────────────────────────
// TEST: Variants / States
// ──────────────────────────────────────────────────────────────
export const SemanticVariantsTest: Story = { /* ... */ };
// ──────────────────────────────────────────────────────────────
// TEST: Dev mode warnings
// ──────────────────────────────────────────────────────────────
export const InvalidVariantWarningTest: Story = { /* ... */ };Use this table to decide where a test belongs:
| Test type | Section |
|---|---|
| Default values after first render | Defaults |
| Setting / clearing a property or attribute | Properties / Attributes |
| Size variations (s, m, l, xl) | Properties / Attributes |
| Static color attribute | Properties / Attributes |
| ARIA label, role override | Properties / Attributes |
| Slot content rendering | Slots |
| Label fallback from slot | Slots |
| Semantic / non-semantic variant list | Variants / States |
| Outline, subtle, fixed positioning | Variants / States |
| Indeterminate, disabled, progress values | Variants / States |
| State transitions (e.g., determinate → indeterminate) | Variants / States |
| Invalid value warnings | Dev mode warnings |
| Unsupported attribute warnings | Dev mode warnings |
| Valid value no-warning checks | Dev mode warnings |
Always destructure step from the play function arguments and wrap assertions in labeled steps. This provides descriptive output in both the Storybook UI and CI logs, making it much easier to identify which assertion failed.
export const OverviewTest: Story = {
...Overview,
play: async ({ canvasElement, step }) => {
const badge = await getComponent<Badge>(canvasElement, 'swc-badge');
await step(
'renders expected default values and slot content',
async () => {
expect(badge.variant).toBe('informative');
expect(badge.size).toBe('m');
expect(badge.textContent?.trim()).toBeTruthy();
}
);
},
};For multi-step tests, use multiple step calls to describe each phase:
play: async ({ canvasElement, step }) => {
const badge = await getComponent<Badge>(canvasElement, 'swc-badge');
await step('initially has fixed attribute', async () => {
expect(badge.fixed).toBe('block-start');
expect(badge.hasAttribute('fixed')).toBe(true);
});
await step('removes fixed attribute when set to undefined', async () => {
badge.fixed = undefined;
await badge.updateComplete;
expect(badge.fixed).toBeFalsy();
expect(badge.hasAttribute('fixed')).toBe(false);
});
},Use the getComponent<T> helper from test-utils.ts to query a component and automatically await its updateComplete promise in one call:
import { getComponent } from '../../../utils/test-utils.js';
// Instead of:
const badge = canvasElement.querySelector('swc-badge') as Badge;
await badge.updateComplete;
// Use:
const badge = await getComponent<Badge>(canvasElement, 'swc-badge');When a story renders multiple components, use the getComponents<T> helper to query all matching elements and await their updates in one call:
import { getComponents } from '../../../utils/test-utils.js';
// Instead of:
const badges = Array.from(canvasElement.querySelectorAll('swc-badge')) as Badge[];
await Promise.all(badges.map((badge) => badge.updateComplete));
// Use:
const badges = await getComponents<Badge>(canvasElement, 'swc-badge');You can also pass more specific selectors to narrow the query:
const staticCircles = await getComponents<ProgressCircle>(
canvasElement,
'swc-progress-circle[static-color]'
);Prefer focused assertions inside each step. Use await element.updateComplete after mutating a property to wait for the render update before asserting.
When a story renders a list (semantic variants, color variants, sizes), loop over the rendered elements inside a step:
await step('renders all semantic variant values', async () => {
for (const variant of BADGE_VARIANTS_SEMANTIC) {
const badge = canvasElement.querySelector(
`swc-badge[variant="${variant}"]`
) as Badge | null;
await badge?.updateComplete;
expect(badge).toBeTruthy();
expect(badge?.variant).toBe(variant);
}
});Use withWarningSpy from test-utils.ts to capture __swc warning calls. It enables DEBUG mode, runs your callback, and automatically restores the original state — no manual try/finally needed.
import { getComponent, withWarningSpy } from '../../../utils/test-utils.js';
export const InvalidVariantWarningTest: Story = {
render: () => html`<swc-badge>Label</swc-badge>`,
play: async ({ canvasElement, step }) => {
const badge = await getComponent<Badge>(canvasElement, 'swc-badge');
await step(
'warns when an invalid variant is set in DEBUG mode',
() =>
withWarningSpy(async (warnCalls) => {
badge.variant = 'not-a-variant' as unknown as Badge['variant'];
await badge.updateComplete;
expect(warnCalls.length).toBeGreaterThan(0);
expect(String(warnCalls[0]?.[1] || '')).toContain('variant');
})
);
},
};If you need lower-level control (e.g., checking swcGlobals directly), setupSwcWarningSpy() is still available — just remember to call restore() in a finally block.
Coverage is collected via the Storybook Vitest project using the V8 provider. Run it with:
yarn workspace @adobe/swc test:coverageWe enforce per-path coverage thresholds in 2nd-gen/packages/swc/vitest.config.js. Different parts of the codebase have different targets:
| Path pattern | Target | What it covers |
|---|---|---|
components/**/*.{ts,js} |
100% lines, functions, statements | SWC component implementations |
**/packages/core/components/**/*.{ts,js} |
100% lines, functions, statements | Core component base classes |
**/packages/core/shared/**/*.{ts,js} |
70% lines, functions, statements | Shared utilities (lower bar while starting out) |
The higher bar on component code ensures that every component and its base class are thoroughly tested. The shared utilities threshold is lower because some utility code may be difficult to exercise through component-level tests alone — the threshold will ratchet upward automatically as coverage improves.
The coverage config uses autoUpdate with Math.floor to automatically ratchet thresholds upward. When your tests push coverage above the current threshold, the config file updates itself to lock in the new minimum. This prevents regressions without requiring manual threshold bumps.
thresholds: {
autoUpdate: (value) => Math.floor(value),
// ...per-path thresholds
}Some components include dedicated Playwright accessibility test files (e.g., badge.a11y.spec.ts, status-light.a11y.spec.ts) that use @axe-core/playwright for comprehensive WCAG validation and ARIA snapshot testing.
Run the Storybook test suite with:
yarn workspace @adobe/swc test| Purpose | Command | Notes |
|---|---|---|
| Run Storybook tests (headless) | yarn workspace @adobe/swc test |
Runs Storybook tests with aXe checks. |
| Run tests in UI mode | yarn workspace @adobe/swc test:ui |
Uses Vitest UI for interactive debugging. |
| Run tests in a visible browser | yarn workspace @adobe/swc test:browser |
Launches browser for live debugging. |
| Run tests with coverage | yarn workspace @adobe/swc test:coverage |
Generates coverage for Storybook tests. |
- Keep each test story focused on one behavior.
- Always destructure
stepfrom the play function and wrap assertions in labeled steps. - Use
getComponent<T>fromtest-utils.tsto query and await a single component. - Use
getComponents<T>fromtest-utils.tsto query and await multiple components. - Use
await element.updateCompleteafter property mutations before asserting. - Use
canvasElementinside storyplayto scope selectors. - Keep test stories in the component's
testfolder and reuse base stories. - Organize test stories into the 5 standard sections in order: Defaults → Properties / Attributes → Slots → Variants / States → Dev mode warnings. Skip sections that don't apply.
- Use
withWarningSpyfor warning tests — it handles setup and teardown automatically.