@xincli/opentui-core 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. package/README.md +63 -0
  2. package/dev/keypress-debug-renderer.ts +148 -0
  3. package/dev/keypress-debug.ts +43 -0
  4. package/dev/print-env-vars.ts +32 -0
  5. package/dev/test-tmux-graphics-334.sh +68 -0
  6. package/dev/test-tmux-theme-queries.sh +151 -0
  7. package/dev/thai-debug-test.ts +68 -0
  8. package/dev/theme-mode.ts +290 -0
  9. package/docs/development.md +144 -0
  10. package/package.json +102 -0
  11. package/prebuilt/aarch64-android/libopentui.so +0 -0
  12. package/scripts/build-android.ts +226 -0
  13. package/scripts/build-native-termux.sh +525 -0
  14. package/scripts/build.ts +530 -0
  15. package/scripts/dist-test.ts +312 -0
  16. package/scripts/package-prebuilt.ts +110 -0
  17. package/scripts/publish.ts +60 -0
  18. package/scripts/test-node-hook.mjs +16 -0
  19. package/scripts/test-node.ts +241 -0
  20. package/scripts/vendor-deps.sh +73 -0
  21. package/src/NativeSpanFeed.ts +375 -0
  22. package/src/Renderable.ts +1878 -0
  23. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  24. package/src/animation/Timeline.test.ts +2709 -0
  25. package/src/animation/Timeline.ts +598 -0
  26. package/src/ansi.ts +22 -0
  27. package/src/audio.ts +479 -0
  28. package/src/benchmark/attenuation-benchmark.ts +81 -0
  29. package/src/benchmark/audio-playback-benchmark.ts +344 -0
  30. package/src/benchmark/box-draw-benchmark.ts +1042 -0
  31. package/src/benchmark/colormatrix-benchmark.ts +123 -0
  32. package/src/benchmark/gain-benchmark.ts +80 -0
  33. package/src/benchmark/latest-all-bench-run.json +707 -0
  34. package/src/benchmark/latest-async-bench-run.json +336 -0
  35. package/src/benchmark/latest-default-bench-run.json +657 -0
  36. package/src/benchmark/latest-large-bench-run.json +707 -0
  37. package/src/benchmark/latest-quick-bench-run.json +207 -0
  38. package/src/benchmark/layout-benchmark.ts +2547 -0
  39. package/src/benchmark/markdown-benchmark.ts +1796 -0
  40. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  41. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  42. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  43. package/src/benchmark/native-span-feed-compare.ts +280 -0
  44. package/src/benchmark/render-traversal-benchmark.ts +928 -0
  45. package/src/benchmark/text-buffer-render-benchmark.ts +874 -0
  46. package/src/benchmark/text-table-benchmark.ts +948 -0
  47. package/src/buffer.test.ts +292 -0
  48. package/src/buffer.ts +564 -0
  49. package/src/console.test.ts +629 -0
  50. package/src/console.ts +1253 -0
  51. package/src/edit-buffer.test.ts +1852 -0
  52. package/src/edit-buffer.ts +417 -0
  53. package/src/editor-view.test.ts +1056 -0
  54. package/src/editor-view.ts +290 -0
  55. package/src/index.ts +25 -0
  56. package/src/lib/KeyHandler.integration.test.ts +292 -0
  57. package/src/lib/KeyHandler.stopPropagation.test.ts +289 -0
  58. package/src/lib/KeyHandler.test.ts +662 -0
  59. package/src/lib/KeyHandler.ts +222 -0
  60. package/src/lib/RGBA.test.ts +60 -0
  61. package/src/lib/RGBA.ts +343 -0
  62. package/src/lib/ascii.font.ts +330 -0
  63. package/src/lib/border.test.ts +83 -0
  64. package/src/lib/border.ts +170 -0
  65. package/src/lib/bunfs.test.ts +27 -0
  66. package/src/lib/bunfs.ts +18 -0
  67. package/src/lib/clipboard.test.ts +63 -0
  68. package/src/lib/clipboard.ts +41 -0
  69. package/src/lib/clock.ts +35 -0
  70. package/src/lib/data-paths.test.ts +133 -0
  71. package/src/lib/data-paths.ts +109 -0
  72. package/src/lib/debounce.ts +106 -0
  73. package/src/lib/detect-links.test.ts +98 -0
  74. package/src/lib/detect-links.ts +56 -0
  75. package/src/lib/env.test.ts +228 -0
  76. package/src/lib/env.ts +209 -0
  77. package/src/lib/extmarks-history.ts +51 -0
  78. package/src/lib/extmarks-multiwidth.test.ts +324 -0
  79. package/src/lib/extmarks.test.ts +3547 -0
  80. package/src/lib/extmarks.ts +861 -0
  81. package/src/lib/fonts/block.json +405 -0
  82. package/src/lib/fonts/grid.json +265 -0
  83. package/src/lib/fonts/huge.json +741 -0
  84. package/src/lib/fonts/pallet.json +314 -0
  85. package/src/lib/fonts/shade.json +591 -0
  86. package/src/lib/fonts/slick.json +321 -0
  87. package/src/lib/fonts/tiny.json +69 -0
  88. package/src/lib/hast-styled-text.ts +59 -0
  89. package/src/lib/index.ts +22 -0
  90. package/src/lib/keybinding.internal.test.ts +342 -0
  91. package/src/lib/keybinding.internal.ts +182 -0
  92. package/src/lib/objects-in-viewport.test.ts +789 -0
  93. package/src/lib/objects-in-viewport.ts +153 -0
  94. package/src/lib/output.capture.ts +58 -0
  95. package/src/lib/parse.keypress-kitty.protocol.test.ts +347 -0
  96. package/src/lib/parse.keypress-kitty.test.ts +712 -0
  97. package/src/lib/parse.keypress-kitty.ts +470 -0
  98. package/src/lib/parse.keypress.test.ts +2020 -0
  99. package/src/lib/parse.keypress.ts +480 -0
  100. package/src/lib/parse.mouse.test.ts +552 -0
  101. package/src/lib/parse.mouse.ts +232 -0
  102. package/src/lib/paste.ts +18 -0
  103. package/src/lib/queue.ts +65 -0
  104. package/src/lib/render-geometry.ts +36 -0
  105. package/src/lib/renderable.validations.test.ts +87 -0
  106. package/src/lib/renderable.validations.ts +83 -0
  107. package/src/lib/scroll-acceleration.ts +98 -0
  108. package/src/lib/selection.ts +261 -0
  109. package/src/lib/singleton.ts +34 -0
  110. package/src/lib/stdin-parser.test.ts +2439 -0
  111. package/src/lib/stdin-parser.ts +2007 -0
  112. package/src/lib/styled-text.ts +178 -0
  113. package/src/lib/terminal-capability-detection.test.ts +226 -0
  114. package/src/lib/terminal-capability-detection.ts +98 -0
  115. package/src/lib/terminal-palette.test.ts +954 -0
  116. package/src/lib/terminal-palette.ts +441 -0
  117. package/src/lib/tree-sitter/assets/README.md +119 -0
  118. package/src/lib/tree-sitter/assets/javascript/highlights.scm +205 -0
  119. package/src/lib/tree-sitter/assets/javascript/tree-sitter-javascript.wasm +0 -0
  120. package/src/lib/tree-sitter/assets/markdown/highlights.scm +150 -0
  121. package/src/lib/tree-sitter/assets/markdown/injections.scm +27 -0
  122. package/src/lib/tree-sitter/assets/markdown/tree-sitter-markdown.wasm +0 -0
  123. package/src/lib/tree-sitter/assets/markdown_inline/highlights.scm +115 -0
  124. package/src/lib/tree-sitter/assets/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  125. package/src/lib/tree-sitter/assets/typescript/highlights.scm +604 -0
  126. package/src/lib/tree-sitter/assets/typescript/tree-sitter-typescript.wasm +0 -0
  127. package/src/lib/tree-sitter/assets/update.ts +360 -0
  128. package/src/lib/tree-sitter/assets/zig/highlights.scm +284 -0
  129. package/src/lib/tree-sitter/assets/zig/tree-sitter-zig.wasm +0 -0
  130. package/src/lib/tree-sitter/assets.d.ts +9 -0
  131. package/src/lib/tree-sitter/cache.test.ts +291 -0
  132. package/src/lib/tree-sitter/client.test.ts +1434 -0
  133. package/src/lib/tree-sitter/client.ts +847 -0
  134. package/src/lib/tree-sitter/default-parsers.ts +133 -0
  135. package/src/lib/tree-sitter/download-utils.ts +148 -0
  136. package/src/lib/tree-sitter/index.ts +75 -0
  137. package/src/lib/tree-sitter/parser.worker.ts +1098 -0
  138. package/src/lib/tree-sitter/parsers-config.ts +81 -0
  139. package/src/lib/tree-sitter/resolve-ft.test.ts +55 -0
  140. package/src/lib/tree-sitter/resolve-ft.ts +189 -0
  141. package/src/lib/tree-sitter/types.ts +132 -0
  142. package/src/lib/tree-sitter/update-assets.ts +10 -0
  143. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  144. package/src/lib/tree-sitter-styled-text.ts +314 -0
  145. package/src/lib/validate-dir-name.ts +55 -0
  146. package/src/lib/yoga.options.test.ts +628 -0
  147. package/src/lib/yoga.options.ts +346 -0
  148. package/src/native-event-worker-repro.test.ts +48 -0
  149. package/src/native-event-worker-repro.worker.ts +9 -0
  150. package/src/native-handle.test.ts +221 -0
  151. package/src/platform/ffi.test.ts +516 -0
  152. package/src/platform/ffi.ts +653 -0
  153. package/src/platform/runtime.test.ts +52 -0
  154. package/src/platform/runtime.ts +121 -0
  155. package/src/platform/test.ts +45 -0
  156. package/src/platform/worker-handler-cleanup.fixture.ts +12 -0
  157. package/src/platform/worker-onmessage-startup.fixture.ts +10 -0
  158. package/src/platform/worker-startup.fixture.ts +9 -0
  159. package/src/platform/worker.node-test.ts +153 -0
  160. package/src/platform/worker.ts +425 -0
  161. package/src/plugins/core-slot.ts +579 -0
  162. package/src/plugins/registry.ts +402 -0
  163. package/src/plugins/types.ts +46 -0
  164. package/src/post/effects.ts +948 -0
  165. package/src/post/filters.ts +512 -0
  166. package/src/post/matrices.ts +288 -0
  167. package/src/renderables/ASCIIFont.ts +219 -0
  168. package/src/renderables/Box.test.ts +474 -0
  169. package/src/renderables/Box.ts +353 -0
  170. package/src/renderables/Code.test.ts +2388 -0
  171. package/src/renderables/Code.ts +555 -0
  172. package/src/renderables/Diff.regression.test.ts +381 -0
  173. package/src/renderables/Diff.test.ts +3212 -0
  174. package/src/renderables/Diff.ts +1278 -0
  175. package/src/renderables/EditBufferRenderable.test.ts +288 -0
  176. package/src/renderables/EditBufferRenderable.ts +1152 -0
  177. package/src/renderables/FrameBuffer.ts +47 -0
  178. package/src/renderables/Input.test.ts +1310 -0
  179. package/src/renderables/Input.ts +273 -0
  180. package/src/renderables/LineNumberRenderable.ts +725 -0
  181. package/src/renderables/Markdown.ts +2144 -0
  182. package/src/renderables/ScrollBar.ts +423 -0
  183. package/src/renderables/ScrollBox.ts +903 -0
  184. package/src/renderables/Select.test.ts +1134 -0
  185. package/src/renderables/Select.ts +535 -0
  186. package/src/renderables/Slider.test.ts +456 -0
  187. package/src/renderables/Slider.ts +342 -0
  188. package/src/renderables/TabSelect.test.ts +238 -0
  189. package/src/renderables/TabSelect.ts +447 -0
  190. package/src/renderables/Text.selection-buffer.test.ts +118 -0
  191. package/src/renderables/Text.test.ts +2662 -0
  192. package/src/renderables/Text.ts +147 -0
  193. package/src/renderables/TextBufferRenderable.ts +504 -0
  194. package/src/renderables/TextNode.test.ts +1137 -0
  195. package/src/renderables/TextNode.ts +379 -0
  196. package/src/renderables/TextTable.test.ts +1411 -0
  197. package/src/renderables/TextTable.ts +1434 -0
  198. package/src/renderables/Textarea.ts +425 -0
  199. package/src/renderables/TimeToFirstDraw.ts +89 -0
  200. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  201. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  202. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  203. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  204. package/src/renderables/__tests__/Code.test.ts +55 -0
  205. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  206. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +827 -0
  207. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1906 -0
  208. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  209. package/src/renderables/__tests__/Markdown.code-colors.test.ts +288 -0
  210. package/src/renderables/__tests__/Markdown.test.ts +4175 -0
  211. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  212. package/src/renderables/__tests__/Textarea.buffer.test.ts +683 -0
  213. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  214. package/src/renderables/__tests__/Textarea.editing.test.ts +2120 -0
  215. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  216. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  217. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  218. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3275 -0
  219. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  220. package/src/renderables/__tests__/Textarea.rendering.test.ts +1927 -0
  221. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  222. package/src/renderables/__tests__/Textarea.selection.test.ts +1589 -0
  223. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  224. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  225. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  226. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  227. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  228. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  229. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  230. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  231. package/src/renderables/__tests__/markdown-parser.test.ts +241 -0
  232. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  233. package/src/renderables/composition/README.md +8 -0
  234. package/src/renderables/composition/VRenderable.ts +32 -0
  235. package/src/renderables/composition/constructs.ts +127 -0
  236. package/src/renderables/composition/vnode.ts +277 -0
  237. package/src/renderables/index.ts +23 -0
  238. package/src/renderables/markdown-parser.ts +79 -0
  239. package/src/renderer-theme-mode.ts +199 -0
  240. package/src/renderer.ts +4975 -0
  241. package/src/runtime-plugin-support-configure.ts +63 -0
  242. package/src/runtime-plugin-support.ts +13 -0
  243. package/src/runtime-plugin.ts +626 -0
  244. package/src/specs/terminal-startup.md +64 -0
  245. package/src/syntax-style.test.ts +841 -0
  246. package/src/syntax-style.ts +274 -0
  247. package/src/testing/README.md +219 -0
  248. package/src/testing/bun-test-node.ts +880 -0
  249. package/src/testing/capture-spans.test.ts +216 -0
  250. package/src/testing/integration.test.ts +276 -0
  251. package/src/testing/manual-clock.ts +117 -0
  252. package/src/testing/mock-keys.test.ts +1378 -0
  253. package/src/testing/mock-keys.ts +457 -0
  254. package/src/testing/mock-mouse.test.ts +218 -0
  255. package/src/testing/mock-mouse.ts +247 -0
  256. package/src/testing/mock-tree-sitter-client.ts +81 -0
  257. package/src/testing/spy.ts +13 -0
  258. package/src/testing/terminal-capabilities.ts +47 -0
  259. package/src/testing/test-recorder.test.ts +410 -0
  260. package/src/testing/test-recorder.ts +133 -0
  261. package/src/testing/test-renderer.ts +375 -0
  262. package/src/testing/test-renderer.wait.test.ts +278 -0
  263. package/src/testing/test-streams.ts +31 -0
  264. package/src/testing.ts +9 -0
  265. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  266. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  267. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  268. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  269. package/src/tests/allocator-stats.test.ts +53 -0
  270. package/src/tests/audio.test.ts +303 -0
  271. package/src/tests/destroy-during-render.test.ts +200 -0
  272. package/src/tests/destroy-on-exit.fixture.ts +36 -0
  273. package/src/tests/destroy-on-exit.test.ts +62 -0
  274. package/src/tests/ffi-borrowed-pointer-callsites.test.ts +153 -0
  275. package/src/tests/hover-cursor.test.ts +98 -0
  276. package/src/tests/native-backed-measurement-lifecycle.test.ts +133 -0
  277. package/src/tests/native-backed-measurement-parity.test.ts +322 -0
  278. package/src/tests/native-span-feed-async.test.ts +173 -0
  279. package/src/tests/native-span-feed-close.test.ts +120 -0
  280. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  281. package/src/tests/native-span-feed-edge-cases.test.ts +384 -0
  282. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  283. package/src/tests/opacity.test.ts +123 -0
  284. package/src/tests/renderable.snapshot.test.ts +524 -0
  285. package/src/tests/renderable.test.ts +1851 -0
  286. package/src/tests/renderer.clock.test.ts +270 -0
  287. package/src/tests/renderer.console-startup.test.ts +2151 -0
  288. package/src/tests/renderer.control.test.ts +458 -0
  289. package/src/tests/renderer.core-slot-binding.test.ts +962 -0
  290. package/src/tests/renderer.cursor.test.ts +26 -0
  291. package/src/tests/renderer.custom-stdout.test.ts +1112 -0
  292. package/src/tests/renderer.destroy-during-render.test.ts +147 -0
  293. package/src/tests/renderer.focus-restore.test.ts +257 -0
  294. package/src/tests/renderer.focus.test.ts +329 -0
  295. package/src/tests/renderer.idle.test.ts +219 -0
  296. package/src/tests/renderer.input.test.ts +2615 -0
  297. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  298. package/src/tests/renderer.lifecycle.test.ts +34 -0
  299. package/src/tests/renderer.mouse.test.ts +1315 -0
  300. package/src/tests/renderer.notifications.test.ts +27 -0
  301. package/src/tests/renderer.palette.test.ts +1199 -0
  302. package/src/tests/renderer.remote.test.ts +247 -0
  303. package/src/tests/renderer.render-stats.test.ts +146 -0
  304. package/src/tests/renderer.scrollback-surface.test.ts +474 -0
  305. package/src/tests/renderer.selection.test.ts +138 -0
  306. package/src/tests/renderer.slot-registry.test.ts +684 -0
  307. package/src/tests/renderer.tracker.test.ts +202 -0
  308. package/src/tests/renderer.useMouse.test.ts +83 -0
  309. package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
  310. package/src/tests/runtime-plugin-node-modules-import-like-string.fixture.ts +48 -0
  311. package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +43 -0
  312. package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +67 -0
  313. package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +72 -0
  314. package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +44 -0
  315. package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +85 -0
  316. package/src/tests/runtime-plugin-path-alias.fixture.ts +43 -0
  317. package/src/tests/runtime-plugin-resolve-roots.fixture.ts +65 -0
  318. package/src/tests/runtime-plugin-support-configure.fixture.ts +31 -0
  319. package/src/tests/runtime-plugin-support-late-addition.fixture.ts +17 -0
  320. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  321. package/src/tests/runtime-plugin-support.test.ts +54 -0
  322. package/src/tests/runtime-plugin-windows-file-url.fixture.ts +30 -0
  323. package/src/tests/runtime-plugin.fixture.ts +40 -0
  324. package/src/tests/runtime-plugin.test.ts +369 -0
  325. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  326. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  327. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  328. package/src/tests/scrollbox.test.ts +1895 -0
  329. package/src/tests/wrap-resize-perf.test.ts +276 -0
  330. package/src/tests/yoga-api-coverage.test.ts +409 -0
  331. package/src/tests/yoga-callback-stress.test.ts +122 -0
  332. package/src/tests/yoga-ffi.test.ts +117 -0
  333. package/src/tests/yoga-setters.test.ts +921 -0
  334. package/src/tests/yoga-upstream/YGAlignBaselineTest.test.ts +124 -0
  335. package/src/tests/yoga-upstream/YGComputedBorderTest.test.ts +27 -0
  336. package/src/tests/yoga-upstream/YGComputedMarginTest.test.ts +27 -0
  337. package/src/tests/yoga-upstream/YGComputedPaddingTest.test.ts +27 -0
  338. package/src/tests/yoga-upstream/YGDirtiedTest.test.ts +157 -0
  339. package/src/tests/yoga-upstream/YGErrataTest.test.ts +40 -0
  340. package/src/tests/yoga-upstream/YGFlexBasisAuto.test.ts +23 -0
  341. package/src/tests/yoga-upstream/YGHasNewLayout.test.ts +81 -0
  342. package/src/tests/yoga-upstream/YGMeasureCacheTest.test.ts +31 -0
  343. package/src/tests/yoga-upstream/YGMeasureTest.test.ts +70 -0
  344. package/src/tests/yoga-upstream/generated/YGAbsolutePositionTest.test.ts +1864 -0
  345. package/src/tests/yoga-upstream/generated/YGAlignContentTest.test.ts +5829 -0
  346. package/src/tests/yoga-upstream/generated/YGAlignItemsTest.test.ts +2444 -0
  347. package/src/tests/yoga-upstream/generated/YGAlignSelfTest.test.ts +293 -0
  348. package/src/tests/yoga-upstream/generated/YGAndroidNewsFeed.test.ts +315 -0
  349. package/src/tests/yoga-upstream/generated/YGAspectRatioTest.test.ts +231 -0
  350. package/src/tests/yoga-upstream/generated/YGAutoTest.test.ts +347 -0
  351. package/src/tests/yoga-upstream/generated/YGBorderTest.test.ts +256 -0
  352. package/src/tests/yoga-upstream/generated/YGBoxSizingTest.test.ts +2747 -0
  353. package/src/tests/yoga-upstream/generated/YGDimensionTest.test.ts +129 -0
  354. package/src/tests/yoga-upstream/generated/YGDisplayContentsTest.test.ts +109 -0
  355. package/src/tests/yoga-upstream/generated/YGDisplayTest.test.ts +1097 -0
  356. package/src/tests/yoga-upstream/generated/YGFlexDirectionTest.test.ts +4562 -0
  357. package/src/tests/yoga-upstream/generated/YGFlexTest.test.ts +677 -0
  358. package/src/tests/yoga-upstream/generated/YGFlexWrapTest.test.ts +2071 -0
  359. package/src/tests/yoga-upstream/generated/YGGapTest.test.ts +3303 -0
  360. package/src/tests/yoga-upstream/generated/YGIntrinsicSizeTest.test.ts +582 -0
  361. package/src/tests/yoga-upstream/generated/YGJustifyContentTest.test.ts +2150 -0
  362. package/src/tests/yoga-upstream/generated/YGMarginTest.test.ts +1963 -0
  363. package/src/tests/yoga-upstream/generated/YGMinMaxDimensionTest.test.ts +1446 -0
  364. package/src/tests/yoga-upstream/generated/YGPaddingTest.test.ts +354 -0
  365. package/src/tests/yoga-upstream/generated/YGPercentageTest.test.ts +1760 -0
  366. package/src/tests/yoga-upstream/generated/YGRoundingTest.test.ts +1161 -0
  367. package/src/tests/yoga-upstream/generated/YGSizeOverflowTest.test.ts +207 -0
  368. package/src/tests/yoga-upstream/generated/YGStaticPositionTest.test.ts +6338 -0
  369. package/src/tests/yoga-upstream/tools/MeasureCounter.ts +54 -0
  370. package/src/tests/yoga-upstream/tools/utils.ts +81 -0
  371. package/src/text-buffer-view.test.ts +722 -0
  372. package/src/text-buffer-view.ts +193 -0
  373. package/src/text-buffer.test.ts +512 -0
  374. package/src/text-buffer.ts +250 -0
  375. package/src/types.ts +199 -0
  376. package/src/utils.ts +88 -0
  377. package/src/yoga.ts +1146 -0
  378. package/src/zig/ansi.zig +502 -0
  379. package/src/zig/audio.zig +1206 -0
  380. package/src/zig/bench/README.md +50 -0
  381. package/src/zig/bench/buffer-color-blending_bench.zig +350 -0
  382. package/src/zig/bench/buffer-draw-box_bench.zig +786 -0
  383. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +894 -0
  384. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  385. package/src/zig/bench/editor-view_bench.zig +169 -0
  386. package/src/zig/bench/native-span-feed_bench.zig +107 -0
  387. package/src/zig/bench/rope-markers_bench.zig +713 -0
  388. package/src/zig/bench/rope_bench.zig +514 -0
  389. package/src/zig/bench/styled-text_bench.zig +476 -0
  390. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  391. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  392. package/src/zig/bench/text-chunk-graphemes_bench.zig +272 -0
  393. package/src/zig/bench/utf8_bench.zig +799 -0
  394. package/src/zig/bench-utils.zig +431 -0
  395. package/src/zig/bench.zig +223 -0
  396. package/src/zig/buffer-methods.zig +215 -0
  397. package/src/zig/buffer.zig +2515 -0
  398. package/src/zig/build.zig +700 -0
  399. package/src/zig/build.zig.zon +19 -0
  400. package/src/zig/edit-buffer.zig +900 -0
  401. package/src/zig/editor-view.zig +812 -0
  402. package/src/zig/event-bus.zig +28 -0
  403. package/src/zig/event-emitter.zig +65 -0
  404. package/src/zig/file-logger.zig +92 -0
  405. package/src/zig/grapheme.zig +838 -0
  406. package/src/zig/handles.zig +267 -0
  407. package/src/zig/lib.zig +2813 -0
  408. package/src/zig/link.zig +349 -0
  409. package/src/zig/logger.zig +43 -0
  410. package/src/zig/mem-registry.zig +126 -0
  411. package/src/zig/miniaudio_shim.c +21 -0
  412. package/src/zig/native-renderable.zig +112 -0
  413. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  414. package/src/zig/native-span-feed.zig +747 -0
  415. package/src/zig/renderer-output.zig +677 -0
  416. package/src/zig/renderer.zig +2106 -0
  417. package/src/zig/rope.zig +1220 -0
  418. package/src/zig/split-scrollback.zig +88 -0
  419. package/src/zig/syntax-style.zig +174 -0
  420. package/src/zig/terminal.zig +1652 -0
  421. package/src/zig/test.zig +82 -0
  422. package/src/zig/tests/README.md +18 -0
  423. package/src/zig/tests/ansi_test.zig +18 -0
  424. package/src/zig/tests/audio_test.zig +439 -0
  425. package/src/zig/tests/buffer-methods_test.zig +1111 -0
  426. package/src/zig/tests/buffer_test.zig +2926 -0
  427. package/src/zig/tests/edit-buffer-history_test.zig +299 -0
  428. package/src/zig/tests/edit-buffer_test.zig +1688 -0
  429. package/src/zig/tests/editor-view_test.zig +3454 -0
  430. package/src/zig/tests/event-emitter_test.zig +249 -0
  431. package/src/zig/tests/grapheme_test.zig +1329 -0
  432. package/src/zig/tests/handles_test.zig +123 -0
  433. package/src/zig/tests/link_test.zig +220 -0
  434. package/src/zig/tests/mem-registry_test.zig +473 -0
  435. package/src/zig/tests/memory_leak_regression_test.zig +234 -0
  436. package/src/zig/tests/native-renderable_test.zig +16 -0
  437. package/src/zig/tests/native-span-feed_test.zig +1266 -0
  438. package/src/zig/tests/renderer_test.zig +2604 -0
  439. package/src/zig/tests/rope-nested_test.zig +712 -0
  440. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  441. package/src/zig/tests/rope_test.zig +2362 -0
  442. package/src/zig/tests/segment-merge.test.zig +148 -0
  443. package/src/zig/tests/split-scrollback_test.zig +70 -0
  444. package/src/zig/tests/syntax-style_test.zig +558 -0
  445. package/src/zig/tests/terminal_test.zig +1489 -0
  446. package/src/zig/tests/test-renderer.zig +126 -0
  447. package/src/zig/tests/text-buffer-drawing_test.zig +3291 -0
  448. package/src/zig/tests/text-buffer-highlights_test.zig +706 -0
  449. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  450. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  451. package/src/zig/tests/text-buffer-selection_test.zig +1056 -0
  452. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  453. package/src/zig/tests/text-buffer-view_test.zig +3672 -0
  454. package/src/zig/tests/text-buffer_test.zig +2191 -0
  455. package/src/zig/tests/unicode-width-map.zon +3909 -0
  456. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  457. package/src/zig/tests/utf8_test.zig +4060 -0
  458. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  459. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  460. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  461. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  462. package/src/zig/tests/yoga_test.zig +65 -0
  463. package/src/zig/text-buffer-iterators.zig +496 -0
  464. package/src/zig/text-buffer-segment.zig +403 -0
  465. package/src/zig/text-buffer-view.zig +1428 -0
  466. package/src/zig/text-buffer.zig +1272 -0
  467. package/src/zig/utf8.zig +1954 -0
  468. package/src/zig/utils.zig +12 -0
  469. package/src/zig/uucode-stub.zig +76 -0
  470. package/src/zig/vendor/miniaudio/miniaudio.h +93468 -0
  471. package/src/zig/yoga.zig +658 -0
  472. package/src/zig-structs.ts +381 -0
  473. package/src/zig.ts +5260 -0
  474. package/tsconfig.build.json +17 -0
  475. package/tsconfig.json +27 -0
  476. package/tsconfig.node-test.json +138 -0
@@ -0,0 +1,274 @@
1
+ import { RGBA, parseColor, type ColorInput } from "./lib/RGBA.js"
2
+ import { resolveRenderLib, type RenderLib, type SyntaxStyleHandle } from "./zig.js"
3
+ import { createTextAttributes } from "./utils.js"
4
+
5
+ export interface StyleDefinition {
6
+ fg?: RGBA
7
+ bg?: RGBA
8
+ bold?: boolean
9
+ italic?: boolean
10
+ underline?: boolean
11
+ dim?: boolean
12
+ }
13
+
14
+ export interface StyleDefinitionInput {
15
+ fg?: ColorInput
16
+ bg?: ColorInput
17
+ bold?: boolean
18
+ italic?: boolean
19
+ underline?: boolean
20
+ dim?: boolean
21
+ }
22
+
23
+ export interface MergedStyle {
24
+ fg?: RGBA
25
+ bg?: RGBA
26
+ attributes: number
27
+ }
28
+
29
+ export interface ThemeTokenStyle {
30
+ scope: string[]
31
+ style: {
32
+ foreground?: ColorInput
33
+ background?: ColorInput
34
+ bold?: boolean
35
+ italic?: boolean
36
+ underline?: boolean
37
+ dim?: boolean
38
+ }
39
+ }
40
+
41
+ export function convertThemeToStyles(theme: ThemeTokenStyle[]): Record<string, StyleDefinition> {
42
+ const flatStyles: Record<string, StyleDefinition> = {}
43
+
44
+ for (const tokenStyle of theme) {
45
+ const styleDefinition: StyleDefinition = {}
46
+
47
+ if (tokenStyle.style.foreground) {
48
+ styleDefinition.fg = parseColor(tokenStyle.style.foreground)
49
+ }
50
+ if (tokenStyle.style.background) {
51
+ styleDefinition.bg = parseColor(tokenStyle.style.background)
52
+ }
53
+
54
+ if (tokenStyle.style.bold !== undefined) {
55
+ styleDefinition.bold = tokenStyle.style.bold
56
+ }
57
+ if (tokenStyle.style.italic !== undefined) {
58
+ styleDefinition.italic = tokenStyle.style.italic
59
+ }
60
+ if (tokenStyle.style.underline !== undefined) {
61
+ styleDefinition.underline = tokenStyle.style.underline
62
+ }
63
+ if (tokenStyle.style.dim !== undefined) {
64
+ styleDefinition.dim = tokenStyle.style.dim
65
+ }
66
+
67
+ // Apply the same style to all scopes
68
+ for (const scope of tokenStyle.scope) {
69
+ flatStyles[scope] = styleDefinition
70
+ }
71
+ }
72
+
73
+ return flatStyles
74
+ }
75
+
76
+ export class SyntaxStyle {
77
+ private lib: RenderLib
78
+ private stylePtr: SyntaxStyleHandle
79
+ private _destroyed: boolean = false
80
+ private nameCache: Map<string, number> = new Map()
81
+ private styleDefs: Map<string, StyleDefinition> = new Map()
82
+ private mergedCache: Map<string, MergedStyle> = new Map()
83
+
84
+ constructor(lib: RenderLib, ptr: SyntaxStyleHandle) {
85
+ this.lib = lib
86
+ this.stylePtr = ptr
87
+ }
88
+
89
+ static create(): SyntaxStyle {
90
+ const lib = resolveRenderLib()
91
+ const ptr = lib.createSyntaxStyle()
92
+ return new SyntaxStyle(lib, ptr)
93
+ }
94
+
95
+ static fromTheme(theme: ThemeTokenStyle[]): SyntaxStyle {
96
+ const style = SyntaxStyle.create()
97
+ const flatStyles = convertThemeToStyles(theme)
98
+
99
+ for (const [name, styleDef] of Object.entries(flatStyles)) {
100
+ style.registerStyle(name, styleDef)
101
+ }
102
+
103
+ return style
104
+ }
105
+
106
+ static fromStyles(styles: Record<string, StyleDefinitionInput>): SyntaxStyle {
107
+ const style = SyntaxStyle.create()
108
+
109
+ for (const [name, styleDef] of Object.entries(styles)) {
110
+ style.registerStyle(name, styleDef)
111
+ }
112
+
113
+ return style
114
+ }
115
+
116
+ private guard(): void {
117
+ if (this._destroyed) throw new Error("NativeSyntaxStyle is destroyed")
118
+ }
119
+
120
+ public registerStyle(name: string, style: StyleDefinitionInput): number {
121
+ this.guard()
122
+
123
+ const attributes = createTextAttributes({
124
+ bold: style.bold,
125
+ italic: style.italic,
126
+ underline: style.underline,
127
+ dim: style.dim,
128
+ })
129
+
130
+ const fg = style.fg ? parseColor(style.fg) : null
131
+ const bg = style.bg ? parseColor(style.bg) : null
132
+ const id = this.lib.syntaxStyleRegister(this.stylePtr, name, fg, bg, attributes)
133
+
134
+ this.nameCache.set(name, id)
135
+ this.styleDefs.set(name, { ...style, fg: fg ?? undefined, bg: bg ?? undefined })
136
+
137
+ return id
138
+ }
139
+
140
+ public resolveStyleId(name: string): number | null {
141
+ this.guard()
142
+
143
+ // Check cache first
144
+ const cached = this.nameCache.get(name)
145
+ if (cached !== undefined) return cached
146
+
147
+ const id = this.lib.syntaxStyleResolveByName(this.stylePtr, name)
148
+
149
+ if (id !== null) {
150
+ this.nameCache.set(name, id)
151
+ }
152
+
153
+ return id
154
+ }
155
+
156
+ public getStyleId(name: string): number | null {
157
+ this.guard()
158
+
159
+ const id = this.resolveStyleId(name)
160
+ if (id !== null) return id
161
+
162
+ // Try base name if it's a scoped style
163
+ if (name.includes(".")) {
164
+ const baseName = name.split(".")[0]
165
+ return this.resolveStyleId(baseName)
166
+ }
167
+
168
+ return null
169
+ }
170
+
171
+ public get ptr(): SyntaxStyleHandle {
172
+ this.guard()
173
+ return this.stylePtr
174
+ }
175
+
176
+ public getStyleCount(): number {
177
+ this.guard()
178
+ return this.lib.syntaxStyleGetStyleCount(this.stylePtr)
179
+ }
180
+
181
+ public clearNameCache(): void {
182
+ this.nameCache.clear()
183
+ }
184
+
185
+ public getStyle(name: string): StyleDefinition | undefined {
186
+ this.guard()
187
+
188
+ if (Object.prototype.hasOwnProperty.call(this.styleDefs, name)) {
189
+ return undefined
190
+ }
191
+
192
+ const style = this.styleDefs.get(name)
193
+ if (style) return style
194
+
195
+ if (name.includes(".")) {
196
+ const baseName = name.split(".")[0]
197
+ if (Object.prototype.hasOwnProperty.call(this.styleDefs, baseName)) {
198
+ return undefined
199
+ }
200
+ return this.styleDefs.get(baseName)
201
+ }
202
+
203
+ return undefined
204
+ }
205
+
206
+ public mergeStyles(...styleNames: string[]): MergedStyle {
207
+ this.guard()
208
+
209
+ const cacheKey = styleNames.join(":")
210
+ const cached = this.mergedCache.get(cacheKey)
211
+ if (cached) return cached
212
+
213
+ const styleDefinition: StyleDefinition = {}
214
+
215
+ for (const name of styleNames) {
216
+ const style = this.getStyle(name)
217
+
218
+ if (!style) continue
219
+
220
+ if (style.fg) styleDefinition.fg = style.fg
221
+ if (style.bg) styleDefinition.bg = style.bg
222
+ if (style.bold !== undefined) styleDefinition.bold = style.bold
223
+ if (style.italic !== undefined) styleDefinition.italic = style.italic
224
+ if (style.underline !== undefined) styleDefinition.underline = style.underline
225
+ if (style.dim !== undefined) styleDefinition.dim = style.dim
226
+ }
227
+
228
+ const attributes = createTextAttributes({
229
+ bold: styleDefinition.bold,
230
+ italic: styleDefinition.italic,
231
+ underline: styleDefinition.underline,
232
+ dim: styleDefinition.dim,
233
+ })
234
+
235
+ const merged: MergedStyle = {
236
+ fg: styleDefinition.fg,
237
+ bg: styleDefinition.bg,
238
+ attributes,
239
+ }
240
+
241
+ this.mergedCache.set(cacheKey, merged)
242
+
243
+ return merged
244
+ }
245
+
246
+ public clearCache(): void {
247
+ this.guard()
248
+ this.mergedCache.clear()
249
+ }
250
+
251
+ public getCacheSize(): number {
252
+ this.guard()
253
+ return this.mergedCache.size
254
+ }
255
+
256
+ public getAllStyles(): Map<string, StyleDefinition> {
257
+ this.guard()
258
+ return new Map(this.styleDefs)
259
+ }
260
+
261
+ public getRegisteredNames(): string[] {
262
+ this.guard()
263
+ return Array.from(this.styleDefs.keys())
264
+ }
265
+
266
+ public destroy(): void {
267
+ if (this._destroyed) return
268
+ this._destroyed = true
269
+ this.nameCache.clear()
270
+ this.styleDefs.clear()
271
+ this.mergedCache.clear()
272
+ this.lib.destroySyntaxStyle(this.stylePtr)
273
+ }
274
+ }
@@ -0,0 +1,219 @@
1
+ # Testing Utilities
2
+
3
+ Test utilities for opentui terminal UI testing.
4
+
5
+ ## Test Renderer
6
+
7
+ ```ts
8
+ import { createTestRenderer } from "@opentui/core/testing"
9
+
10
+ const { renderer, mockInput, mockMouse, renderOnce, captureCharFrame, resize } = await createTestRenderer({
11
+ width: 80,
12
+ height: 24,
13
+ })
14
+
15
+ // Render once and capture output
16
+ await renderOnce()
17
+ const output = captureCharFrame()
18
+
19
+ // Resize terminal
20
+ resize(100, 30)
21
+ ```
22
+
23
+ ## Terminal Capabilities
24
+
25
+ ```ts
26
+ import { createTerminalCapabilities, setRendererCapabilities } from "@opentui/core/testing"
27
+
28
+ const capabilities = createTerminalCapabilities({ kitty_keyboard: true })
29
+ setRendererCapabilities(renderer, { notifications: true })
30
+ ```
31
+
32
+ ## Mock Keyboard Input
33
+
34
+ ```ts
35
+ import { createMockKeys, KeyCodes } from "@opentui/core/testing"
36
+
37
+ const mockInput = createMockKeys(renderer)
38
+
39
+ // Type text
40
+ mockInput.typeText("hello world")
41
+ await mockInput.typeText("hello", 10) // 10ms delay between keys
42
+
43
+ // Press single keys
44
+ mockInput.pressKey("a")
45
+ mockInput.pressKey(KeyCodes.ENTER)
46
+
47
+ // Press keys with modifiers
48
+ mockInput.pressKey("a", { ctrl: true })
49
+ mockInput.pressKey("f", { meta: true })
50
+ mockInput.pressKey("z", { ctrl: true, shift: true })
51
+ mockInput.pressKey(KeyCodes.ARROW_LEFT, { meta: true })
52
+
53
+ // Press multiple keys
54
+ mockInput.pressKeys(["h", "e", "l", "l", "o"])
55
+ await mockInput.pressKeys(["a", "b"], 10) // with delay
56
+
57
+ // Convenience methods
58
+ mockInput.pressEnter()
59
+ mockInput.pressEnter({ meta: true })
60
+ mockInput.pressEscape()
61
+ mockInput.pressTab()
62
+ mockInput.pressBackspace()
63
+ mockInput.pressArrow("up" | "down" | "left" | "right")
64
+ mockInput.pressArrow("left", { meta: true })
65
+ mockInput.pressCtrlC()
66
+ mockInput.pasteBracketedText("paste content")
67
+ ```
68
+
69
+ ### KeyCodes
70
+
71
+ Special keycodes available: `RETURN`, `LINEFEED`, `TAB`, `BACKSPACE`, `DELETE`, `HOME`, `END`, `ESCAPE`, `ARROW_UP`, `ARROW_DOWN`, `ARROW_LEFT`, `ARROW_RIGHT`, `F1`-`F12`
72
+
73
+ ### Modifiers
74
+
75
+ All `pressKey()`, `pressEnter()`, `pressEscape()`, `pressTab()`, `pressBackspace()`, and `pressArrow()` methods support an optional modifiers object:
76
+
77
+ ```ts
78
+ { ctrl?: boolean; shift?: boolean; meta?: boolean }
79
+ ```
80
+
81
+ ## Mock Mouse Input
82
+
83
+ ```ts
84
+ import { createMockMouse, MouseButtons } from "@opentui/core/testing"
85
+
86
+ const mockMouse = createMockMouse(renderer)
87
+
88
+ // Click
89
+ await mockMouse.click(x, y)
90
+ await mockMouse.click(x, y, MouseButtons.RIGHT)
91
+ await mockMouse.click(x, y, MouseButtons.LEFT, {
92
+ modifiers: { ctrl: true, shift: true, alt: true },
93
+ delayMs: 10,
94
+ })
95
+
96
+ // Double click
97
+ await mockMouse.doubleClick(x, y)
98
+
99
+ // Press and release
100
+ await mockMouse.pressDown(x, y, MouseButtons.MIDDLE)
101
+ await mockMouse.release(x, y, MouseButtons.MIDDLE)
102
+
103
+ // Move
104
+ await mockMouse.moveTo(x, y)
105
+ await mockMouse.moveTo(x, y, { modifiers: { shift: true } })
106
+
107
+ // Drag
108
+ await mockMouse.drag(startX, startY, endX, endY)
109
+ await mockMouse.drag(startX, startY, endX, endY, MouseButtons.RIGHT, {
110
+ modifiers: { alt: true },
111
+ })
112
+
113
+ // Scroll
114
+ await mockMouse.scroll(x, y, "up" | "down" | "left" | "right")
115
+ await mockMouse.scroll(x, y, "up", { modifiers: { shift: true } })
116
+
117
+ // State
118
+ const pos = mockMouse.getCurrentPosition() // { x, y }
119
+ const buttons = mockMouse.getPressedButtons() // MouseButton[]
120
+ ```
121
+
122
+ ### MouseButtons
123
+
124
+ `LEFT` (0), `MIDDLE` (1), `RIGHT` (2)
125
+
126
+ `WHEEL_UP` (64), `WHEEL_DOWN` (65), `WHEEL_LEFT` (66), `WHEEL_RIGHT` (67)
127
+
128
+ ## Spy
129
+
130
+ Simple function spy for testing callbacks.
131
+
132
+ ```ts
133
+ import { createSpy } from "@opentui/core/testing"
134
+
135
+ const spy = createSpy()
136
+
137
+ // Use as callback
138
+ someFunction(spy)
139
+
140
+ // Assertions
141
+ spy.callCount() // number
142
+ spy.calledWith(arg1, arg2) // boolean
143
+ spy.calls // any[][]
144
+ spy.reset()
145
+ ```
146
+
147
+ ## Test Recorder
148
+
149
+ Record frames during rendering for testing or analysis.
150
+
151
+ ```ts
152
+ import { TestRecorder } from "@opentui/core/testing"
153
+
154
+ const { renderer, renderOnce } = await createTestRenderer({ width: 80, height: 24 })
155
+ const recorder = new TestRecorder(renderer)
156
+
157
+ // Start recording
158
+ recorder.rec()
159
+
160
+ // Add content and trigger renders
161
+ const text = new TextRenderable(renderer, { content: "Hello" })
162
+ renderer.root.add(text)
163
+ await Bun.sleep(1) // Wait for automatic render from add()
164
+
165
+ text.content = "World"
166
+ await Bun.sleep(1) // Wait for automatic render from content change
167
+
168
+ // Stop recording
169
+ recorder.stop()
170
+
171
+ // Access recorded frames
172
+ const frames = recorder.recordedFrames
173
+ console.log(`Recorded ${frames.length} frames`)
174
+
175
+ frames.forEach((frame) => {
176
+ console.log(`Frame ${frame.frameNumber} at ${frame.timestamp}ms:`)
177
+ console.log(frame.frame)
178
+ })
179
+
180
+ // Clear and start new recording
181
+ recorder.clear()
182
+ recorder.rec()
183
+ ```
184
+
185
+ ### TestRecorder API
186
+
187
+ - `rec()` - Start recording frames
188
+ - `stop()` - Stop recording frames
189
+ - `clear()` - Clear all recorded frames
190
+ - `isRecording` - Check if currently recording
191
+ - `recordedFrames` - Get array of recorded frames (returns a copy)
192
+
193
+ ### RecordedFrame
194
+
195
+ Each frame contains:
196
+
197
+ - `frame: string` - The captured frame content
198
+ - `timestamp: number` - Time in milliseconds since recording started
199
+ - `frameNumber: number` - Sequential frame number (0-indexed)
200
+
201
+ ## Example
202
+
203
+ ```ts
204
+ import { test, expect } from "bun:test"
205
+ import { createTestRenderer } from "@opentui/core/testing"
206
+
207
+ test("button click", async () => {
208
+ const { renderer, mockMouse, renderOnce, captureCharFrame } = await createTestRenderer({ width: 80, height: 24 })
209
+
210
+ const clicked = createSpy()
211
+ const button = new Button("btn", { text: "Click me", onClick: clicked })
212
+
213
+ renderer.add(button)
214
+ await renderOnce()
215
+
216
+ await mockMouse.click(10, 5)
217
+ expect(clicked.callCount()).toBe(1)
218
+ })
219
+ ```