--- name: ds-test-component description: Auto-generate all test files for DS components including visual, a11y, component, page object, and unit tests. Use when creating new components or setting up comprehensive test coverage. --- # Test Component Auto-generates all test files for a Helvetia Design System component in one command. ## Quick Start Generate all tests for a component: ```bash ds-test-component button ``` Process: 1. Parse component props, events, slots, parts, states 2. Show checklists for visual props and slots 3. Generate 6 file types (or fewer if not applicable) 4. Report summary with test counts Output files: - `button.visual.html` — Visual test fixture with variant sections - `button.visual.play.ts` — Playwright visual regression tests - `button.a11y.play.ts` — Comprehensive accessibility tests - `button.component.play.ts` — Event and behavior tests - `button.po.ts` — Page object in `packages/playwright/src/lib/components/` - `button.util.spec.ts` — Unit tests (if `.util.ts` exists) ## What Gets Generated ### 1. Visual Tests **visual.html** — Test fixture with sections: - **Basic** — Minimal component demo with auto-detected common props - **Enum variants** — All color/size values in one section per enum - **State variants** — Separate section per state (disabled, loading, invalid, etc.) - **Slot demos** — One section per slot, auto-detected from `render()` JSX **visual.play.ts** — Playwright tests: - "style" suite: screenshots from `button.style.html` - "host" suite: screenshots from `button.visual.html` - One test per variant #### Data-Driven Test Pattern (VARIANTS constant) Use a simple string array `VARIANTS` constant to avoid repetitive test code: **Define VARIANTS with testid strings:** ```ts const TAG = 'data' const VARIANTS = ['basic', 'border', 'horizontal', 'multiline', 'required', 'disabled', 'custom-form'] as const const image = screenshot(TAG) ``` **Loop through variants in forEach:** ```ts test.describe('host', () => { test.beforeEach('Setup', async ({ page }) => { await page.setupVisualTest(`/components/${TAG}/test/${TAG}.visual.html`) }) VARIANTS.forEach(variant => { test(variant, async ({ page }) => { const el = page.getByTestId(variant) await expectScreenshot(el, image(variant)) }) }) }) ``` **If component has both style.html and visual.html:** ```ts test.describe('style', () => { test.beforeEach('Setup', async ({ page }) => { await page.setupVisualTest(`/components/${TAG}/test/${TAG}.style.html`) }) VARIANTS.forEach(variant => { test(variant, async ({ page }) => { const el = page.getByTestId(variant) await expectScreenshot(el, image(`style-${variant}`)) }) }) }) test.describe('host', () => { test.beforeEach('Setup', async ({ page }) => { await page.setupVisualTest(`/components/${TAG}/test/${TAG}.visual.html`) }) VARIANTS.forEach(variant => { test(variant, async ({ page }) => { const el = page.getByTestId(variant) await expectScreenshot(el, image(variant)) }) }) }) ``` **Benefits:** - **Simple** — just a string array, no complex objects - **DRY** — one forEach covers all variants - **Maintainable** — testid is both the test name and selector - **Scalable** — works for any number of variants - **Consistent** — matches the tag component pattern ### 2. Accessibility Tests **a11y.play.ts** — Comprehensive a11y coverage: - Axe-core checks (auto-detected violations) - Semantic checks (ARIA labels, roles, heading hierarchy) - Contrast checks (WCAG AA compliance) - Tests all variants (colors, sizes, states) ### 3. Component Tests **component.play.ts** — Behavior tests: - **Event tests** — Auto-discovered from `@Event()` decorators - Verify each event fires - Verify event doesn't fire when expected (e.g., disabled) - **State tests** — Auto-discovered from boolean props - Assertions for each state (disabled, loading, invalid, etc.) - **Slot tests** — Verify each slot renders correctly - **Value tests** — If component has `value` prop, test `hasValue()` assertion ### 4. Page Object **button.po.ts** — Page object for testing: - **Part locators** — `readonly` property for each `@part` JSDoc tag - Pattern: `native = this.el.locator('[part="native"]')` - **Action methods** — `click()`, `focus()`, etc. - **Assertion methods** — `assertToBeDisabled()`, `assertToBeLoading()`, etc. - One method per boolean state prop - Plus `hasValue()` if component has `value` prop` Located in: `packages/playwright/src/lib/components/button.po.ts` ### 5. Unit Tests (if applicable) **button.util.spec.ts** — Tests for utility functions: - Auto-detects `button.util.ts` if it exists - Generates comprehensive test cases per function - Happy paths (valid inputs → expected outputs) - Edge cases (undefined, null, empty string, invalid values) - Type variations (if function accepts multiple types) - Ready to run with `pnpm test` ## Workflow ### Step 1: Invoke ```bash ds-test-component button ``` ### Step 2: Confirm Visual Props ``` Detected props: color, size, disabled, loading, icon, label Which are visual props? (deselect unwanted) ✓ color (enum) ✓ size (enum) ✓ disabled (state) ✓ loading (state) ✓ icon (prop) ✗ label (skip) ``` ### Step 3: Confirm Slots ``` Detected slots: icon, label, badge Which to demo? (deselect unwanted) ✓ icon ✓ label ✓ badge ``` ### Step 4: Files Generated ``` ✓ button.visual.html (8 variant sections) ✓ button.visual.play.ts (16 visual tests) ✓ button.a11y.play.ts (12 a11y tests) ✓ button.component.play.ts (8 event + 4 state tests) ✓ button.po.ts (5 parts + 4 assertions) ✓ button.util.spec.ts (4 utils, 18 test cases) Next: Review files, run tests: pnpm play, pnpm test ``` ## Example: Button Component ### Generated visual.html sections: ```html
Basic Button
Colors Primary Secondary ...
Disabled Disabled
Slot: Icon Button
``` ### Generated component test: ```ts test('should fire dsClick event', async ({ page }) => { await page.mount(`Click me`) const dsButton = new DsButton(page.locator('ds-button')) const spy = await dsButton.el.spyOnEvent('dsClick') await dsButton.click() expect(spy).toHaveReceivedEventTimes(1) }) test('should not fire dsClick when disabled', async ({ page }) => { await page.mount(`Disabled`) const dsButton = new DsButton(page.locator('ds-button')) const spy = await dsButton.el.spyOnEvent('dsClick') await dsButton.assertToBeDisabled() expect(spy).toHaveReceivedEventTimes(0) }) ``` ### Generated page object: ```ts export class DsButton extends PageObject { readonly native = this.el.locator('[part="native"]') readonly spinner = this.el.locator('[part="spinner"]') readonly icon = this.el.locator('[part="icon"]') async click() { await this.native.click() } async assertToBeDisabled() { await expect(this.el).toHaveAttribute('disabled') } async assertToBeLoading() { await expect(this.el).toHaveAttribute('loading') } async hasValue(value: string) { await expect(this.el).toHaveValue(value) } } ``` ## Key Behaviors - ✅ **Auto-discovers events** from `@Event()` decorators - ✅ **Auto-discovers states** from boolean props (disabled, loading, invalid, checked, etc.) - ✅ **Auto-discovers slots** from `render()` JSX, auto-generates demo content - ✅ **Auto-discovers parts** from `@part` JSDoc tags - ✅ **Auto-discovers utils** — generates comprehensive tests if `.util.ts` exists - ✅ **Never skips visual props** that user confirms — all get sections - ✅ **Never skips events or states** — all get test coverage - ✅ **Comprehensive unit tests** — not stubs; edge cases included ## Related See [REFERENCE.md](REFERENCE.md) for detailed generation rules for each test type.