buninu-linux 0.2.0
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/LICENSE +21 -0
- package/LICENSES/Bun-LICENSE.md +84 -0
- package/LICENSES/GCC-Runtime-Library-Exception-3.1.txt +73 -0
- package/LICENSES/GPL-2.0.txt +338 -0
- package/LICENSES/GPL-3.0.txt +674 -0
- package/LICENSES/LGPL-2.1.txt +501 -0
- package/LICENSES/Linux-syscall-note.txt +25 -0
- package/LICENSES/musl-COPYRIGHT.txt +193 -0
- package/NOTICE.md +184 -0
- package/README.md +537 -0
- package/build-image.sh +44 -0
- package/build-uki.sh +75 -0
- package/fetch-alpine.sh +80 -0
- package/fetch-bun.sh +41 -0
- package/fetch-plus-build.sh +11 -0
- package/hello/build-hello.sh +14 -0
- package/hello/hello.c +50 -0
- package/index.js +235 -0
- package/initramfs/bin/sh +3 -0
- package/initramfs/buninu/.bashrc +11 -0
- package/initramfs/buninu/ARCHITECTURE.md +452 -0
- package/initramfs/buninu/CHANGELOG.md +580 -0
- package/initramfs/buninu/LICENSE +21 -0
- package/initramfs/buninu/README.md +1161 -0
- package/initramfs/buninu/apps/bun/make-bunBin.sh +99 -0
- package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +493 -0
- package/initramfs/buninu/apps/bunmsh/LICENSE +21 -0
- package/initramfs/buninu/apps/bunmsh/LICENSE-MICRO +58 -0
- package/initramfs/buninu/apps/bunmsh/LICENSE-MKSH +295 -0
- package/initramfs/buninu/apps/bunmsh/PORTING.md +192 -0
- package/initramfs/buninu/apps/bunmsh/README.md +1042 -0
- package/initramfs/buninu/apps/bunmsh/bunmsh +7 -0
- package/initramfs/buninu/apps/bunmsh/bunshell.md +61 -0
- package/initramfs/buninu/apps/bunmsh/help/README.md +2237 -0
- package/initramfs/buninu/apps/bunmsh/help/alias.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/basename.md +38 -0
- package/initramfs/buninu/apps/bunmsh/help/builtin.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/bun.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/bunmsh.md +47 -0
- package/initramfs/buninu/apps/bunmsh/help/cat.md +31 -0
- package/initramfs/buninu/apps/bunmsh/help/catfancy.md +35 -0
- package/initramfs/buninu/apps/bunmsh/help/cd.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/chmod.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/clear.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/colon.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/command.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/cp.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/curl.md +181 -0
- package/initramfs/buninu/apps/bunmsh/help/cut.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/date.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/dirname.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/echo.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/env.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/eval.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/exec.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/exit.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/export.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/false.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/find.md +33 -0
- package/initramfs/buninu/apps/bunmsh/help/getopts.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/grep.md +34 -0
- package/initramfs/buninu/apps/bunmsh/help/head.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/kill.md +43 -0
- package/initramfs/buninu/apps/bunmsh/help/ln.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/ls.md +37 -0
- package/initramfs/buninu/apps/bunmsh/help/lsbun.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/lsfancy.md +37 -0
- package/initramfs/buninu/apps/bunmsh/help/md5sum.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/mkdir.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/mktemp.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/mv.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/print.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/printf.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/pspa.md +38 -0
- package/initramfs/buninu/apps/bunmsh/help/pspac.md +57 -0
- package/initramfs/buninu/apps/bunmsh/help/pwd.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/read.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/readonly.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/realpath.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/rm.md +33 -0
- package/initramfs/buninu/apps/bunmsh/help/rmdir.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/sed.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/seq.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/serve.md +50 -0
- package/initramfs/buninu/apps/bunmsh/help/set.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/sha256sum.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/shift.md +23 -0
- package/initramfs/buninu/apps/bunmsh/help/sleep.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/sort.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/source.md +23 -0
- package/initramfs/buninu/apps/bunmsh/help/tab.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/tac.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/tail.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/tee.md +25 -0
- package/initramfs/buninu/apps/bunmsh/help/test.md +30 -0
- package/initramfs/buninu/apps/bunmsh/help/time.md +29 -0
- package/initramfs/buninu/apps/bunmsh/help/touch.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/tr.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/true.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/type.md +21 -0
- package/initramfs/buninu/apps/bunmsh/help/umask.md +22 -0
- package/initramfs/buninu/apps/bunmsh/help/unalias.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/uname.md +31 -0
- package/initramfs/buninu/apps/bunmsh/help/unset.md +28 -0
- package/initramfs/buninu/apps/bunmsh/help/wc.md +27 -0
- package/initramfs/buninu/apps/bunmsh/help/whence.md +26 -0
- package/initramfs/buninu/apps/bunmsh/help/which.md +41 -0
- package/initramfs/buninu/apps/bunmsh/help/yes.md +23 -0
- package/initramfs/buninu/apps/bunmsh/index.js +7 -0
- package/initramfs/buninu/apps/bunmsh/package.json +30 -0
- package/initramfs/buninu/apps/bunmsh/serve.js +468 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/README.md +402 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/assetsHelper.js +371 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/assetsLoader.mjs +85 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/assetsPacker.js +218 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/compiled.js +527 -0
- package/initramfs/buninu/apps/bunmsh/single-exe/entry.mjs +17 -0
- package/initramfs/buninu/apps/bunmsh/src/cat-fancy.js +117 -0
- package/initramfs/buninu/apps/bunmsh/src/cat-operands.js +31 -0
- package/initramfs/buninu/apps/bunmsh/src/completion.js +363 -0
- package/initramfs/buninu/apps/bunmsh/src/curl.js +1191 -0
- package/initramfs/buninu/apps/bunmsh/src/environment.js +61 -0
- package/initramfs/buninu/apps/bunmsh/src/fancy-ls.js +186 -0
- package/initramfs/buninu/apps/bunmsh/src/find.js +119 -0
- package/initramfs/buninu/apps/bunmsh/src/history.js +198 -0
- package/initramfs/buninu/apps/bunmsh/src/main.js +850 -0
- package/initramfs/buninu/apps/bunmsh/src/mouse.js +103 -0
- package/initramfs/buninu/apps/bunmsh/src/shell.js +3932 -0
- package/initramfs/buninu/apps/bunmsh/test/catfancy.test.js +102 -0
- package/initramfs/buninu/apps/bunmsh/test/curl.test.js +335 -0
- package/initramfs/buninu/apps/bunmsh/test/reference-source.sh +1 -0
- package/initramfs/buninu/apps/bunmsh/test/reference.test.js +817 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/README.md +3 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/bad.json +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.json +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.json5 +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.jsonc +2 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.jsonl +2 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.ndjson +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.toml +3 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.ts +15 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.xml +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.yaml +3 -0
- package/initramfs/buninu/apps/bunmsh/test/serve/data.yml +1 -0
- package/initramfs/buninu/apps/bunmsh/test/serve.test.js +241 -0
- package/initramfs/buninu/apps/bunmsh/test/shell.test.js +2092 -0
- package/initramfs/buninu/apps/bunmsh/test/shell1.test.js +1087 -0
- package/initramfs/buninu/apps/bunmsh/test/shell2.test.js +457 -0
- package/initramfs/buninu/apps/bunmsh/test/shell3.test.js +741 -0
- package/initramfs/buninu/apps/bunmsh/test/t.sh +12 -0
- package/initramfs/buninu/apps/bunx/bunx.js +172 -0
- package/initramfs/buninu/apps/cmdlist +48 -0
- package/initramfs/buninu/apps/jsgotty/LICENSE +23 -0
- package/initramfs/buninu/apps/jsgotty/LICENSES/Apache-2.0-zmodem.js.txt +201 -0
- package/initramfs/buninu/apps/jsgotty/NOTICE +21 -0
- package/initramfs/buninu/apps/jsgotty/README.en.md +294 -0
- package/initramfs/buninu/apps/jsgotty/README.md +296 -0
- package/initramfs/buninu/apps/jsgotty/client.mjs +700 -0
- package/initramfs/buninu/apps/jsgotty/env.sh +2 -0
- package/initramfs/buninu/apps/jsgotty/gotty.js +3334 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/.package-lock.json +35 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/LICENSE.md +9 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/README.md +319 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/common.gypi +20 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/except.gypi +25 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/index.js +12 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/napi-inl.h +6607 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/napi.h +3201 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/node_addon_api.gyp +32 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/node_api.gyp +9 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/nothing.c +0 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/package-support.json +21 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/package.json +480 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/README.md +73 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/node-addon-api/tools/eslint-format.js +79 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/LICENSE +20 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/README.md +548 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/browser.js +8 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/index.js +22 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/buffer-util.js +131 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/constants.js +19 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/event-target.js +292 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/extension.js +203 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/limiter.js +55 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/receiver.js +760 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/sender.js +607 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/stream.js +161 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/subprotocol.js +62 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/validation.js +152 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/websocket-server.js +562 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/lib/websocket.js +1407 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/package.json +70 -0
- package/initramfs/buninu/apps/jsgotty/node_modules/ws/wrapper.mjs +21 -0
- package/initramfs/buninu/apps/jsgotty/package-lock.json +46 -0
- package/initramfs/buninu/apps/jsgotty/package.json +19 -0
- package/initramfs/buninu/apps/jsgotty/patches/windowsPtyAgent.js +326 -0
- package/initramfs/buninu/apps/jsgotty/patches/windowsTerminal.js +204 -0
- package/initramfs/buninu/apps/jsgotty/rz.js +199 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/README.md +384 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/assetsHelper.js +371 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/assetsLoader.mjs +85 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/assetsPacker.js +218 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/compiled.js +498 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/entry.mjs +23 -0
- package/initramfs/buninu/apps/jsgotty/single-exe/packAssets.sh +7 -0
- package/initramfs/buninu/apps/jsgotty/static/css/index.css +13 -0
- package/initramfs/buninu/apps/jsgotty/static/css/kitty.css +61 -0
- package/initramfs/buninu/apps/jsgotty/static/css/xterm.css +218 -0
- package/initramfs/buninu/apps/jsgotty/static/css/xterm_customize.css +35 -0
- package/initramfs/buninu/apps/jsgotty/static/favicon.ico +0 -0
- package/initramfs/buninu/apps/jsgotty/static/help.md +125 -0
- package/initramfs/buninu/apps/jsgotty/static/icon.svg +66 -0
- package/initramfs/buninu/apps/jsgotty/static/icon_192.png +0 -0
- package/initramfs/buninu/apps/jsgotty/static/index.html +164 -0
- package/initramfs/buninu/apps/jsgotty/static/js/gotty.js +31537 -0
- package/initramfs/buninu/apps/jsgotty/static/js/gotty.js.LICENSE.txt +7 -0
- package/initramfs/buninu/apps/jsgotty/static/js/gotty.js.map +1 -0
- package/initramfs/buninu/apps/jsgotty/static/js/gotty.licenses.txt +650 -0
- package/initramfs/buninu/apps/jsgotty/static/js/kitty.js +1101 -0
- package/initramfs/buninu/apps/jsgotty/static/js/windows_bridge.js +194 -0
- package/initramfs/buninu/apps/jsgotty/static/js/xterm-addon-unicode11.js +2 -0
- package/initramfs/buninu/apps/jsgotty/static/js/zmodem.js +1 -0
- package/initramfs/buninu/apps/jsgotty/static/manifest.json +15 -0
- package/initramfs/buninu/apps/jsgotty/sz.js +273 -0
- package/initramfs/buninu/apps/jsgotty/tests/kitty-order.test.js +58 -0
- package/initramfs/buninu/apps/jsgotty/tests/websocket-origin.test.js +54 -0
- package/initramfs/buninu/apps/jsgotty/viu.mjs +422 -0
- package/initramfs/buninu/apps/jsgotty/wgotty +62 -0
- package/initramfs/buninu/apps/jsgotty/windows-bridge-node.js +60 -0
- package/initramfs/buninu/apps/jsgotty/zmodem-node.js +262 -0
- package/initramfs/buninu/apps/jsmdcui/CHANGELOG.md +1118 -0
- package/initramfs/buninu/apps/jsmdcui/LICENSE +22 -0
- package/initramfs/buninu/apps/jsmdcui/README.md +1283 -0
- package/initramfs/buninu/apps/jsmdcui/cdp-maze.js +141 -0
- package/initramfs/buninu/apps/jsmdcui/clean.sh +9 -0
- package/initramfs/buninu/apps/jsmdcui/demos/basic.jpg +0 -0
- package/initramfs/buninu/apps/jsmdcui/demos/chinese.md +32 -0
- package/initramfs/buninu/apps/jsmdcui/demos/event.md +170 -0
- package/initramfs/buninu/apps/jsmdcui/demos/good.jpg +0 -0
- package/initramfs/buninu/apps/jsmdcui/demos/image-processor.md +366 -0
- package/initramfs/buninu/apps/jsmdcui/demos/image-processor.zh-TW.md +366 -0
- package/initramfs/buninu/apps/jsmdcui/demos/img-change.md +73 -0
- package/initramfs/buninu/apps/jsmdcui/demos/imgtool-zh.md +298 -0
- package/initramfs/buninu/apps/jsmdcui/demos/imgtool.md +298 -0
- package/initramfs/buninu/apps/jsmdcui/demos/maze.md +149 -0
- package/initramfs/buninu/apps/jsmdcui/demos/reactive.md +40 -0
- package/initramfs/buninu/apps/jsmdcui/demos/reader.md +513 -0
- package/initramfs/buninu/apps/jsmdcui/demos/select.md +63 -0
- package/initramfs/buninu/apps/jsmdcui/demos/todo-zh.md +190 -0
- package/initramfs/buninu/apps/jsmdcui/demos/todo.md +191 -0
- package/initramfs/buninu/apps/jsmdcui/edit +9 -0
- package/initramfs/buninu/apps/jsmdcui/jsmdcui.sh +16 -0
- package/initramfs/buninu/apps/jsmdcui/llm-maze.txt +94 -0
- package/initramfs/buninu/apps/jsmdcui/package.json +29 -0
- package/initramfs/buninu/apps/jsmdcui/project_structure.txt +35 -0
- package/initramfs/buninu/apps/jsmdcui/runmd.mjs +1140 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/atom-dark.micro +33 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/bubblegum.micro +31 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/cmc-16.micro +47 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/cmc-tc.micro +43 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/darcula.micro +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/default.micro +1 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dracula-tc.micro +49 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dukedark-tc.micro +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dukelight-tc.micro +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/dukeubuntu-tc.micro +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/geany.micro +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/gotham.micro +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/gruvbox-tc.micro +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/gruvbox.micro +26 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/material-tc.micro +36 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/monokai-dark.micro +28 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/monokai.micro +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/one-dark.micro +39 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/railscast.micro +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/simple.micro +33 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/solarized-tc.micro +31 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/solarized.micro +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/sunny-day.micro +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/twilight.micro +40 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/colorschemes/zenburn.micro +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/actions.md +161 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/cdp.md +130 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/colors.md +421 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/commands.md +161 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/copypaste.md +149 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/defaultkeys.md +148 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/help.md +1283 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/keybindings.md +355 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/linter.md +90 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/options.md +709 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/plugins.md +544 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/help/tutorial.md +112 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/cdp/cdp-server.js +1263 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/cdp/cdp.js +203 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/chapter/chapter.js +111 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/diff/diff.js +46 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/example/example.js +117 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/jsplugins/linter/linter.js +281 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/autoclose/autoclose.lua +75 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/ftoptions/ftoptions.lua +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/literate/README.md +5 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/literate/literate.lua +55 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/status/help/status.md +21 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/plugins/status/status.lua +62 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/LICENSE +22 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/PowerShell.yaml +128 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/README.md +63 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ada.yaml +43 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/apacheconf.yaml +59 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/arduino.yaml +101 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/asciidoc.yaml +51 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/asm.yaml +123 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ats.yaml +99 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/awk.yaml +44 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/b.yaml +87 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/bat.yaml +57 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/c.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/caddyfile.yaml +23 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cake.yaml +7 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/clojure.yaml +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cmake.yaml +42 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/coffeescript.yaml +56 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/colortest.yaml +19 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/conky.yaml +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cpp.yaml +91 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/crontab.yaml +36 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/crystal.yaml +72 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/csharp.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/css.yaml +44 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/csx.yaml +8 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cuda.yaml +68 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/cython.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/d.yaml +121 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/dart.yaml +46 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/default.yaml +10 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/dockerfile.yaml +36 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/dot.yaml +29 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/elixir.yaml +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/elm.yaml +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/erb.yaml +42 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/erlang.yaml +45 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/fish.yaml +48 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/forth.yaml +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/fortran.yaml +64 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/freebsd-kernel.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/fsharp.yaml +48 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gdscript.yaml +61 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gemini.yaml +19 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gentoo-ebuild.yaml +48 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gentoo-etc-portage.yaml +23 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/git-commit.yaml +35 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/git-config.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/git-rebase-todo.yaml +19 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gleam.yaml +69 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/glsl.yaml +26 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gnuplot.yaml +15 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/go.yaml +62 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/godoc.yaml +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/golo.yaml +73 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/gomod.yaml +31 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/graphql.yaml +47 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/groff.yaml +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/groovy.yaml +111 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/haml.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/hare.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/haskell.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/hc.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/html.yaml +70 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/html4.yaml +25 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/html5.yaml +25 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ini.yaml +23 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/inputrc.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/java.yaml +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/javascript.yaml +124 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/jinja2.yaml +19 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/json.yaml +39 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/jsonnet.yaml +92 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/julia.yaml +57 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/justfile.yaml +40 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/keymap.yaml +27 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/kickstart.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/kotlin.yaml +66 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/kvlang.yaml +67 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ledger.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lfe.yaml +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lilypond.yaml +26 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lisp.yaml +17 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/log.yaml +92 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/lua.yaml +111 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/mail.yaml +25 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/makefile.yaml +38 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/man.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/markdown.yaml +65 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/mc.yaml +23 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/meson.yaml +51 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/micro.yaml +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/mpdconf.yaml +13 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/msbuild.yaml +6 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nanorc.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nftables.yaml +30 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nginx.yaml +22 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nim.yaml +27 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nix.yaml +32 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/nu.yaml +114 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/objc.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ocaml.yaml +43 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/octave.yaml +83 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/odin.yaml +64 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pascal.yaml +45 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/patch.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/peg.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/perl.yaml +58 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/php.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pkg-config.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/po.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pony.yaml +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/pov.yaml +21 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/privoxy-action.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/privoxy-config.yaml +10 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/privoxy-filter.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/proto.yaml +40 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/prql.yaml +84 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/puppet.yaml +22 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/python2.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/python3.yaml +80 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/r.yaml +32 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/raku.yaml +42 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/reST.yaml +18 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/renpy.yaml +15 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/rpmspec.yaml +43 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/ruby.yaml +73 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/rust.yaml +78 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sage.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/scad.yaml +53 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/scala.yaml +33 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sed.yaml +13 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sh.yaml +69 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sls.yaml +15 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/smalltalk.yaml +55 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/solidity.yaml +41 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/sql.yaml +35 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/stata.yaml +67 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/svelte.yaml +27 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/swift.yaml +103 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/systemd.yaml +16 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/tcl.yaml +18 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/terraform.yaml +87 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/tex.yaml +32 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/toml.yaml +56 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/twig.yaml +55 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/typescript.yaml +49 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/v.yaml +80 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vala.yaml +26 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/verilog.yaml +60 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vhdl.yaml +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vi.yaml +31 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/vue.yaml +64 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/xml.yaml +37 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/xresources.yaml +14 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/yaml.yaml +34 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/yum.yaml +12 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/zig.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/zscript.yaml +72 -0
- package/initramfs/buninu/apps/jsmdcui/runtime/syntax/zsh.yaml +52 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/README.md +396 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/assetsHelper.js +371 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/assetsLoader.mjs +85 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/assetsPacker.js +218 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/compiled.js +498 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/entry.mjs +17 -0
- package/initramfs/buninu/apps/jsmdcui/single-exe/packAssets.sh +7 -0
- package/initramfs/buninu/apps/jsmdcui/src/MDCUI_DEFAULT_EDIT +0 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/backup.js +165 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/buffer.js +126 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/fixed3-codec.js +140 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/loc.js +38 -0
- package/initramfs/buninu/apps/jsmdcui/src/buffer/message.js +29 -0
- package/initramfs/buninu/apps/jsmdcui/src/build-args.js +40 -0
- package/initramfs/buninu/apps/jsmdcui/src/config/clean.js +172 -0
- package/initramfs/buninu/apps/jsmdcui/src/config/colorscheme.js +99 -0
- package/initramfs/buninu/apps/jsmdcui/src/config/config.js +125 -0
- package/initramfs/buninu/apps/jsmdcui/src/config/defaults.js +109 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/fence-events.mjs +144 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/heading-ids.mjs +1568 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/id-collision.mjs +111 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/identity.mjs +26 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/kitty-debug.mjs +25 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/kitty-images.mjs +280 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/kitty-mode.mjs +6 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/rerender-queue.mjs +66 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/rpc.mjs +1554 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/server.mjs +128 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/table-render.mjs +286 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/table-row-edit.mjs +226 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/task-checkbox.mjs +60 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/template-components.mjs +253 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/text-block-resize.mjs +177 -0
- package/initramfs/buninu/apps/jsmdcui/src/cui/tui-links.mjs +560 -0
- package/initramfs/buninu/apps/jsmdcui/src/display/ansi-style.js +60 -0
- package/initramfs/buninu/apps/jsmdcui/src/highlight/highlighter.js +243 -0
- package/initramfs/buninu/apps/jsmdcui/src/highlight/parser.js +225 -0
- package/initramfs/buninu/apps/jsmdcui/src/index.js +9586 -0
- package/initramfs/buninu/apps/jsmdcui/src/lua/engine.js +68 -0
- package/initramfs/buninu/apps/jsmdcui/src/platform/archive.js +50 -0
- package/initramfs/buninu/apps/jsmdcui/src/platform/clipboard.js +278 -0
- package/initramfs/buninu/apps/jsmdcui/src/platform/commands.js +221 -0
- package/initramfs/buninu/apps/jsmdcui/src/platform/terminal.js +5 -0
- package/initramfs/buninu/apps/jsmdcui/src/plugins/js-bridge.js +3377 -0
- package/initramfs/buninu/apps/jsmdcui/src/plugins/manager.js +674 -0
- package/initramfs/buninu/apps/jsmdcui/src/runtime/encodings.js +10 -0
- package/initramfs/buninu/apps/jsmdcui/src/runtime/registry.js +134 -0
- package/initramfs/buninu/apps/jsmdcui/src/screen/cell-buffer.js +81 -0
- package/initramfs/buninu/apps/jsmdcui/src/screen/events.js +263 -0
- package/initramfs/buninu/apps/jsmdcui/src/screen/screen.js +249 -0
- package/initramfs/buninu/apps/jsmdcui/src/screen/vt100.js +571 -0
- package/initramfs/buninu/apps/jsmdcui/src/shell/shell.js +70 -0
- package/initramfs/buninu/apps/jsmdcui/testapp.md +78 -0
- package/initramfs/buninu/apps/jsmdcui/tests/assets-helper.test.js +150 -0
- package/initramfs/buninu/apps/jsmdcui/tests/backup.test.js +158 -0
- package/initramfs/buninu/apps/jsmdcui/tests/cat-markdown.test.js +139 -0
- package/initramfs/buninu/apps/jsmdcui/tests/cdp-security.test.js +32 -0
- package/initramfs/buninu/apps/jsmdcui/tests/cdp-websocket-test.html +46 -0
- package/initramfs/buninu/apps/jsmdcui/tests/cmphex3b64.js +95 -0
- package/initramfs/buninu/apps/jsmdcui/tests/compiled-runtime.test.js +139 -0
- package/initramfs/buninu/apps/jsmdcui/tests/config-settings.test.js +52 -0
- package/initramfs/buninu/apps/jsmdcui/tests/demo.test.js +448 -0
- package/initramfs/buninu/apps/jsmdcui/tests/docs-sync.test.js +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/fence-events.test.js +1293 -0
- package/initramfs/buninu/apps/jsmdcui/tests/heading-ids.test.js +559 -0
- package/initramfs/buninu/apps/jsmdcui/tests/heading-list-selector.test.js +2393 -0
- package/initramfs/buninu/apps/jsmdcui/tests/id-collision.test.js +514 -0
- package/initramfs/buninu/apps/jsmdcui/tests/jmigt.js +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/js-plugin-prompts.test.js +110 -0
- package/initramfs/buninu/apps/jsmdcui/tests/key-event.md +10 -0
- package/initramfs/buninu/apps/jsmdcui/tests/kitty-demo.md +184 -0
- package/initramfs/buninu/apps/jsmdcui/tests/kitty-images.test.js +288 -0
- package/initramfs/buninu/apps/jsmdcui/tests/kitty-mode.test.js +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/platform-commands.test.js +18 -0
- package/initramfs/buninu/apps/jsmdcui/tests/platform-terminal.test.js +8 -0
- package/initramfs/buninu/apps/jsmdcui/tests/pty-demo.js +492 -0
- package/initramfs/buninu/apps/jsmdcui/tests/rerender-queue.test.js +130 -0
- package/initramfs/buninu/apps/jsmdcui/tests/table-render.test.js +150 -0
- package/initramfs/buninu/apps/jsmdcui/tests/table-row-edit.test.js +138 -0
- package/initramfs/buninu/apps/jsmdcui/tests/task-checkbox.test.js +33 -0
- package/initramfs/buninu/apps/jsmdcui/tests/template-components.test.js +422 -0
- package/initramfs/buninu/apps/jsmdcui/tests/text-block-resize.test.js +222 -0
- package/initramfs/buninu/apps/jsmdcui/tests/textarea.md +8 -0
- package/initramfs/buninu/apps/jsmdcui/tests/tui-links.test.js +298 -0
- package/initramfs/buninu/apps/jsmdcui/tests/wui-responsive-images.test.js +60 -0
- package/initramfs/buninu/apps/jsmdcui/tests/wui.test.js +108 -0
- package/initramfs/buninu/apps/jsmdcui/tests/wv-client.js +96 -0
- package/initramfs/buninu/apps/jsmdcui/tui +7 -0
- package/initramfs/buninu/apps/jsmdcui/wui +9 -0
- package/initramfs/buninu/apps/musl-la/LICENSES/GPL-3.0.txt +674 -0
- package/initramfs/buninu/apps/musl-la/LICENSES/gcc_runtime_exception.txt +73 -0
- package/initramfs/buninu/apps/musl-la/LICENSE_musl.txt +193 -0
- package/initramfs/buninu/apps/musl-la/NOTICE +41 -0
- package/initramfs/buninu/apps/musl-la/ld-musl-aarch64.so.1 +0 -0
- package/initramfs/buninu/apps/musl-la/libgcc_s.so.1 +0 -0
- package/initramfs/buninu/apps/musl-la/libstdc++.so.6 +0 -0
- package/initramfs/buninu/apps/musl-la/musl-la.sh +36 -0
- package/initramfs/buninu/apps/native-bridge/native-bridge.js +199 -0
- package/initramfs/buninu/apps/native-bridge/package.json +8 -0
- package/initramfs/buninu/apps/tts/tts.js +214 -0
- package/initramfs/buninu/apps/xclip/xclip.js +121 -0
- package/initramfs/buninu/apps/xdg-open/xdg-open.js +151 -0
- package/initramfs/buninu/bin/bun.bat +37 -0
- package/initramfs/buninu/bin/bun.sh +57 -0
- package/initramfs/buninu/bin/buninu-help.bat +4 -0
- package/initramfs/buninu/bin/buninu-help.sh +7 -0
- package/initramfs/buninu/bin/bunmsh.bat +3 -0
- package/initramfs/buninu/bin/bunmsh.sh +5 -0
- package/initramfs/buninu/bin/bunx.bat +3 -0
- package/initramfs/buninu/bin/glow.bat +3 -0
- package/initramfs/buninu/bin/glow.sh +5 -0
- package/initramfs/buninu/bin/init.js +779 -0
- package/initramfs/buninu/bin/install.js +570 -0
- package/initramfs/buninu/bin/jmi.bat +3 -0
- package/initramfs/buninu/bin/jmi.sh +5 -0
- package/initramfs/buninu/bin/jsgotty.bat +3 -0
- package/initramfs/buninu/bin/jsgotty.sh +5 -0
- package/initramfs/buninu/bin/jsmdcui.bat +11 -0
- package/initramfs/buninu/bin/libsh-loader.c +135 -0
- package/initramfs/buninu/bin/libsh-loader.so +0 -0
- package/initramfs/buninu/bin/multicall.sh +103 -0
- package/initramfs/buninu/bin/native-bridge.bat +3 -0
- package/initramfs/buninu/bin/rz.bat +3 -0
- package/initramfs/buninu/bin/rz.sh +5 -0
- package/initramfs/buninu/bin/shc.js +5 -0
- package/initramfs/buninu/bin/showimg.bat +3 -0
- package/initramfs/buninu/bin/showimg.sh +5 -0
- package/initramfs/buninu/bin/switch_to_android.sh +9 -0
- package/initramfs/buninu/bin/switch_to_linux.sh +7 -0
- package/initramfs/buninu/bin/sz.bat +3 -0
- package/initramfs/buninu/bin/sz.sh +5 -0
- package/initramfs/buninu/bin/tts.bat +3 -0
- package/initramfs/buninu/bin/xclip.bat +3 -0
- package/initramfs/buninu/bin/xdg-open.bat +3 -0
- package/initramfs/buninu/demo-server.js +64 -0
- package/initramfs/buninu/icon.png +0 -0
- package/initramfs/buninu/package.json +59 -0
- package/initramfs/etc/resolv.conf +1 -0
- package/initramfs/init.js +339 -0
- package/initramfs/lib/ld-musl-x86_64.so.1 +0 -0
- package/initramfs/lib/libc.musl-x86_64.so.1 +0 -0
- package/initramfs/lib/libgcc_s.so.1 +0 -0
- package/initramfs/lib/libstdc++.so.6 +0 -0
- package/initramfs/usr/bin/env +7 -0
- package/package.json +42 -0
- package/run-qemu.sh +44 -0
- package/scripts/fetch.sh +26 -0
- package/scripts/pack-initramfs.sh +41 -0
- package/test/build-init-bootstrap.sh +23 -0
- package/test/init-bootstrap.c +55 -0
|
@@ -0,0 +1,2393 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createWebDollar } from "../src/cui/rpc.mjs";
|
|
3
|
+
import {
|
|
4
|
+
addTuiTableRowSeparators,
|
|
5
|
+
markHeadingTableRows,
|
|
6
|
+
} from "../src/cui/table-render.mjs";
|
|
7
|
+
import {
|
|
8
|
+
captureTuiRerenderState,
|
|
9
|
+
clearTuiSourceDependentState,
|
|
10
|
+
createTuiSelector,
|
|
11
|
+
indexTuiHeadingRows,
|
|
12
|
+
markTuiTableCellDirtyAtPosition,
|
|
13
|
+
restoreTuiRerenderState,
|
|
14
|
+
spliceTuiBufferLines,
|
|
15
|
+
toggleTuiHeadingAt,
|
|
16
|
+
tuiTableCellAtPosition,
|
|
17
|
+
tuiCheckboxRerenderMismatchMessage,
|
|
18
|
+
} from "../src/plugins/js-bridge.js";
|
|
19
|
+
import { colorAnsiPlainRange } from "../src/cui/table-row-edit.mjs";
|
|
20
|
+
|
|
21
|
+
function tuiSelector(markdown) {
|
|
22
|
+
const ansi = String(Bun.markdown.ansi(markdown, { hyperlinks: true, columns: 80 }));
|
|
23
|
+
const buffer = {
|
|
24
|
+
lines: Bun.stripANSI(ansi).split("\n"),
|
|
25
|
+
_mdcuiTuiSourceText: markdown,
|
|
26
|
+
_mdcuiAnsiText: ansi,
|
|
27
|
+
cursor: { x: 0, y: 0 },
|
|
28
|
+
ensureCursor() {},
|
|
29
|
+
invalidateHighlightFrom() {},
|
|
30
|
+
};
|
|
31
|
+
indexTuiHeadingRows(buffer);
|
|
32
|
+
return { $: createTuiSelector(() => buffer), buffer };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function tuiTableSelector(markdown, columns = 24) {
|
|
36
|
+
const plan = markHeadingTableRows(markdown);
|
|
37
|
+
const rendered = String(Bun.markdown.ansi(
|
|
38
|
+
plan.markdown,
|
|
39
|
+
{ hyperlinks: true, columns },
|
|
40
|
+
));
|
|
41
|
+
const ansi = addTuiTableRowSeparators(rendered, plan);
|
|
42
|
+
const buffer = {
|
|
43
|
+
lines: Bun.stripANSI(ansi).split("\n"),
|
|
44
|
+
_mdcuiTuiSourceText: markdown,
|
|
45
|
+
_mdcuiAnsiText: ansi,
|
|
46
|
+
_ansiStyleLines: Bun.stripANSI(ansi).split("\n").map(line =>
|
|
47
|
+
Array.from({ length: line.length }, () => ({ fg: "white" }))
|
|
48
|
+
),
|
|
49
|
+
cursor: { x: 0, y: 0 },
|
|
50
|
+
ensureCursor() {},
|
|
51
|
+
invalidateHighlightFrom() {},
|
|
52
|
+
pushUndo() {
|
|
53
|
+
this.undoCount = (this.undoCount ?? 0) + 1;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
indexTuiHeadingRows(buffer);
|
|
57
|
+
return { $: createTuiSelector(() => buffer), buffer };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const markdown = `## Features
|
|
61
|
+
|
|
62
|
+
- [x] Search
|
|
63
|
+
- [ ] Notifications
|
|
64
|
+
- [x] Nested notification option
|
|
65
|
+
- [x] Offline
|
|
66
|
+
|
|
67
|
+
Paragraph between lists.
|
|
68
|
+
|
|
69
|
+
- [x] Later list item
|
|
70
|
+
|
|
71
|
+
## Next
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
describe("TUI heading task-list Array methods", () => {
|
|
75
|
+
test("push and unshift accept multiple items and return the new direct length", () => {
|
|
76
|
+
const { $ } = tuiSelector(markdown);
|
|
77
|
+
const features = $("#features");
|
|
78
|
+
|
|
79
|
+
expect(features.push("Export", { value: "Sync", checked: true })).toBe(5);
|
|
80
|
+
expect(features.val()).toEqual(["Search", "Offline", "Sync"]);
|
|
81
|
+
expect(features.unshift("First", { label: "Pinned", checked: true })).toBe(7);
|
|
82
|
+
expect(features.val()).toEqual(["Pinned", "Search", "Offline", "Sync"]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("pop and shift return removed labels and remove nested children with the parent", () => {
|
|
86
|
+
const { $, buffer } = tuiSelector(markdown);
|
|
87
|
+
const features = $("#features");
|
|
88
|
+
|
|
89
|
+
expect(features.shift()).toBe("Search");
|
|
90
|
+
expect(buffer.lines.some((line) => line.includes("Search"))).toBe(false);
|
|
91
|
+
expect(features.shift()).toBe("Notifications");
|
|
92
|
+
expect(buffer.lines.some((line) => line.includes("Nested notification option"))).toBe(false);
|
|
93
|
+
expect(features.pop()).toBe("Offline");
|
|
94
|
+
expect(features.pop()).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("an emptied list keeps its insertion point for later pushes", () => {
|
|
98
|
+
const { $ } = tuiSelector("## Features\n\n- [ ] Only\n\n## Next\n");
|
|
99
|
+
const features = $("#features");
|
|
100
|
+
|
|
101
|
+
expect(features.pop()).toBe("Only");
|
|
102
|
+
expect(features.pop()).toBeUndefined();
|
|
103
|
+
expect(features.push({ value: "Again", checked: true })).toBe(1);
|
|
104
|
+
expect(features.val()).toEqual(["Again"]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("an empty child list keeps its position across block resizing and ancestor visibility", () => {
|
|
108
|
+
const scenarios = [
|
|
109
|
+
{ tag: "text", value: "one\ntwo\nthree\nfour", label: "After growing text" },
|
|
110
|
+
{ tag: "textarea", value: "one", label: "After shrinking textarea" },
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
for (const scenario of scenarios) {
|
|
114
|
+
const source = `# Parent
|
|
115
|
+
|
|
116
|
+
\`\`\`${scenario.tag}#editor
|
|
117
|
+
one
|
|
118
|
+
two
|
|
119
|
+
\`\`\`
|
|
120
|
+
|
|
121
|
+
## Child
|
|
122
|
+
|
|
123
|
+
- [ ] Only child item
|
|
124
|
+
|
|
125
|
+
## Later
|
|
126
|
+
|
|
127
|
+
Later body.
|
|
128
|
+
|
|
129
|
+
# End
|
|
130
|
+
`;
|
|
131
|
+
const { $, buffer } = tuiSelector(source);
|
|
132
|
+
const child = $("#child");
|
|
133
|
+
|
|
134
|
+
expect(child.pop()).toBe("Only child item");
|
|
135
|
+
$(`${scenario.tag}#editor`).val(scenario.value);
|
|
136
|
+
$("#parent").hide().show();
|
|
137
|
+
|
|
138
|
+
expect(child.push(scenario.label)).toBe(1);
|
|
139
|
+
|
|
140
|
+
const childHeading = buffer.lines.findIndex((line) => line.trim() === "Child");
|
|
141
|
+
const inserted = buffer.lines.findIndex((line) => line.includes(scenario.label));
|
|
142
|
+
const laterHeading = buffer.lines.findIndex((line) => line.trim() === "Later");
|
|
143
|
+
expect(inserted).toBeGreaterThan(childHeading);
|
|
144
|
+
expect(inserted).toBeLessThan(laterHeading);
|
|
145
|
+
expect(child.slice()).toEqual([{ value: scenario.label, checked: false }]);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("a hidden section keeps relative empty-list anchors when a preceding block grows", () => {
|
|
150
|
+
const source = `\`\`\`text#before
|
|
151
|
+
one
|
|
152
|
+
\`\`\`
|
|
153
|
+
|
|
154
|
+
# Parent
|
|
155
|
+
|
|
156
|
+
## Child
|
|
157
|
+
|
|
158
|
+
- [ ] Only child item
|
|
159
|
+
|
|
160
|
+
# End
|
|
161
|
+
`;
|
|
162
|
+
const { $, buffer } = tuiSelector(source);
|
|
163
|
+
const child = $("#child");
|
|
164
|
+
|
|
165
|
+
expect(child.pop()).toBe("Only child item");
|
|
166
|
+
$("#parent").hide();
|
|
167
|
+
$("text#before").val("one\ntwo\nthree\nfour");
|
|
168
|
+
$("#parent").show();
|
|
169
|
+
expect(child.push("After hidden growth")).toBe(1);
|
|
170
|
+
|
|
171
|
+
const childHeading = buffer.lines.findIndex((line) => line.trim() === "Child");
|
|
172
|
+
const inserted = buffer.lines.findIndex((line) => line.includes("After hidden growth"));
|
|
173
|
+
const endHeading = buffer.lines.findIndex((line) => line.trim() === "End");
|
|
174
|
+
expect(inserted).toBeGreaterThan(childHeading);
|
|
175
|
+
expect(inserted).toBeLessThan(endHeading);
|
|
176
|
+
expect(child.slice()).toEqual([{ value: "After hidden growth", checked: false }]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("nested hidden sections restore every empty-list anchor independently", () => {
|
|
180
|
+
const source = `# Parent
|
|
181
|
+
|
|
182
|
+
## Alpha
|
|
183
|
+
|
|
184
|
+
- [ ] Only alpha
|
|
185
|
+
|
|
186
|
+
### Deep
|
|
187
|
+
|
|
188
|
+
- [ ] Only deep
|
|
189
|
+
|
|
190
|
+
## Beta
|
|
191
|
+
|
|
192
|
+
- [ ] Only beta
|
|
193
|
+
|
|
194
|
+
# End
|
|
195
|
+
`;
|
|
196
|
+
const { $, buffer } = tuiSelector(source);
|
|
197
|
+
const alpha = $("#alpha");
|
|
198
|
+
const deep = $("#deep");
|
|
199
|
+
const beta = $("#beta");
|
|
200
|
+
|
|
201
|
+
expect(alpha.pop()).toBe("Only alpha");
|
|
202
|
+
expect(deep.pop()).toBe("Only deep");
|
|
203
|
+
expect(beta.pop()).toBe("Only beta");
|
|
204
|
+
|
|
205
|
+
alpha.hide();
|
|
206
|
+
$("#parent").hide().show();
|
|
207
|
+
alpha.show();
|
|
208
|
+
|
|
209
|
+
expect(alpha.push("Restored alpha")).toBe(1);
|
|
210
|
+
expect(deep.push("Restored deep")).toBe(1);
|
|
211
|
+
expect(beta.push("Restored beta")).toBe(1);
|
|
212
|
+
expect(alpha.slice()).toEqual([{ value: "Restored alpha", checked: false }]);
|
|
213
|
+
expect(deep.slice()).toEqual([{ value: "Restored deep", checked: false }]);
|
|
214
|
+
expect(beta.slice()).toEqual([{ value: "Restored beta", checked: false }]);
|
|
215
|
+
|
|
216
|
+
const positions = Object.fromEntries(
|
|
217
|
+
["Alpha", "Restored alpha", "Deep", "Restored deep", "Beta", "Restored beta", "End"]
|
|
218
|
+
.map((text) => [text, buffer.lines.findIndex((line) => line.includes(text))]),
|
|
219
|
+
);
|
|
220
|
+
expect(positions.Alpha).toBeLessThan(positions["Restored alpha"]);
|
|
221
|
+
expect(positions["Restored alpha"]).toBeLessThan(positions.Deep);
|
|
222
|
+
expect(positions.Deep).toBeLessThan(positions["Restored deep"]);
|
|
223
|
+
expect(positions["Restored deep"]).toBeLessThan(positions.Beta);
|
|
224
|
+
expect(positions.Beta).toBeLessThan(positions["Restored beta"]);
|
|
225
|
+
expect(positions["Restored beta"]).toBeLessThan(positions.End);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("val and mutations stop at the first rendered task list", () => {
|
|
229
|
+
const { $ } = tuiSelector(markdown);
|
|
230
|
+
const features = $("#features");
|
|
231
|
+
|
|
232
|
+
expect(features.val()).toEqual(["Search", "Offline"]);
|
|
233
|
+
expect(features.pop()).toBe("Offline");
|
|
234
|
+
expect(features.val()).toEqual(["Search"]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("a loose task list remains one list across indented item content", () => {
|
|
238
|
+
const { $ } = tuiSelector(`## Features
|
|
239
|
+
|
|
240
|
+
- [x] First
|
|
241
|
+
|
|
242
|
+
More detail
|
|
243
|
+
|
|
244
|
+
- [x] Second
|
|
245
|
+
|
|
246
|
+
Paragraph after the list.
|
|
247
|
+
|
|
248
|
+
- [x] Later list
|
|
249
|
+
`);
|
|
250
|
+
const features = $("#features");
|
|
251
|
+
|
|
252
|
+
expect(features.val()).toEqual(["First", "Second"]);
|
|
253
|
+
expect(features.pop()).toBe("Second");
|
|
254
|
+
expect(features.pop()).toBe("First");
|
|
255
|
+
expect(features.pop()).toBeUndefined();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("splice follows Array indexing, insertion, deletion, and return semantics", () => {
|
|
259
|
+
const { $, buffer } = tuiSelector(markdown);
|
|
260
|
+
const features = $("#features");
|
|
261
|
+
|
|
262
|
+
expect(features.splice()).toEqual([]);
|
|
263
|
+
expect(features.splice(1, 1, { value: "Replacement", checked: true }, "Extra"))
|
|
264
|
+
.toEqual(["Notifications"]);
|
|
265
|
+
expect(buffer.lines.some((line) => line.includes("Nested notification option"))).toBe(false);
|
|
266
|
+
expect(features.val()).toEqual(["Search", "Replacement", "Offline"]);
|
|
267
|
+
expect(features.splice(-1)).toEqual(["Offline"]);
|
|
268
|
+
expect(features.splice(1, undefined, "Inserted")).toEqual([]);
|
|
269
|
+
expect(features.splice(0, Infinity)).toEqual(["Search", "Inserted", "Replacement", "Extra"]);
|
|
270
|
+
expect(features.val()).toEqual([]);
|
|
271
|
+
expect(features.splice(0, 0, { label: "Again", checked: true })).toEqual([]);
|
|
272
|
+
expect(features.val()).toEqual(["Again"]);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("slice returns read-only item snapshots with checked state", () => {
|
|
276
|
+
const { $ } = tuiSelector(markdown);
|
|
277
|
+
const features = $("#features");
|
|
278
|
+
|
|
279
|
+
const result = features.slice(0, 2);
|
|
280
|
+
expect(result).toEqual([
|
|
281
|
+
{ value: "Search", checked: true },
|
|
282
|
+
{ value: "Notifications", checked: false },
|
|
283
|
+
]);
|
|
284
|
+
result[0].value = "Changed snapshot";
|
|
285
|
+
result[0].checked = false;
|
|
286
|
+
expect(features.slice(-1)).toEqual([{ value: "Offline", checked: true }]);
|
|
287
|
+
expect(features.val()).toEqual(["Search", "Offline"]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("a heading without a task list cannot create one implicitly", () => {
|
|
291
|
+
const { $, buffer } = tuiSelector("## Empty\n\nParagraph.\n");
|
|
292
|
+
expect($("#empty").push("No target")).toBe(0);
|
|
293
|
+
expect($("#empty").unshift("No target")).toBe(0);
|
|
294
|
+
expect($("#empty").pop()).toBeUndefined();
|
|
295
|
+
expect($("#empty").shift()).toBeUndefined();
|
|
296
|
+
expect(buffer._mdcuiMutationMacros).toBeUndefined();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("does not borrow a task list from outside the heading's Markdown container", () => {
|
|
300
|
+
const scenarios = [
|
|
301
|
+
{
|
|
302
|
+
source: `> ## Quoted
|
|
303
|
+
>
|
|
304
|
+
> Inside quote.
|
|
305
|
+
|
|
306
|
+
- [x] Outside quote
|
|
307
|
+
`,
|
|
308
|
+
id: "quoted",
|
|
309
|
+
outside: "Outside quote",
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
source: `- Item
|
|
313
|
+
|
|
314
|
+
## Listed
|
|
315
|
+
|
|
316
|
+
Inside list item.
|
|
317
|
+
|
|
318
|
+
- [x] Outside list item
|
|
319
|
+
`,
|
|
320
|
+
id: "listed",
|
|
321
|
+
outside: "Outside list item",
|
|
322
|
+
},
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
for (const scenario of scenarios) {
|
|
326
|
+
const { $, buffer } = tuiSelector(scenario.source);
|
|
327
|
+
const heading = $(`#${scenario.id}`);
|
|
328
|
+
|
|
329
|
+
expect(heading.val()).toEqual([]);
|
|
330
|
+
expect(heading.slice()).toEqual([]);
|
|
331
|
+
expect(heading.push("Must stay absent")).toBe(0);
|
|
332
|
+
expect(buffer.lines.some(line => line.includes(scenario.outside)))
|
|
333
|
+
.toBe(true);
|
|
334
|
+
expect(buffer.lines.some(line => line.includes("Must stay absent")))
|
|
335
|
+
.toBe(false);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("uses and mutates a direct task list inside a blockquote", () => {
|
|
340
|
+
const source = `> ## Quoted
|
|
341
|
+
>
|
|
342
|
+
> - [x] Inside checked
|
|
343
|
+
> - [ ] Inside unchecked
|
|
344
|
+
|
|
345
|
+
- [x] Outside quote
|
|
346
|
+
`;
|
|
347
|
+
const { $, buffer } = tuiSelector(source);
|
|
348
|
+
const quoted = $("#quoted");
|
|
349
|
+
|
|
350
|
+
expect(quoted.val()).toEqual(["Inside checked"]);
|
|
351
|
+
expect(quoted.slice()).toEqual([
|
|
352
|
+
{ value: "Inside checked", checked: true },
|
|
353
|
+
{ value: "Inside unchecked", checked: false },
|
|
354
|
+
]);
|
|
355
|
+
expect(quoted.push({ value: "Inside added", checked: true })).toBe(3);
|
|
356
|
+
expect(quoted.val()).toEqual(["Inside checked", "Inside added"]);
|
|
357
|
+
expect(buffer.lines.some(line =>
|
|
358
|
+
line.startsWith("│") && line.includes("Inside added")
|
|
359
|
+
)).toBe(true);
|
|
360
|
+
expect(buffer.lines.some(line =>
|
|
361
|
+
line.includes("Outside quote") && line.includes("☒")
|
|
362
|
+
)).toBe(true);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("does not mistake a blockquote table cell for a task-list item", () => {
|
|
366
|
+
const source = `> ## Quoted
|
|
367
|
+
>
|
|
368
|
+
> | Value |
|
|
369
|
+
> | --- |
|
|
370
|
+
> | ☒ Not a task |
|
|
371
|
+
>
|
|
372
|
+
> - [x] Real task
|
|
373
|
+
`;
|
|
374
|
+
const { $ } = tuiSelector(source);
|
|
375
|
+
|
|
376
|
+
expect($("#quoted").val()).toEqual(["Real task"]);
|
|
377
|
+
expect($("#quoted").slice()).toEqual([
|
|
378
|
+
{ value: "Real task", checked: true },
|
|
379
|
+
]);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("mutations against a hidden heading fail without recording a macro", () => {
|
|
383
|
+
const { $, buffer } = tuiSelector(markdown);
|
|
384
|
+
const features = $("#features");
|
|
385
|
+
|
|
386
|
+
features.hide();
|
|
387
|
+
expect(features.push("Hidden addition")).toBe(0);
|
|
388
|
+
expect(features.pop()).toBeUndefined();
|
|
389
|
+
expect(features.shift()).toBeUndefined();
|
|
390
|
+
expect(features.unshift("Hidden first")).toBe(0);
|
|
391
|
+
expect(features.splice(0, 1, "Hidden replacement")).toEqual([]);
|
|
392
|
+
expect(buffer._mdcuiMutationMacros).toBeUndefined();
|
|
393
|
+
|
|
394
|
+
features.show();
|
|
395
|
+
expect(buffer.lines.some((line) => line.includes("Hidden addition"))).toBe(false);
|
|
396
|
+
expect(features.val()).toEqual(["Search", "Offline"]);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("a preceding hidden section does not extend a later heading into its child list", () => {
|
|
400
|
+
const source = `## Earlier
|
|
401
|
+
|
|
402
|
+
### Earlier child
|
|
403
|
+
|
|
404
|
+
Earlier body.
|
|
405
|
+
|
|
406
|
+
## Parent
|
|
407
|
+
|
|
408
|
+
Parent has no list.
|
|
409
|
+
|
|
410
|
+
### Child
|
|
411
|
+
|
|
412
|
+
- [x] Child checkbox
|
|
413
|
+
`;
|
|
414
|
+
const { $, buffer } = tuiSelector(source);
|
|
415
|
+
|
|
416
|
+
$("#earlier").hide();
|
|
417
|
+
expect($("#parent").val()).toEqual([]);
|
|
418
|
+
expect($("#parent").slice()).toEqual([]);
|
|
419
|
+
expect($("#parent").push("Must not reach child")).toBe(0);
|
|
420
|
+
expect(buffer._mdcuiMutationMacros).toBeUndefined();
|
|
421
|
+
expect($("#child").val()).toEqual(["Child checkbox"]);
|
|
422
|
+
expect(buffer.lines.some((line) => line.includes("Must not reach child"))).toBe(false);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("a heading concealed by its parent cannot resolve to an earlier same-level heading", () => {
|
|
426
|
+
const source = `## Wrong
|
|
427
|
+
|
|
428
|
+
- [x] Wrong item
|
|
429
|
+
|
|
430
|
+
# Parent
|
|
431
|
+
|
|
432
|
+
## Hidden
|
|
433
|
+
|
|
434
|
+
- [x] Hidden item
|
|
435
|
+
|
|
436
|
+
## Also hidden
|
|
437
|
+
|
|
438
|
+
Body.
|
|
439
|
+
|
|
440
|
+
# End
|
|
441
|
+
`;
|
|
442
|
+
const { $, buffer } = tuiSelector(source);
|
|
443
|
+
const hidden = $("#hidden");
|
|
444
|
+
|
|
445
|
+
$("#parent").hide();
|
|
446
|
+
expect(hidden.line()).toBe(0);
|
|
447
|
+
expect(hidden.text()).toBe("");
|
|
448
|
+
expect(hidden.val()).toEqual([]);
|
|
449
|
+
expect(hidden.slice()).toEqual([]);
|
|
450
|
+
expect(hidden.push("Must not reach Wrong")).toBe(0);
|
|
451
|
+
expect(hidden.pop()).toBeUndefined();
|
|
452
|
+
expect(hidden.shift()).toBeUndefined();
|
|
453
|
+
expect(hidden.unshift("Must not reach Wrong first")).toBe(0);
|
|
454
|
+
expect(hidden.splice(0, 1, "Must not replace Wrong")).toEqual([]);
|
|
455
|
+
expect($("#wrong").slice()).toEqual([{ value: "Wrong item", checked: true }]);
|
|
456
|
+
expect(buffer._mdcuiMutationMacros).toBeUndefined();
|
|
457
|
+
expect(buffer.lines.some((line) => line.includes("Must not"))).toBe(false);
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
describe("TUI heading section visibility", () => {
|
|
462
|
+
const sectionMarkdown = `# First
|
|
463
|
+
|
|
464
|
+
First body.
|
|
465
|
+
|
|
466
|
+
## Child
|
|
467
|
+
|
|
468
|
+
Child body.
|
|
469
|
+
|
|
470
|
+
### Grandchild
|
|
471
|
+
|
|
472
|
+
Grandchild body.
|
|
473
|
+
|
|
474
|
+
## Sibling
|
|
475
|
+
|
|
476
|
+
Sibling body.
|
|
477
|
+
|
|
478
|
+
# Second
|
|
479
|
+
|
|
480
|
+
Second body.
|
|
481
|
+
`;
|
|
482
|
+
|
|
483
|
+
test("hide keeps the heading and hides through the next equal or higher heading", () => {
|
|
484
|
+
const { $, buffer } = tuiSelector(sectionMarkdown);
|
|
485
|
+
const child = $("#child");
|
|
486
|
+
|
|
487
|
+
expect(child.hide()).toBe(child);
|
|
488
|
+
expect(buffer.lines.some(line => line.includes("Child"))).toBe(true);
|
|
489
|
+
expect(buffer.lines.some(line => line.includes("Child body."))).toBe(false);
|
|
490
|
+
expect(buffer.lines.some(line => line.includes("Grandchild"))).toBe(false);
|
|
491
|
+
expect(buffer.lines.some(line => line.includes("Sibling body."))).toBe(true);
|
|
492
|
+
expect(buffer.lines.some(line => line.includes("Second body."))).toBe(true);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test("show restores the section and toggle switches the id-centered state", () => {
|
|
496
|
+
const { $, buffer } = tuiSelector(sectionMarkdown);
|
|
497
|
+
const child = $("#child");
|
|
498
|
+
|
|
499
|
+
child.hide();
|
|
500
|
+
child.show();
|
|
501
|
+
expect(buffer.lines.some(line => line.includes("Child body."))).toBe(true);
|
|
502
|
+
expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(true);
|
|
503
|
+
|
|
504
|
+
child.toggle();
|
|
505
|
+
expect(buffer.lines.some(line => line.includes("Child body."))).toBe(false);
|
|
506
|
+
child.toggle();
|
|
507
|
+
expect(buffer.lines.some(line => line.includes("Child body."))).toBe(true);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("show, hide, and toggle update a child while its parent is hidden", () => {
|
|
511
|
+
const scenarios = [
|
|
512
|
+
{ initiallyHidden: false, method: "hide", visible: false },
|
|
513
|
+
{ initiallyHidden: true, method: "show", visible: true },
|
|
514
|
+
{ initiallyHidden: false, method: "toggle", visible: false },
|
|
515
|
+
{ initiallyHidden: true, method: "toggle", visible: true },
|
|
516
|
+
];
|
|
517
|
+
|
|
518
|
+
for (const scenario of scenarios) {
|
|
519
|
+
const { $, buffer } = tuiSelector(sectionMarkdown);
|
|
520
|
+
const first = $("#first");
|
|
521
|
+
const child = $("#child");
|
|
522
|
+
if (scenario.initiallyHidden) child.hide();
|
|
523
|
+
first.hide();
|
|
524
|
+
|
|
525
|
+
child[scenario.method]();
|
|
526
|
+
|
|
527
|
+
expect(buffer._mdcuiIdStore.get("first").headingVisibility.hidden).toBe(true);
|
|
528
|
+
expect(buffer.lines.some(line => line.includes("First body."))).toBe(false);
|
|
529
|
+
first.show();
|
|
530
|
+
expect(buffer.lines.some(line => line.includes("Child body.")))
|
|
531
|
+
.toBe(scenario.visible);
|
|
532
|
+
expect(Boolean(buffer._mdcuiIdStore.get("child")?.headingVisibility?.hidden))
|
|
533
|
+
.toBe(!scenario.visible);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
test("a deep toggle crosses multiple hidden ancestors without exposing them", () => {
|
|
538
|
+
const { $, buffer } = tuiSelector(sectionMarkdown);
|
|
539
|
+
const first = $("#first");
|
|
540
|
+
const child = $("#child");
|
|
541
|
+
const grandchild = $("#grandchild");
|
|
542
|
+
|
|
543
|
+
$("#second").hide();
|
|
544
|
+
child.hide();
|
|
545
|
+
first.hide();
|
|
546
|
+
buffer.cursor = { x: 0, y: buffer.lines.length - 1 };
|
|
547
|
+
buffer.modified = false;
|
|
548
|
+
const cursor = { ...buffer.cursor };
|
|
549
|
+
|
|
550
|
+
grandchild.toggle();
|
|
551
|
+
|
|
552
|
+
expect(buffer.cursor).toEqual(cursor);
|
|
553
|
+
expect(buffer.modified).toBe(false);
|
|
554
|
+
expect(buffer._mdcuiIdStore.get("first").headingVisibility.hidden).toBe(true);
|
|
555
|
+
expect(buffer._mdcuiIdStore.get("child").headingVisibility.hidden).toBe(true);
|
|
556
|
+
expect(buffer._mdcuiIdStore.get("grandchild").headingVisibility.hidden).toBe(true);
|
|
557
|
+
expect(buffer._mdcuiIdStore.get("second").headingVisibility.hidden).toBe(true);
|
|
558
|
+
|
|
559
|
+
first.show();
|
|
560
|
+
child.show();
|
|
561
|
+
expect(buffer.lines.some(line => line.includes("Grandchild"))).toBe(true);
|
|
562
|
+
expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(false);
|
|
563
|
+
grandchild.show();
|
|
564
|
+
expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(true);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test("visibility changes do not mark the read-only rendered buffer modified", () => {
|
|
568
|
+
const { $, buffer } = tuiSelector(sectionMarkdown);
|
|
569
|
+
buffer.modified = false;
|
|
570
|
+
|
|
571
|
+
$("#first").hide();
|
|
572
|
+
expect(buffer.modified).toBe(false);
|
|
573
|
+
expect(buffer._mdcuiIdStore.get("first").headingVisibility.hidden).toBe(true);
|
|
574
|
+
$("#first").show();
|
|
575
|
+
expect(buffer.modified).toBe(false);
|
|
576
|
+
expect(buffer._mdcuiIdStore.has("first")).toBe(false);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test("heading visibility shares a general id record without replacing other state", () => {
|
|
580
|
+
const { $, buffer } = tuiSelector(sectionMarkdown);
|
|
581
|
+
const shared = { customState: { count: 1 } };
|
|
582
|
+
buffer._mdcuiIdStore = new Map([["child", shared]]);
|
|
583
|
+
|
|
584
|
+
$("#child").hide();
|
|
585
|
+
expect(buffer._mdcuiIdStore.get("child").customState).toEqual({ count: 1 });
|
|
586
|
+
expect(buffer._mdcuiIdStore.get("child").headingVisibility.hidden).toBe(true);
|
|
587
|
+
$("#child").show();
|
|
588
|
+
expect(buffer._mdcuiIdStore.get("child")).toBe(shared);
|
|
589
|
+
expect(buffer._mdcuiIdStore.get("child").headingVisibility).toBeUndefined();
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("a collapsed earlier section does not break a later heading reference", () => {
|
|
593
|
+
const { $, buffer } = tuiSelector(sectionMarkdown);
|
|
594
|
+
|
|
595
|
+
$("#child").hide();
|
|
596
|
+
$("#sibling").hide();
|
|
597
|
+
expect(buffer.lines.some(line => line.includes("Sibling"))).toBe(true);
|
|
598
|
+
expect(buffer.lines.some(line => line.includes("Sibling body."))).toBe(false);
|
|
599
|
+
|
|
600
|
+
$("#sibling").show();
|
|
601
|
+
expect(buffer.lines.some(line => line.includes("Sibling body."))).toBe(true);
|
|
602
|
+
expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(false);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test("show resolves the heading again after preceding text changes the line count", () => {
|
|
606
|
+
const { $, buffer } = tuiSelector(sectionMarkdown);
|
|
607
|
+
const sibling = $("#sibling");
|
|
608
|
+
|
|
609
|
+
sibling.hide();
|
|
610
|
+
spliceTuiBufferLines(buffer, 0, 0, ["Inserted one", "Inserted two"], {
|
|
611
|
+
ansi: ["Inserted one", "Inserted two"],
|
|
612
|
+
});
|
|
613
|
+
sibling.show();
|
|
614
|
+
|
|
615
|
+
const headingIndex = buffer.lines.findIndex(line => line.includes("Sibling"));
|
|
616
|
+
expect(headingIndex).toBeGreaterThan(1);
|
|
617
|
+
expect(buffer.lines.slice(headingIndex + 1).some(line => line.includes("Sibling body."))).toBe(true);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("the heading's first character is a fixed toggle target", () => {
|
|
621
|
+
const { buffer } = tuiSelector(sectionMarkdown);
|
|
622
|
+
const headingRow = buffer.lines.findIndex(line => line.includes("Child"));
|
|
623
|
+
const firstCharacter = buffer.lines[headingRow].search(/\S/);
|
|
624
|
+
|
|
625
|
+
expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter + 1)).toBe(false);
|
|
626
|
+
expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter)).toBe(true);
|
|
627
|
+
const headingRows = buffer._mdcuiHeadingRowIndex;
|
|
628
|
+
expect(headingRows.byRow.get(headingRow).id).toBe("child");
|
|
629
|
+
expect(headingRows.entries.some((heading) => heading.id === "grandchild")).toBe(false);
|
|
630
|
+
expect(buffer.lines.some(line => line.includes("Child body."))).toBe(false);
|
|
631
|
+
expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter)).toBe(true);
|
|
632
|
+
expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
|
|
633
|
+
expect(headingRows.entries.some((heading) => heading.id === "grandchild")).toBe(true);
|
|
634
|
+
expect(buffer.lines.some(line => line.includes("Child body."))).toBe(true);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("a normalized Chinese heading ID restores first-character toggling", () => {
|
|
638
|
+
const source = `# 中文 設定!
|
|
639
|
+
|
|
640
|
+
Chinese body.
|
|
641
|
+
|
|
642
|
+
# 結束
|
|
643
|
+
|
|
644
|
+
End body.
|
|
645
|
+
`;
|
|
646
|
+
const { $, buffer } = tuiSelector(source);
|
|
647
|
+
const headingRow = buffer.lines.findIndex(line =>
|
|
648
|
+
line.trim() === "中文 設定!"
|
|
649
|
+
);
|
|
650
|
+
const firstCharacter = buffer.lines[headingRow].search(/\S/);
|
|
651
|
+
const heading = buffer._mdcuiHeadingRowIndex.byRow.get(headingRow);
|
|
652
|
+
|
|
653
|
+
expect(heading).toMatchObject({
|
|
654
|
+
id: "中文-設定",
|
|
655
|
+
level: 1,
|
|
656
|
+
ordinal: 0,
|
|
657
|
+
});
|
|
658
|
+
const selection = $("#中文-設定");
|
|
659
|
+
const nested = $($({ id: "中文-設定", value: "decoy" }));
|
|
660
|
+
expect(selection.id).toBe("中文-設定");
|
|
661
|
+
expect(nested.id).toBe("中文-設定");
|
|
662
|
+
expect(nested.text()).toBe("中文 設定!");
|
|
663
|
+
expect(nested.data()).toBe(selection.data());
|
|
664
|
+
|
|
665
|
+
expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter)).toBe(true);
|
|
666
|
+
expect(buffer.lines.some(line => line.includes("Chinese body."))).toBe(false);
|
|
667
|
+
expect(buffer.lines.some(line => line.trim() === "結束")).toBe(true);
|
|
668
|
+
expect(toggleTuiHeadingAt(buffer, headingRow, firstCharacter)).toBe(true);
|
|
669
|
+
expect(buffer.lines.some(line => line.includes("Chinese body."))).toBe(true);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test("blockquote and list headings keep the TUI row index valid", () => {
|
|
673
|
+
const source = `# Top
|
|
674
|
+
|
|
675
|
+
> ## 引用標題
|
|
676
|
+
>
|
|
677
|
+
> Quoted body.
|
|
678
|
+
|
|
679
|
+
- ## 清單標題
|
|
680
|
+
|
|
681
|
+
List body.
|
|
682
|
+
|
|
683
|
+
# End
|
|
684
|
+
`;
|
|
685
|
+
const { $, buffer } = tuiSelector(source);
|
|
686
|
+
const quotedRow = $("#引用標題").line() - 1;
|
|
687
|
+
const listRow = $("#清單標題").line() - 1;
|
|
688
|
+
const topRow = $("#top").line() - 1;
|
|
689
|
+
|
|
690
|
+
expect(buffer._mdcuiHeadingRowIndex.valid).toBe(true);
|
|
691
|
+
expect(buffer._mdcuiHeadingRowIndex.entries).toHaveLength(4);
|
|
692
|
+
expect(buffer._mdcuiHeadingRowIndex.byRow.get(quotedRow)?.column)
|
|
693
|
+
.toBe(buffer.lines[quotedRow].indexOf("引"));
|
|
694
|
+
expect(buffer._mdcuiHeadingRowIndex.byRow.get(listRow)?.column)
|
|
695
|
+
.toBe(buffer.lines[listRow].indexOf("清"));
|
|
696
|
+
|
|
697
|
+
expect(toggleTuiHeadingAt(
|
|
698
|
+
buffer,
|
|
699
|
+
quotedRow,
|
|
700
|
+
buffer.lines[quotedRow].search(/\S/),
|
|
701
|
+
)).toBe(false);
|
|
702
|
+
expect(toggleTuiHeadingAt(
|
|
703
|
+
buffer,
|
|
704
|
+
quotedRow,
|
|
705
|
+
buffer.lines[quotedRow].indexOf("引"),
|
|
706
|
+
)).toBe(true);
|
|
707
|
+
expect(buffer.lines.some(line => line.includes("Quoted body."))).toBe(false);
|
|
708
|
+
expect(buffer.lines.some(line => line.includes("清單標題"))).toBe(true);
|
|
709
|
+
expect(toggleTuiHeadingAt(
|
|
710
|
+
buffer,
|
|
711
|
+
topRow,
|
|
712
|
+
buffer.lines[topRow].indexOf("Top"),
|
|
713
|
+
)).toBe(true);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
test("nested heading sections stop at their Markdown container boundary", () => {
|
|
717
|
+
const scenarios = [
|
|
718
|
+
{
|
|
719
|
+
source: `> ## Quoted
|
|
720
|
+
>
|
|
721
|
+
> Inside quote.
|
|
722
|
+
|
|
723
|
+
Outside quote.
|
|
724
|
+
|
|
725
|
+
### Outside lower
|
|
726
|
+
|
|
727
|
+
Outside heading body.
|
|
728
|
+
`,
|
|
729
|
+
id: "quoted",
|
|
730
|
+
inside: "Inside quote.",
|
|
731
|
+
outside: "Outside quote.",
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
source: `- Item
|
|
735
|
+
|
|
736
|
+
## Listed
|
|
737
|
+
|
|
738
|
+
Inside list.
|
|
739
|
+
|
|
740
|
+
Outside list.
|
|
741
|
+
|
|
742
|
+
### Outside lower
|
|
743
|
+
|
|
744
|
+
Outside heading body.
|
|
745
|
+
`,
|
|
746
|
+
id: "listed",
|
|
747
|
+
inside: "Inside list.",
|
|
748
|
+
outside: "Outside list.",
|
|
749
|
+
},
|
|
750
|
+
];
|
|
751
|
+
|
|
752
|
+
for (const scenario of scenarios) {
|
|
753
|
+
const { $, buffer } = tuiSelector(scenario.source);
|
|
754
|
+
const heading = $(`#${scenario.id}`);
|
|
755
|
+
|
|
756
|
+
expect(heading.hide()).toBe(heading);
|
|
757
|
+
expect(buffer.lines.some(line => line.includes(scenario.inside)))
|
|
758
|
+
.toBe(false);
|
|
759
|
+
expect(buffer.lines.some(line => line.includes(scenario.outside)))
|
|
760
|
+
.toBe(true);
|
|
761
|
+
expect(buffer.lines.some(line => line.includes("Outside lower")))
|
|
762
|
+
.toBe(true);
|
|
763
|
+
|
|
764
|
+
heading.show();
|
|
765
|
+
expect(buffer.lines.some(line => line.includes(scenario.inside)))
|
|
766
|
+
.toBe(true);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
test("blockquote visibility stops before a following top-level list", () => {
|
|
771
|
+
const source = `> ## Quoted
|
|
772
|
+
>
|
|
773
|
+
> Inside quote.
|
|
774
|
+
|
|
775
|
+
- Outside list item
|
|
776
|
+
`;
|
|
777
|
+
const { $, buffer } = tuiSelector(source);
|
|
778
|
+
const original = buffer.lines.slice();
|
|
779
|
+
const quoted = $("#quoted");
|
|
780
|
+
|
|
781
|
+
quoted.hide();
|
|
782
|
+
expect(buffer.lines.some(line => line.includes("Inside quote."))).toBe(false);
|
|
783
|
+
expect(buffer.lines.some(line => line.includes("Outside list item"))).toBe(true);
|
|
784
|
+
|
|
785
|
+
quoted.show();
|
|
786
|
+
expect(buffer.lines).toEqual(original);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test("double-blockquote visibility crosses compact quote marker rows only", () => {
|
|
790
|
+
const source = `> > ## Deep
|
|
791
|
+
> >
|
|
792
|
+
> > Deep body.
|
|
793
|
+
>
|
|
794
|
+
> Outer body.
|
|
795
|
+
|
|
796
|
+
Outside body.
|
|
797
|
+
`;
|
|
798
|
+
const { $, buffer } = tuiSelector(source);
|
|
799
|
+
const original = buffer.lines.slice();
|
|
800
|
+
const deep = $("#deep");
|
|
801
|
+
|
|
802
|
+
expect(buffer.lines.some(line => line === "││")).toBe(true);
|
|
803
|
+
deep.hide();
|
|
804
|
+
expect(buffer.lines.some(line => line.includes("Deep body."))).toBe(false);
|
|
805
|
+
expect(buffer.lines.some(line => line.includes("Outer body."))).toBe(true);
|
|
806
|
+
expect(buffer.lines.some(line => line.includes("Outside body."))).toBe(true);
|
|
807
|
+
|
|
808
|
+
deep.show();
|
|
809
|
+
expect(buffer.lines).toEqual(original);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test("a large document resolves the cursor row without rebuilding per heading", () => {
|
|
813
|
+
const headingCount = 2000;
|
|
814
|
+
const source = Array.from(
|
|
815
|
+
{ length: headingCount },
|
|
816
|
+
(_, index) => `## Topic ${index}\n\nBody ${index}.`,
|
|
817
|
+
).join("\n\n");
|
|
818
|
+
const { buffer } = tuiSelector(source);
|
|
819
|
+
const lastRow = buffer.lines.findLastIndex((line) =>
|
|
820
|
+
line.trim() === `Topic ${headingCount - 1}`
|
|
821
|
+
);
|
|
822
|
+
const firstCharacter = buffer.lines[lastRow].search(/\S/);
|
|
823
|
+
const headingRows = buffer._mdcuiHeadingRowIndex;
|
|
824
|
+
|
|
825
|
+
expect(toggleTuiHeadingAt(buffer, lastRow, firstCharacter)).toBe(true);
|
|
826
|
+
expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
|
|
827
|
+
expect(headingRows.valid).toBe(true);
|
|
828
|
+
expect(headingRows.entries).toHaveLength(headingCount);
|
|
829
|
+
expect(headingRows.byRow.get(lastRow)).toMatchObject({
|
|
830
|
+
id: `topic-${headingCount - 1}`,
|
|
831
|
+
ordinal: headingCount - 1,
|
|
832
|
+
level: 2,
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
expect(toggleTuiHeadingAt(buffer, lastRow, firstCharacter)).toBe(true);
|
|
836
|
+
expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
|
|
837
|
+
const bodyRow = buffer.lines.findIndex((line) =>
|
|
838
|
+
line.trim() === `Body ${headingCount - 1}.`
|
|
839
|
+
);
|
|
840
|
+
expect(toggleTuiHeadingAt(buffer, bodyRow, buffer.lines[bodyRow].search(/\S/))).toBe(false);
|
|
841
|
+
expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
test("text block row-count changes move cached heading rows in place", () => {
|
|
845
|
+
const source = `# Parent
|
|
846
|
+
|
|
847
|
+
\`\`\`text#editor
|
|
848
|
+
one
|
|
849
|
+
\`\`\`
|
|
850
|
+
|
|
851
|
+
## Child
|
|
852
|
+
|
|
853
|
+
Child body.
|
|
854
|
+
|
|
855
|
+
# End
|
|
856
|
+
`;
|
|
857
|
+
const { $, buffer } = tuiSelector(source);
|
|
858
|
+
const parentRow = buffer.lines.findIndex((line) => line.trim() === "Parent");
|
|
859
|
+
const parentColumn = buffer.lines[parentRow].search(/\S/);
|
|
860
|
+
expect(toggleTuiHeadingAt(buffer, parentRow, parentColumn)).toBe(true);
|
|
861
|
+
expect(toggleTuiHeadingAt(buffer, parentRow, parentColumn)).toBe(true);
|
|
862
|
+
const headingRows = buffer._mdcuiHeadingRowIndex;
|
|
863
|
+
const oldChildRow = headingRows.entries.find((heading) => heading.id === "child").row;
|
|
864
|
+
|
|
865
|
+
$("text#editor").val("one\ntwo\nthree\nfour");
|
|
866
|
+
|
|
867
|
+
const child = headingRows.entries.find((heading) => heading.id === "child");
|
|
868
|
+
expect(buffer._mdcuiHeadingRowIndex).toBe(headingRows);
|
|
869
|
+
expect(child.row).toBeGreaterThan(oldChildRow);
|
|
870
|
+
expect(headingRows.byRow.has(oldChildRow)).toBe(false);
|
|
871
|
+
expect(headingRows.byRow.get(child.row)?.id).toBe("child");
|
|
872
|
+
expect(toggleTuiHeadingAt(
|
|
873
|
+
buffer,
|
|
874
|
+
child.row,
|
|
875
|
+
buffer.lines[child.row].search(/\S/),
|
|
876
|
+
)).toBe(true);
|
|
877
|
+
expect(buffer.lines.some((line) => line.includes("Child body."))).toBe(false);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
test("a direct rerender invalidates row metadata and mismatches fail closed", () => {
|
|
881
|
+
const source = `# First
|
|
882
|
+
|
|
883
|
+
This paragraph is deliberately long enough to wrap at a narrow render width.
|
|
884
|
+
|
|
885
|
+
## Child
|
|
886
|
+
|
|
887
|
+
Child body.
|
|
888
|
+
`;
|
|
889
|
+
const { buffer } = tuiSelector(source);
|
|
890
|
+
const firstRow = buffer.lines.findIndex((line) => line.trim() === "First");
|
|
891
|
+
const firstColumn = buffer.lines[firstRow].search(/\S/);
|
|
892
|
+
expect(toggleTuiHeadingAt(buffer, firstRow, firstColumn)).toBe(true);
|
|
893
|
+
expect(toggleTuiHeadingAt(buffer, firstRow, firstColumn)).toBe(true);
|
|
894
|
+
const wideHeadingRows = buffer._mdcuiHeadingRowIndex;
|
|
895
|
+
|
|
896
|
+
const narrowAnsi = String(Bun.markdown.ansi(
|
|
897
|
+
source,
|
|
898
|
+
{ hyperlinks: true, columns: 20 },
|
|
899
|
+
));
|
|
900
|
+
buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
|
|
901
|
+
buffer._mdcuiAnsiText = narrowAnsi;
|
|
902
|
+
const childRow = buffer.lines.findIndex((line) => line.trim() === "Child");
|
|
903
|
+
expect(toggleTuiHeadingAt(
|
|
904
|
+
buffer,
|
|
905
|
+
childRow,
|
|
906
|
+
buffer.lines[childRow].search(/\S/),
|
|
907
|
+
)).toBe(true);
|
|
908
|
+
expect(buffer._mdcuiHeadingRowIndex).not.toBe(wideHeadingRows);
|
|
909
|
+
expect(buffer._mdcuiHeadingRowIndex.byRow.get(childRow)?.id).toBe("child");
|
|
910
|
+
|
|
911
|
+
const mismatched = tuiSelector("# First\n\n# Second\n").buffer;
|
|
912
|
+
const oneHeadingAnsi = String(Bun.markdown.ansi(
|
|
913
|
+
"# First\n",
|
|
914
|
+
{ hyperlinks: true, columns: 80 },
|
|
915
|
+
));
|
|
916
|
+
mismatched.lines = Bun.stripANSI(oneHeadingAnsi).split("\n");
|
|
917
|
+
mismatched._mdcuiAnsiText = oneHeadingAnsi;
|
|
918
|
+
const row = mismatched.lines.findIndex((line) => line.trim() === "First");
|
|
919
|
+
const column = mismatched.lines[row].search(/\S/);
|
|
920
|
+
expect(toggleTuiHeadingAt(mismatched, row, column)).toBe(false);
|
|
921
|
+
const failedIndex = mismatched._mdcuiHeadingRowIndex;
|
|
922
|
+
expect(failedIndex.valid).toBe(false);
|
|
923
|
+
expect(toggleTuiHeadingAt(mismatched, row, column)).toBe(false);
|
|
924
|
+
expect(mismatched._mdcuiHeadingRowIndex).toBe(failedIndex);
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
describe("TUI id-centered user data", () => {
|
|
929
|
+
test("data returns the same object stored under the selector id", () => {
|
|
930
|
+
const { $, buffer } = tuiSelector("## Features\n\nBody.\n");
|
|
931
|
+
const features = $("#features");
|
|
932
|
+
const data = features.data();
|
|
933
|
+
|
|
934
|
+
data.count = 1;
|
|
935
|
+
expect(features.data()).toBe(data);
|
|
936
|
+
expect(buffer._mdcuiIdStore.get("features").data).toBe(data);
|
|
937
|
+
expect(features.data("count")).toBe(1);
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
test("data setters and removeData preserve other id state", () => {
|
|
941
|
+
const { $, buffer } = tuiSelector("## Features\n\nBody.\n");
|
|
942
|
+
const features = $("#features");
|
|
943
|
+
|
|
944
|
+
features.data("model", { ready: true }).data({ count: 2, label: "x" });
|
|
945
|
+
features.hide();
|
|
946
|
+
features.removeData("count label");
|
|
947
|
+
expect(features.data("model")).toEqual({ ready: true });
|
|
948
|
+
features.removeData();
|
|
949
|
+
expect(buffer._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
|
|
950
|
+
expect(buffer._mdcuiIdStore.get("features").data).toBeUndefined();
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
test("legal object ids discard the object and retain identity through arbitrary nesting", () => {
|
|
954
|
+
const { $, buffer } = tuiSelector("## Features\n\n- [x] Search\n");
|
|
955
|
+
const features = $("#features");
|
|
956
|
+
const objectTarget = {
|
|
957
|
+
id: "features",
|
|
958
|
+
innerHTML: "DECOY HTML",
|
|
959
|
+
textContent: "DECOY TEXT",
|
|
960
|
+
value: "DECOY VALUE",
|
|
961
|
+
};
|
|
962
|
+
const direct = $(objectTarget);
|
|
963
|
+
expect(direct.text()).toBe("Features");
|
|
964
|
+
expect(direct.val()).toEqual(["Search"]);
|
|
965
|
+
objectTarget.id = "missing";
|
|
966
|
+
let nested = direct;
|
|
967
|
+
for (let depth = 0; depth < 8; depth++) nested = $(nested);
|
|
968
|
+
|
|
969
|
+
expect(features.id).toBe("features");
|
|
970
|
+
expect(nested.id).toBe("features");
|
|
971
|
+
expect(nested.html()).toBe("Features");
|
|
972
|
+
expect(nested.text()).toBe("Features");
|
|
973
|
+
expect(nested.val()).toEqual(["Search"]);
|
|
974
|
+
expect(nested.text("Ignored")).toBe(nested);
|
|
975
|
+
expect(nested.val("Ignored")).toBe(nested);
|
|
976
|
+
expect(objectTarget).toEqual({
|
|
977
|
+
id: "missing",
|
|
978
|
+
innerHTML: "DECOY HTML",
|
|
979
|
+
textContent: "DECOY TEXT",
|
|
980
|
+
value: "DECOY VALUE",
|
|
981
|
+
});
|
|
982
|
+
features.data("count", 1);
|
|
983
|
+
expect($({ id: "features" }).data("count")).toBe(1);
|
|
984
|
+
expect(nested.data()).toBe(features.data());
|
|
985
|
+
expect(nested.push("Nested addition")).toBe(2);
|
|
986
|
+
expect(buffer._mdcuiMutationMacros.at(-1).selector).toBe("#features");
|
|
987
|
+
nested.hide();
|
|
988
|
+
expect(buffer._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
test("Unicode and numeric heading IDs remain canonical through object wrapping", () => {
|
|
992
|
+
const { $ } = tuiSelector("# 中文\n\nBody.\n\n# 2026 中文\n\nLater.\n");
|
|
993
|
+
|
|
994
|
+
for (const id of ["中文", "2026"]) {
|
|
995
|
+
const direct = $(`#${id}`);
|
|
996
|
+
let nested = $({ id, value: "decoy" });
|
|
997
|
+
for (let depth = 0; depth < 4; depth++) nested = $(nested);
|
|
998
|
+
expect(nested.id).toBe(id);
|
|
999
|
+
expect(nested.text()).toBe(direct.text());
|
|
1000
|
+
expect(nested.data()).toBe(direct.data());
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
describe("TUI source-dependent state reset", () => {
|
|
1006
|
+
test("checkbox count mismatches provide a visible rerender message", () => {
|
|
1007
|
+
expect(tuiCheckboxRerenderMismatchMessage({
|
|
1008
|
+
name: "tasks.md",
|
|
1009
|
+
_mdcuiRerenderMismatch: {
|
|
1010
|
+
checkboxes: { before: 3, after: 2 },
|
|
1011
|
+
},
|
|
1012
|
+
})).toBe(
|
|
1013
|
+
"Checkbox state restore skipped in tasks.md: count changed from 3 to 2",
|
|
1014
|
+
);
|
|
1015
|
+
expect(tuiCheckboxRerenderMismatchMessage({
|
|
1016
|
+
_mdcuiRerenderMismatch: {
|
|
1017
|
+
fenceBlocks: { before: 1, after: 2 },
|
|
1018
|
+
},
|
|
1019
|
+
})).toBe("");
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
test("reopen cache clearing drops old render state while preserving user data", () => {
|
|
1023
|
+
const oldSource = `# Topic
|
|
1024
|
+
|
|
1025
|
+
OLD BODY
|
|
1026
|
+
|
|
1027
|
+
- [ ] Original
|
|
1028
|
+
|
|
1029
|
+
# End
|
|
1030
|
+
`;
|
|
1031
|
+
const { $, buffer } = tuiSelector(oldSource);
|
|
1032
|
+
const topic = $("#topic");
|
|
1033
|
+
const data = topic.data();
|
|
1034
|
+
data.count = 1;
|
|
1035
|
+
topic.push("Old runtime item");
|
|
1036
|
+
topic.hide();
|
|
1037
|
+
const record = buffer._mdcuiIdStore.get("topic");
|
|
1038
|
+
record.customState = { keep: true };
|
|
1039
|
+
buffer._mdcuiRerenderMismatch = { checkboxes: { before: 2, after: 1 } };
|
|
1040
|
+
buffer._mdcuiFenceBlockIndex = [{ stale: true }];
|
|
1041
|
+
buffer._mdcuiControlBlockIndex = [{ stale: true }];
|
|
1042
|
+
|
|
1043
|
+
const newSource = `# Topic
|
|
1044
|
+
|
|
1045
|
+
NEW BODY
|
|
1046
|
+
|
|
1047
|
+
- [ ] Fresh
|
|
1048
|
+
|
|
1049
|
+
# End
|
|
1050
|
+
`;
|
|
1051
|
+
const newAnsi = String(Bun.markdown.ansi(
|
|
1052
|
+
newSource,
|
|
1053
|
+
{ hyperlinks: true, columns: 80 },
|
|
1054
|
+
));
|
|
1055
|
+
buffer.lines = Bun.stripANSI(newAnsi).split("\n");
|
|
1056
|
+
buffer._mdcuiTuiSourceText = newSource;
|
|
1057
|
+
buffer._mdcuiAnsiText = newAnsi;
|
|
1058
|
+
clearTuiSourceDependentState(buffer);
|
|
1059
|
+
|
|
1060
|
+
expect(buffer._mdcuiIdStore.get("topic")).toBe(record);
|
|
1061
|
+
expect(topic.data()).toBe(data);
|
|
1062
|
+
expect(topic.data("count")).toBe(1);
|
|
1063
|
+
expect(record.customState).toEqual({ keep: true });
|
|
1064
|
+
expect(record.headingVisibility).toBeUndefined();
|
|
1065
|
+
expect(buffer._mdcuiMutationMacros).toBeUndefined();
|
|
1066
|
+
expect(buffer._mdcuiReplayingMutations).toBe(false);
|
|
1067
|
+
expect(buffer._mdcuiHeadingTaskListAnchors).toBeNull();
|
|
1068
|
+
expect(buffer._mdcuiRerenderMismatch).toBeNull();
|
|
1069
|
+
expect(buffer._mdcuiFenceBlockIndex).toBeNull();
|
|
1070
|
+
expect(buffer._mdcuiControlBlockIndex).toBeNull();
|
|
1071
|
+
expect(buffer._mdcuiSourceHeadingIndex).toBeNull();
|
|
1072
|
+
expect(buffer._mdcuiHeadingRowIndex).toBeNull();
|
|
1073
|
+
|
|
1074
|
+
topic.show();
|
|
1075
|
+
expect(buffer.lines.some(line => line.includes("OLD BODY"))).toBe(false);
|
|
1076
|
+
expect(buffer.lines.some(line => line.includes("Old runtime item"))).toBe(false);
|
|
1077
|
+
expect(buffer.lines.some(line => line.includes("Original"))).toBe(false);
|
|
1078
|
+
expect(buffer.lines.filter(line => line.includes("NEW BODY"))).toHaveLength(1);
|
|
1079
|
+
expect(topic.slice()).toEqual([{ value: "Fresh", checked: false }]);
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
test("cache clearing prunes id records that contain only visibility state", () => {
|
|
1083
|
+
const buffer = {
|
|
1084
|
+
_mdcuiIdStore: new Map([
|
|
1085
|
+
["only-visibility", {
|
|
1086
|
+
headingVisibility: {
|
|
1087
|
+
hidden: true,
|
|
1088
|
+
ordinal: 0,
|
|
1089
|
+
headingCount: 0,
|
|
1090
|
+
segment: { lines: [], images: [] },
|
|
1091
|
+
},
|
|
1092
|
+
}],
|
|
1093
|
+
]),
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
clearTuiSourceDependentState(buffer);
|
|
1097
|
+
|
|
1098
|
+
expect(buffer._mdcuiIdStore.has("only-visibility")).toBe(false);
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
describe("TUI object target wrapper", () => {
|
|
1103
|
+
test("text gets and sets textContent for synthetic event targets", () => {
|
|
1104
|
+
const { $ } = tuiSelector("Body.\n");
|
|
1105
|
+
const target = { textContent: "Save" };
|
|
1106
|
+
const wrapped = $(target);
|
|
1107
|
+
|
|
1108
|
+
expect(wrapped.text()).toBe("Save");
|
|
1109
|
+
expect(wrapped.text("Saved")).toBe(wrapped);
|
|
1110
|
+
expect(target.textContent).toBe("Saved");
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
test("val uses value when present and otherwise falls back to textContent", () => {
|
|
1114
|
+
const { $ } = tuiSelector("Body.\n");
|
|
1115
|
+
const input = { value: "old", textContent: "label" };
|
|
1116
|
+
const link = { textContent: "Open" };
|
|
1117
|
+
|
|
1118
|
+
expect($(input).val()).toBe("old");
|
|
1119
|
+
$(input).val("new");
|
|
1120
|
+
expect(input.value).toBe("new");
|
|
1121
|
+
expect(input.textContent).toBe("label");
|
|
1122
|
+
expect($(link).val()).toBe("Open");
|
|
1123
|
+
$(link).val("Close");
|
|
1124
|
+
expect(link.textContent).toBe("Close");
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
test("html reads generic innerHTML and val stringifies object values", () => {
|
|
1128
|
+
const { $ } = tuiSelector("Body.\n");
|
|
1129
|
+
|
|
1130
|
+
expect($({ innerHTML: "<b>Body</b>" }).html()).toBe("<b>Body</b>");
|
|
1131
|
+
expect($({ value: 123 }).val()).toBe("123");
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
test("invalid object ids stay generic while missing legal ids do not", () => {
|
|
1135
|
+
const { $ } = tuiSelector("## Features\n\nBody.\n");
|
|
1136
|
+
const invalidTargets = [
|
|
1137
|
+
{ id: "-invalid", value: "leading punctuation" },
|
|
1138
|
+
{ id: "invalid id", value: "spaced" },
|
|
1139
|
+
];
|
|
1140
|
+
const missing = { id: "missing", value: "decoy", textContent: "decoy" };
|
|
1141
|
+
|
|
1142
|
+
for (const target of invalidTargets) {
|
|
1143
|
+
const wrapped = $(target);
|
|
1144
|
+
expect(wrapped.id).toBe("");
|
|
1145
|
+
expect(wrapped.val()).toBe(target.value);
|
|
1146
|
+
wrapped.val("updated");
|
|
1147
|
+
expect(target.value).toBe("updated");
|
|
1148
|
+
}
|
|
1149
|
+
expect($(missing).val()).toBe("");
|
|
1150
|
+
$(missing).val("ignored");
|
|
1151
|
+
expect(missing.value).toBe("decoy");
|
|
1152
|
+
expect(missing.textContent).toBe("decoy");
|
|
1153
|
+
});
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
describe("TUI heading text getter", () => {
|
|
1157
|
+
test("returns the rendered heading text without enabling a string-selector setter", () => {
|
|
1158
|
+
const { $, buffer } = tuiSelector("## Hello *world* & `<tag>`\n\nBody.\n");
|
|
1159
|
+
const heading = $("#hello-world-tag");
|
|
1160
|
+
|
|
1161
|
+
expect(heading.text()).toBe("Hello world & <tag>");
|
|
1162
|
+
expect(heading.text("Replacement")).toBe(heading);
|
|
1163
|
+
expect(heading.text()).toBe("Hello world & <tag>");
|
|
1164
|
+
expect(buffer.lines.some((line) => line.includes("Replacement"))).toBe(false);
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
test("remains available while the heading section is hidden", () => {
|
|
1168
|
+
const { $ } = tuiSelector("## Hidden title\n\nBody.\n");
|
|
1169
|
+
const heading = $("#hidden-title");
|
|
1170
|
+
|
|
1171
|
+
heading.hide();
|
|
1172
|
+
expect(heading.text()).toBe("Hidden title");
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
describe("TUI heading image selection", () => {
|
|
1177
|
+
test("img(index).src reads OSC 8 image destinations before the next heading", () => {
|
|
1178
|
+
const { $ } = tuiSelector(`# Gallery
|
|
1179
|
+
|
|
1180
|
+

|
|
1181
|
+
|
|
1182
|
+

|
|
1183
|
+
|
|
1184
|
+
## Child
|
|
1185
|
+
|
|
1186
|
+

|
|
1187
|
+
`);
|
|
1188
|
+
|
|
1189
|
+
expect($("#gallery").img(0).src).toBe("one.png");
|
|
1190
|
+
expect($("#gallery").img(1).src).toBe("two.png");
|
|
1191
|
+
expect($("#gallery").img(2).src).toBe("");
|
|
1192
|
+
expect($("#child").img(0).src).toBe("child.png");
|
|
1193
|
+
expect($("#gallery").img(-1).src).toBe("");
|
|
1194
|
+
});
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
describe("TUI heading-associated table cells", () => {
|
|
1198
|
+
const tableMarkdown = `## Table With Id
|
|
1199
|
+
|
|
1200
|
+
| Name | Status |
|
|
1201
|
+
| --- | --- |
|
|
1202
|
+
| very long content here | ready |
|
|
1203
|
+
| other | done |
|
|
1204
|
+
`;
|
|
1205
|
+
|
|
1206
|
+
test("cell text uses zero-based coordinates and joins wrapped visual lines", () => {
|
|
1207
|
+
const { $ } = tuiTableSelector(tableMarkdown, 24);
|
|
1208
|
+
const table = $("#table-with-id");
|
|
1209
|
+
|
|
1210
|
+
expect(table.cell(0, 0).text()).toBe("Name");
|
|
1211
|
+
expect(table.cell(1, 0).text()).toBe("very longcontenthere");
|
|
1212
|
+
expect(table.cell(2, 1).text()).toBe("done");
|
|
1213
|
+
expect(table.cell(99, 0).text()).toBe("");
|
|
1214
|
+
expect(table.cell(-1, 0).text("ignored")).toBeDefined();
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
test("cell properties and physical neighbors stay inside the table", () => {
|
|
1218
|
+
const { $ } = tuiTableSelector(tableMarkdown, 24);
|
|
1219
|
+
const table = $("#table-with-id");
|
|
1220
|
+
const cell = table.cell(1, 0);
|
|
1221
|
+
|
|
1222
|
+
expect(cell.row).toBe(1);
|
|
1223
|
+
expect(cell.col).toBe(0);
|
|
1224
|
+
expect(cell.right().text()).toBe("ready");
|
|
1225
|
+
expect(cell.rt().text()).toBe("ready");
|
|
1226
|
+
expect(cell.up().text()).toBe("Name");
|
|
1227
|
+
expect(cell.down().text()).toBe("other");
|
|
1228
|
+
expect(cell.dn().text()).toBe("other");
|
|
1229
|
+
expect(cell.right().lt().text()).toBe("very longcontenthere");
|
|
1230
|
+
expect(cell.right().down().text()).toBe("done");
|
|
1231
|
+
expect(table.cell(0, 0).left()).toBeNull();
|
|
1232
|
+
expect(table.cell(0, 0).lt()).toBeNull();
|
|
1233
|
+
expect(table.cell(0, 0).up()).toBeNull();
|
|
1234
|
+
expect(table.cell(2, 1).right()).toBeNull();
|
|
1235
|
+
expect(table.cell(2, 1).rt()).toBeNull();
|
|
1236
|
+
expect(table.cell(2, 1).down()).toBeNull();
|
|
1237
|
+
expect(table.cell(2, 1).dn()).toBeNull();
|
|
1238
|
+
expect(table.cell(99, 0).left()).toBeNull();
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
test("cell val returns the first checkbox state or falls back to text", () => {
|
|
1242
|
+
const markdown = `## Checks
|
|
1243
|
+
|
|
1244
|
+
| Name | State |
|
|
1245
|
+
| --- | --- |
|
|
1246
|
+
| plain | [x] checked |
|
|
1247
|
+
| empty | [ ] pending |
|
|
1248
|
+
`;
|
|
1249
|
+
const { $, buffer } = tuiTableSelector(markdown, 30);
|
|
1250
|
+
const table = $("#checks");
|
|
1251
|
+
|
|
1252
|
+
expect(table.cell(0, 0).val()).toBe("Name");
|
|
1253
|
+
expect(table.cell(1, 0).val()).toBe("plain");
|
|
1254
|
+
expect(table.cell(1, 1).val()).toBe(true);
|
|
1255
|
+
expect(table.cell(2, 1).val()).toBe(false);
|
|
1256
|
+
const uncheckedCell = table.cell(2, 1);
|
|
1257
|
+
expect(uncheckedCell.val(true)).toBe(uncheckedCell);
|
|
1258
|
+
expect(uncheckedCell.val()).toBe(true);
|
|
1259
|
+
expect(table.cell(1, 1).val(false).val()).toBe(false);
|
|
1260
|
+
expect(table.cell(1, 0).val(true).text()).toBe("plain");
|
|
1261
|
+
expect(buffer._mdcuiAnsiText).toContain("\x1b[32m☒");
|
|
1262
|
+
expect(table.cell(99, 0).val()).toBe("");
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
test("cell text replacement stays inside its rectangle and updates ANSI", () => {
|
|
1266
|
+
const { $, buffer } = tuiTableSelector(tableMarkdown, 24);
|
|
1267
|
+
const cell = $("#table-with-id").cell(2, 1);
|
|
1268
|
+
const widths = buffer.lines.map(line => Bun.stringWidth(line));
|
|
1269
|
+
const result = cell.text("更新✅abcdef");
|
|
1270
|
+
|
|
1271
|
+
expect(result).toBe(cell);
|
|
1272
|
+
expect(cell.text()).toBe("更新✅");
|
|
1273
|
+
expect(buffer.lines.map(line => Bun.stringWidth(line))).toEqual(widths);
|
|
1274
|
+
expect(Bun.stripANSI(buffer._mdcuiAnsiText)).toBe(buffer.lines.join("\n"));
|
|
1275
|
+
expect(buffer.undoCount).toBe(1);
|
|
1276
|
+
expect(buffer._mdcuiMutationMacros).toBeUndefined();
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
test("cell text replacement preserves every ANSI and OSC 8 control", () => {
|
|
1280
|
+
const linkedMarkdown = `## Linked Table
|
|
1281
|
+
|
|
1282
|
+
| Name | Action |
|
|
1283
|
+
| --- | --- |
|
|
1284
|
+
| item | [open](javascript:run()) |
|
|
1285
|
+
`;
|
|
1286
|
+
const { $, buffer } = tuiTableSelector(linkedMarkdown, 40);
|
|
1287
|
+
const controls = value => String(value).match(
|
|
1288
|
+
/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\x07|\x1b\\))/gu,
|
|
1289
|
+
) ?? [];
|
|
1290
|
+
const before = controls(buffer._mdcuiAnsiText);
|
|
1291
|
+
|
|
1292
|
+
$("#linked-table").cell(1, 1).text("執行");
|
|
1293
|
+
|
|
1294
|
+
expect($("#linked-table").cell(1, 1).text()).toBe("執行");
|
|
1295
|
+
expect(controls(buffer._mdcuiAnsiText)).toEqual(before);
|
|
1296
|
+
expect(buffer._mdcuiAnsiText).toContain("javascript:run()");
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
test("a table link parent exposes the complete cell selection", () => {
|
|
1300
|
+
const linkedMarkdown = `## Linked Table
|
|
1301
|
+
|
|
1302
|
+
| Name | Action |
|
|
1303
|
+
| --- | --- |
|
|
1304
|
+
| item | [open](javascript:run()) |
|
|
1305
|
+
| next | later |
|
|
1306
|
+
`;
|
|
1307
|
+
const { $, buffer } = tuiTableSelector(linkedMarkdown, 40);
|
|
1308
|
+
const y = buffer.lines.findIndex(line => line.includes("open"));
|
|
1309
|
+
const x = buffer.lines[y].indexOf("open");
|
|
1310
|
+
const parentCell = tuiTableCellAtPosition(buffer, y, x);
|
|
1311
|
+
const linkTarget = { parent: () => parentCell };
|
|
1312
|
+
const parent = $(linkTarget).parent();
|
|
1313
|
+
|
|
1314
|
+
expect(parent.row).toBe(1);
|
|
1315
|
+
expect(parent.col).toBe(1);
|
|
1316
|
+
expect(parent.text()).toBe("open");
|
|
1317
|
+
expect(parent.lt().text()).toBe("item");
|
|
1318
|
+
expect(parent.dn().text()).toBe("later");
|
|
1319
|
+
expect(parent.text("執行")).toBe(parent);
|
|
1320
|
+
expect(parent.text()).toBe("執行");
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
test("cell snapshots restore API replacements after a width rerender", () => {
|
|
1324
|
+
const { $, buffer } = tuiTableSelector(tableMarkdown, 24);
|
|
1325
|
+
$("#table-with-id").cell(1, 0).text("replacement value");
|
|
1326
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1327
|
+
|
|
1328
|
+
const rerendered = tuiTableSelector(tableMarkdown, 32).buffer;
|
|
1329
|
+
buffer.lines = rerendered.lines;
|
|
1330
|
+
buffer._mdcuiAnsiText = rerendered._mdcuiAnsiText;
|
|
1331
|
+
buffer._ansiStyleLines = rerendered._ansiStyleLines;
|
|
1332
|
+
buffer._mdcuiHeadingRowIndex = null;
|
|
1333
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1334
|
+
|
|
1335
|
+
expect($("#table-with-id").cell(1, 0).text()).toBe("replacement value");
|
|
1336
|
+
expect(buffer.undoCount).toBe(1);
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
test("cell snapshots restore direct edits with ANSI after a width rerender", () => {
|
|
1340
|
+
const { $, buffer } = tuiTableSelector(tableMarkdown, 24);
|
|
1341
|
+
const y = buffer.lines.findIndex(line => line.includes("very long"));
|
|
1342
|
+
const x = buffer.lines[y].indexOf("very");
|
|
1343
|
+
markTuiTableCellDirtyAtPosition(buffer, y, x);
|
|
1344
|
+
buffer.lines[y] =
|
|
1345
|
+
buffer.lines[y].slice(0, x) + "USER" + buffer.lines[y].slice(x + 4);
|
|
1346
|
+
const ansiLines = buffer._mdcuiAnsiText.split("\n");
|
|
1347
|
+
ansiLines[y] = colorAnsiPlainRange(
|
|
1348
|
+
ansiLines[y],
|
|
1349
|
+
x,
|
|
1350
|
+
x + 4,
|
|
1351
|
+
"USER",
|
|
1352
|
+
"\x1b[35m",
|
|
1353
|
+
);
|
|
1354
|
+
buffer._mdcuiAnsiText = ansiLines.join("\n");
|
|
1355
|
+
for (let index = x; index < x + 4; index++)
|
|
1356
|
+
buffer._ansiStyleLines[y][index] = { fg: "magenta" };
|
|
1357
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1358
|
+
|
|
1359
|
+
const rerendered = tuiTableSelector(tableMarkdown, 32).buffer;
|
|
1360
|
+
buffer.lines = rerendered.lines;
|
|
1361
|
+
buffer._mdcuiAnsiText = rerendered._mdcuiAnsiText;
|
|
1362
|
+
buffer._ansiStyleLines = rerendered._ansiStyleLines;
|
|
1363
|
+
buffer._mdcuiHeadingRowIndex = null;
|
|
1364
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1365
|
+
|
|
1366
|
+
expect($("#table-with-id").cell(1, 0).text())
|
|
1367
|
+
.toBe("USER longcontenthere");
|
|
1368
|
+
expect(buffer._mdcuiAnsiText).toContain("\x1b[35mUSER");
|
|
1369
|
+
const restoredY = buffer.lines.findIndex(line => line.includes("USER"));
|
|
1370
|
+
const restoredX = buffer.lines[restoredY].indexOf("USER");
|
|
1371
|
+
expect(buffer._ansiStyleLines[restoredY][restoredX]?.fg).toBe("magenta");
|
|
1372
|
+
expect(buffer.undoCount).toBeUndefined();
|
|
1373
|
+
});
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
describe("TUI resize state restoration", () => {
|
|
1377
|
+
test("restores every checkbox glyph by full-text order without block filtering", () => {
|
|
1378
|
+
const buffer = {
|
|
1379
|
+
name: "glyphs.md",
|
|
1380
|
+
lines: [
|
|
1381
|
+
"Legend: ☒ done and ☐ pending",
|
|
1382
|
+
"│ ☒ table value │",
|
|
1383
|
+
"```text#sample",
|
|
1384
|
+
"☐ fenced value",
|
|
1385
|
+
"```",
|
|
1386
|
+
],
|
|
1387
|
+
_mdcuiTuiSourceText: "",
|
|
1388
|
+
_mdcuiAnsiText: "",
|
|
1389
|
+
cursor: { x: 0, y: 0 },
|
|
1390
|
+
ensureCursor() {},
|
|
1391
|
+
invalidateHighlightFrom() {},
|
|
1392
|
+
};
|
|
1393
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1394
|
+
buffer.lines = [
|
|
1395
|
+
"Legend: ☐ done and ☒ pending",
|
|
1396
|
+
"│ ☐ table value │",
|
|
1397
|
+
"```text#sample",
|
|
1398
|
+
"☒ fenced value",
|
|
1399
|
+
"```",
|
|
1400
|
+
];
|
|
1401
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1402
|
+
|
|
1403
|
+
expect(buffer.lines).toEqual([
|
|
1404
|
+
"Legend: ☒ done and ☐ pending",
|
|
1405
|
+
"│ ☒ table value │",
|
|
1406
|
+
"```text#sample",
|
|
1407
|
+
"☐ fenced value",
|
|
1408
|
+
"```",
|
|
1409
|
+
]);
|
|
1410
|
+
expect(buffer._mdcuiRerenderMismatch).toBeNull();
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
test("restores exact ANSI styles for checkbox glyphs by visible column", () => {
|
|
1414
|
+
const originalAnsi = [
|
|
1415
|
+
"中文 \x1b[1;38;2;12;34;56;48;5;20m☒\x1b[0m suffix",
|
|
1416
|
+
"\x1b[35m😀 ☐ tail\x1b[0m",
|
|
1417
|
+
].join("\n");
|
|
1418
|
+
const buffer = {
|
|
1419
|
+
name: "styled.md",
|
|
1420
|
+
lines: Bun.stripANSI(originalAnsi).split("\n"),
|
|
1421
|
+
_mdcuiTuiSourceText: "",
|
|
1422
|
+
_mdcuiAnsiText: originalAnsi,
|
|
1423
|
+
_ansiStyleLines: [
|
|
1424
|
+
Array.from({ length: 20 }, () => null),
|
|
1425
|
+
Array.from({ length: 20 }, () => null),
|
|
1426
|
+
],
|
|
1427
|
+
cursor: { x: 0, y: 0 },
|
|
1428
|
+
ensureCursor() {},
|
|
1429
|
+
invalidateHighlightFrom() {},
|
|
1430
|
+
};
|
|
1431
|
+
const firstX = buffer.lines[0].indexOf("☒");
|
|
1432
|
+
const secondX = buffer.lines[1].indexOf("☐");
|
|
1433
|
+
buffer._ansiStyleLines[0][firstX] = {
|
|
1434
|
+
bold: true,
|
|
1435
|
+
fg: "#0c2238",
|
|
1436
|
+
bg: 20,
|
|
1437
|
+
};
|
|
1438
|
+
buffer._ansiStyleLines[1][secondX] = { fg: "magenta" };
|
|
1439
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1440
|
+
|
|
1441
|
+
const rerenderedAnsi = [
|
|
1442
|
+
"中文 \x1b[31m☐\x1b[0m suffix",
|
|
1443
|
+
"\x1b[36m😀 ☒ tail\x1b[0m",
|
|
1444
|
+
].join("\n");
|
|
1445
|
+
buffer.lines = Bun.stripANSI(rerenderedAnsi).split("\n");
|
|
1446
|
+
buffer._mdcuiAnsiText = rerenderedAnsi;
|
|
1447
|
+
buffer._ansiStyleLines[0][firstX] = { fg: "red" };
|
|
1448
|
+
buffer._ansiStyleLines[1][secondX] = { fg: "cyan" };
|
|
1449
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1450
|
+
|
|
1451
|
+
expect(buffer.lines).toEqual(Bun.stripANSI(originalAnsi).split("\n"));
|
|
1452
|
+
expect(buffer._ansiStyleLines[0][firstX]).toEqual({
|
|
1453
|
+
bold: true,
|
|
1454
|
+
fg: "#0c2238",
|
|
1455
|
+
bg: 20,
|
|
1456
|
+
});
|
|
1457
|
+
expect(buffer._ansiStyleLines[1][secondX]).toEqual({ fg: "magenta" });
|
|
1458
|
+
expect(
|
|
1459
|
+
Bun.sliceAnsi(buffer._mdcuiAnsiText.split("\n")[0], 5, 6),
|
|
1460
|
+
).toBe(
|
|
1461
|
+
Bun.sliceAnsi(originalAnsi.split("\n")[0], 5, 6),
|
|
1462
|
+
);
|
|
1463
|
+
expect(
|
|
1464
|
+
Bun.sliceAnsi(buffer._mdcuiAnsiText.split("\n")[1], 3, 4),
|
|
1465
|
+
).toBe(
|
|
1466
|
+
Bun.sliceAnsi(originalAnsi.split("\n")[1], 3, 4),
|
|
1467
|
+
);
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
test("preserves direct checkbox state inside a blockquote", () => {
|
|
1471
|
+
const source = `> ## Quoted
|
|
1472
|
+
>
|
|
1473
|
+
> - [ ] Inside quote
|
|
1474
|
+
|
|
1475
|
+
- [ ] Outside quote
|
|
1476
|
+
`;
|
|
1477
|
+
const { $, buffer } = tuiSelector(source);
|
|
1478
|
+
const inside = buffer.lines.findIndex(line => line.includes("Inside quote"));
|
|
1479
|
+
buffer.lines[inside] = buffer.lines[inside].replace("☐", "☒");
|
|
1480
|
+
|
|
1481
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1482
|
+
const narrowAnsi = String(Bun.markdown.ansi(
|
|
1483
|
+
source,
|
|
1484
|
+
{ hyperlinks: true, columns: 24 },
|
|
1485
|
+
));
|
|
1486
|
+
buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
|
|
1487
|
+
buffer._mdcuiAnsiText = narrowAnsi;
|
|
1488
|
+
buffer._ansiStyleLines = null;
|
|
1489
|
+
buffer._mdcuiHeadingTaskListAnchors = null;
|
|
1490
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1491
|
+
|
|
1492
|
+
expect($("#quoted").val()).toEqual(["Inside quote"]);
|
|
1493
|
+
expect(buffer.lines.some(line =>
|
|
1494
|
+
line.includes("Outside quote") && line.includes("☐")
|
|
1495
|
+
)).toBe(true);
|
|
1496
|
+
expect(buffer._mdcuiRerenderMismatch).toBeNull();
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
test("replays a mutation recorded through arbitrarily nested id selections", () => {
|
|
1500
|
+
const source = "## Features\n\n- [ ] Original\n";
|
|
1501
|
+
const { $, buffer } = tuiSelector(source);
|
|
1502
|
+
const objectTarget = { id: "features", value: "DECOY" };
|
|
1503
|
+
let nested = $(objectTarget);
|
|
1504
|
+
for (let depth = 0; depth < 8; depth++) nested = $(nested);
|
|
1505
|
+
|
|
1506
|
+
expect(nested.push({ value: "Added through nesting", checked: true })).toBe(2);
|
|
1507
|
+
expect(buffer._mdcuiMutationMacros).toHaveLength(1);
|
|
1508
|
+
expect(buffer._mdcuiMutationMacros[0].selector).toBe("#features");
|
|
1509
|
+
|
|
1510
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1511
|
+
const narrowAnsi = String(Bun.markdown.ansi(
|
|
1512
|
+
source,
|
|
1513
|
+
{ hyperlinks: true, columns: 24 },
|
|
1514
|
+
));
|
|
1515
|
+
buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
|
|
1516
|
+
buffer._mdcuiAnsiText = narrowAnsi;
|
|
1517
|
+
buffer._ansiStyleLines = null;
|
|
1518
|
+
buffer._mdcuiHeadingTaskListAnchors = null;
|
|
1519
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1520
|
+
|
|
1521
|
+
const items = nested.slice();
|
|
1522
|
+
expect(items).toEqual([
|
|
1523
|
+
{ value: "Original", checked: false },
|
|
1524
|
+
{ value: "Added through nesting", checked: true },
|
|
1525
|
+
]);
|
|
1526
|
+
expect(items.filter(item => item.value === "Added through nesting"))
|
|
1527
|
+
.toHaveLength(1);
|
|
1528
|
+
expect(buffer._mdcuiRerenderMismatch).toBeNull();
|
|
1529
|
+
expect(objectTarget.value).toBe("DECOY");
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
test("replays Infinity, NaN, BigInt, and cyclic list mutation arguments", () => {
|
|
1533
|
+
const source = `## Features
|
|
1534
|
+
|
|
1535
|
+
- [ ] Original A
|
|
1536
|
+
- [x] Original B
|
|
1537
|
+
`;
|
|
1538
|
+
const { $, buffer } = tuiSelector(source);
|
|
1539
|
+
const features = $("#features");
|
|
1540
|
+
const cyclic = { value: "Cyclic item", checked: false };
|
|
1541
|
+
const bigintItem = { value: 9007199254740993n, checked: true };
|
|
1542
|
+
cyclic.self = cyclic;
|
|
1543
|
+
|
|
1544
|
+
expect(features.splice(0, Infinity)).toEqual(["Original A", "Original B"]);
|
|
1545
|
+
expect(features.push(bigintItem)).toBe(1);
|
|
1546
|
+
expect(features.splice(NaN, 0, cyclic)).toEqual([]);
|
|
1547
|
+
expect(features.slice()).toEqual([
|
|
1548
|
+
{ value: "Cyclic item", checked: false },
|
|
1549
|
+
{ value: "9007199254740993", checked: true },
|
|
1550
|
+
]);
|
|
1551
|
+
cyclic.value = "Mutated cyclic item";
|
|
1552
|
+
cyclic.checked = true;
|
|
1553
|
+
bigintItem.value = 0n;
|
|
1554
|
+
bigintItem.checked = false;
|
|
1555
|
+
|
|
1556
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1557
|
+
const narrowAnsi = String(Bun.markdown.ansi(
|
|
1558
|
+
source,
|
|
1559
|
+
{ hyperlinks: true, columns: 24 },
|
|
1560
|
+
));
|
|
1561
|
+
buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
|
|
1562
|
+
buffer._mdcuiAnsiText = narrowAnsi;
|
|
1563
|
+
buffer._ansiStyleLines = null;
|
|
1564
|
+
buffer._mdcuiHeadingTaskListAnchors = null;
|
|
1565
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1566
|
+
|
|
1567
|
+
expect(features.slice()).toEqual([
|
|
1568
|
+
{ value: "Cyclic item", checked: false },
|
|
1569
|
+
{ value: "9007199254740993", checked: true },
|
|
1570
|
+
]);
|
|
1571
|
+
expect(buffer._mdcuiRerenderMismatch).toBeNull();
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
test("preserves a deep visibility change made through hidden ancestors", () => {
|
|
1575
|
+
const source = `# First
|
|
1576
|
+
|
|
1577
|
+
First body.
|
|
1578
|
+
|
|
1579
|
+
## Child
|
|
1580
|
+
|
|
1581
|
+
Child body.
|
|
1582
|
+
|
|
1583
|
+
### Grandchild
|
|
1584
|
+
|
|
1585
|
+
Grandchild body.
|
|
1586
|
+
|
|
1587
|
+
# End
|
|
1588
|
+
`;
|
|
1589
|
+
const { $, buffer } = tuiSelector(source);
|
|
1590
|
+
$("#grandchild").hide();
|
|
1591
|
+
$("#child").hide();
|
|
1592
|
+
$("#first").hide();
|
|
1593
|
+
$("#grandchild").show();
|
|
1594
|
+
|
|
1595
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1596
|
+
const narrowAnsi = String(Bun.markdown.ansi(
|
|
1597
|
+
source,
|
|
1598
|
+
{ hyperlinks: true, columns: 24 },
|
|
1599
|
+
));
|
|
1600
|
+
buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
|
|
1601
|
+
buffer._mdcuiAnsiText = narrowAnsi;
|
|
1602
|
+
buffer._ansiStyleLines = null;
|
|
1603
|
+
buffer._mdcuiHeadingTaskListAnchors = null;
|
|
1604
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1605
|
+
|
|
1606
|
+
expect(buffer._mdcuiIdStore.get("first").headingVisibility.hidden).toBe(true);
|
|
1607
|
+
expect(buffer._mdcuiIdStore.get("child").headingVisibility.hidden).toBe(true);
|
|
1608
|
+
expect(buffer._mdcuiIdStore.get("grandchild")?.headingVisibility).toBeUndefined();
|
|
1609
|
+
$("#first").show();
|
|
1610
|
+
$("#child").show();
|
|
1611
|
+
expect(buffer.lines.some(line => line.includes("Grandchild body."))).toBe(true);
|
|
1612
|
+
expect(buffer._mdcuiRerenderMismatch).toBeNull();
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
test("replays list macros, restores checkbox order, and re-hides deepest headings first", () => {
|
|
1616
|
+
const source = `# Parent
|
|
1617
|
+
|
|
1618
|
+
- [ ] A checkbox label long enough to wrap after resizing
|
|
1619
|
+
|
|
1620
|
+
## Child
|
|
1621
|
+
|
|
1622
|
+
- [ ] Child checkbox
|
|
1623
|
+
|
|
1624
|
+
### Deep
|
|
1625
|
+
|
|
1626
|
+
- [ ] Deep checkbox
|
|
1627
|
+
|
|
1628
|
+
# End
|
|
1629
|
+
`;
|
|
1630
|
+
const { $, buffer } = tuiSelector(source);
|
|
1631
|
+
$("#parent").push({ value: "Added by macro", checked: false });
|
|
1632
|
+
for (const needle of ["Child checkbox", "Deep checkbox"]) {
|
|
1633
|
+
const y = buffer.lines.findIndex((line) => line.includes(needle));
|
|
1634
|
+
buffer.lines[y] = buffer.lines[y].replace("☐", "☒");
|
|
1635
|
+
}
|
|
1636
|
+
$("#deep").hide();
|
|
1637
|
+
$("#parent").hide();
|
|
1638
|
+
|
|
1639
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1640
|
+
const narrowAnsi = String(Bun.markdown.ansi(source, { hyperlinks: true, columns: 24 }));
|
|
1641
|
+
buffer.lines = Bun.stripANSI(narrowAnsi).split("\n");
|
|
1642
|
+
buffer._mdcuiAnsiText = narrowAnsi;
|
|
1643
|
+
buffer._ansiStyleLines = null;
|
|
1644
|
+
buffer._mdcuiHeadingTaskListAnchors = null;
|
|
1645
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1646
|
+
|
|
1647
|
+
expect(buffer._mdcuiIdStore.get("parent").headingVisibility.hidden).toBe(true);
|
|
1648
|
+
expect(buffer._mdcuiIdStore.get("deep").headingVisibility.hidden).toBe(true);
|
|
1649
|
+
$("#parent").show();
|
|
1650
|
+
expect(buffer.lines.some((line) => line.includes("Added by macro"))).toBe(true);
|
|
1651
|
+
expect(buffer.lines.some((line) => line.includes("Child checkbox") && line.includes("☒"))).toBe(true);
|
|
1652
|
+
$("#deep").show();
|
|
1653
|
+
expect(buffer.lines.some((line) => line.includes("Deep checkbox") && line.includes("☒"))).toBe(true);
|
|
1654
|
+
expect(buffer._mdcuiRerenderMismatch).toBeNull();
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
test("restores direct fence body edits by block order and identity", () => {
|
|
1658
|
+
const source = "```text#editor\noriginal\n```\n";
|
|
1659
|
+
const buffer = {
|
|
1660
|
+
lines: source.split("\n"),
|
|
1661
|
+
_mdcuiTuiSourceText: source,
|
|
1662
|
+
_mdcuiAnsiText: source,
|
|
1663
|
+
cursor: { x: 0, y: 0 },
|
|
1664
|
+
ensureCursor() {},
|
|
1665
|
+
invalidateHighlightFrom() {},
|
|
1666
|
+
};
|
|
1667
|
+
buffer.lines[1] = "user input";
|
|
1668
|
+
buffer._mdcuiAnsiText = buffer.lines.join("\n");
|
|
1669
|
+
const snapshot = captureTuiRerenderState(buffer);
|
|
1670
|
+
|
|
1671
|
+
buffer.lines = source.split("\n");
|
|
1672
|
+
buffer._mdcuiAnsiText = source;
|
|
1673
|
+
restoreTuiRerenderState(buffer, snapshot);
|
|
1674
|
+
|
|
1675
|
+
expect(buffer.lines[1]).toBe("user input");
|
|
1676
|
+
expect(buffer._mdcuiRerenderMismatch).toBeNull();
|
|
1677
|
+
});
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
class TestText {
|
|
1681
|
+
constructor(text, ownerDocument) {
|
|
1682
|
+
this.textContent = String(text);
|
|
1683
|
+
this.ownerDocument = ownerDocument;
|
|
1684
|
+
this.parentElement = null;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
class TestElement {
|
|
1689
|
+
constructor(tagName, ownerDocument) {
|
|
1690
|
+
this.tagName = tagName.toUpperCase();
|
|
1691
|
+
this.ownerDocument = ownerDocument;
|
|
1692
|
+
this.parentElement = null;
|
|
1693
|
+
this.childNodes = [];
|
|
1694
|
+
this.classList = new Set();
|
|
1695
|
+
this.id = "";
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
get children() {
|
|
1699
|
+
return this.childNodes.filter((node) => node instanceof TestElement);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
get firstChild() {
|
|
1703
|
+
return this.childNodes[0] ?? null;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
get nextElementSibling() {
|
|
1707
|
+
const siblings = this.parentElement?.children ?? [];
|
|
1708
|
+
const index = siblings.indexOf(this);
|
|
1709
|
+
return index >= 0 ? siblings[index + 1] ?? null : null;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
get textContent() {
|
|
1713
|
+
return this.childNodes.map((node) => node.textContent).join("");
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
set textContent(value) {
|
|
1717
|
+
this.childNodes = [];
|
|
1718
|
+
this.append(this.ownerDocument.createTextNode(value));
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
append(...nodes) {
|
|
1722
|
+
for (const node of nodes) {
|
|
1723
|
+
node.parentElement = this;
|
|
1724
|
+
this.childNodes.push(node);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
insertBefore(node, before) {
|
|
1729
|
+
node.parentElement = this;
|
|
1730
|
+
const index = before == null ? -1 : this.childNodes.indexOf(before);
|
|
1731
|
+
if (index < 0) this.childNodes.push(node);
|
|
1732
|
+
else this.childNodes.splice(index, 0, node);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
remove() {
|
|
1736
|
+
const siblings = this.parentElement?.childNodes;
|
|
1737
|
+
const index = siblings?.indexOf(this) ?? -1;
|
|
1738
|
+
if (index >= 0) siblings.splice(index, 1);
|
|
1739
|
+
this.parentElement = null;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
matches(selector) {
|
|
1743
|
+
if (selector === "ul, ol") return this.tagName === "UL" || this.tagName === "OL";
|
|
1744
|
+
if (selector === "li.task-list-item")
|
|
1745
|
+
return this.tagName === "LI" && this.classList.has("task-list-item");
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
closest(selector) {
|
|
1750
|
+
for (let node = this; node; node = node.parentElement) {
|
|
1751
|
+
if (selector === "label" && node.tagName === "LABEL") return node;
|
|
1752
|
+
if (selector === "li.task-list-item" && node.matches(selector)) return node;
|
|
1753
|
+
}
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
querySelector(selector) {
|
|
1758
|
+
return this.querySelectorAll(selector)[0] ?? null;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
querySelectorAll(selector) {
|
|
1762
|
+
const matches = [];
|
|
1763
|
+
const visit = (node) => {
|
|
1764
|
+
for (const child of node.children) {
|
|
1765
|
+
if (selector === 'input[type="checkbox"]' &&
|
|
1766
|
+
child.tagName === "INPUT" && child.type === "checkbox") matches.push(child);
|
|
1767
|
+
else if (selector === "img" && child.tagName === "IMG") matches.push(child);
|
|
1768
|
+
else if (selector === "ul, ol" && child.matches(selector)) matches.push(child);
|
|
1769
|
+
else if (selector === "li.task-list-item" && child.matches(selector)) matches.push(child);
|
|
1770
|
+
visit(child);
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
visit(this);
|
|
1774
|
+
return matches;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
describe("WUI heading image selection", () => {
|
|
1779
|
+
test("img(index).src reads actual image elements before the next heading section", () => {
|
|
1780
|
+
const documentObject = new TestDocument();
|
|
1781
|
+
const section = documentObject.createElement("section");
|
|
1782
|
+
const heading = documentObject.createElement("h1");
|
|
1783
|
+
heading.id = "gallery";
|
|
1784
|
+
const body = documentObject.createElement("p");
|
|
1785
|
+
const first = documentObject.createElement("img");
|
|
1786
|
+
const second = documentObject.createElement("img");
|
|
1787
|
+
first.src = "https://example.test/one.png";
|
|
1788
|
+
second.src = "https://example.test/two.png";
|
|
1789
|
+
body.append(first, second);
|
|
1790
|
+
const childSection = documentObject.createElement("section");
|
|
1791
|
+
const childHeading = documentObject.createElement("h2");
|
|
1792
|
+
childHeading.id = "child";
|
|
1793
|
+
const childImage = documentObject.createElement("img");
|
|
1794
|
+
childImage.src = "https://example.test/child.png";
|
|
1795
|
+
childSection.append(childHeading, childImage);
|
|
1796
|
+
section.append(heading, body, childSection);
|
|
1797
|
+
documentObject.root.append(section);
|
|
1798
|
+
const $ = createWebDollar(documentObject);
|
|
1799
|
+
|
|
1800
|
+
expect($("#gallery").img(0).src).toBe("https://example.test/one.png");
|
|
1801
|
+
expect($("#gallery").img(1).src).toBe("https://example.test/two.png");
|
|
1802
|
+
expect($("#gallery").img(2).src).toBe("");
|
|
1803
|
+
expect($("#child").img(0).src).toBe("https://example.test/child.png");
|
|
1804
|
+
});
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
describe("heading scrollIntoView", () => {
|
|
1808
|
+
test("TUI maps browser booleans and numeric alignment shortcuts", () => {
|
|
1809
|
+
const ansi = String(Bun.markdown.ansi("# First\n\nBody\n\n## Target\n", {
|
|
1810
|
+
hyperlinks: true,
|
|
1811
|
+
columns: 80,
|
|
1812
|
+
}));
|
|
1813
|
+
const buffer = {
|
|
1814
|
+
lines: Bun.stripANSI(ansi).split("\n"),
|
|
1815
|
+
_mdcuiTuiSourceText: "# First\n\nBody\n\n## Target\n",
|
|
1816
|
+
_mdcuiAnsiText: ansi,
|
|
1817
|
+
cursor: { x: 0, y: 0 },
|
|
1818
|
+
ensureCursor() {},
|
|
1819
|
+
};
|
|
1820
|
+
indexTuiHeadingRows(buffer);
|
|
1821
|
+
const blocks = [];
|
|
1822
|
+
const $ = createTuiSelector(
|
|
1823
|
+
() => buffer,
|
|
1824
|
+
null,
|
|
1825
|
+
({ block }) => blocks.push(block),
|
|
1826
|
+
);
|
|
1827
|
+
const target = $("#target");
|
|
1828
|
+
|
|
1829
|
+
target.scrollIntoView();
|
|
1830
|
+
target.scrollIntoView(true);
|
|
1831
|
+
target.scrollIntoView(false);
|
|
1832
|
+
target.scrollIntoView(-1);
|
|
1833
|
+
target.scrollIntoView(0);
|
|
1834
|
+
target.scrollIntoView(1);
|
|
1835
|
+
target.scrollIntoView("anything", "ignored");
|
|
1836
|
+
|
|
1837
|
+
expect(blocks).toEqual([
|
|
1838
|
+
"start", "start", "end", "start", "center", "end", "center",
|
|
1839
|
+
]);
|
|
1840
|
+
expect(buffer.lines[buffer.cursor.y]).toContain("Target");
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
test("WUI forwards browser forms and translates numeric shortcuts", () => {
|
|
1844
|
+
const { $, heading } = webSelector();
|
|
1845
|
+
const calls = [];
|
|
1846
|
+
heading.scrollIntoView = (...args) => calls.push(args);
|
|
1847
|
+
const features = $("#features");
|
|
1848
|
+
|
|
1849
|
+
features.scrollIntoView();
|
|
1850
|
+
features.scrollIntoView(true);
|
|
1851
|
+
features.scrollIntoView(false);
|
|
1852
|
+
features.scrollIntoView(-1);
|
|
1853
|
+
features.scrollIntoView(0);
|
|
1854
|
+
features.scrollIntoView(1);
|
|
1855
|
+
features.scrollIntoView({ block: "start" });
|
|
1856
|
+
|
|
1857
|
+
expect(calls).toEqual([
|
|
1858
|
+
[],
|
|
1859
|
+
[true],
|
|
1860
|
+
[false],
|
|
1861
|
+
[true],
|
|
1862
|
+
[{ block: "center", inline: "nearest" }],
|
|
1863
|
+
[false],
|
|
1864
|
+
[{ block: "center", inline: "nearest" }],
|
|
1865
|
+
]);
|
|
1866
|
+
});
|
|
1867
|
+
});
|
|
1868
|
+
|
|
1869
|
+
class TestDocument {
|
|
1870
|
+
constructor() {
|
|
1871
|
+
this.root = new TestElement("main", this);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
createElement(tagName) {
|
|
1875
|
+
return new TestElement(tagName, this);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
createTextNode(text) {
|
|
1879
|
+
return new TestText(text, this);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
getElementById(id) {
|
|
1883
|
+
return this.querySelectorAll("heading").find((element) => element.id === id) ?? null;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
querySelector(selector) {
|
|
1887
|
+
if (!selector.startsWith("#")) return null;
|
|
1888
|
+
const id = selector.slice(1);
|
|
1889
|
+
return this.querySelectorAll("heading").find((element) => element.id === id) ?? null;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
querySelectorAll(selector) {
|
|
1893
|
+
if (selector === "[data-mdcui-tag]" || selector === "pre > code") return [];
|
|
1894
|
+
if (selector === "heading") {
|
|
1895
|
+
const headings = [];
|
|
1896
|
+
const visit = (node) => {
|
|
1897
|
+
for (const child of node.children) {
|
|
1898
|
+
if (/^H[1-6]$/.test(child.tagName)) headings.push(child);
|
|
1899
|
+
visit(child);
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
visit(this.root);
|
|
1903
|
+
return headings;
|
|
1904
|
+
}
|
|
1905
|
+
return [];
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
function appendWebItem(documentObject, list, value, checked = false) {
|
|
1910
|
+
const item = documentObject.createElement("li");
|
|
1911
|
+
item.classList.add("task-list-item");
|
|
1912
|
+
const label = documentObject.createElement("label");
|
|
1913
|
+
const checkbox = documentObject.createElement("input");
|
|
1914
|
+
checkbox.type = "checkbox";
|
|
1915
|
+
checkbox.checked = checked;
|
|
1916
|
+
label.append(checkbox, documentObject.createTextNode(value));
|
|
1917
|
+
item.append(label);
|
|
1918
|
+
list.append(item);
|
|
1919
|
+
return item;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
function webSelector() {
|
|
1923
|
+
const documentObject = new TestDocument();
|
|
1924
|
+
const section = documentObject.createElement("section");
|
|
1925
|
+
const heading = documentObject.createElement("h2");
|
|
1926
|
+
heading.id = "features";
|
|
1927
|
+
heading.append(documentObject.createTextNode("Features"));
|
|
1928
|
+
const list = documentObject.createElement("ul");
|
|
1929
|
+
appendWebItem(documentObject, list, "Search", true);
|
|
1930
|
+
appendWebItem(documentObject, list, "Notifications");
|
|
1931
|
+
appendWebItem(documentObject, list, "Offline", true);
|
|
1932
|
+
section.append(heading, list);
|
|
1933
|
+
documentObject.root.append(section);
|
|
1934
|
+
return { $: createWebDollar(documentObject), documentObject, section, heading, list };
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
function nestedWebSelector() {
|
|
1938
|
+
const documentObject = new TestDocument();
|
|
1939
|
+
const outerSection = documentObject.createElement("section");
|
|
1940
|
+
const outerHeading = documentObject.createElement("h1");
|
|
1941
|
+
outerHeading.id = "first";
|
|
1942
|
+
outerHeading.append(documentObject.createTextNode("First"));
|
|
1943
|
+
const outerBody = documentObject.createElement("p");
|
|
1944
|
+
outerBody.append(documentObject.createTextNode("First web body."));
|
|
1945
|
+
const childSection = documentObject.createElement("section");
|
|
1946
|
+
const childHeading = documentObject.createElement("h2");
|
|
1947
|
+
childHeading.id = "child";
|
|
1948
|
+
childHeading.append(documentObject.createTextNode("Child"));
|
|
1949
|
+
const childBody = documentObject.createElement("p");
|
|
1950
|
+
childBody.append(documentObject.createTextNode("Child web body."));
|
|
1951
|
+
childSection.append(childHeading, childBody);
|
|
1952
|
+
outerSection.append(outerHeading, outerBody, childSection);
|
|
1953
|
+
documentObject.root.append(outerSection);
|
|
1954
|
+
return {
|
|
1955
|
+
$: createWebDollar(documentObject),
|
|
1956
|
+
documentObject,
|
|
1957
|
+
outerSection,
|
|
1958
|
+
outerHeading,
|
|
1959
|
+
childSection,
|
|
1960
|
+
childHeading,
|
|
1961
|
+
childBody,
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
function webTableSelector() {
|
|
1966
|
+
const documentObject = new TestDocument();
|
|
1967
|
+
const section = documentObject.createElement("section");
|
|
1968
|
+
const heading = documentObject.createElement("h2");
|
|
1969
|
+
heading.id = "table-with-id";
|
|
1970
|
+
heading.append(documentObject.createTextNode("Table With Id"));
|
|
1971
|
+
const table = documentObject.createElement("table");
|
|
1972
|
+
const header = documentObject.createElement("tr");
|
|
1973
|
+
const name = documentObject.createElement("th");
|
|
1974
|
+
const action = documentObject.createElement("th");
|
|
1975
|
+
name.append(documentObject.createTextNode("Name"));
|
|
1976
|
+
action.append(documentObject.createTextNode("Action"));
|
|
1977
|
+
header.append(name, action);
|
|
1978
|
+
const body = documentObject.createElement("tr");
|
|
1979
|
+
const item = documentObject.createElement("td");
|
|
1980
|
+
const linked = documentObject.createElement("td");
|
|
1981
|
+
const anchor = documentObject.createElement("a");
|
|
1982
|
+
item.append(documentObject.createTextNode("item"));
|
|
1983
|
+
anchor.append(documentObject.createTextNode("open"));
|
|
1984
|
+
linked.append(anchor);
|
|
1985
|
+
body.append(item, linked);
|
|
1986
|
+
table.append(header, body);
|
|
1987
|
+
section.append(heading, table);
|
|
1988
|
+
documentObject.root.append(section);
|
|
1989
|
+
return {
|
|
1990
|
+
$: createWebDollar(documentObject),
|
|
1991
|
+
documentObject,
|
|
1992
|
+
heading,
|
|
1993
|
+
table,
|
|
1994
|
+
anchor,
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
describe("WUI heading-associated table cells", () => {
|
|
1999
|
+
test("cell navigation matches TUI coordinates and bounds", () => {
|
|
2000
|
+
const { $ } = webTableSelector();
|
|
2001
|
+
const cell = $("#table-with-id").cell(1, 0);
|
|
2002
|
+
|
|
2003
|
+
expect(cell.row).toBe(1);
|
|
2004
|
+
expect(cell.col).toBe(0);
|
|
2005
|
+
expect(cell.text()).toBe("item");
|
|
2006
|
+
expect(cell.rt().text()).toBe("open");
|
|
2007
|
+
expect(cell.up().text()).toBe("Name");
|
|
2008
|
+
expect(cell.left()).toBeNull();
|
|
2009
|
+
expect(cell.down()).toBeNull();
|
|
2010
|
+
});
|
|
2011
|
+
|
|
2012
|
+
test("link parent returns its cell and text replacement retains the anchor", () => {
|
|
2013
|
+
const { $, anchor } = webTableSelector();
|
|
2014
|
+
const parent = $(anchor).parent();
|
|
2015
|
+
|
|
2016
|
+
expect(parent.row).toBe(1);
|
|
2017
|
+
expect(parent.col).toBe(1);
|
|
2018
|
+
expect(parent.lt().text()).toBe("item");
|
|
2019
|
+
expect(parent.text("執行")).toBe(parent);
|
|
2020
|
+
expect(parent.text()).toBe("執行");
|
|
2021
|
+
expect(anchor.parentElement.textContent).toBe("執行");
|
|
2022
|
+
expect(anchor.parentElement.children[0]).toBe(anchor);
|
|
2023
|
+
});
|
|
2024
|
+
|
|
2025
|
+
test("cell val returns the first checkbox state or falls back to text", () => {
|
|
2026
|
+
const { $, documentObject, table } = webTableSelector();
|
|
2027
|
+
const checkbox = documentObject.createElement("input");
|
|
2028
|
+
checkbox.type = "checkbox";
|
|
2029
|
+
checkbox.checked = true;
|
|
2030
|
+
table.children[1].children[1].insertBefore(
|
|
2031
|
+
checkbox,
|
|
2032
|
+
table.children[1].children[1].children[0],
|
|
2033
|
+
);
|
|
2034
|
+
|
|
2035
|
+
expect($("#table-with-id").cell(1, 0).val()).toBe("item");
|
|
2036
|
+
expect($("#table-with-id").cell(1, 1).val()).toBe(true);
|
|
2037
|
+
checkbox.checked = false;
|
|
2038
|
+
expect($("#table-with-id").cell(1, 1).val()).toBe(false);
|
|
2039
|
+
const checkedCell = $("#table-with-id").cell(1, 1);
|
|
2040
|
+
expect(checkedCell.val(true)).toBe(checkedCell);
|
|
2041
|
+
expect(checkedCell.val()).toBe(true);
|
|
2042
|
+
expect($("#table-with-id").cell(1, 0).val(false).text()).toBe("item");
|
|
2043
|
+
expect($("#table-with-id").cell(99, 0).val()).toBe("");
|
|
2044
|
+
});
|
|
2045
|
+
});
|
|
2046
|
+
|
|
2047
|
+
describe("WUI heading task-list Array methods", () => {
|
|
2048
|
+
test("uses the first list and follows Array return values", () => {
|
|
2049
|
+
const { $ } = webSelector();
|
|
2050
|
+
const features = $("#features");
|
|
2051
|
+
|
|
2052
|
+
expect(features.push("Export", { value: "Sync", checked: true })).toBe(5);
|
|
2053
|
+
expect(features.val()).toEqual(["Search", "Offline", "Sync"]);
|
|
2054
|
+
expect(features.unshift("First", "Second")).toBe(7);
|
|
2055
|
+
expect(features.shift()).toBe("First");
|
|
2056
|
+
expect(features.pop()).toBe("Sync");
|
|
2057
|
+
expect(features.val()).toEqual(["Search", "Offline"]);
|
|
2058
|
+
});
|
|
2059
|
+
|
|
2060
|
+
test("an empty first list remains the target", () => {
|
|
2061
|
+
const { $, list } = webSelector();
|
|
2062
|
+
const features = $("#features");
|
|
2063
|
+
|
|
2064
|
+
expect(features.shift()).toBe("Search");
|
|
2065
|
+
expect(features.shift()).toBe("Notifications");
|
|
2066
|
+
expect(features.shift()).toBe("Offline");
|
|
2067
|
+
expect(features.shift()).toBeUndefined();
|
|
2068
|
+
expect(features.val()).toEqual([]);
|
|
2069
|
+
expect(features.push({ label: "Again", checked: true })).toBe(1);
|
|
2070
|
+
expect(list.children).toHaveLength(1);
|
|
2071
|
+
expect(features.val()).toEqual(["Again"]);
|
|
2072
|
+
});
|
|
2073
|
+
|
|
2074
|
+
test("splice follows Array indexing and returns removed labels", () => {
|
|
2075
|
+
const { $ } = webSelector();
|
|
2076
|
+
const features = $("#features");
|
|
2077
|
+
|
|
2078
|
+
expect(features.splice()).toEqual([]);
|
|
2079
|
+
expect(features.splice(1, 1, { value: "Replacement", checked: true }, "Extra"))
|
|
2080
|
+
.toEqual(["Notifications"]);
|
|
2081
|
+
expect(features.val()).toEqual(["Search", "Replacement", "Offline"]);
|
|
2082
|
+
expect(features.splice(-1)).toEqual(["Offline"]);
|
|
2083
|
+
expect(features.splice(0, Infinity)).toEqual(["Search", "Replacement", "Extra"]);
|
|
2084
|
+
expect(features.splice(0, 0, { value: "Again", checked: true })).toEqual([]);
|
|
2085
|
+
expect(features.val()).toEqual(["Again"]);
|
|
2086
|
+
});
|
|
2087
|
+
|
|
2088
|
+
test("slice returns read-only item snapshots with checked state", () => {
|
|
2089
|
+
const { $ } = webSelector();
|
|
2090
|
+
const features = $("#features");
|
|
2091
|
+
|
|
2092
|
+
const result = features.slice(0, 2);
|
|
2093
|
+
expect(result).toEqual([
|
|
2094
|
+
{ value: "Search", checked: true },
|
|
2095
|
+
{ value: "Notifications", checked: false },
|
|
2096
|
+
]);
|
|
2097
|
+
result[0].value = "Changed snapshot";
|
|
2098
|
+
result[0].checked = false;
|
|
2099
|
+
expect(features.slice(-1)).toEqual([{ value: "Offline", checked: true }]);
|
|
2100
|
+
expect(features.val()).toEqual(["Search", "Offline"]);
|
|
2101
|
+
});
|
|
2102
|
+
});
|
|
2103
|
+
|
|
2104
|
+
describe("WUI heading section visibility", () => {
|
|
2105
|
+
test("hide promotes the heading and hides its section", () => {
|
|
2106
|
+
const { $, documentObject, section, heading } = webSelector();
|
|
2107
|
+
const features = $("#features");
|
|
2108
|
+
|
|
2109
|
+
expect(features.hide()).toBe(features);
|
|
2110
|
+
expect(documentObject.root.children).toEqual([heading, section]);
|
|
2111
|
+
expect(heading.parentElement).toBe(documentObject.root);
|
|
2112
|
+
expect(section.hidden).toBe(true);
|
|
2113
|
+
expect(documentObject._mdcuiIdStore.get("features").headingVisibility.section).toBe(section);
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
test("show restores the heading as the section's first item", () => {
|
|
2117
|
+
const { $, documentObject, section, heading, list } = webSelector();
|
|
2118
|
+
const features = $("#features");
|
|
2119
|
+
|
|
2120
|
+
features.hide().show();
|
|
2121
|
+
expect(documentObject.root.children).toEqual([section]);
|
|
2122
|
+
expect(section.children).toEqual([heading, list]);
|
|
2123
|
+
expect(section.firstChild).toBe(heading);
|
|
2124
|
+
expect(section.hidden).toBe(false);
|
|
2125
|
+
expect(documentObject._mdcuiIdStore.has("features")).toBe(false);
|
|
2126
|
+
});
|
|
2127
|
+
|
|
2128
|
+
test("toggle switches between the promoted and restored states", () => {
|
|
2129
|
+
const { $, section, heading } = webSelector();
|
|
2130
|
+
const features = $("#features");
|
|
2131
|
+
|
|
2132
|
+
features.toggle();
|
|
2133
|
+
expect(section.hidden).toBe(true);
|
|
2134
|
+
expect(heading.parentElement).not.toBe(section);
|
|
2135
|
+
features.toggle();
|
|
2136
|
+
expect(section.hidden).toBe(false);
|
|
2137
|
+
expect(heading.parentElement).toBe(section);
|
|
2138
|
+
});
|
|
2139
|
+
|
|
2140
|
+
test("show, hide, and toggle update a child while its parent is hidden", () => {
|
|
2141
|
+
const scenarios = [
|
|
2142
|
+
{ initiallyHidden: false, method: "hide", visible: false },
|
|
2143
|
+
{ initiallyHidden: true, method: "show", visible: true },
|
|
2144
|
+
{ initiallyHidden: false, method: "toggle", visible: false },
|
|
2145
|
+
{ initiallyHidden: true, method: "toggle", visible: true },
|
|
2146
|
+
];
|
|
2147
|
+
|
|
2148
|
+
for (const scenario of scenarios) {
|
|
2149
|
+
const {
|
|
2150
|
+
$, documentObject, outerSection, childSection, childHeading,
|
|
2151
|
+
} = nestedWebSelector();
|
|
2152
|
+
const first = $("#first");
|
|
2153
|
+
const child = $("#child");
|
|
2154
|
+
if (scenario.initiallyHidden) child.hide();
|
|
2155
|
+
first.hide();
|
|
2156
|
+
|
|
2157
|
+
child[scenario.method]();
|
|
2158
|
+
|
|
2159
|
+
expect(outerSection.hidden).toBe(true);
|
|
2160
|
+
expect(documentObject._mdcuiIdStore.get("first").headingVisibility.hidden)
|
|
2161
|
+
.toBe(true);
|
|
2162
|
+
first.show();
|
|
2163
|
+
expect(Boolean(childSection.hidden)).toBe(!scenario.visible);
|
|
2164
|
+
expect(Boolean(
|
|
2165
|
+
documentObject._mdcuiIdStore.get("child")?.headingVisibility?.hidden,
|
|
2166
|
+
)).toBe(!scenario.visible);
|
|
2167
|
+
expect(childHeading.parentElement === childSection).toBe(scenario.visible);
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
});
|
|
2171
|
+
|
|
2172
|
+
describe("WUI id-centered user data", () => {
|
|
2173
|
+
test("document store and DOM expose the exact same data object", () => {
|
|
2174
|
+
const { $, documentObject, heading } = webSelector();
|
|
2175
|
+
const features = $("#features");
|
|
2176
|
+
const data = features.data();
|
|
2177
|
+
|
|
2178
|
+
data.count = 1;
|
|
2179
|
+
expect(features.data()).toBe(data);
|
|
2180
|
+
expect(documentObject._mdcuiIdStore.get("features").data).toBe(data);
|
|
2181
|
+
expect(heading.mdcuiData).toBe(data);
|
|
2182
|
+
expect(features.data("count")).toBe(1);
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2185
|
+
test("document data survives DOM removal and reattaches to a replacement", () => {
|
|
2186
|
+
const { $, documentObject, heading } = webSelector();
|
|
2187
|
+
const features = $("#features");
|
|
2188
|
+
const model = { ready: true };
|
|
2189
|
+
features.data("model", model);
|
|
2190
|
+
|
|
2191
|
+
heading.remove();
|
|
2192
|
+
expect(features.data("model")).toBe(model);
|
|
2193
|
+
|
|
2194
|
+
const replacement = documentObject.createElement("h2");
|
|
2195
|
+
replacement.id = "features";
|
|
2196
|
+
documentObject.root.append(replacement);
|
|
2197
|
+
expect(features.data()).toBe(documentObject._mdcuiIdStore.get("features").data);
|
|
2198
|
+
expect(replacement.mdcuiData).toBe(features.data());
|
|
2199
|
+
});
|
|
2200
|
+
|
|
2201
|
+
test("removeData supports keys and keeps framework visibility state", () => {
|
|
2202
|
+
const { $, documentObject, heading } = webSelector();
|
|
2203
|
+
const features = $("#features");
|
|
2204
|
+
|
|
2205
|
+
features.data({ count: 1, label: "x" }).hide();
|
|
2206
|
+
features.removeData(["count", "label"]);
|
|
2207
|
+
expect(features.data()).toEqual({});
|
|
2208
|
+
features.data("temporary", true).removeData();
|
|
2209
|
+
expect(heading.mdcuiData).toBeUndefined();
|
|
2210
|
+
expect(documentObject._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
|
|
2211
|
+
});
|
|
2212
|
+
|
|
2213
|
+
test("selection ids allow a selection to be wrapped again", () => {
|
|
2214
|
+
const { $, documentObject, heading } = webSelector();
|
|
2215
|
+
const features = $("#features");
|
|
2216
|
+
const nested = $($($(features)));
|
|
2217
|
+
|
|
2218
|
+
expect(features.id).toBe("features");
|
|
2219
|
+
expect(nested.id).toBe("features");
|
|
2220
|
+
features.data("count", 1);
|
|
2221
|
+
expect(nested.data()).toBe(features.data());
|
|
2222
|
+
expect(nested.text()).toBe(heading.textContent);
|
|
2223
|
+
nested.hide();
|
|
2224
|
+
expect(documentObject._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
|
|
2225
|
+
});
|
|
2226
|
+
|
|
2227
|
+
test("plain id objects resolve headings and nested data survives DOM removal", () => {
|
|
2228
|
+
const { $, documentObject, heading } = webSelector();
|
|
2229
|
+
const byId = $({ id: "features" });
|
|
2230
|
+
const nested = $($($("#features")));
|
|
2231
|
+
|
|
2232
|
+
expect(byId.text()).toBe(heading.textContent);
|
|
2233
|
+
byId.data("count", 2);
|
|
2234
|
+
expect($("#features").data("count")).toBe(2);
|
|
2235
|
+
byId.hide();
|
|
2236
|
+
expect(documentObject._mdcuiIdStore.get("features").headingVisibility.hidden).toBe(true);
|
|
2237
|
+
byId.show();
|
|
2238
|
+
expect(documentObject._mdcuiIdStore.get("features").headingVisibility).toBeUndefined();
|
|
2239
|
+
|
|
2240
|
+
heading.remove();
|
|
2241
|
+
expect(nested.data("count")).toBe(2);
|
|
2242
|
+
nested.data("afterRemoval", true);
|
|
2243
|
+
expect(documentObject._mdcuiIdStore.get("features").data.afterRemoval).toBe(true);
|
|
2244
|
+
});
|
|
2245
|
+
|
|
2246
|
+
test("legal object ids discard decoy fields through arbitrary nesting", () => {
|
|
2247
|
+
const { $, heading } = webSelector();
|
|
2248
|
+
const objectTarget = {
|
|
2249
|
+
id: "features",
|
|
2250
|
+
innerHTML: "DECOY HTML",
|
|
2251
|
+
textContent: "DECOY TEXT",
|
|
2252
|
+
value: "DECOY VALUE",
|
|
2253
|
+
};
|
|
2254
|
+
const direct = $(objectTarget);
|
|
2255
|
+
expect(direct.text()).toBe(heading.textContent);
|
|
2256
|
+
expect(direct.val()).toEqual(["Search", "Offline"]);
|
|
2257
|
+
objectTarget.id = "missing";
|
|
2258
|
+
let nested = direct;
|
|
2259
|
+
for (let depth = 0; depth < 8; depth++) nested = $(nested);
|
|
2260
|
+
|
|
2261
|
+
expect(nested.id).toBe("features");
|
|
2262
|
+
expect(nested.text()).toBe(heading.textContent);
|
|
2263
|
+
expect(nested.val()).toEqual(["Search", "Offline"]);
|
|
2264
|
+
expect(nested.val("Ignored")).toBe(nested);
|
|
2265
|
+
expect(heading.textContent).toBe("Features");
|
|
2266
|
+
expect(objectTarget.value).toBe("DECOY VALUE");
|
|
2267
|
+
expect(nested.push({ value: "Nested addition", checked: true })).toBe(4);
|
|
2268
|
+
expect(nested.val()).toEqual(["Search", "Offline", "Nested addition"]);
|
|
2269
|
+
});
|
|
2270
|
+
|
|
2271
|
+
test("Unicode and numeric element IDs remain canonical through object wrapping", () => {
|
|
2272
|
+
const { $, heading } = webSelector();
|
|
2273
|
+
heading.classList.add("field");
|
|
2274
|
+
|
|
2275
|
+
for (const id of ["中文-設定", "2026"]) {
|
|
2276
|
+
heading.id = id;
|
|
2277
|
+
let nested = $({ id, value: "decoy" });
|
|
2278
|
+
for (let depth = 0; depth < 4; depth++) nested = $(nested);
|
|
2279
|
+
expect(nested.id).toBe(id);
|
|
2280
|
+
expect(nested.text()).toBe("Features");
|
|
2281
|
+
expect(nested.data()).toBe($(`#${id}`).data());
|
|
2282
|
+
expect($(`h2#${id}.field`).text()).toBe("Features");
|
|
2283
|
+
expect($(`#${id}.field`).text()).toBe("Features");
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2286
|
+
});
|
|
2287
|
+
|
|
2288
|
+
describe("WUI object target wrapper", () => {
|
|
2289
|
+
test("supports $(this).text() and $(event.target).text()", () => {
|
|
2290
|
+
const { $, documentObject } = webSelector();
|
|
2291
|
+
const link = documentObject.createElement("a");
|
|
2292
|
+
link.append(documentObject.createTextNode("Save"));
|
|
2293
|
+
const wrapped = $(link);
|
|
2294
|
+
|
|
2295
|
+
expect(wrapped.text()).toBe("Save");
|
|
2296
|
+
expect(wrapped.text("Saved")).toBe(wrapped);
|
|
2297
|
+
expect(link.textContent).toBe("Saved");
|
|
2298
|
+
expect($(link).text()).toBe("Saved");
|
|
2299
|
+
});
|
|
2300
|
+
|
|
2301
|
+
test("val uses an object's value property or falls back to textContent", () => {
|
|
2302
|
+
const { $, documentObject } = webSelector();
|
|
2303
|
+
const input = documentObject.createElement("input");
|
|
2304
|
+
input.value = "old";
|
|
2305
|
+
const link = documentObject.createElement("a");
|
|
2306
|
+
link.append(documentObject.createTextNode("Open"));
|
|
2307
|
+
|
|
2308
|
+
expect($(input).val()).toBe("old");
|
|
2309
|
+
$(input).val("new");
|
|
2310
|
+
expect(input.value).toBe("new");
|
|
2311
|
+
expect($(link).val()).toBe("Open");
|
|
2312
|
+
$(link).val("Close");
|
|
2313
|
+
expect(link.textContent).toBe("Close");
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
test("heading html hides the internal first-character toggle wrapper", () => {
|
|
2317
|
+
const { $ } = webSelector();
|
|
2318
|
+
const copy = {
|
|
2319
|
+
innerHTML: '<em><span class="mdcui-heading-toggle">T</span>itle</em>',
|
|
2320
|
+
querySelectorAll() {
|
|
2321
|
+
return [{
|
|
2322
|
+
childNodes: ["T"],
|
|
2323
|
+
replaceWith() {
|
|
2324
|
+
copy.innerHTML = "<em>Title</em>";
|
|
2325
|
+
},
|
|
2326
|
+
}];
|
|
2327
|
+
},
|
|
2328
|
+
};
|
|
2329
|
+
const heading = {
|
|
2330
|
+
tagName: "H2",
|
|
2331
|
+
cloneNode() {
|
|
2332
|
+
return copy;
|
|
2333
|
+
},
|
|
2334
|
+
};
|
|
2335
|
+
|
|
2336
|
+
expect($(heading).html()).toBe("<em>Title</em>");
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2339
|
+
test("invalid object ids stay generic while missing legal ids do not", () => {
|
|
2340
|
+
const { $ } = webSelector();
|
|
2341
|
+
const invalidTargets = [
|
|
2342
|
+
{ id: "-invalid", value: "leading punctuation" },
|
|
2343
|
+
{ id: "invalid id", value: "spaced" },
|
|
2344
|
+
];
|
|
2345
|
+
const missing = { id: "missing", value: "decoy", textContent: "decoy" };
|
|
2346
|
+
|
|
2347
|
+
for (const target of invalidTargets) {
|
|
2348
|
+
const wrapped = $(target);
|
|
2349
|
+
expect(wrapped.id).toBe("");
|
|
2350
|
+
expect(wrapped.val()).toBe(target.value);
|
|
2351
|
+
wrapped.val("updated");
|
|
2352
|
+
expect(target.value).toBe("updated");
|
|
2353
|
+
}
|
|
2354
|
+
expect($(missing).val()).toBe("");
|
|
2355
|
+
$(missing).val("ignored");
|
|
2356
|
+
expect(missing.value).toBe("decoy");
|
|
2357
|
+
expect(missing.textContent).toBe("decoy");
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
test("pure ids with selector punctuation use id lookup", () => {
|
|
2361
|
+
const documentObject = new TestDocument();
|
|
2362
|
+
const section = documentObject.createElement("section");
|
|
2363
|
+
const heading = documentObject.createElement("h2");
|
|
2364
|
+
heading.id = "feature:item";
|
|
2365
|
+
heading.append(documentObject.createTextNode("Punctuation ID"));
|
|
2366
|
+
section.append(heading);
|
|
2367
|
+
documentObject.root.append(section);
|
|
2368
|
+
documentObject.querySelector = () => {
|
|
2369
|
+
throw new SyntaxError("CSS parser treats ':' as a pseudo-class");
|
|
2370
|
+
};
|
|
2371
|
+
const $ = createWebDollar(documentObject);
|
|
2372
|
+
|
|
2373
|
+
expect($("#feature:item").text()).toBe("Punctuation ID");
|
|
2374
|
+
expect($({ id: "feature:item", textContent: "DECOY" }).text())
|
|
2375
|
+
.toBe("Punctuation ID");
|
|
2376
|
+
});
|
|
2377
|
+
|
|
2378
|
+
test("a missing literal colon ID cannot fall through to a CSS pseudo-selector", () => {
|
|
2379
|
+
const documentObject = new TestDocument();
|
|
2380
|
+
const section = documentObject.createElement("section");
|
|
2381
|
+
const heading = documentObject.createElement("h2");
|
|
2382
|
+
heading.id = "feature";
|
|
2383
|
+
heading.append(documentObject.createTextNode("Wrong CSS match"));
|
|
2384
|
+
section.append(heading);
|
|
2385
|
+
documentObject.root.append(section);
|
|
2386
|
+
documentObject.querySelector = selector =>
|
|
2387
|
+
selector === "#feature:first-child" ? heading : null;
|
|
2388
|
+
const $ = createWebDollar(documentObject);
|
|
2389
|
+
|
|
2390
|
+
expect($("#feature:first-child").id).toBe("feature:first-child");
|
|
2391
|
+
expect($("#feature:first-child").text()).toBe("");
|
|
2392
|
+
});
|
|
2393
|
+
});
|