{"_id":"@danypops/pi-tui-harness","_rev":"2-7f4f4c1a553cbdd094470701ed79a15e","name":"@danypops/pi-tui-harness","dist-tags":{"latest":"0.0.2"},"versions":{"0.0.1":{"name":"@danypops/pi-tui-harness","version":"0.0.1","keywords":["pi","pi-tui","testing","test-harness","terminal","vt100","snapshot-testing"],"license":"MIT","_id":"@danypops/pi-tui-harness@0.0.1","maintainers":[{"name":"danypops","email":"danypops@protonmail.com"}],"homepage":"https://github.com/DanyPops/pi-integral/tree/main/packages/pi-tui-harness#readme","bugs":{"url":"https://github.com/DanyPops/pi-integral/issues"},"dist":{"shasum":"988c55016225ee32380264ef37af1b410534b337","tarball":"https://registry.npmjs.org/@danypops/pi-tui-harness/-/pi-tui-harness-0.0.1.tgz","fileCount":18,"integrity":"sha512-k9aaRoh/Q0uNL6TEoeNdXJ6EX1F3THzezhcgESEA/7I40Cu2Vx3GzIgNbHQ69881pkBFSb8uVlP2xJ6a8ZgzbA==","signatures":[{"sig":"MEYCIQDEBJfCDpog4y+Gy8A4p/LQFqefP99aTjDtGzj80XjsKAIhAJeiIPlug3js8LFK1bKHvF6dEV3oXqJp0obhwj8tOwzS","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":27875},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"dda6f59430dccc4492c0a9ad24660990399a93e0","scripts":{"test":"bun run build && bun test test","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"biome format --write .","typecheck":"tsc --noEmit"},"_npmUser":{"name":"danypops","email":"danypops@protonmail.com"},"repository":{"url":"git+https://github.com/DanyPops/pi-integral.git","type":"git","directory":"packages/pi-tui-harness"},"_npmVersion":"11.14.1","description":"Tests real @earendil-works/pi-tui Component instances without hand-rolled ANSI-stripping regexes: named-key input helpers, and a real VT state machine (@xterm/headless) for structured cell/plain-text assertions and golden-file snapshots -- Ghostty's libgh","directories":{},"_nodeVersion":"22.22.2","dependencies":{"@xterm/headless":"^6.0.0"},"_hasShrinkwrap":false,"devDependencies":{"bun-types":"latest","typescript":"^5.9.2","@types/node":"^22.0.0","@biomejs/biome":"^2.5.6","@earendil-works/pi-tui":"*"},"peerDependencies":{"@earendil-works/pi-tui":"*"},"_npmOperationalInternal":{"tmp":"tmp/pi-tui-harness_0.0.1_1785747488289_0.6276423625029124","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@danypops/pi-tui-harness","version":"0.0.2","description":"Tests real @earendil-works/pi-tui Component instances without hand-rolled ANSI-stripping regexes: named-key input helpers, and a real VT state machine (@xterm/headless) for structured cell/plain-text assertions and golden-file snapshots -- Ghostty's libgh","license":"MIT","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"repository":{"type":"git","url":"git+https://github.com/DanyPops/pi-integral.git","directory":"packages/pi-tui-harness"},"homepage":"https://github.com/DanyPops/pi-integral/tree/main/packages/pi-tui-harness#readme","bugs":{"url":"https://github.com/DanyPops/pi-integral/issues"},"scripts":{"build":"rm -rf dist && tsc -p tsconfig.build.json","test":"bun run build && bun test test","typecheck":"tsc --noEmit","format":"biome format --write ."},"dependencies":{"@xterm/headless":"^6.0.0"},"peerDependencies":{"@earendil-works/pi-tui":"*"},"devDependencies":{"@biomejs/biome":"^2.5.6","@earendil-works/pi-tui":"*","@types/node":"^22.0.0","bun-types":"latest","typescript":"^5.9.2"},"keywords":["pi","pi-tui","testing","test-harness","terminal","vt100","snapshot-testing"],"gitHead":"77fbf7f25ced73b90413c569750ba0d80c030698","_id":"@danypops/pi-tui-harness@0.0.2","_nodeVersion":"24.18.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-XIQI2qNiH6/DSOXWOu27P7CG+ptXJsafbsEiS2LZMmuEMG9Ve5bpVhbC4xgOu8wuZF9ysAZEi8p5L6ZOoK50vw==","shasum":"405a26275396a8839284772a54e95d9b2e4b5852","tarball":"https://registry.npmjs.org/@danypops/pi-tui-harness/-/pi-tui-harness-0.0.2.tgz","fileCount":21,"unpackedSize":33491,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@danypops%2fpi-tui-harness@0.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFw7c1eilEqODeZRWDVuIUhHuxh3Y+omgaBPyOHbo77kAiEAwbD260PfNuw3DGgNF3MlItEsu2zGWtBo2h/6FS9WqrU="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b0786f65-c505-4585-90a8-c7ba58307798"}},"directories":{},"maintainers":[{"name":"danypops","email":"danypops@protonmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pi-tui-harness_0.0.2_1785784757972_0.4919694149138112"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-03T08:58:08.092Z","modified":"2026-08-03T19:19:18.452Z","0.0.1":"2026-08-03T08:58:08.452Z","0.0.2":"2026-08-03T19:19:18.110Z"},"bugs":{"url":"https://github.com/DanyPops/pi-integral/issues"},"license":"MIT","homepage":"https://github.com/DanyPops/pi-integral/tree/main/packages/pi-tui-harness#readme","keywords":["pi","pi-tui","testing","test-harness","terminal","vt100","snapshot-testing"],"repository":{"type":"git","url":"git+https://github.com/DanyPops/pi-integral.git","directory":"packages/pi-tui-harness"},"description":"Tests real @earendil-works/pi-tui Component instances without hand-rolled ANSI-stripping regexes: named-key input helpers, and a real VT state machine (@xterm/headless) for structured cell/plain-text assertions and golden-file snapshots -- Ghostty's libgh","maintainers":[{"name":"danypops","email":"danypops@protonmail.com"}],"readme":"# @danypops/pi-tui-harness\n\nTests real [`@earendil-works/pi-tui`](https://www.npmjs.com/package/@earendil-works/pi-tui)\n`Component` instances without a hand-rolled ANSI-stripping regex. Applies\nGhostty's `libghostty-vt` testing philosophy (Mitchell Hashimoto: \"isolate\nside effects, use a real engine for the rest\") to Pi's own TUI layer.\n\nThree layers, in order of speed and coverage:\n\n1. **`driveComponent`** -- fast, in-process, named-key input helpers. No\n   terminal, no ANSI interpretation, just direct `handleInput()` calls with\n   real legacy byte sequences (cross-checked against `@earendil-works/pi-tui`'s\n   own `matchesKey`).\n2. **`renderToTerminal`** -- feeds a component's real `render()` output\n   through [`@xterm/headless`](https://www.npmjs.com/package/@xterm/headless)\n   (the same VT engine VS Code's integrated terminal uses) for structured\n   `plainLines()`/`cellAt()` assertions -- real bold/underline/color\n   interpretation, not regex guesses.\n3. **`expectSnapshot`** -- golden-file snapshot testing for a captured\n   frame. \"One frame in, one frame out\": tightly control the input state,\n   run one render pass, diff the result against a known-good copy.\n\n`runMultiSelectViewportScenario` supplies the thin production regression path\nfor a four-row multi-select: move to the fifth row, toggle it twice, then move\nback to the first row. Component owners keep navigation rules in pure model\ntests and assert this helper's VT-interpreted frames at the integration layer.\n\nA real subprocess/`node-pty` layer (an actual terminal emulator, real\nkeyboard-driver-level input) is deliberately out of scope for now --\n`driveComponent` + `renderToTerminal` already cover unit and structural\nassertions; a future fourth layer would only add value for behavior that\ndepends on a real pty (resize signals, real terminal capability detection).\n\n## Install\n\n```\nnpm install --save-dev @danypops/pi-tui-harness @earendil-works/pi-tui\n```\n\n## Example\n\n```ts\nimport { driveComponent, renderToTerminal, expectSnapshot } from \"@danypops/pi-tui-harness\";\nimport { MyComponent } from \"../src/my-component.js\";\n\nconst component = new MyComponent();\nconst driven = driveComponent(component);\n\ndriven.pressKey(\"down\");\ndriven.pressKey(\"space\");\n\n// Layer 2: real VT interpretation, structured cell assertions.\nconst terminal = await renderToTerminal(driven.render(40));\nexpect(terminal.plainLines()[0]).toBe(\"[x] selected item\");\nexpect(terminal.cellAt(0, 0)?.bold).toBe(true);\nterminal.dispose();\n\n// Layer 3: golden-file snapshot of one captured frame.\nexpectSnapshot(\"test/__snapshots__/my-component.snap\", terminal.plainLines());\n```\n\nSet `UPDATE_SNAPSHOTS=1` to accept a new golden file after an intentional\nrendering change.\n","readmeFilename":"README.md"}