@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,880 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks"
|
|
2
|
+
import assert from "node:assert/strict"
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { after, afterEach, before, beforeEach, describe as nodeDescribe, test as nodeTest } from "node:test"
|
|
6
|
+
import { fileURLToPath } from "node:url"
|
|
7
|
+
import { inspect, isDeepStrictEqual } from "node:util"
|
|
8
|
+
|
|
9
|
+
type AnyFunction = (...args: any[]) => any
|
|
10
|
+
type ThrowMatcher = RegExp | string | Error | ((value: unknown) => boolean) | (new (...args: any[]) => Error)
|
|
11
|
+
const asymmetricMatcher = Symbol("asymmetricMatcher")
|
|
12
|
+
|
|
13
|
+
interface AsymmetricMatcher {
|
|
14
|
+
readonly [asymmetricMatcher]: true
|
|
15
|
+
matches(received: unknown): boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface SnapshotTestContext {
|
|
19
|
+
filePath: string
|
|
20
|
+
fullTestName: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface MockedFunction<Fn extends AnyFunction = AnyFunction> {
|
|
24
|
+
(...args: Parameters<Fn>): ReturnType<Fn>
|
|
25
|
+
mock: {
|
|
26
|
+
calls: unknown[][]
|
|
27
|
+
}
|
|
28
|
+
mockImplementation(implementation: Fn): MockedFunction<Fn>
|
|
29
|
+
mockRestore(): void
|
|
30
|
+
mockClear(): void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function fail(message: string): never {
|
|
34
|
+
throw new assert.AssertionError({ message })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatValue(value: unknown): string {
|
|
38
|
+
return inspect(value, { depth: 5 })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const snapshotContextStorage = new AsyncLocalStorage<SnapshotTestContext>()
|
|
42
|
+
const snapshotFileCache = new Map<string, Map<string, string>>()
|
|
43
|
+
const snapshotCounters = new Map<string, number>()
|
|
44
|
+
const describeNameStack: string[] = []
|
|
45
|
+
|
|
46
|
+
function normalizeNewlines(value: string): string {
|
|
47
|
+
return value.replace(/\r\n/g, "\n")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function serializeSnapshotValue(value: unknown): string {
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
return `"${normalizeNewlines(value).replace(/\\/g, "\\\\")}"`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return normalizeNewlines(formatValue(value))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeInlineSnapshot(snapshot: string): string {
|
|
59
|
+
const lines = normalizeNewlines(snapshot).split("\n")
|
|
60
|
+
const hasWrapperWhitespace = lines.length >= 2 && lines[0]?.trim() === "" && lines.at(-1)?.trim() === ""
|
|
61
|
+
|
|
62
|
+
if (!hasWrapperWhitespace) {
|
|
63
|
+
return lines.join("\n").replace(/\\/g, "\\\\")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
lines.shift()
|
|
67
|
+
lines.pop()
|
|
68
|
+
|
|
69
|
+
const indentation = lines.reduce((smallest, line) => {
|
|
70
|
+
if (line.trim() === "") {
|
|
71
|
+
return smallest
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const lineIndentation = line.match(/^\s*/)?.[0].length ?? 0
|
|
75
|
+
return Math.min(smallest, lineIndentation)
|
|
76
|
+
}, Number.POSITIVE_INFINITY)
|
|
77
|
+
|
|
78
|
+
if (!Number.isFinite(indentation)) {
|
|
79
|
+
return lines.join("\n")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return lines
|
|
83
|
+
.map((line) => line.slice(indentation))
|
|
84
|
+
.join("\n")
|
|
85
|
+
.replace(/\\/g, "\\\\")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getSnapshotContext(): SnapshotTestContext {
|
|
89
|
+
const context = snapshotContextStorage.getStore()
|
|
90
|
+
if (!context) {
|
|
91
|
+
fail("Snapshot matchers can only be used inside a test callback")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return context
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getSnapshotFilePath(testFilePath: string): string {
|
|
98
|
+
const emittedMarker = `${path.sep}.node-test${path.sep}`
|
|
99
|
+
const normalizedFilePath = testFilePath.includes(emittedMarker)
|
|
100
|
+
? testFilePath.replace(emittedMarker, path.sep)
|
|
101
|
+
: testFilePath
|
|
102
|
+
const sourceFilePath = normalizedFilePath.endsWith(".js")
|
|
103
|
+
? `${normalizedFilePath.slice(0, -3)}.ts`
|
|
104
|
+
: normalizedFilePath
|
|
105
|
+
|
|
106
|
+
return path.join(path.dirname(sourceFilePath), "__snapshots__", `${path.basename(sourceFilePath)}.snap`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function readSnapshotFile(snapshotPath: string): Map<string, string> {
|
|
110
|
+
const cached = snapshotFileCache.get(snapshotPath)
|
|
111
|
+
if (cached) {
|
|
112
|
+
return cached
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const snapshots = new Map<string, string>()
|
|
116
|
+
|
|
117
|
+
if (existsSync(snapshotPath)) {
|
|
118
|
+
const contents = normalizeNewlines(readFileSync(snapshotPath, "utf8"))
|
|
119
|
+
const snapshotPattern = /exports\[`([\s\S]*?)`\] = `\n([\s\S]*?)\n`;/g
|
|
120
|
+
|
|
121
|
+
for (const match of contents.matchAll(snapshotPattern)) {
|
|
122
|
+
snapshots.set(match[1], match[2])
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
snapshotFileCache.set(snapshotPath, snapshots)
|
|
127
|
+
return snapshots
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function nextSnapshotIndex(filePath: string, snapshotName: string): number {
|
|
131
|
+
const counterKey = `${filePath}\u0000${snapshotName}`
|
|
132
|
+
const nextIndex = (snapshotCounters.get(counterKey) ?? 0) + 1
|
|
133
|
+
snapshotCounters.set(counterKey, nextIndex)
|
|
134
|
+
return nextIndex
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function assertSnapshotMatch(received: unknown, expected: string, label: string, inverted: boolean): void {
|
|
138
|
+
if (inverted) {
|
|
139
|
+
fail(`Snapshot negation is not supported for ${label}`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const actual = serializeSnapshotValue(received)
|
|
143
|
+
if (actual !== expected) {
|
|
144
|
+
fail(`Expected ${label} to match\n\nExpected:\n${expected}\n\nReceived:\n${actual}`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function parseStackFilePath(candidate: string): string {
|
|
149
|
+
return candidate.startsWith("file://") ? fileURLToPath(candidate) : candidate
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getRegistrationFilePath(): string {
|
|
153
|
+
const stack = new Error().stack?.split("\n") ?? []
|
|
154
|
+
|
|
155
|
+
for (const line of stack.slice(1)) {
|
|
156
|
+
const match = line.match(/\((file:\/\/.+?|\/.+?):\d+:\d+\)$/) ?? line.match(/at (file:\/\/.+?|\/.+?):\d+:\d+$/)
|
|
157
|
+
if (!match) {
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const filePath = parseStackFilePath(match[1])
|
|
162
|
+
const fileName = path.basename(filePath)
|
|
163
|
+
if (fileName === "bun-test-node.ts" || fileName === "bun-test-node.js") {
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return filePath
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fail("Could not determine the current test file")
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function wrapTestCallback(filePath: string, fullTestName: string, callback: AnyFunction): AnyFunction {
|
|
174
|
+
return function wrappedTestCallback(this: unknown, ...args: unknown[]) {
|
|
175
|
+
return snapshotContextStorage.run({ filePath, fullTestName }, () => callback.apply(this, args))
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getFullTestName(name: string): string {
|
|
180
|
+
return [...describeNameStack, name].join(" ")
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function registerTest(base: AnyFunction, name: string, optionsOrFn?: unknown, maybeFn?: unknown): unknown {
|
|
184
|
+
const filePath = getRegistrationFilePath()
|
|
185
|
+
const fullTestName = getFullTestName(name)
|
|
186
|
+
|
|
187
|
+
if (typeof optionsOrFn === "function") {
|
|
188
|
+
return base(name, wrapTestCallback(filePath, fullTestName, optionsOrFn as AnyFunction))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (typeof maybeFn === "function") {
|
|
192
|
+
return base(name, optionsOrFn, wrapTestCallback(filePath, fullTestName, maybeFn as AnyFunction))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return base(name, optionsOrFn, maybeFn)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function wrapDescribeCallback(name: string, callback: AnyFunction): AnyFunction {
|
|
199
|
+
return function wrappedDescribeCallback(this: unknown, ...args: unknown[]) {
|
|
200
|
+
describeNameStack.push(name)
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
return callback.apply(this, args)
|
|
204
|
+
} finally {
|
|
205
|
+
describeNameStack.pop()
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function registerDescribe(base: AnyFunction, name: string, optionsOrFn?: unknown, maybeFn?: unknown): unknown {
|
|
211
|
+
if (typeof optionsOrFn === "function") {
|
|
212
|
+
return base(name, wrapDescribeCallback(name, optionsOrFn as AnyFunction))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (typeof maybeFn === "function") {
|
|
216
|
+
return base(name, optionsOrFn, wrapDescribeCallback(name, maybeFn as AnyFunction))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return base(name, optionsOrFn, maybeFn)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function createTestVariant(base: AnyFunction): AnyFunction {
|
|
223
|
+
return function wrappedTest(name: string, optionsOrFn?: unknown, maybeFn?: unknown): unknown {
|
|
224
|
+
return registerTest(base, name, optionsOrFn, maybeFn)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function formatEachName(name: string, args: readonly unknown[]): string {
|
|
229
|
+
let index = 0
|
|
230
|
+
|
|
231
|
+
return name.replace(/%s/g, () => String(args[index++]))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function createEach(base: AnyFunction) {
|
|
235
|
+
return (cases: readonly unknown[]) => {
|
|
236
|
+
return (name: string, optionsOrFn?: unknown, maybeFn?: unknown): void => {
|
|
237
|
+
for (const testCase of cases) {
|
|
238
|
+
const args = Array.isArray(testCase) ? [...testCase] : [testCase]
|
|
239
|
+
const testName = formatEachName(name, args)
|
|
240
|
+
|
|
241
|
+
if (typeof optionsOrFn === "function") {
|
|
242
|
+
registerTest(base, testName, function eachCallback(this: unknown) {
|
|
243
|
+
return (optionsOrFn as AnyFunction).apply(this, args)
|
|
244
|
+
})
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (typeof maybeFn === "function") {
|
|
249
|
+
registerTest(base, testName, optionsOrFn, function eachCallback(this: unknown) {
|
|
250
|
+
return (maybeFn as AnyFunction).apply(this, args)
|
|
251
|
+
})
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
registerTest(base, testName, optionsOrFn, maybeFn)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function attachEach(target: AnyFunction, base: AnyFunction): void {
|
|
262
|
+
;(target as AnyFunction & { each?: ReturnType<typeof createEach> }).each = createEach(base)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function createTest(base: typeof nodeTest): typeof nodeTest {
|
|
266
|
+
const wrapped = createTestVariant(base) as typeof nodeTest
|
|
267
|
+
|
|
268
|
+
Object.assign(wrapped, base)
|
|
269
|
+
attachEach(wrapped, base)
|
|
270
|
+
|
|
271
|
+
if (typeof base.skip === "function") {
|
|
272
|
+
wrapped.skip = createTestVariant(base.skip) as typeof base.skip
|
|
273
|
+
attachEach(wrapped.skip as AnyFunction, base.skip)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (typeof base.only === "function") {
|
|
277
|
+
wrapped.only = createTestVariant(base.only) as typeof base.only
|
|
278
|
+
attachEach(wrapped.only as AnyFunction, base.only)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (typeof base.todo === "function") {
|
|
282
|
+
wrapped.todo = createTestVariant(base.todo) as typeof base.todo
|
|
283
|
+
attachEach(wrapped.todo as AnyFunction, base.todo)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return wrapped
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function createDescribeVariant(base: AnyFunction): AnyFunction {
|
|
290
|
+
return function wrappedDescribe(name: string, optionsOrFn?: unknown, maybeFn?: unknown): unknown {
|
|
291
|
+
return registerDescribe(base, name, optionsOrFn, maybeFn)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function createDescribe(base: typeof nodeDescribe): typeof nodeDescribe {
|
|
296
|
+
const wrapped = createDescribeVariant(base) as typeof nodeDescribe
|
|
297
|
+
|
|
298
|
+
Object.assign(wrapped, base)
|
|
299
|
+
|
|
300
|
+
if (typeof base.skip === "function") {
|
|
301
|
+
wrapped.skip = createDescribeVariant(base.skip) as typeof base.skip
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (typeof base.only === "function") {
|
|
305
|
+
wrapped.only = createDescribeVariant(base.only) as typeof base.only
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (typeof base.todo === "function") {
|
|
309
|
+
wrapped.todo = createDescribeVariant(base.todo) as typeof base.todo
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return wrapped
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function hasLength(value: unknown): value is { length: number } {
|
|
316
|
+
return value != null && typeof (value as { length?: unknown }).length === "number"
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function isComparable(value: unknown): value is number | bigint {
|
|
320
|
+
return typeof value === "number" || typeof value === "bigint"
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function isObjectLike(value: unknown): value is Record<PropertyKey, unknown> {
|
|
324
|
+
return (typeof value === "object" || typeof value === "function") && value !== null
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function isAsymmetricMatcher(value: unknown): value is AsymmetricMatcher {
|
|
328
|
+
return isObjectLike(value) && value[asymmetricMatcher] === true && typeof value.matches === "function"
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function createAnyMatcher(expectedType: unknown): AsymmetricMatcher {
|
|
332
|
+
if (typeof expectedType !== "function") {
|
|
333
|
+
fail("expect.any() requires a constructor")
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
[asymmetricMatcher]: true,
|
|
338
|
+
matches(received) {
|
|
339
|
+
if (expectedType === Number) return typeof received === "number" || received instanceof Number
|
|
340
|
+
if (expectedType === String) return typeof received === "string" || received instanceof String
|
|
341
|
+
if (expectedType === Boolean) return typeof received === "boolean" || received instanceof Boolean
|
|
342
|
+
if (expectedType === BigInt) return typeof received === "bigint"
|
|
343
|
+
if (expectedType === Symbol) return typeof received === "symbol"
|
|
344
|
+
|
|
345
|
+
return received instanceof expectedType
|
|
346
|
+
},
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function valuesEqual(received: unknown, expected: unknown): boolean {
|
|
351
|
+
if (isAsymmetricMatcher(expected)) {
|
|
352
|
+
return expected.matches(received)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (Array.isArray(received) && Array.isArray(expected)) {
|
|
356
|
+
if (received.length !== expected.length) {
|
|
357
|
+
return false
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
for (let index = 0; index < expected.length; index += 1) {
|
|
361
|
+
if (!valuesEqual(received[index], expected[index])) {
|
|
362
|
+
return false
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return true
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return isDeepStrictEqual(received, expected)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function objectMatches(received: unknown, expected: unknown): boolean {
|
|
373
|
+
if (isAsymmetricMatcher(expected)) {
|
|
374
|
+
return expected.matches(received)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!isObjectLike(expected)) {
|
|
378
|
+
return valuesEqual(received, expected)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (!isObjectLike(received)) {
|
|
382
|
+
return false
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
for (const key of Reflect.ownKeys(expected)) {
|
|
386
|
+
if (!Object.hasOwn(received, key)) {
|
|
387
|
+
return false
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (!objectMatches(received[key], expected[key])) {
|
|
391
|
+
return false
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return true
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function createMock<Fn extends AnyFunction>(implementation: Fn, restore?: () => void): MockedFunction<Fn> {
|
|
399
|
+
const calls: unknown[][] = []
|
|
400
|
+
let currentImplementation = implementation
|
|
401
|
+
|
|
402
|
+
const mocked = function (this: unknown, ...args: Parameters<Fn>): ReturnType<Fn> {
|
|
403
|
+
calls.push(args)
|
|
404
|
+
return currentImplementation.apply(this, args)
|
|
405
|
+
} as MockedFunction<Fn>
|
|
406
|
+
|
|
407
|
+
mocked.mock = { calls }
|
|
408
|
+
mocked.mockImplementation = (nextImplementation: Fn) => {
|
|
409
|
+
currentImplementation = nextImplementation
|
|
410
|
+
return mocked
|
|
411
|
+
}
|
|
412
|
+
mocked.mockRestore = () => {
|
|
413
|
+
calls.length = 0
|
|
414
|
+
currentImplementation = implementation
|
|
415
|
+
restore?.()
|
|
416
|
+
}
|
|
417
|
+
mocked.mockClear = () => {
|
|
418
|
+
calls.length = 0
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return mocked
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function getMockCalls(received: unknown): unknown[][] {
|
|
425
|
+
const calls = (received as MockedFunction | undefined)?.mock?.calls
|
|
426
|
+
|
|
427
|
+
if (!Array.isArray(calls)) {
|
|
428
|
+
fail(`Expected ${formatValue(received)} to be a mock or spy`)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return calls
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function isErrorConstructor(matcher: ThrowMatcher): matcher is new (...args: any[]) => Error {
|
|
435
|
+
return typeof matcher === "function" && matcher.prototype instanceof Error
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function matchesThrow(error: unknown, expected?: ThrowMatcher): boolean {
|
|
439
|
+
if (expected == null) {
|
|
440
|
+
return true
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (typeof expected === "string") {
|
|
444
|
+
return String(error instanceof Error ? error.message : error).includes(expected)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (expected instanceof RegExp) {
|
|
448
|
+
return expected.test(String(error instanceof Error ? error.message : error))
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (expected instanceof Error) {
|
|
452
|
+
return (
|
|
453
|
+
error === expected ||
|
|
454
|
+
(error instanceof Error && error.name === expected.name && error.message === expected.message)
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (isErrorConstructor(expected)) {
|
|
459
|
+
return error instanceof expected
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return Boolean(expected(error))
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function formatThrowMatcher(expected?: ThrowMatcher): string {
|
|
466
|
+
if (expected == null) {
|
|
467
|
+
return ""
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return ` matching ${formatValue(expected)}`
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
class AsyncExpectation<T> {
|
|
474
|
+
constructor(
|
|
475
|
+
private readonly received: PromiseLike<T> | T,
|
|
476
|
+
private readonly mode: "resolves" | "rejects",
|
|
477
|
+
private readonly inverted = false,
|
|
478
|
+
) {}
|
|
479
|
+
|
|
480
|
+
get not(): AsyncExpectation<T> {
|
|
481
|
+
return new AsyncExpectation(this.received, this.mode, !this.inverted)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async toBe(expected: unknown): Promise<void> {
|
|
485
|
+
new Expectation(await this.unwrap(), this.inverted).toBe(expected)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async toEqual(expected: unknown): Promise<void> {
|
|
489
|
+
new Expectation(await this.unwrap(), this.inverted).toEqual(expected)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async toBeNull(): Promise<void> {
|
|
493
|
+
new Expectation(await this.unwrap(), this.inverted).toBeNull()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async toBeUndefined(): Promise<void> {
|
|
497
|
+
new Expectation(await this.unwrap(), this.inverted).toBeUndefined()
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async toContain(expected: unknown): Promise<void> {
|
|
501
|
+
new Expectation(await this.unwrap(), this.inverted).toContain(expected)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async toMatchSnapshot(snapshotName?: string): Promise<void> {
|
|
505
|
+
new Expectation(await this.unwrap(), this.inverted).toMatchSnapshot(snapshotName)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async toMatchInlineSnapshot(snapshot: string): Promise<void> {
|
|
509
|
+
new Expectation(await this.unwrap(), this.inverted).toMatchInlineSnapshot(snapshot)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async toThrow(expected?: ThrowMatcher): Promise<void> {
|
|
513
|
+
const error = await this.unwrap()
|
|
514
|
+
new Expectation(() => {
|
|
515
|
+
throw error
|
|
516
|
+
}, this.inverted).toThrow(expected)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async toHaveBeenCalled(): Promise<void> {
|
|
520
|
+
new Expectation(await this.unwrap(), this.inverted).toHaveBeenCalled()
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async toHaveBeenCalledTimes(expected: number): Promise<void> {
|
|
524
|
+
new Expectation(await this.unwrap(), this.inverted).toHaveBeenCalledTimes(expected)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async toHaveBeenCalledWith(...expectedArgs: unknown[]): Promise<void> {
|
|
528
|
+
new Expectation(await this.unwrap(), this.inverted).toHaveBeenCalledWith(...expectedArgs)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private async unwrap(): Promise<unknown> {
|
|
532
|
+
try {
|
|
533
|
+
const value = await this.received
|
|
534
|
+
|
|
535
|
+
if (this.mode === "rejects") {
|
|
536
|
+
fail(`Expected promise to reject but it resolved to ${formatValue(value)}`)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return value
|
|
540
|
+
} catch (error) {
|
|
541
|
+
if (this.mode === "resolves") {
|
|
542
|
+
fail(`Expected promise to resolve but it rejected with ${formatValue(error)}`)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return error
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
class Expectation<T> {
|
|
551
|
+
constructor(
|
|
552
|
+
private readonly received: T,
|
|
553
|
+
private readonly inverted = false,
|
|
554
|
+
) {}
|
|
555
|
+
|
|
556
|
+
get not(): Expectation<T> {
|
|
557
|
+
return new Expectation(this.received, !this.inverted)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
get resolves(): AsyncExpectation<T> {
|
|
561
|
+
return new AsyncExpectation(this.received, "resolves", this.inverted)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
get rejects(): AsyncExpectation<T> {
|
|
565
|
+
return new AsyncExpectation(this.received, "rejects", this.inverted)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
toBe(expected: unknown): void {
|
|
569
|
+
this.assertMatch(
|
|
570
|
+
Object.is(this.received, expected),
|
|
571
|
+
`Expected ${formatValue(this.received)} to be ${formatValue(expected)}`,
|
|
572
|
+
`Expected ${formatValue(this.received)} not to be ${formatValue(expected)}`,
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
toEqual(expected: unknown): void {
|
|
577
|
+
this.assertMatch(
|
|
578
|
+
valuesEqual(this.received, expected),
|
|
579
|
+
`Expected ${formatValue(this.received)} to equal ${formatValue(expected)}`,
|
|
580
|
+
`Expected ${formatValue(this.received)} not to equal ${formatValue(expected)}`,
|
|
581
|
+
)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
toBeNull(): void {
|
|
585
|
+
this.assertMatch(
|
|
586
|
+
this.received === null,
|
|
587
|
+
`Expected ${formatValue(this.received)} to be null`,
|
|
588
|
+
`Expected ${formatValue(this.received)} not to be null`,
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
toBeUndefined(): void {
|
|
593
|
+
this.assertMatch(
|
|
594
|
+
this.received === undefined,
|
|
595
|
+
`Expected ${formatValue(this.received)} to be undefined`,
|
|
596
|
+
`Expected ${formatValue(this.received)} not to be undefined`,
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
toBeDefined(): void {
|
|
601
|
+
this.assertMatch(
|
|
602
|
+
this.received !== undefined,
|
|
603
|
+
`Expected ${formatValue(this.received)} to be defined`,
|
|
604
|
+
`Expected ${formatValue(this.received)} not to be defined`,
|
|
605
|
+
)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
toContain(expected: unknown): void {
|
|
609
|
+
if (typeof this.received === "string") {
|
|
610
|
+
this.assertMatch(
|
|
611
|
+
this.received.includes(String(expected)),
|
|
612
|
+
`Expected ${formatValue(this.received)} to contain ${formatValue(expected)}`,
|
|
613
|
+
`Expected ${formatValue(this.received)} not to contain ${formatValue(expected)}`,
|
|
614
|
+
)
|
|
615
|
+
return
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (Array.isArray(this.received)) {
|
|
619
|
+
this.assertMatch(
|
|
620
|
+
this.received.some((value) => isDeepStrictEqual(value, expected)),
|
|
621
|
+
`Expected ${formatValue(this.received)} to contain ${formatValue(expected)}`,
|
|
622
|
+
`Expected ${formatValue(this.received)} not to contain ${formatValue(expected)}`,
|
|
623
|
+
)
|
|
624
|
+
return
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
fail(`Expected ${formatValue(this.received)} to support toContain()`)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
toHaveLength(expected: number): void {
|
|
631
|
+
if (!hasLength(this.received)) {
|
|
632
|
+
fail(`Expected ${formatValue(this.received)} to have a length property`)
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
this.assertMatch(
|
|
636
|
+
this.received.length === expected,
|
|
637
|
+
`Expected ${formatValue(this.received)} to have length ${expected} but got ${this.received.length}`,
|
|
638
|
+
`Expected ${formatValue(this.received)} not to have length ${expected}`,
|
|
639
|
+
)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
toBeInstanceOf(expected: new (...args: any[]) => unknown): void {
|
|
643
|
+
this.assertMatch(
|
|
644
|
+
this.received instanceof expected,
|
|
645
|
+
`Expected ${formatValue(this.received)} to be an instance of ${expected.name}`,
|
|
646
|
+
`Expected ${formatValue(this.received)} not to be an instance of ${expected.name}`,
|
|
647
|
+
)
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
toMatchObject(expected: object): void {
|
|
651
|
+
this.assertMatch(
|
|
652
|
+
objectMatches(this.received, expected),
|
|
653
|
+
`Expected ${formatValue(this.received)} to match object ${formatValue(expected)}`,
|
|
654
|
+
`Expected ${formatValue(this.received)} not to match object ${formatValue(expected)}`,
|
|
655
|
+
)
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
toBeTruthy(): void {
|
|
659
|
+
this.assertMatch(
|
|
660
|
+
Boolean(this.received),
|
|
661
|
+
`Expected ${formatValue(this.received)} to be truthy`,
|
|
662
|
+
`Expected ${formatValue(this.received)} not to be truthy`,
|
|
663
|
+
)
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
toBeFalsy(): void {
|
|
667
|
+
this.assertMatch(
|
|
668
|
+
!this.received,
|
|
669
|
+
`Expected ${formatValue(this.received)} to be falsy`,
|
|
670
|
+
`Expected ${formatValue(this.received)} not to be falsy`,
|
|
671
|
+
)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
toBeGreaterThan(expected: number | bigint): void {
|
|
675
|
+
if (!isComparable(this.received) || !isComparable(expected)) {
|
|
676
|
+
fail(`Expected ${formatValue(this.received)} and ${formatValue(expected)} to be comparable numbers`)
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
this.assertMatch(
|
|
680
|
+
this.received > expected,
|
|
681
|
+
`Expected ${formatValue(this.received)} to be greater than ${formatValue(expected)}`,
|
|
682
|
+
`Expected ${formatValue(this.received)} not to be greater than ${formatValue(expected)}`,
|
|
683
|
+
)
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
toBeGreaterThanOrEqual(expected: number | bigint): void {
|
|
687
|
+
if (!isComparable(this.received) || !isComparable(expected)) {
|
|
688
|
+
fail(`Expected ${formatValue(this.received)} and ${formatValue(expected)} to be comparable numbers`)
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
this.assertMatch(
|
|
692
|
+
this.received >= expected,
|
|
693
|
+
`Expected ${formatValue(this.received)} to be greater than or equal to ${formatValue(expected)}`,
|
|
694
|
+
`Expected ${formatValue(this.received)} not to be greater than or equal to ${formatValue(expected)}`,
|
|
695
|
+
)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
toBeLessThan(expected: number | bigint): void {
|
|
699
|
+
if (!isComparable(this.received) || !isComparable(expected)) {
|
|
700
|
+
fail(`Expected ${formatValue(this.received)} and ${formatValue(expected)} to be comparable numbers`)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
this.assertMatch(
|
|
704
|
+
this.received < expected,
|
|
705
|
+
`Expected ${formatValue(this.received)} to be less than ${formatValue(expected)}`,
|
|
706
|
+
`Expected ${formatValue(this.received)} not to be less than ${formatValue(expected)}`,
|
|
707
|
+
)
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
toBeLessThanOrEqual(expected: number | bigint): void {
|
|
711
|
+
if (!isComparable(this.received) || !isComparable(expected)) {
|
|
712
|
+
fail(`Expected ${formatValue(this.received)} and ${formatValue(expected)} to be comparable numbers`)
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
this.assertMatch(
|
|
716
|
+
this.received <= expected,
|
|
717
|
+
`Expected ${formatValue(this.received)} to be less than or equal to ${formatValue(expected)}`,
|
|
718
|
+
`Expected ${formatValue(this.received)} not to be less than or equal to ${formatValue(expected)}`,
|
|
719
|
+
)
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
toBeCloseTo(expected: number, precision = 2): void {
|
|
723
|
+
if (typeof this.received !== "number" || typeof expected !== "number") {
|
|
724
|
+
fail(`Expected ${formatValue(this.received)} and ${formatValue(expected)} to be numbers`)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const threshold = 0.5 * 10 ** -precision
|
|
728
|
+
const difference = Math.abs(this.received - expected)
|
|
729
|
+
|
|
730
|
+
this.assertMatch(
|
|
731
|
+
difference <= threshold,
|
|
732
|
+
`Expected ${formatValue(this.received)} to be close to ${formatValue(expected)}`,
|
|
733
|
+
`Expected ${formatValue(this.received)} not to be close to ${formatValue(expected)}`,
|
|
734
|
+
)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
toMatch(expected: RegExp | string): void {
|
|
738
|
+
if (typeof this.received !== "string") {
|
|
739
|
+
fail(`Expected ${formatValue(this.received)} to be a string for toMatch()`)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const matches = typeof expected === "string" ? this.received.includes(expected) : expected.test(this.received)
|
|
743
|
+
|
|
744
|
+
this.assertMatch(
|
|
745
|
+
matches,
|
|
746
|
+
`Expected ${formatValue(this.received)} to match ${formatValue(expected)}`,
|
|
747
|
+
`Expected ${formatValue(this.received)} not to match ${formatValue(expected)}`,
|
|
748
|
+
)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
toMatchSnapshot(snapshotName?: string): void {
|
|
752
|
+
const { filePath, fullTestName } = getSnapshotContext()
|
|
753
|
+
const snapshotBaseName = snapshotName ? `${fullTestName}: ${snapshotName}` : fullTestName
|
|
754
|
+
const snapshotIndex = nextSnapshotIndex(filePath, snapshotBaseName)
|
|
755
|
+
const snapshotKey = `${snapshotBaseName} ${snapshotIndex}`
|
|
756
|
+
const snapshotPath = getSnapshotFilePath(filePath)
|
|
757
|
+
const snapshot = readSnapshotFile(snapshotPath).get(snapshotKey)
|
|
758
|
+
|
|
759
|
+
if (snapshot === undefined) {
|
|
760
|
+
fail(`Snapshot ${formatValue(snapshotKey)} not found in ${snapshotPath}`)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
assertSnapshotMatch(this.received, snapshot, `snapshot ${formatValue(snapshotKey)}`, this.inverted)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
toMatchInlineSnapshot(snapshot: string): void {
|
|
767
|
+
assertSnapshotMatch(this.received, normalizeInlineSnapshot(snapshot), "inline snapshot", this.inverted)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
toThrow(expected?: ThrowMatcher): void {
|
|
771
|
+
if (typeof this.received !== "function") {
|
|
772
|
+
fail(`Expected ${formatValue(this.received)} to be a function for toThrow()`)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
let thrown = false
|
|
776
|
+
let error: unknown
|
|
777
|
+
|
|
778
|
+
try {
|
|
779
|
+
;(this.received as AnyFunction)()
|
|
780
|
+
} catch (caughtError) {
|
|
781
|
+
thrown = true
|
|
782
|
+
error = caughtError
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
this.assertMatch(
|
|
786
|
+
thrown && matchesThrow(error, expected),
|
|
787
|
+
`Expected function to throw${formatThrowMatcher(expected)}`,
|
|
788
|
+
`Expected function not to throw${formatThrowMatcher(expected)}`,
|
|
789
|
+
)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
toHaveBeenCalled(): void {
|
|
793
|
+
const calls = getMockCalls(this.received)
|
|
794
|
+
|
|
795
|
+
this.assertMatch(calls.length > 0, `Expected mock to have been called`, `Expected mock not to have been called`)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
toHaveBeenCalledTimes(expected: number): void {
|
|
799
|
+
const calls = getMockCalls(this.received)
|
|
800
|
+
|
|
801
|
+
this.assertMatch(
|
|
802
|
+
calls.length === expected,
|
|
803
|
+
`Expected mock to have been called ${expected} times but it was called ${calls.length} times`,
|
|
804
|
+
`Expected mock not to have been called ${expected} times`,
|
|
805
|
+
)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
toHaveBeenCalledWith(...expectedArgs: unknown[]): void {
|
|
809
|
+
const calls = getMockCalls(this.received)
|
|
810
|
+
|
|
811
|
+
this.assertMatch(
|
|
812
|
+
calls.some((args) => isDeepStrictEqual(args, expectedArgs)),
|
|
813
|
+
`Expected mock to have been called with ${formatValue(expectedArgs)} but got ${formatValue(calls)}`,
|
|
814
|
+
`Expected mock not to have been called with ${formatValue(expectedArgs)}`,
|
|
815
|
+
)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
toHaveProperty(property: PropertyKey, expectedValue?: unknown): void {
|
|
819
|
+
if (!isObjectLike(this.received)) {
|
|
820
|
+
fail(`Expected ${formatValue(this.received)} to be an object for toHaveProperty()`)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const received = this.received as Record<PropertyKey, unknown>
|
|
824
|
+
const hasProperty = property in received
|
|
825
|
+
const matchesValue = arguments.length < 2 || valuesEqual(received[property], expectedValue)
|
|
826
|
+
|
|
827
|
+
this.assertMatch(
|
|
828
|
+
hasProperty && matchesValue,
|
|
829
|
+
`Expected ${formatValue(this.received)} to have property ${String(property)}`,
|
|
830
|
+
`Expected ${formatValue(this.received)} not to have property ${String(property)}`,
|
|
831
|
+
)
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
private assertMatch(condition: boolean, positiveMessage: string, negativeMessage: string): void {
|
|
835
|
+
if (this.inverted ? condition : !condition) {
|
|
836
|
+
fail(this.inverted ? negativeMessage : positiveMessage)
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
interface ExpectApi {
|
|
842
|
+
<T>(received: T): Expectation<T>
|
|
843
|
+
any(expectedType: unknown): AsymmetricMatcher
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
export const expect = Object.assign(<T>(received: T): Expectation<T> => new Expectation(received), {
|
|
847
|
+
any: createAnyMatcher,
|
|
848
|
+
}) as ExpectApi
|
|
849
|
+
|
|
850
|
+
export function mock<Fn extends AnyFunction = () => undefined>(
|
|
851
|
+
implementation = (() => undefined) as Fn,
|
|
852
|
+
): MockedFunction<Fn> {
|
|
853
|
+
return createMock(implementation)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
export function spyOn(object: object, key: string | symbol): MockedFunction {
|
|
857
|
+
const record = object as Record<string | symbol, unknown>
|
|
858
|
+
const original = record[key]
|
|
859
|
+
|
|
860
|
+
assert.strictEqual(typeof original, "function", `Cannot spy on ${String(key)} because it is not a function`)
|
|
861
|
+
|
|
862
|
+
const spy = createMock(
|
|
863
|
+
function (this: unknown, ...args: unknown[]) {
|
|
864
|
+
return (original as AnyFunction).apply(this, args)
|
|
865
|
+
},
|
|
866
|
+
() => {
|
|
867
|
+
record[key] = original
|
|
868
|
+
},
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
record[key] = spy
|
|
872
|
+
return spy
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
export const beforeAll = before
|
|
876
|
+
export const afterAll = after
|
|
877
|
+
export const test = createTest(nodeTest) as typeof nodeTest & { each: ReturnType<typeof createEach> }
|
|
878
|
+
export const it = test
|
|
879
|
+
export const describe = createDescribe(nodeDescribe)
|
|
880
|
+
export { afterEach, beforeEach }
|