@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.
- package/README.md +63 -0
- package/dev/keypress-debug-renderer.ts +148 -0
- package/dev/keypress-debug.ts +43 -0
- package/dev/print-env-vars.ts +32 -0
- package/dev/test-tmux-graphics-334.sh +68 -0
- package/dev/test-tmux-theme-queries.sh +151 -0
- package/dev/thai-debug-test.ts +68 -0
- package/dev/theme-mode.ts +290 -0
- package/docs/development.md +144 -0
- package/package.json +102 -0
- package/prebuilt/aarch64-android/libopentui.so +0 -0
- package/scripts/build-android.ts +226 -0
- package/scripts/build-native-termux.sh +525 -0
- package/scripts/build.ts +530 -0
- package/scripts/dist-test.ts +312 -0
- package/scripts/package-prebuilt.ts +110 -0
- package/scripts/publish.ts +60 -0
- package/scripts/test-node-hook.mjs +16 -0
- package/scripts/test-node.ts +241 -0
- package/scripts/vendor-deps.sh +73 -0
- package/src/NativeSpanFeed.ts +375 -0
- package/src/Renderable.ts +1878 -0
- package/src/__snapshots__/buffer.test.ts.snap +28 -0
- package/src/animation/Timeline.test.ts +2709 -0
- package/src/animation/Timeline.ts +598 -0
- package/src/ansi.ts +22 -0
- package/src/audio.ts +479 -0
- package/src/benchmark/attenuation-benchmark.ts +81 -0
- package/src/benchmark/audio-playback-benchmark.ts +344 -0
- package/src/benchmark/box-draw-benchmark.ts +1042 -0
- package/src/benchmark/colormatrix-benchmark.ts +123 -0
- package/src/benchmark/gain-benchmark.ts +80 -0
- package/src/benchmark/latest-all-bench-run.json +707 -0
- package/src/benchmark/latest-async-bench-run.json +336 -0
- package/src/benchmark/latest-default-bench-run.json +657 -0
- package/src/benchmark/latest-large-bench-run.json +707 -0
- package/src/benchmark/latest-quick-bench-run.json +207 -0
- package/src/benchmark/layout-benchmark.ts +2547 -0
- package/src/benchmark/markdown-benchmark.ts +1796 -0
- package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
- package/src/benchmark/native-span-feed-benchmark.md +56 -0
- package/src/benchmark/native-span-feed-benchmark.ts +596 -0
- package/src/benchmark/native-span-feed-compare.ts +280 -0
- package/src/benchmark/render-traversal-benchmark.ts +928 -0
- package/src/benchmark/text-buffer-render-benchmark.ts +874 -0
- package/src/benchmark/text-table-benchmark.ts +948 -0
- package/src/buffer.test.ts +292 -0
- package/src/buffer.ts +564 -0
- package/src/console.test.ts +629 -0
- package/src/console.ts +1253 -0
- package/src/edit-buffer.test.ts +1852 -0
- package/src/edit-buffer.ts +417 -0
- package/src/editor-view.test.ts +1056 -0
- package/src/editor-view.ts +290 -0
- package/src/index.ts +25 -0
- package/src/lib/KeyHandler.integration.test.ts +292 -0
- package/src/lib/KeyHandler.stopPropagation.test.ts +289 -0
- package/src/lib/KeyHandler.test.ts +662 -0
- package/src/lib/KeyHandler.ts +222 -0
- package/src/lib/RGBA.test.ts +60 -0
- package/src/lib/RGBA.ts +343 -0
- package/src/lib/ascii.font.ts +330 -0
- package/src/lib/border.test.ts +83 -0
- package/src/lib/border.ts +170 -0
- package/src/lib/bunfs.test.ts +27 -0
- package/src/lib/bunfs.ts +18 -0
- package/src/lib/clipboard.test.ts +63 -0
- package/src/lib/clipboard.ts +41 -0
- package/src/lib/clock.ts +35 -0
- package/src/lib/data-paths.test.ts +133 -0
- package/src/lib/data-paths.ts +109 -0
- package/src/lib/debounce.ts +106 -0
- package/src/lib/detect-links.test.ts +98 -0
- package/src/lib/detect-links.ts +56 -0
- package/src/lib/env.test.ts +228 -0
- package/src/lib/env.ts +209 -0
- package/src/lib/extmarks-history.ts +51 -0
- package/src/lib/extmarks-multiwidth.test.ts +324 -0
- package/src/lib/extmarks.test.ts +3547 -0
- package/src/lib/extmarks.ts +861 -0
- package/src/lib/fonts/block.json +405 -0
- package/src/lib/fonts/grid.json +265 -0
- package/src/lib/fonts/huge.json +741 -0
- package/src/lib/fonts/pallet.json +314 -0
- package/src/lib/fonts/shade.json +591 -0
- package/src/lib/fonts/slick.json +321 -0
- package/src/lib/fonts/tiny.json +69 -0
- package/src/lib/hast-styled-text.ts +59 -0
- package/src/lib/index.ts +22 -0
- package/src/lib/keybinding.internal.test.ts +342 -0
- package/src/lib/keybinding.internal.ts +182 -0
- package/src/lib/objects-in-viewport.test.ts +789 -0
- package/src/lib/objects-in-viewport.ts +153 -0
- package/src/lib/output.capture.ts +58 -0
- package/src/lib/parse.keypress-kitty.protocol.test.ts +347 -0
- package/src/lib/parse.keypress-kitty.test.ts +712 -0
- package/src/lib/parse.keypress-kitty.ts +470 -0
- package/src/lib/parse.keypress.test.ts +2020 -0
- package/src/lib/parse.keypress.ts +480 -0
- package/src/lib/parse.mouse.test.ts +552 -0
- package/src/lib/parse.mouse.ts +232 -0
- package/src/lib/paste.ts +18 -0
- package/src/lib/queue.ts +65 -0
- package/src/lib/render-geometry.ts +36 -0
- package/src/lib/renderable.validations.test.ts +87 -0
- package/src/lib/renderable.validations.ts +83 -0
- package/src/lib/scroll-acceleration.ts +98 -0
- package/src/lib/selection.ts +261 -0
- package/src/lib/singleton.ts +34 -0
- package/src/lib/stdin-parser.test.ts +2439 -0
- package/src/lib/stdin-parser.ts +2007 -0
- package/src/lib/styled-text.ts +178 -0
- package/src/lib/terminal-capability-detection.test.ts +226 -0
- package/src/lib/terminal-capability-detection.ts +98 -0
- package/src/lib/terminal-palette.test.ts +954 -0
- package/src/lib/terminal-palette.ts +441 -0
- package/src/lib/tree-sitter/assets/README.md +119 -0
- package/src/lib/tree-sitter/assets/javascript/highlights.scm +205 -0
- package/src/lib/tree-sitter/assets/javascript/tree-sitter-javascript.wasm +0 -0
- package/src/lib/tree-sitter/assets/markdown/highlights.scm +150 -0
- package/src/lib/tree-sitter/assets/markdown/injections.scm +27 -0
- package/src/lib/tree-sitter/assets/markdown/tree-sitter-markdown.wasm +0 -0
- package/src/lib/tree-sitter/assets/markdown_inline/highlights.scm +115 -0
- package/src/lib/tree-sitter/assets/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
- package/src/lib/tree-sitter/assets/typescript/highlights.scm +604 -0
- package/src/lib/tree-sitter/assets/typescript/tree-sitter-typescript.wasm +0 -0
- package/src/lib/tree-sitter/assets/update.ts +360 -0
- package/src/lib/tree-sitter/assets/zig/highlights.scm +284 -0
- package/src/lib/tree-sitter/assets/zig/tree-sitter-zig.wasm +0 -0
- package/src/lib/tree-sitter/assets.d.ts +9 -0
- package/src/lib/tree-sitter/cache.test.ts +291 -0
- package/src/lib/tree-sitter/client.test.ts +1434 -0
- package/src/lib/tree-sitter/client.ts +847 -0
- package/src/lib/tree-sitter/default-parsers.ts +133 -0
- package/src/lib/tree-sitter/download-utils.ts +148 -0
- package/src/lib/tree-sitter/index.ts +75 -0
- package/src/lib/tree-sitter/parser.worker.ts +1098 -0
- package/src/lib/tree-sitter/parsers-config.ts +81 -0
- package/src/lib/tree-sitter/resolve-ft.test.ts +55 -0
- package/src/lib/tree-sitter/resolve-ft.ts +189 -0
- package/src/lib/tree-sitter/types.ts +132 -0
- package/src/lib/tree-sitter/update-assets.ts +10 -0
- package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
- package/src/lib/tree-sitter-styled-text.ts +314 -0
- package/src/lib/validate-dir-name.ts +55 -0
- package/src/lib/yoga.options.test.ts +628 -0
- package/src/lib/yoga.options.ts +346 -0
- package/src/native-event-worker-repro.test.ts +48 -0
- package/src/native-event-worker-repro.worker.ts +9 -0
- package/src/native-handle.test.ts +221 -0
- package/src/platform/ffi.test.ts +516 -0
- package/src/platform/ffi.ts +653 -0
- package/src/platform/runtime.test.ts +52 -0
- package/src/platform/runtime.ts +121 -0
- package/src/platform/test.ts +45 -0
- package/src/platform/worker-handler-cleanup.fixture.ts +12 -0
- package/src/platform/worker-onmessage-startup.fixture.ts +10 -0
- package/src/platform/worker-startup.fixture.ts +9 -0
- package/src/platform/worker.node-test.ts +153 -0
- package/src/platform/worker.ts +425 -0
- package/src/plugins/core-slot.ts +579 -0
- package/src/plugins/registry.ts +402 -0
- package/src/plugins/types.ts +46 -0
- package/src/post/effects.ts +948 -0
- package/src/post/filters.ts +512 -0
- package/src/post/matrices.ts +288 -0
- package/src/renderables/ASCIIFont.ts +219 -0
- package/src/renderables/Box.test.ts +474 -0
- package/src/renderables/Box.ts +353 -0
- package/src/renderables/Code.test.ts +2388 -0
- package/src/renderables/Code.ts +555 -0
- package/src/renderables/Diff.regression.test.ts +381 -0
- package/src/renderables/Diff.test.ts +3212 -0
- package/src/renderables/Diff.ts +1278 -0
- package/src/renderables/EditBufferRenderable.test.ts +288 -0
- package/src/renderables/EditBufferRenderable.ts +1152 -0
- package/src/renderables/FrameBuffer.ts +47 -0
- package/src/renderables/Input.test.ts +1310 -0
- package/src/renderables/Input.ts +273 -0
- package/src/renderables/LineNumberRenderable.ts +725 -0
- package/src/renderables/Markdown.ts +2144 -0
- package/src/renderables/ScrollBar.ts +423 -0
- package/src/renderables/ScrollBox.ts +903 -0
- package/src/renderables/Select.test.ts +1134 -0
- package/src/renderables/Select.ts +535 -0
- package/src/renderables/Slider.test.ts +456 -0
- package/src/renderables/Slider.ts +342 -0
- package/src/renderables/TabSelect.test.ts +238 -0
- package/src/renderables/TabSelect.ts +447 -0
- package/src/renderables/Text.selection-buffer.test.ts +118 -0
- package/src/renderables/Text.test.ts +2662 -0
- package/src/renderables/Text.ts +147 -0
- package/src/renderables/TextBufferRenderable.ts +504 -0
- package/src/renderables/TextNode.test.ts +1137 -0
- package/src/renderables/TextNode.ts +379 -0
- package/src/renderables/TextTable.test.ts +1411 -0
- package/src/renderables/TextTable.ts +1434 -0
- package/src/renderables/Textarea.ts +425 -0
- package/src/renderables/TimeToFirstDraw.ts +89 -0
- package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
- package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
- package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
- package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
- package/src/renderables/__tests__/Code.test.ts +55 -0
- package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
- package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +827 -0
- package/src/renderables/__tests__/LineNumberRenderable.test.ts +1906 -0
- package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
- package/src/renderables/__tests__/Markdown.code-colors.test.ts +288 -0
- package/src/renderables/__tests__/Markdown.test.ts +4175 -0
- package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
- package/src/renderables/__tests__/Textarea.buffer.test.ts +683 -0
- package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
- package/src/renderables/__tests__/Textarea.editing.test.ts +2120 -0
- package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
- package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
- package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
- package/src/renderables/__tests__/Textarea.keybinding.test.ts +3275 -0
- package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
- package/src/renderables/__tests__/Textarea.rendering.test.ts +1927 -0
- package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
- package/src/renderables/__tests__/Textarea.selection.test.ts +1589 -0
- package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
- package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
- package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
- package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
- package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
- package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
- package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
- package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
- package/src/renderables/__tests__/markdown-parser.test.ts +241 -0
- package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
- package/src/renderables/composition/README.md +8 -0
- package/src/renderables/composition/VRenderable.ts +32 -0
- package/src/renderables/composition/constructs.ts +127 -0
- package/src/renderables/composition/vnode.ts +277 -0
- package/src/renderables/index.ts +23 -0
- package/src/renderables/markdown-parser.ts +79 -0
- package/src/renderer-theme-mode.ts +199 -0
- package/src/renderer.ts +4975 -0
- package/src/runtime-plugin-support-configure.ts +63 -0
- package/src/runtime-plugin-support.ts +13 -0
- package/src/runtime-plugin.ts +626 -0
- package/src/specs/terminal-startup.md +64 -0
- package/src/syntax-style.test.ts +841 -0
- package/src/syntax-style.ts +274 -0
- package/src/testing/README.md +219 -0
- package/src/testing/bun-test-node.ts +880 -0
- package/src/testing/capture-spans.test.ts +216 -0
- package/src/testing/integration.test.ts +276 -0
- package/src/testing/manual-clock.ts +117 -0
- package/src/testing/mock-keys.test.ts +1378 -0
- package/src/testing/mock-keys.ts +457 -0
- package/src/testing/mock-mouse.test.ts +218 -0
- package/src/testing/mock-mouse.ts +247 -0
- package/src/testing/mock-tree-sitter-client.ts +81 -0
- package/src/testing/spy.ts +13 -0
- package/src/testing/terminal-capabilities.ts +47 -0
- package/src/testing/test-recorder.test.ts +410 -0
- package/src/testing/test-recorder.ts +133 -0
- package/src/testing/test-renderer.ts +375 -0
- package/src/testing/test-renderer.wait.test.ts +278 -0
- package/src/testing/test-streams.ts +31 -0
- package/src/testing.ts +9 -0
- package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
- package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
- package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
- package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
- package/src/tests/allocator-stats.test.ts +53 -0
- package/src/tests/audio.test.ts +303 -0
- package/src/tests/destroy-during-render.test.ts +200 -0
- package/src/tests/destroy-on-exit.fixture.ts +36 -0
- package/src/tests/destroy-on-exit.test.ts +62 -0
- package/src/tests/ffi-borrowed-pointer-callsites.test.ts +153 -0
- package/src/tests/hover-cursor.test.ts +98 -0
- package/src/tests/native-backed-measurement-lifecycle.test.ts +133 -0
- package/src/tests/native-backed-measurement-parity.test.ts +322 -0
- package/src/tests/native-span-feed-async.test.ts +173 -0
- package/src/tests/native-span-feed-close.test.ts +120 -0
- package/src/tests/native-span-feed-coverage.test.ts +227 -0
- package/src/tests/native-span-feed-edge-cases.test.ts +384 -0
- package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
- package/src/tests/opacity.test.ts +123 -0
- package/src/tests/renderable.snapshot.test.ts +524 -0
- package/src/tests/renderable.test.ts +1851 -0
- package/src/tests/renderer.clock.test.ts +270 -0
- package/src/tests/renderer.console-startup.test.ts +2151 -0
- package/src/tests/renderer.control.test.ts +458 -0
- package/src/tests/renderer.core-slot-binding.test.ts +962 -0
- package/src/tests/renderer.cursor.test.ts +26 -0
- package/src/tests/renderer.custom-stdout.test.ts +1112 -0
- package/src/tests/renderer.destroy-during-render.test.ts +147 -0
- package/src/tests/renderer.focus-restore.test.ts +257 -0
- package/src/tests/renderer.focus.test.ts +329 -0
- package/src/tests/renderer.idle.test.ts +219 -0
- package/src/tests/renderer.input.test.ts +2615 -0
- package/src/tests/renderer.kitty-flags.test.ts +195 -0
- package/src/tests/renderer.lifecycle.test.ts +34 -0
- package/src/tests/renderer.mouse.test.ts +1315 -0
- package/src/tests/renderer.notifications.test.ts +27 -0
- package/src/tests/renderer.palette.test.ts +1199 -0
- package/src/tests/renderer.remote.test.ts +247 -0
- package/src/tests/renderer.render-stats.test.ts +146 -0
- package/src/tests/renderer.scrollback-surface.test.ts +474 -0
- package/src/tests/renderer.selection.test.ts +138 -0
- package/src/tests/renderer.slot-registry.test.ts +684 -0
- package/src/tests/renderer.tracker.test.ts +202 -0
- package/src/tests/renderer.useMouse.test.ts +83 -0
- package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
- package/src/tests/runtime-plugin-node-modules-import-like-string.fixture.ts +48 -0
- package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +43 -0
- package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +67 -0
- package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +72 -0
- package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +44 -0
- package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +85 -0
- package/src/tests/runtime-plugin-path-alias.fixture.ts +43 -0
- package/src/tests/runtime-plugin-resolve-roots.fixture.ts +65 -0
- package/src/tests/runtime-plugin-support-configure.fixture.ts +31 -0
- package/src/tests/runtime-plugin-support-late-addition.fixture.ts +17 -0
- package/src/tests/runtime-plugin-support.fixture.ts +11 -0
- package/src/tests/runtime-plugin-support.test.ts +54 -0
- package/src/tests/runtime-plugin-windows-file-url.fixture.ts +30 -0
- package/src/tests/runtime-plugin.fixture.ts +40 -0
- package/src/tests/runtime-plugin.test.ts +369 -0
- package/src/tests/scrollbox-culling-bug.test.ts +114 -0
- package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
- package/src/tests/scrollbox-hitgrid.test.ts +909 -0
- package/src/tests/scrollbox.test.ts +1895 -0
- package/src/tests/wrap-resize-perf.test.ts +276 -0
- package/src/tests/yoga-api-coverage.test.ts +409 -0
- package/src/tests/yoga-callback-stress.test.ts +122 -0
- package/src/tests/yoga-ffi.test.ts +117 -0
- package/src/tests/yoga-setters.test.ts +921 -0
- package/src/tests/yoga-upstream/YGAlignBaselineTest.test.ts +124 -0
- package/src/tests/yoga-upstream/YGComputedBorderTest.test.ts +27 -0
- package/src/tests/yoga-upstream/YGComputedMarginTest.test.ts +27 -0
- package/src/tests/yoga-upstream/YGComputedPaddingTest.test.ts +27 -0
- package/src/tests/yoga-upstream/YGDirtiedTest.test.ts +157 -0
- package/src/tests/yoga-upstream/YGErrataTest.test.ts +40 -0
- package/src/tests/yoga-upstream/YGFlexBasisAuto.test.ts +23 -0
- package/src/tests/yoga-upstream/YGHasNewLayout.test.ts +81 -0
- package/src/tests/yoga-upstream/YGMeasureCacheTest.test.ts +31 -0
- package/src/tests/yoga-upstream/YGMeasureTest.test.ts +70 -0
- package/src/tests/yoga-upstream/generated/YGAbsolutePositionTest.test.ts +1864 -0
- package/src/tests/yoga-upstream/generated/YGAlignContentTest.test.ts +5829 -0
- package/src/tests/yoga-upstream/generated/YGAlignItemsTest.test.ts +2444 -0
- package/src/tests/yoga-upstream/generated/YGAlignSelfTest.test.ts +293 -0
- package/src/tests/yoga-upstream/generated/YGAndroidNewsFeed.test.ts +315 -0
- package/src/tests/yoga-upstream/generated/YGAspectRatioTest.test.ts +231 -0
- package/src/tests/yoga-upstream/generated/YGAutoTest.test.ts +347 -0
- package/src/tests/yoga-upstream/generated/YGBorderTest.test.ts +256 -0
- package/src/tests/yoga-upstream/generated/YGBoxSizingTest.test.ts +2747 -0
- package/src/tests/yoga-upstream/generated/YGDimensionTest.test.ts +129 -0
- package/src/tests/yoga-upstream/generated/YGDisplayContentsTest.test.ts +109 -0
- package/src/tests/yoga-upstream/generated/YGDisplayTest.test.ts +1097 -0
- package/src/tests/yoga-upstream/generated/YGFlexDirectionTest.test.ts +4562 -0
- package/src/tests/yoga-upstream/generated/YGFlexTest.test.ts +677 -0
- package/src/tests/yoga-upstream/generated/YGFlexWrapTest.test.ts +2071 -0
- package/src/tests/yoga-upstream/generated/YGGapTest.test.ts +3303 -0
- package/src/tests/yoga-upstream/generated/YGIntrinsicSizeTest.test.ts +582 -0
- package/src/tests/yoga-upstream/generated/YGJustifyContentTest.test.ts +2150 -0
- package/src/tests/yoga-upstream/generated/YGMarginTest.test.ts +1963 -0
- package/src/tests/yoga-upstream/generated/YGMinMaxDimensionTest.test.ts +1446 -0
- package/src/tests/yoga-upstream/generated/YGPaddingTest.test.ts +354 -0
- package/src/tests/yoga-upstream/generated/YGPercentageTest.test.ts +1760 -0
- package/src/tests/yoga-upstream/generated/YGRoundingTest.test.ts +1161 -0
- package/src/tests/yoga-upstream/generated/YGSizeOverflowTest.test.ts +207 -0
- package/src/tests/yoga-upstream/generated/YGStaticPositionTest.test.ts +6338 -0
- package/src/tests/yoga-upstream/tools/MeasureCounter.ts +54 -0
- package/src/tests/yoga-upstream/tools/utils.ts +81 -0
- package/src/text-buffer-view.test.ts +722 -0
- package/src/text-buffer-view.ts +193 -0
- package/src/text-buffer.test.ts +512 -0
- package/src/text-buffer.ts +250 -0
- package/src/types.ts +199 -0
- package/src/utils.ts +88 -0
- package/src/yoga.ts +1146 -0
- package/src/zig/ansi.zig +502 -0
- package/src/zig/audio.zig +1206 -0
- package/src/zig/bench/README.md +50 -0
- package/src/zig/bench/buffer-color-blending_bench.zig +350 -0
- package/src/zig/bench/buffer-draw-box_bench.zig +786 -0
- package/src/zig/bench/buffer-draw-text-buffer_bench.zig +894 -0
- package/src/zig/bench/edit-buffer_bench.zig +476 -0
- package/src/zig/bench/editor-view_bench.zig +169 -0
- package/src/zig/bench/native-span-feed_bench.zig +107 -0
- package/src/zig/bench/rope-markers_bench.zig +713 -0
- package/src/zig/bench/rope_bench.zig +514 -0
- package/src/zig/bench/styled-text_bench.zig +476 -0
- package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
- package/src/zig/bench/text-buffer-view_bench.zig +459 -0
- package/src/zig/bench/text-chunk-graphemes_bench.zig +272 -0
- package/src/zig/bench/utf8_bench.zig +799 -0
- package/src/zig/bench-utils.zig +431 -0
- package/src/zig/bench.zig +223 -0
- package/src/zig/buffer-methods.zig +215 -0
- package/src/zig/buffer.zig +2515 -0
- package/src/zig/build.zig +700 -0
- package/src/zig/build.zig.zon +19 -0
- package/src/zig/edit-buffer.zig +900 -0
- package/src/zig/editor-view.zig +812 -0
- package/src/zig/event-bus.zig +28 -0
- package/src/zig/event-emitter.zig +65 -0
- package/src/zig/file-logger.zig +92 -0
- package/src/zig/grapheme.zig +838 -0
- package/src/zig/handles.zig +267 -0
- package/src/zig/lib.zig +2813 -0
- package/src/zig/link.zig +349 -0
- package/src/zig/logger.zig +43 -0
- package/src/zig/mem-registry.zig +126 -0
- package/src/zig/miniaudio_shim.c +21 -0
- package/src/zig/native-renderable.zig +112 -0
- package/src/zig/native-span-feed-bench-lib.zig +7 -0
- package/src/zig/native-span-feed.zig +747 -0
- package/src/zig/renderer-output.zig +677 -0
- package/src/zig/renderer.zig +2106 -0
- package/src/zig/rope.zig +1220 -0
- package/src/zig/split-scrollback.zig +88 -0
- package/src/zig/syntax-style.zig +174 -0
- package/src/zig/terminal.zig +1652 -0
- package/src/zig/test.zig +82 -0
- package/src/zig/tests/README.md +18 -0
- package/src/zig/tests/ansi_test.zig +18 -0
- package/src/zig/tests/audio_test.zig +439 -0
- package/src/zig/tests/buffer-methods_test.zig +1111 -0
- package/src/zig/tests/buffer_test.zig +2926 -0
- package/src/zig/tests/edit-buffer-history_test.zig +299 -0
- package/src/zig/tests/edit-buffer_test.zig +1688 -0
- package/src/zig/tests/editor-view_test.zig +3454 -0
- package/src/zig/tests/event-emitter_test.zig +249 -0
- package/src/zig/tests/grapheme_test.zig +1329 -0
- package/src/zig/tests/handles_test.zig +123 -0
- package/src/zig/tests/link_test.zig +220 -0
- package/src/zig/tests/mem-registry_test.zig +473 -0
- package/src/zig/tests/memory_leak_regression_test.zig +234 -0
- package/src/zig/tests/native-renderable_test.zig +16 -0
- package/src/zig/tests/native-span-feed_test.zig +1266 -0
- package/src/zig/tests/renderer_test.zig +2604 -0
- package/src/zig/tests/rope-nested_test.zig +712 -0
- package/src/zig/tests/rope_fuzz_test.zig +238 -0
- package/src/zig/tests/rope_test.zig +2362 -0
- package/src/zig/tests/segment-merge.test.zig +148 -0
- package/src/zig/tests/split-scrollback_test.zig +70 -0
- package/src/zig/tests/syntax-style_test.zig +558 -0
- package/src/zig/tests/terminal_test.zig +1489 -0
- package/src/zig/tests/test-renderer.zig +126 -0
- package/src/zig/tests/text-buffer-drawing_test.zig +3291 -0
- package/src/zig/tests/text-buffer-highlights_test.zig +706 -0
- package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
- package/src/zig/tests/text-buffer-segment_test.zig +320 -0
- package/src/zig/tests/text-buffer-selection_test.zig +1056 -0
- package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
- package/src/zig/tests/text-buffer-view_test.zig +3672 -0
- package/src/zig/tests/text-buffer_test.zig +2191 -0
- package/src/zig/tests/unicode-width-map.zon +3909 -0
- package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
- package/src/zig/tests/utf8_test.zig +4060 -0
- package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
- package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
- package/src/zig/tests/word-wrap-editing_test.zig +498 -0
- package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
- package/src/zig/tests/yoga_test.zig +65 -0
- package/src/zig/text-buffer-iterators.zig +496 -0
- package/src/zig/text-buffer-segment.zig +403 -0
- package/src/zig/text-buffer-view.zig +1428 -0
- package/src/zig/text-buffer.zig +1272 -0
- package/src/zig/utf8.zig +1954 -0
- package/src/zig/utils.zig +12 -0
- package/src/zig/uucode-stub.zig +76 -0
- package/src/zig/vendor/miniaudio/miniaudio.h +93468 -0
- package/src/zig/yoga.zig +658 -0
- package/src/zig-structs.ts +381 -0
- package/src/zig.ts +5260 -0
- package/tsconfig.build.json +17 -0
- package/tsconfig.json +27 -0
- 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
|
+
})
|