Files
vue-tui/docs/superpowers/specs/2026-05-27-ink-test-parity-design.md
T
Yunfei He 39b124e86d docs: add Ink test parity design spec (~290 tests across 4 parts)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:39:24 +08:00

308 lines
11 KiB
Markdown

# Ink Test Parity — Design Spec
## Goal
Supplement ~290 missing tests to reach parity with Ink v7.0.4's test suite. Only covers code already implemented in vue-tui. Excludes kitty keyboard protocol management, alternateScreen, synchronized output (BSU/ESU), and React concurrent mode (assigned to other agents or N/A).
## Approach
Expand existing test files in-place. No new test files unless a module has zero tests today. Follow existing test patterns: unit tests colocated with source (`packages/runtime/src/**/*.test.ts`), integration tests in `packages/runtime-tests/integration/**/*.test.tsx`.
All tests reference Ink's test suite at `/tmp/ink/test/` as the source of truth. Each test should be adapted to Vue's API (`defineComponent`, `h()`, `shallowRef`) and vue-tui's test helper (`render()` from `@vue-tui/testing`).
## Scope Exclusions
- **kittyKeyboard** (77 tests) — feature not implemented, separate agent
- **alternateScreen** (12 tests) — feature not implemented, separate agent
- **synchronized output** (5 tests) — feature not implemented
- **concurrent mode** (20 tests) — React-specific, N/A
- **build-output** (4 tests) — Ink build verification, not applicable
- **reconciler** (10 tests) — React reconciler internals, not applicable
---
## Part A: Unit Tests (~125 tests)
Pure function tests. No render helper needed. Colocated with source in `packages/runtime/src/`.
### A1: sanitize-ansi (+24 tests)
**File:** `packages/runtime/src/paint/sanitize-ansi.test.ts` (5 → 29)
**Ink ref:** `/tmp/ink/test/sanitize-ansi.ts`
Missing categories:
- C1 variant stripping (CSI, OSC with C1/ESC ST/BEL terminators)
- DCS passthrough stripping (tmux, incomplete, BEL in payload)
- SOS control strings (all terminator variants, escaped ESC in payload, SGR preserved around SOS)
- Private-parameter m-sequences
- ESC ST stripping, malformed ESC sequences, incomplete CSI
- Standalone ST bytes, standalone C1 controls
### A2: ansi-tokenizer (+19 tests)
**File:** `packages/runtime/src/paint/ansi-tokenizer.test.ts` (7 → 26)
**Ink ref:** `/tmp/ink/test/ansi-tokenizer.ts`
Missing categories:
- C1 CSI with intermediates, C1 SGR CSI, incomplete C1 CSI/OSC
- OSC with ST terminator (vs BEL)
- tmux DCS as single token, DCS with BEL in payload
- ESC/C1 SOS (ST, C1 ST, BEL-invalid, incomplete, escaped ESC)
- Incomplete CSI/ESC-intermediate marked as invalid
- Standalone C1 controls
### A3: input-parser (+32 tests)
**File:** `packages/runtime/src/io/input-parser.test.ts` (12 → 44)
**Ink ref:** `/tmp/ink/test/input-parser.ts`
Missing categories:
- Multi-key chunks, CSI parameters, SS3 sequences
- Meta+CSI/SS3 double escape, escaped printable/supplementary codepoints
- Legacy ESC[[ (mixed, across chunks, combined)
- Incomplete holding states (legacy, SS3, double-escape)
- Flush pending (escape, CSI, SS3, legacy), meta+SS3
- Empty chunk handling, text + incomplete escape
- 14 backspace/delete splitting cases
- 10 bracketed paste tests (event creation, split delivery, empty paste, chunk assembly, backspace in paste)
### A4: cursor-helpers (+7 tests)
**File:** `packages/runtime/src/io/cursor-helpers.test.ts` (10 → 17)
**Ink ref:** `/tmp/ink/test/cursor-helpers.tsx`
Missing:
- `buildCursorSuffix` (first line, single-line output)
- `buildReturnToBottom` (moves down, no cursorDown at bottom)
- `buildCursorOnlySequence` (full sequence with hide prefix, no hide when cursor not shown)
- `buildReturnToBottomPrefix` (undefined previousCursorPosition)
### A5: frame-writer / log-update (+34 tests)
**File:** `packages/runtime/src/io/frame-writer.test.ts` (1 → 35)
**Ink ref:** `/tmp/ink/test/log-update.tsx`
vue-tui's frame-writer wraps a self-built log-update. Tests should cover the frame-writer's public API (not log-update internals).
Missing categories:
- Standard rendering: render, update, skip identical output
- Incremental rendering: render, update, skip identical, surgical line updates, cursor rewind, clear extra lines, grow, single write call, shrinking output
- clear()/done() reset behavior, multiple clear(), sync()+update
- 14 cursor positioning tests (setCursorPosition, cursor suffix, return-to-bottom)
- 6 no-trailing-newline tests (fullscreen mode behavior)
- Render-to-empty edge case
### A6: text-width (+9 tests)
**File:** `packages/runtime/src/host/text-measure.test.ts` (4 → 13)
**Ink ref:** `/tmp/ink/test/text-width.tsx`
Missing:
- Wide chars in fixed-width Box, CJK fixed-width
- Mixed ASCII+wide char width
- ANSI styled text width
- Empty Text sibling
- Truncate CJK (3 variants + box-width)
- Overlay CJK 2nd/1st cell, CJK overlay on CJK
- Clipped empty write
---
## Part B: Component & Composable Tests (~114 tests)
Functional tests using `render()` from `@vue-tui/testing`. Located in `packages/runtime-tests/integration/`.
### B1: text component (+15 tests)
**File:** `packages/runtime-tests/integration/components/text.test.tsx` (31 → 46)
**Ink ref:** `/tmp/ink/test/text.tsx`
Missing: ANSI sanitization integration through `<Text>` — the text component should strip non-SGR ANSI sequences while preserving SGR styling and OSC hyperlinks. Ink has ~18 tests for this; ~15 are unique after deduplication.
Categories:
- Strip cursor movement/position/erase sequences
- Preserve SGR, OSC hyperlinks (BEL+ST), C1 OSC, SGR colon params
- Strip non-SGR CSI, C1 non-SGR CSI, ESC controls
- Strip tmux DCS (BEL+ST), C1 DCS, PM/APC, C1 PM/APC, ESC/C1 SOS
- Strip malformed SOS, preserve SGR around SOS
- Strip incomplete DCS/C1 DCS/OSC/C1 OSC/ESC, standalone ST/C1
### B2: use-animation (+36 tests)
**File:** `packages/runtime-tests/integration/composables/use-animation.test.tsx` (9 → 45)
**Ink ref:** `/tmp/ink/test/use-animation.tsx`
Missing categories:
- Shared timer: same/different intervals, cleanup/recreation, stays alive, inactive animations
- Timer leak prevention, frame catch-up
- isActive/interval reset behavior (frame, time, delta)
- Different interval rates
- Edge intervals: NaN, Infinity, -Infinity, oversized, zero, negative
- maxFps interaction (4 tests)
- Delta with throttled ticks
- Pausing stops ticks, interval change unsubscribes
- Wall clock protection, debug+non-interactive modes
- Newly mounted/activated don't inherit time
- Same-interval rerender stability
- time/delta progression
- reset() (function, stable ref, while paused)
- Unmount before first tick, resume cycles, isActive false from mount
### B3: use-box-metrics / measure (+19 tests)
**Files:**
- `packages/runtime-tests/integration/composables/use-box-metrics.test.tsx` (9 → ~24)
- `packages/runtime/src/host/text-measure.test.ts` (see A6)
**Ink ref:** `/tmp/ink/test/use-box-metrics.tsx` + `measure-element.tsx` + `measure-text.tsx`
Missing useBoxMetrics (11):
- Resize updates, tracked ref on resize
- Sibling content changes (2 variants)
- Tracked ref attaches after initial render
- No extra re-renders, resize listener cleanup
- No crash after unmount+resize, zeros when ref unattached
- hasMeasured lifecycle (3), resets on unmount
Missing measure-element (4):
- After state update, multiple updates, useLayoutEffect, throttled
Missing measure-text (4, beyond A6):
- Cache behavior, multiline+wide chars
### B4: screen-reader (+11 tests)
**File:** `packages/runtime-tests/integration/accessibility/screen-reader.test.tsx` (14 → 25)
**Ink ref:** `/tmp/ink/test/screen-reader.tsx`
Missing:
- aria-label on Text/Box in screen-reader mode
- Omit ANSI styling
- Multiple Text components, nested Box+Text, null component
- aria-state variants (busy, disabled, expanded, multiline, multiselectable, readonly, required, selected)
- Multi-line text/nested row layout, roles on multi-line
- Listbox with multiselectable
### B5: render-to-string (+18 tests)
**File:** `packages/runtime-tests/integration/render-to-string.test.tsx` (14 → 32)
**Ink ref:** `/tmp/ink/test/render-to-string.tsx`
Missing:
- Text with variable, nested text, empty fragment
- Box padding, flex row/column, margin, gap
- Fixed width+height, Spacer, Newline, Border
- Colored/bold text, wrap/truncate
- Default/custom columns, Transform
- Static (3 variants)
- Effect behavior (3: initial before effects, useLayoutEffect, cleanup)
- Error handling (3: propagation, text-outside-Text, recovery)
- Independent calls, deeply nested tree
### B6: cursor composable (+7 tests)
**File:** `packages/runtime-tests/integration/composables/use-cursor.test.tsx` (5 → 12)
**Ink ref:** `/tmp/ink/test/cursor.tsx`
Missing:
- Cursor follows text input, space input cursor move
- Cursor cleared on unmount
- No screen scroll on subsequent renders
- useStdout/useStderr write with cursor
- Debug mode write interactions
### B7: components general (+8 tests)
**Files:** various in `packages/runtime-tests/integration/components/`
**Ink ref:** `/tmp/ink/test/components.tsx` (63 in-scope after exclusions)
vue-tui already covers most. Gap is ~8 tests:
- OSC hyperlink wrapping (6: BEL/ST wrap, non-hyperlink, hard-wrap BEL/ST single-word)
- Text with fragment
- Link ANSI escapes closed properly
---
## Part C: Layout Tests (+22 tests)
Small gaps across 9 existing layout test files. All use `render()` helper.
| File | Current | Target | Gap |
| ----------------------------- | ------- | ------ | --- |
| display.test.tsx | 2 | 4 | +2 |
| gap.test.tsx | 3 | 6 | +3 |
| flex-direction.test.tsx | 5 | 7 | +2 |
| flex-justify-content.test.tsx | 10 | 12 | +2 |
| margin.test.tsx | 11 | 13 | +2 |
| padding.test.tsx | 12 | 14 | +2 |
| width-height.test.tsx | 18 | 22 | +4 |
| overflow.test.tsx | 36 | 40 | +4 |
| position.test.tsx | 11 | 12 | +1 |
---
## Part D: Render Lifecycle & Integration (+29 tests)
### D1: render lifecycle (+27 tests)
**Files:** `packages/runtime-tests/integration/lifecycle/*.test.tsx` + `pty/render.test.ts`
**Ink ref:** `/tmp/ink/test/render.tsx` (56 in-scope after exclusions)
Missing categories:
- Clear output behavior
- Console intercept
- Rerender on resize
- Throttle to maxFps
- onRender metrics
- No throttled render after unmount, unmount forces pending
- waitUntilExit timing
- waitUntilRenderFlush suite (6 tests)
- Issue-596 effect timing
- Exit value ordering (3 tests)
- Cross-realm Error (2 tests)
- Unmount with ended stdout (3 tests)
- Primary screen cleanup
- CI/non-TTY rendering (4 tests)
- Non-interactive mode suite
### D2: hooks misc (+2 tests)
**Files:** existing use-input / use-stdout tests
**Ink ref:** `/tmp/ink/test/hooks.tsx`
Missing:
- "ignore input if not active" (multiple hooks variant)
- useStdout PTY write variant
---
## Execution
4 Parts, executed sequentially via subagent-driven development. Each Part produces a branch → PR.
**Estimated effort:**
- Part A: ~125 tests, mostly mechanical (copy Ink test logic, adapt to vitest/vue-tui API)
- Part B: ~114 tests, moderate (need render helper, Vue component adaptation)
- Part C: ~22 tests, small (fill gaps in existing files)
- Part D: ~29 tests, moderate (lifecycle timing, some may need PTY)
**Total: ~290 tests across ~25 existing files.**