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 patternspackages/core/src/app/__tests__/widgetRenderer.transition.test.ts—ui.boxtransition expectationsscripts/run-tests.mjs— test runnerpackages/core/src/testing/snapshot.ts— golden frame snapshot utilities (captureSnapshot,serializeSnapshot,diffSnapshots)scripts/rezi-snap.mjs— CLI for snapshot capture and verification
Steps
Create test file in the appropriate
__tests__/directoryUse
createTestRenderer()for widget/render tests: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")); }); });Test state logic by testing reducer functions directly:
it("increments count", () => { const state = reducer({ count: 0 }, { type: "increment" }); assert.strictEqual(state.count, 1); });Use
result.toText()for snapshot-style assertionsUse
result.findById()to locate specific nodes in the render treeUse golden frame snapshots for visual regression:
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()Run snapshot CLI for bulk capture/verify:
node scripts/rezi-snap.mjs --update # Capture new snapshots node scripts/rezi-snap.mjs --verify # Verify against storedFor animation features, cover:
- mount value
- retarget while running
- cleanup on unmount (no timer leak)
- property filtering (
position/size/opacity) forui.boxtransitions
For extended routed actions, verify
UiEventpayloads for widgets that emit non-press actions:- checkbox emits
action: "toggle"withchecked - virtualList emits
action: "select"withindex - table emits
action: "rowPress"withrowIndex - radio/select flows emit
action: "change"
- checkbox emits
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
# 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)