--- name: rezi-write-tests description: Write tests for Rezi widgets, screens, or app logic using createTestRenderer and node:test. user-invocable: true allowed-tools: Read, Glob, Grep, Edit, Write, Bash(node scripts/run-tests.mjs*), Bash(node --test *) argument-hint: "[file-or-widget-to-test]" metadata: short-description: Write tests --- ## When to use Use this skill when: - Writing tests for Rezi widgets or components - Testing app state logic (reducers) - Testing keybinding resolvers - Need snapshot-style output assertions ## Source of truth - `packages/core/src/testing/` — test utilities (`createTestRenderer`, etc.) - `packages/core/src/widgets/__tests__/` — existing widget tests (use as examples) - `packages/core/src/widgets/__tests__/composition.animationHooks.test.ts` — animation hook test patterns - `packages/core/src/app/__tests__/widgetRenderer.transition.test.ts` — `ui.box` transition expectations - `scripts/run-tests.mjs` — test runner - `packages/core/src/testing/snapshot.ts` — golden frame snapshot utilities (`captureSnapshot`, `serializeSnapshot`, `diffSnapshots`) - `scripts/rezi-snap.mjs` — CLI for snapshot capture and verification ## Steps 1. **Create test file** in the appropriate `__tests__/` directory 2. **Use `createTestRenderer()`** for widget/render tests: ```typescript import { describe, it } from "node:test"; import * as assert from "node:assert/strict"; import { createTestRenderer, ui } from "@rezi-ui/core"; describe("MyWidget", () => { it("renders correctly", () => { const r = createTestRenderer({ viewport: { cols: 80, rows: 24 } }); const result = r.render(ui.text("hello")); assert.ok(result.findText("hello")); }); }); ``` 3. **Test state logic** by testing reducer functions directly: ```typescript it("increments count", () => { const state = reducer({ count: 0 }, { type: "increment" }); assert.strictEqual(state.count, 1); }); ``` 4. **Use `result.toText()`** for snapshot-style assertions 5. **Use `result.findById()`** to locate specific nodes in the render tree 6. **Use golden frame snapshots** for visual regression: ```typescript import { captureSnapshot, serializeSnapshot, diffSnapshots, parseSnapshot } from "@rezi-ui/core"; const snapshot = captureSnapshot("my-scene", myView(state), { viewport: { cols: 80, rows: 24 }, theme }, "dark"); const text = serializeSnapshot(snapshot); // Compare with stored snapshot using diffSnapshots() ``` 7. **Run snapshot CLI** for bulk capture/verify: ```bash node scripts/rezi-snap.mjs --update # Capture new snapshots node scripts/rezi-snap.mjs --verify # Verify against stored ``` 8. **For animation features**, cover: - mount value - retarget while running - cleanup on unmount (no timer leak) - property filtering (`position` / `size` / `opacity`) for `ui.box` transitions 9. **For extended routed actions**, verify `UiEvent` payloads for widgets that emit non-press actions: - checkbox emits `action: "toggle"` with `checked` - virtualList emits `action: "select"` with `index` - table emits `action: "rowPress"` with `rowIndex` - radio/select flows emit `action: "change"` 10. **For animation hook behavior**, test: - supported easing presets (`linear`, quad, cubic) resolve as expected - retargeted mid-flight runs continue smoothly without jumping - looping sequences (`loop: true`) remain active and deterministic ## Running tests ```bash # Full suite node scripts/run-tests.mjs # Single file node --test path/to/test.ts ``` ## Verification - All new tests pass - No existing tests broken - Tests are deterministic (bounded timers/explicit waits, no randomness) - Golden snapshots match expected output (if applicable)