@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,789 @@
1
+ import { test, expect, describe } from "bun:test"
2
+ import { getObjectsInViewport } from "./objects-in-viewport.js"
3
+ import type { ViewportBounds } from "../types.js"
4
+
5
+ interface TestObject {
6
+ x: number
7
+ y: number
8
+ screenX: number
9
+ screenY: number
10
+ width: number
11
+ height: number
12
+ zIndex: number
13
+ id: string
14
+ }
15
+
16
+ function createObject(id: string, x: number, y: number, width: number, height: number, zIndex: number = 0): TestObject {
17
+ return { id, x, y, screenX: x, screenY: y, width, height, zIndex }
18
+ }
19
+
20
+ describe("getObjectsInViewport", () => {
21
+ describe("basic functionality", () => {
22
+ test("returns empty array for empty input", () => {
23
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
24
+ const result = getObjectsInViewport(viewport, [])
25
+ expect(result).toEqual([])
26
+ })
27
+
28
+ test("returns all objects when count is below minTriggerSize", () => {
29
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
30
+ const objects = [createObject("1", 0, 0, 10, 10), createObject("2", 200, 200, 10, 10)]
31
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
32
+ expect(result).toEqual(objects)
33
+ })
34
+
35
+ test("filters objects outside viewport in column direction", () => {
36
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
37
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
38
+
39
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
40
+ const visibleIds = result.map((o) => o.id)
41
+
42
+ expect(visibleIds).toContain("obj-5")
43
+ expect(visibleIds).toContain("obj-6")
44
+ expect(visibleIds).toContain("obj-9")
45
+ expect(visibleIds).not.toContain("obj-0")
46
+ expect(visibleIds).not.toContain("obj-15")
47
+ })
48
+
49
+ test("filters objects outside viewport in row direction", () => {
50
+ const viewport: ViewportBounds = { x: 100, y: 0, width: 100, height: 100 }
51
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, i * 20, 0, 20, 100))
52
+
53
+ const result = getObjectsInViewport(viewport, objects, "row", 0, 16)
54
+ const visibleIds = result.map((o) => o.id)
55
+
56
+ expect(visibleIds).toContain("obj-5")
57
+ expect(visibleIds).toContain("obj-6")
58
+ expect(visibleIds).toContain("obj-9")
59
+ expect(visibleIds).not.toContain("obj-0")
60
+ expect(visibleIds).not.toContain("obj-15")
61
+ })
62
+ })
63
+
64
+ describe("padding behavior", () => {
65
+ test("includes objects within padding distance", () => {
66
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
67
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
68
+
69
+ const result = getObjectsInViewport(viewport, objects, "column", 20, 16)
70
+ const visibleIds = result.map((o) => o.id)
71
+
72
+ expect(visibleIds).toContain("obj-4")
73
+ expect(visibleIds).toContain("obj-10")
74
+ })
75
+
76
+ test("respects custom padding values", () => {
77
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
78
+ const objects = Array.from({ length: 30 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
79
+
80
+ const resultNoPadding = getObjectsInViewport(viewport, objects, "column", 0, 16)
81
+ const resultWithPadding = getObjectsInViewport(viewport, objects, "column", 50, 16)
82
+
83
+ expect(resultWithPadding.length).toBeGreaterThan(resultNoPadding.length)
84
+ })
85
+ })
86
+
87
+ describe("zIndex sorting", () => {
88
+ test("sorts visible objects by zIndex", () => {
89
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
90
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 10, 100, 10, 20 - i))
91
+
92
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
93
+
94
+ for (let i = 1; i < result.length; i++) {
95
+ expect(result[i].zIndex).toBeGreaterThanOrEqual(result[i - 1].zIndex)
96
+ }
97
+ })
98
+
99
+ test("handles objects with same zIndex", () => {
100
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
101
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 10, 100, 10, 5))
102
+
103
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
104
+ expect(result.every((obj) => obj.zIndex === 5)).toBe(true)
105
+ })
106
+
107
+ test("handles mixed zIndex values", () => {
108
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
109
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 10, 100, 10, i % 3))
110
+
111
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
112
+
113
+ for (let i = 1; i < result.length; i++) {
114
+ expect(result[i].zIndex).toBeGreaterThanOrEqual(result[i - 1].zIndex)
115
+ }
116
+ })
117
+ })
118
+
119
+ describe("edge cases - boundary conditions", () => {
120
+ test("includes object that starts at viewport top", () => {
121
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
122
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
123
+
124
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
125
+ const visibleIds = result.map((o) => o.id)
126
+
127
+ expect(visibleIds).toContain("obj-5")
128
+ })
129
+
130
+ test("excludes object that ends exactly at viewport start (no padding)", () => {
131
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
132
+ const objects = [
133
+ createObject("before", 0, 50, 100, 50),
134
+ ...Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, (i + 5) * 20, 100, 20)),
135
+ ]
136
+
137
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
138
+ const visibleIds = result.map((o) => o.id)
139
+
140
+ expect(visibleIds).not.toContain("before")
141
+ })
142
+
143
+ test("excludes object that starts exactly at viewport end (no padding)", () => {
144
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
145
+ const objects = [
146
+ createObject("after", 0, 200, 100, 20),
147
+ ...Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20)),
148
+ ]
149
+
150
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
151
+ const visibleIds = result.map((o) => o.id)
152
+
153
+ expect(visibleIds).not.toContain("after")
154
+ })
155
+ })
156
+
157
+ describe("cross-axis filtering", () => {
158
+ test("filters objects outside viewport on cross-axis (column mode)", () => {
159
+ const viewport: ViewportBounds = { x: 50, y: 100, width: 100, height: 100 }
160
+ const objects = Array.from({ length: 20 }, (_, i) =>
161
+ createObject(`obj-${i}`, i % 2 === 0 ? 0 : 60, i * 20, 40, 20),
162
+ )
163
+
164
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
165
+
166
+ result.forEach((obj) => {
167
+ const objectRight = obj.x + obj.width
168
+ expect(objectRight).toBeGreaterThan(viewport.x)
169
+ expect(obj.x).toBeLessThan(viewport.x + viewport.width)
170
+ })
171
+ })
172
+
173
+ test("filters objects outside viewport on cross-axis (row mode)", () => {
174
+ const viewport: ViewportBounds = { x: 100, y: 50, width: 100, height: 100 }
175
+ const objects = Array.from({ length: 20 }, (_, i) =>
176
+ createObject(`obj-${i}`, i * 20, i % 2 === 0 ? 0 : 60, 20, 40),
177
+ )
178
+
179
+ const result = getObjectsInViewport(viewport, objects, "row", 0, 16)
180
+
181
+ result.forEach((obj) => {
182
+ const objectBottom = obj.y + obj.height
183
+ expect(objectBottom).toBeGreaterThan(viewport.y)
184
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
185
+ })
186
+ })
187
+ })
188
+
189
+ describe("scrolling simulation - vertical", () => {
190
+ const createScrollList = () => {
191
+ return Array.from({ length: 100 }, (_, i) => createObject(`item-${i}`, 0, i * 50, 200, 50, i % 10))
192
+ }
193
+
194
+ test("viewport at top", () => {
195
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 200, height: 300 }
196
+ const objects = createScrollList()
197
+
198
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
199
+ const visibleIds = result.map((o) => o.id)
200
+
201
+ expect(visibleIds).toContain("item-0")
202
+ expect(visibleIds).toContain("item-5")
203
+ expect(visibleIds).not.toContain("item-20")
204
+ })
205
+
206
+ test("viewport scrolled to middle", () => {
207
+ const viewport: ViewportBounds = { x: 0, y: 2000, width: 200, height: 300 }
208
+ const objects = createScrollList()
209
+
210
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
211
+ const visibleIds = result.map((o) => o.id)
212
+
213
+ expect(visibleIds).toContain("item-40")
214
+ expect(visibleIds).toContain("item-45")
215
+ expect(visibleIds).not.toContain("item-0")
216
+ expect(visibleIds).not.toContain("item-99")
217
+ })
218
+
219
+ test("viewport at bottom", () => {
220
+ const viewport: ViewportBounds = { x: 0, y: 4700, width: 200, height: 300 }
221
+ const objects = createScrollList()
222
+
223
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
224
+ const visibleIds = result.map((o) => o.id)
225
+
226
+ expect(visibleIds).toContain("item-94")
227
+ expect(visibleIds).toContain("item-99")
228
+ expect(visibleIds).not.toContain("item-0")
229
+ expect(visibleIds).not.toContain("item-50")
230
+ })
231
+
232
+ test("small incremental scrolls", () => {
233
+ const objects = createScrollList()
234
+
235
+ for (let scrollY = 0; scrollY < 1000; scrollY += 10) {
236
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 200, height: 300 }
237
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
238
+
239
+ result.forEach((obj) => {
240
+ const objectBottom = obj.y + obj.height
241
+ expect(objectBottom).toBeGreaterThan(viewport.y - 10)
242
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height + 10)
243
+ })
244
+ }
245
+ })
246
+ })
247
+
248
+ describe("scrolling simulation - horizontal", () => {
249
+ const createHorizontalList = () => {
250
+ return Array.from({ length: 100 }, (_, i) => createObject(`item-${i}`, i * 50, 0, 50, 200, i % 10))
251
+ }
252
+
253
+ test("viewport at left", () => {
254
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 300, height: 200 }
255
+ const objects = createHorizontalList()
256
+
257
+ const result = getObjectsInViewport(viewport, objects, "row", 10, 16)
258
+ const visibleIds = result.map((o) => o.id)
259
+
260
+ expect(visibleIds).toContain("item-0")
261
+ expect(visibleIds).toContain("item-5")
262
+ expect(visibleIds).not.toContain("item-20")
263
+ })
264
+
265
+ test("viewport scrolled to middle", () => {
266
+ const viewport: ViewportBounds = { x: 2000, y: 0, width: 300, height: 200 }
267
+ const objects = createHorizontalList()
268
+
269
+ const result = getObjectsInViewport(viewport, objects, "row", 10, 16)
270
+ const visibleIds = result.map((o) => o.id)
271
+
272
+ expect(visibleIds).toContain("item-40")
273
+ expect(visibleIds).toContain("item-45")
274
+ expect(visibleIds).not.toContain("item-0")
275
+ expect(visibleIds).not.toContain("item-99")
276
+ })
277
+
278
+ test("viewport at right", () => {
279
+ const viewport: ViewportBounds = { x: 4700, y: 0, width: 300, height: 200 }
280
+ const objects = createHorizontalList()
281
+
282
+ const result = getObjectsInViewport(viewport, objects, "row", 10, 16)
283
+ const visibleIds = result.map((o) => o.id)
284
+
285
+ expect(visibleIds).toContain("item-94")
286
+ expect(visibleIds).toContain("item-99")
287
+ expect(visibleIds).not.toContain("item-0")
288
+ })
289
+ })
290
+
291
+ describe("large objects", () => {
292
+ test("handles objects much larger than viewport", () => {
293
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 100, height: 100 }
294
+ const objects = [
295
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 100, 100, 50)),
296
+ createObject("huge", 0, 100, 100, 1000),
297
+ createObject("tiny-after", 0, 1200, 100, 10),
298
+ ].sort((a, b) => a.y - b.y)
299
+
300
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
301
+ const visibleIds = result.map((o) => o.id)
302
+
303
+ expect(visibleIds).toContain("huge")
304
+ })
305
+
306
+ test("large object with many small items before viewport (realistic background panel)", () => {
307
+ // Simulates a background panel spanning entire list with small list items
308
+ const objects = [
309
+ createObject("background-panel", 0, 0, 100, 3000), // Large spanning background
310
+ ...Array.from({ length: 30 }, (_, i) => createObject(`item-${i}`, 0, i * 60, 100, 50)), // List items
311
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 100 + 3000, 100, 50)),
312
+ ]
313
+
314
+ // Viewport at y=1500, background spans 0-3000, with ~25 items between them
315
+ const viewport: ViewportBounds = { x: 0, y: 1500, width: 100, height: 100 }
316
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
317
+ const visibleIds = result.map((o) => o.id)
318
+
319
+ expect(visibleIds).toContain("background-panel")
320
+ })
321
+
322
+ test("handles very tall objects in vertical scroll", () => {
323
+ const objects = [
324
+ createObject("small-1", 0, 0, 100, 50),
325
+ createObject("tall-1", 0, 100, 100, 500),
326
+ createObject("small-2", 0, 650, 100, 50),
327
+ createObject("tall-2", 0, 750, 100, 800),
328
+ createObject("small-3", 0, 1600, 100, 50),
329
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 100 + 2000, 100, 50)),
330
+ ]
331
+
332
+ for (let scrollY = 0; scrollY < 2000; scrollY += 100) {
333
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 100, height: 200 }
334
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
335
+
336
+ result.forEach((obj) => {
337
+ const objectBottom = obj.y + obj.height
338
+ expect(objectBottom).toBeGreaterThan(viewport.y)
339
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
340
+ })
341
+ }
342
+ })
343
+
344
+ test("handles very wide objects in horizontal scroll", () => {
345
+ const objects = [
346
+ createObject("small-1", 0, 0, 50, 100),
347
+ createObject("wide-1", 100, 0, 500, 100),
348
+ createObject("small-2", 650, 0, 50, 100),
349
+ createObject("wide-2", 750, 0, 800, 100),
350
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, i * 100 + 2000, 0, 50, 100)),
351
+ ]
352
+
353
+ for (let scrollX = 0; scrollX < 2000; scrollX += 100) {
354
+ const viewport: ViewportBounds = { x: scrollX, y: 0, width: 200, height: 100 }
355
+ const result = getObjectsInViewport(viewport, objects, "row", 0, 16)
356
+
357
+ result.forEach((obj) => {
358
+ const objectRight = obj.x + obj.width
359
+ expect(objectRight).toBeGreaterThan(viewport.x)
360
+ expect(obj.x).toBeLessThan(viewport.x + viewport.width)
361
+ })
362
+ }
363
+ })
364
+ })
365
+
366
+ describe("viewport size variations", () => {
367
+ const objects = Array.from({ length: 100 }, (_, i) => createObject(`item-${i}`, 0, i * 30, 200, 30, i % 5))
368
+
369
+ test("very small viewport", () => {
370
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 50, height: 50 }
371
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
372
+
373
+ expect(result.length).toBeGreaterThan(0)
374
+ expect(result.length).toBeLessThan(10)
375
+ })
376
+
377
+ test("very large viewport", () => {
378
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 1000, height: 1000 }
379
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
380
+
381
+ expect(result.length).toBeGreaterThan(30)
382
+ })
383
+
384
+ test("viewport larger than all objects", () => {
385
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 500, height: 5000 }
386
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
387
+
388
+ expect(result.length).toBe(objects.length)
389
+ })
390
+ })
391
+
392
+ describe("negative coordinates", () => {
393
+ test("handles negative viewport coordinates", () => {
394
+ const viewport: ViewportBounds = { x: -50, y: -50, width: 100, height: 100 }
395
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, -100, i * 20 - 100, 200, 20))
396
+
397
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
398
+
399
+ result.forEach((obj) => {
400
+ const objectBottom = obj.y + obj.height
401
+ expect(objectBottom).toBeGreaterThan(viewport.y)
402
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
403
+ })
404
+ })
405
+
406
+ test("handles negative object coordinates", () => {
407
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
408
+ const objects = [createObject("negative-y", 0, -50, 100, 100), createObject("positive-y", 0, 50, 100, 100)]
409
+ objects.push(...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 20, 100, 20)))
410
+
411
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
412
+ const visibleIds = result.map((o) => o.id)
413
+
414
+ expect(visibleIds).toContain("negative-y")
415
+ expect(visibleIds).toContain("positive-y")
416
+ })
417
+ })
418
+
419
+ describe("sparse object distributions", () => {
420
+ test("handles large gaps between objects", () => {
421
+ const viewport: ViewportBounds = { x: 0, y: 5000, width: 100, height: 100 }
422
+ const objects = Array.from({ length: 50 }, (_, i) => createObject(`obj-${i}`, 0, i * 1000, 100, 50))
423
+
424
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
425
+ const visibleIds = result.map((o) => o.id)
426
+
427
+ expect(visibleIds).toContain("obj-5")
428
+ expect(result.length).toBeLessThan(5)
429
+ })
430
+
431
+ test("handles clustered objects", () => {
432
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 100, height: 100 }
433
+ const objects = [
434
+ ...Array.from({ length: 10 }, (_, i) => createObject(`cluster-${i}`, 0, 490 + i * 2, 100, 2)),
435
+ ...Array.from({ length: 10 }, (_, i) => createObject(`filler-${i}`, 0, i * 100, 100, 20)),
436
+ ]
437
+
438
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
439
+
440
+ expect(result.length).toBeGreaterThan(5)
441
+ })
442
+ })
443
+
444
+ describe("minTriggerSize parameter", () => {
445
+ test("bypasses optimization when object count is below threshold", () => {
446
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
447
+ const objects = [createObject("far-away", 0, 10000, 100, 100), createObject("visible", 0, 50, 100, 100)]
448
+
449
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 100)
450
+ expect(result.length).toBe(2)
451
+ expect(result.map((o) => o.id)).toContain("far-away")
452
+ })
453
+
454
+ test("applies optimization when object count meets threshold", () => {
455
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
456
+ const objects = [
457
+ ...Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20)),
458
+ createObject("far-away", 0, 10000, 100, 100),
459
+ ]
460
+
461
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
462
+ expect(result.map((o) => o.id)).not.toContain("far-away")
463
+ })
464
+
465
+ test("performs overlap checks when minTriggerSize is 0", () => {
466
+ const viewport: ViewportBounds = { x: 0, y: 10, width: 40, height: 1 }
467
+ const objects = [createObject("above-viewport", 0, 0, 40, 5), createObject("in-viewport", 0, 10, 40, 1)]
468
+
469
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 0)
470
+ expect(result.length).toBe(1)
471
+ expect(result[0].id).toBe("in-viewport")
472
+ })
473
+
474
+ test("filters out objects outside viewport when minTriggerSize is 0", () => {
475
+ const viewport: ViewportBounds = { x: 0, y: 10, width: 40, height: 5 }
476
+ const objects = [
477
+ createObject("above-1", 0, 0, 40, 3),
478
+ createObject("above-2", 0, 5, 40, 4),
479
+ createObject("in-viewport", 0, 12, 40, 2),
480
+ createObject("below", 0, 20, 40, 5),
481
+ ]
482
+
483
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 0)
484
+ expect(result.length).toBe(1)
485
+ expect(result[0].id).toBe("in-viewport")
486
+ })
487
+
488
+ test("respects exact boundary conditions with minTriggerSize 0", () => {
489
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
490
+ const objects = [
491
+ createObject("ends-at-start", 0, 50, 100, 50),
492
+ createObject("overlaps-start", 0, 50, 100, 51),
493
+ createObject("inside", 0, 150, 100, 20),
494
+ createObject("overlaps-end", 0, 199, 100, 10),
495
+ createObject("starts-at-end", 0, 200, 100, 50),
496
+ ]
497
+
498
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 0)
499
+ const visibleIds = result.map((o) => o.id)
500
+
501
+ expect(visibleIds).not.toContain("ends-at-start")
502
+ expect(visibleIds).toContain("overlaps-start")
503
+ expect(visibleIds).toContain("inside")
504
+ expect(visibleIds).toContain("overlaps-end")
505
+ expect(visibleIds).not.toContain("starts-at-end")
506
+ })
507
+ })
508
+
509
+ describe("overlapping objects", () => {
510
+ test("handles completely overlapping objects", () => {
511
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
512
+ const objects = [
513
+ ...Array.from({ length: 10 }, (_, i) => createObject(`filler-before-${i}`, 0, i * 20, 100, 20)),
514
+ createObject("back", 0, 100, 100, 100, 0),
515
+ createObject("middle", 0, 100, 100, 100, 1),
516
+ createObject("front", 0, 100, 100, 100, 2),
517
+ ...Array.from({ length: 10 }, (_, i) => createObject(`filler-after-${i}`, 0, (i + 10) * 20, 100, 20)),
518
+ ]
519
+
520
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
521
+ const overlapping = result.filter(
522
+ (obj) => obj.id.includes("back") || obj.id.includes("middle") || obj.id.includes("front"),
523
+ )
524
+
525
+ expect(overlapping[0].id).toBe("back")
526
+ expect(overlapping[1].id).toBe("middle")
527
+ expect(overlapping[2].id).toBe("front")
528
+ })
529
+
530
+ test("handles partially overlapping objects", () => {
531
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
532
+ const objects = Array.from({ length: 30 }, (_, i) => createObject(`obj-${i}`, 0, i * 15, 100, 30, i % 3))
533
+
534
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
535
+
536
+ for (let i = 1; i < result.length; i++) {
537
+ expect(result[i].zIndex).toBeGreaterThanOrEqual(result[i - 1].zIndex)
538
+ }
539
+ })
540
+ })
541
+
542
+ describe("extreme values", () => {
543
+ test("zero-sized viewport returns empty array (zero width)", () => {
544
+ const viewport: ViewportBounds = { x: 100, y: 100, width: 0, height: 100 }
545
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
546
+
547
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
548
+ expect(result.length).toBe(0)
549
+ })
550
+
551
+ test("zero-sized viewport returns empty array (zero height)", () => {
552
+ const viewport: ViewportBounds = { x: 100, y: 100, width: 100, height: 0 }
553
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
554
+
555
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
556
+ expect(result.length).toBe(0)
557
+ })
558
+
559
+ test("zero-sized viewport returns empty array (both zero)", () => {
560
+ const viewport: ViewportBounds = { x: 100, y: 100, width: 0, height: 0 }
561
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
562
+
563
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
564
+ expect(result.length).toBe(0)
565
+ })
566
+
567
+ test("handles zero-sized objects", () => {
568
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
569
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 0))
570
+
571
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
572
+ expect(result).toBeDefined()
573
+ })
574
+
575
+ test("handles very large coordinates", () => {
576
+ const viewport: ViewportBounds = { x: 1000000, y: 1000000, width: 100, height: 100 }
577
+ const objects = Array.from({ length: 50 }, (_, i) => createObject(`obj-${i}`, 1000000, 1000000 + i * 20, 100, 20))
578
+
579
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
580
+ expect(result.length).toBeGreaterThan(0)
581
+ })
582
+ })
583
+
584
+ describe("performance characteristics", () => {
585
+ test("handles 1000 objects efficiently", () => {
586
+ const viewport: ViewportBounds = { x: 0, y: 50000, width: 100, height: 100 }
587
+ const objects = Array.from({ length: 1000 }, (_, i) => createObject(`obj-${i}`, 0, i * 100, 100, 100, i % 10))
588
+
589
+ const start = performance.now()
590
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
591
+ const duration = performance.now() - start
592
+
593
+ expect(result.length).toBeGreaterThan(0)
594
+ expect(result.length).toBeLessThan(20)
595
+ expect(duration).toBeLessThan(10)
596
+ })
597
+
598
+ test("handles 10000 objects efficiently", () => {
599
+ const viewport: ViewportBounds = { x: 0, y: 500000, width: 100, height: 100 }
600
+ const objects = Array.from({ length: 10000 }, (_, i) => createObject(`obj-${i}`, 0, i * 100, 100, 100, i % 10))
601
+
602
+ const start = performance.now()
603
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
604
+ const duration = performance.now() - start
605
+
606
+ expect(result.length).toBeGreaterThan(0)
607
+ expect(result.length).toBeLessThan(20)
608
+ expect(duration).toBeLessThan(50)
609
+ })
610
+ })
611
+
612
+ describe("additional edge cases", () => {
613
+ test("object that starts before viewport and extends through it", () => {
614
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 100, height: 100 }
615
+ const objects = Array.from({ length: 30 }, (_, i) => {
616
+ if (i === 2) {
617
+ return createObject("spanning", 0, 200, 100, 500)
618
+ }
619
+ return createObject(`obj-${i}`, 0, i * 50, 100, 40)
620
+ })
621
+
622
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
623
+ const visibleIds = result.map((o) => o.id)
624
+
625
+ expect(visibleIds).toContain("spanning")
626
+ })
627
+
628
+ test("multiple large overlapping objects during scroll", () => {
629
+ const objects = [
630
+ createObject("bg-1", 0, 0, 200, 1000, 0),
631
+ createObject("bg-2", 0, 500, 200, 1000, 0),
632
+ createObject("bg-3", 0, 1000, 200, 1000, 0),
633
+ ...Array.from({ length: 50 }, (_, i) => createObject(`small-${i}`, 0, i * 50, 200, 40, 1)),
634
+ ]
635
+
636
+ for (let scrollY = 0; scrollY <= 1500; scrollY += 100) {
637
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 200, height: 300 }
638
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
639
+
640
+ result.forEach((obj) => {
641
+ const objectBottom = obj.y + obj.height
642
+ expect(objectBottom).toBeGreaterThan(viewport.y)
643
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
644
+ })
645
+ }
646
+ })
647
+
648
+ test("viewport moves down through very tall object", () => {
649
+ const objects = [
650
+ ...Array.from({ length: 5 }, (_, i) => createObject(`before-${i}`, 0, i * 50, 100, 40)),
651
+ createObject("very-tall", 0, 300, 100, 2000),
652
+ ...Array.from({ length: 20 }, (_, i) => createObject(`after-${i}`, 0, 2400 + i * 50, 100, 40)),
653
+ ]
654
+
655
+ for (let scrollY = 0; scrollY <= 2500; scrollY += 200) {
656
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 100, height: 200 }
657
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
658
+
659
+ if (scrollY >= 100 && scrollY <= 2100) {
660
+ const visibleIds = result.map((o) => o.id)
661
+ expect(visibleIds).toContain("very-tall")
662
+ }
663
+ }
664
+ })
665
+
666
+ test("objects with zero width or height", () => {
667
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
668
+ const objects = [
669
+ createObject("zero-height", 0, 150, 100, 0),
670
+ createObject("zero-width", 0, 160, 0, 40),
671
+ createObject("point", 0, 170, 0, 0),
672
+ ...Array.from({ length: 20 }, (_, i) => createObject(`normal-${i}`, 0, i * 20, 100, 15)),
673
+ ]
674
+
675
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
676
+
677
+ expect(result).toBeDefined()
678
+ })
679
+
680
+ test("viewport positioned between objects (should return empty)", () => {
681
+ const viewport: ViewportBounds = { x: 0, y: 1000, width: 100, height: 100 }
682
+ const objects = [
683
+ ...Array.from({ length: 10 }, (_, i) => createObject(`before-${i}`, 0, i * 50, 100, 40)),
684
+ ...Array.from({ length: 10 }, (_, i) => createObject(`after-${i}`, 0, 2000 + i * 50, 100, 40)),
685
+ ]
686
+
687
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
688
+
689
+ expect(result.length).toBe(0)
690
+ })
691
+
692
+ test("single pixel gaps between objects and viewport", () => {
693
+ const viewport: ViewportBounds = { x: 0, y: 1000, width: 100, height: 100 }
694
+ const objects = [
695
+ createObject("one-pixel-before", 0, 899, 100, 100), // ends at 999, gap of 1px
696
+ createObject("touching-before", 0, 999, 100, 1), // ends exactly at 1000
697
+ createObject("inside", 0, 1050, 100, 10), // fully inside
698
+ createObject("touching-after", 0, 1100, 100, 1), // starts exactly at 1100
699
+ createObject("one-pixel-after", 0, 1101, 100, 100), // starts at 1101, gap of 1px
700
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 100, 100, 50)),
701
+ ]
702
+
703
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
704
+ const visibleIds = result.map((o) => o.id)
705
+
706
+ // Objects with even 1px gap should be excluded (no overlap)
707
+ expect(visibleIds).not.toContain("one-pixel-before")
708
+ expect(visibleIds).not.toContain("touching-before")
709
+ expect(visibleIds).toContain("inside")
710
+ expect(visibleIds).not.toContain("touching-after")
711
+ expect(visibleIds).not.toContain("one-pixel-after")
712
+ })
713
+ })
714
+
715
+ describe("stress test - continuous scrolling", () => {
716
+ test("scrolling through 1000 objects with varying heights", () => {
717
+ const heights = [20, 50, 30, 100, 40, 60, 25, 80, 35, 70]
718
+ let currentY = 0
719
+ const objects = Array.from({ length: 1000 }, (_, i) => {
720
+ const height = heights[i % heights.length]
721
+ const obj = createObject(`item-${i}`, 0, currentY, 200, height, i % 5)
722
+ currentY += height + 2
723
+ return obj
724
+ })
725
+
726
+ const totalHeight = currentY
727
+ const viewportHeight = 400
728
+
729
+ for (let scrollY = 0; scrollY < totalHeight - viewportHeight; scrollY += 100) {
730
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 200, height: viewportHeight }
731
+ const result = getObjectsInViewport(viewport, objects, "column", 50, 16)
732
+
733
+ result.forEach((obj) => {
734
+ const objectBottom = obj.y + obj.height
735
+ expect(objectBottom).toBeGreaterThan(viewport.y - 50)
736
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height + 50)
737
+ })
738
+
739
+ expect(result.length).toBeGreaterThan(0)
740
+ expect(result.length).toBeLessThan(50)
741
+ }
742
+ })
743
+ })
744
+
745
+ describe("realistic scroll scenarios", () => {
746
+ test("chat-like interface with variable height messages", () => {
747
+ const heights = [30, 60, 45, 90, 120, 35, 50, 75, 40, 100]
748
+ let currentY = 0
749
+ const objects = Array.from({ length: 100 }, (_, i) => {
750
+ const height = heights[i % heights.length]
751
+ const obj = createObject(`msg-${i}`, 0, currentY, 300, height, 0)
752
+ currentY += height + 5
753
+ return obj
754
+ })
755
+
756
+ for (let scroll = 0; scroll < currentY - 500; scroll += 50) {
757
+ const viewport: ViewportBounds = { x: 0, y: scroll, width: 300, height: 500 }
758
+ const result = getObjectsInViewport(viewport, objects, "column", 20, 16)
759
+
760
+ result.forEach((obj) => {
761
+ const objectBottom = obj.y + obj.height
762
+ expect(objectBottom).toBeGreaterThan(viewport.y - 20)
763
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height + 20)
764
+ })
765
+ }
766
+ })
767
+
768
+ test("grid layout with multiple columns", () => {
769
+ const objects = Array.from({ length: 200 }, (_, i) => {
770
+ const col = i % 4
771
+ const row = Math.floor(i / 4)
772
+ return createObject(`item-${i}`, col * 110, row * 110, 100, 100, 0)
773
+ })
774
+
775
+ const viewport: ViewportBounds = { x: 0, y: 1000, width: 440, height: 400 }
776
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
777
+
778
+ expect(result.length).toBeGreaterThan(0)
779
+ expect(result.length).toBeLessThan(30)
780
+
781
+ result.forEach((obj) => {
782
+ expect(obj.y + obj.height).toBeGreaterThan(viewport.y)
783
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
784
+ expect(obj.x + obj.width).toBeGreaterThan(viewport.x)
785
+ expect(obj.x).toBeLessThan(viewport.x + viewport.width)
786
+ })
787
+ })
788
+ })
789
+ })