Commit Graph

20 Commits

Author SHA1 Message Date
Yunfei He 48beac5673 test: add render lifecycle parity tests from Ink (+10) (#12)
Port missing render lifecycle tests from Ink's test/render.tsx:
- onRender fires on input-triggered state update
- throttle renders to maxFps (leading+trailing pattern)
- immediate scheduler in debug mode commits every mutation
- screen reader mode bypasses throttle (immediate commits)
- exit(error) followed by exit(value) still rejects
- exit(value) resolves even when called rapidly twice
- unmount does not write to ended stdout stream
- non-interactive mode writes only last frame at unmount
- non-interactive mode does not emit erase or cursor sequences
- non-interactive unmount does not crash on ended stdout

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:40:47 +08:00
Yunfei He 666cbca73a test: alternate screen teardown-safety and debug-mode tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:59:01 +08:00
Yunfei He 34827b312f test: alternate screen lifecycle tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:59:01 +08:00
Yunfei He 0022c5cc49 test: alternate screen guard condition tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:59:01 +08:00
Yunfei He 2e01fc50a4 chore: remove 8 exit todo stubs (covered by PTY fixtures)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 22:23:39 +08:00
Yunfei He 3422e6edc1 feat: stdin.ref/unref, raw mode default false, conditional listener, maxListeners
- stdin.ref() on raw mode enable, stdin.unref() on disable
- setEncoding("utf8") on raw mode enable
- Raw mode default changed from true to false (hooks acquire it)
- useStdin().setRawMode routed through ref-counted controller
- Stdin listeners attached only when raw mode is active
- EventEmitter maxListeners set to Infinity

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 22:22:58 +08:00
Yunfei He a87cc08d0d Revert "chore: remove 8 subprocess-fixture todo stubs (behaviors already covered)"
This reverts commit a5b0530c04.
2026-05-26 17:35:16 +08:00
Yunfei He a5b0530c04 chore: remove 8 subprocess-fixture todo stubs (behaviors already covered)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:25:06 +08:00
Yunfei He 3ee01dc278 feat: add patchConsole, waitUntilRenderFlush, onRender, and maxFps
Add four behavioral surfaces to the runtime:

- patchConsole (default: true): intercepts console.* methods to route
  output through writeToStdout/writeToStderr, preventing frame corruption.
  Disabled in debug mode. Restored before Vue cleanup on teardown.
- waitUntilRenderFlush(): flushes pending throttled renders and waits
  for the stdout write barrier, exposed on TuiApp and testing RenderResult.
- onRender callback: fires after each commit with { renderTime } in ms.
- maxFps option: overrides the scheduler's default 32ms throttle interval
  with 1000/maxFps. The scheduler now accepts a throttleMs option and
  exposes hasPending() for flush coordination.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 15:54:18 +08:00
Yunfei He f51079a5ea feat: rewrite stdin pipeline with input-parser, exitOnCtrlC via \x03
- Replace data event with readable + input-parser state machine
- Intercept \x03 in input pipeline (not process SIGINT)
- useInput now uses parse-keypress for semantic key mapping
- 20ms pending escape flush timer matching Ink
- Esc focus reset in input pipeline
- Delete old key-parser.ts (replaced by input-parser + parse-keypress)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:04:39 +08:00
Yunfei He 1106be867b feat: add error boundary with onErrorCaptured to catch descendant errors
Wrap user root component in an InternalErrorBoundary that catches all
descendant errors (setup, render, lifecycle) via onErrorCaptured and
routes them through exit(error). This prevents yoga WASM state corruption
that occurred when errors propagated uncaught during Vue's render phase.

- Add ErrorOverview component for rendering error frames
- Replace try-catch around mount with onErrorCaptured boundary
- Update testing render() to detect early exit errors
- Convert 3 todo tests to active tests (nested setup, unhandledRejection, post-mount)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 13:48:23 +08:00
Yunfei He 272f1f0c32 refactor!: tree validation throws on invalid nesting instead of warn+skip
Change Box-in-Text validation from dev-only warn+skip to unconditional
throw, matching Ink's reconciler behavior. Add text-outside-Text
validation that throws when non-empty text-leaf nodes are inserted into
box/root/static containers outside a Text context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 13:37:39 +08:00
Yunfei He 37c4905bbc fix: warn and skip insertion when Box is nested inside Text
Detect invalid <Box> inside <Text> nesting at the DOM insert level and
emit a dev warning instead of crashing the WASM yoga engine. The box
insertion is skipped to prevent layout corruption.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 01:04:16 +08:00
Yunfei He e79c3660f5 test: verify error boundary routes post-mount errors through exit()
Vue's errorHandler (installed after mount) already catches render-time
errors from nested components and routes them through exit(err),
providing error boundary behavior matching Ink's design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 01:00:56 +08:00
Yunfei He 42c69d72f2 feat: hide cursor on mount, show on unmount (matching Ink)
Write ESC[?25l to stdout after mount and ESC[?25h during teardown,
matching Ink's cursor management. Only active in production mode
(debug: false) to avoid interfering with test streams.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 01:00:17 +08:00
Yunfei He bcfdaddec7 feat: exit() accepts result value, waitUntilExit resolves with it
Change exit(error?: Error) to exit(errorOrResult?: unknown). When an
Error is passed, waitUntilExit rejects; otherwise it resolves with the
value. This matches Ink's behavior where exit("hello") resolves the
promise with "hello".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 00:10:52 +08:00
Yunfei He 5e54e5742d refactor: replace ref() with shallowRef() in all test files
AGENTS.md requires shallowRef by default. All test state uses
reassignment (not mutation), so shallowRef is correct.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 23:28:06 +08:00
Yunfei He d1c9945cc4 test: port Ink lifecycle, error, and resize tests (15 tests)
Covers exit lifecycle, error handling, and terminal resize
reactivity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:14:35 +08:00
Yunfei He 562b07b60e test: port Ink component tests (38 tests)
Distributes across text, static, transform, newline-spacer,
error-handling, and prop-reset test files.

Tests requiring features not yet in vue-tui are marked test.todo:
- <Transform> inside <Text> (paint pass limitation)
- multi-line transform (transform nodes not yoga carriers)
- text/box nesting validation (no runtime validation yet)
- complex Static rerender patterns (Ink-specific API)

Also fixes wrap="hard" to use wordWrap:false so text breaks at exact
character boundaries rather than word boundaries, matching Ink behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:01:10 +08:00
Yunfei He a7f7d9957d feat(runtime-tests): integration test suite
21 test files covering components, composables, focus, lifecycle,
and scheduler through the public render() API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:13:53 +08:00