Pin the pick-gate disclosure at the render layer, not only the question spec - #657
Pin the pick-gate disclosure at the render layer, not only the question spec#657philcunliffe wants to merge 2 commits into
Conversation
The defaults gate's accept option carries the only side-effect disclosure on the wizard's happy path, but the only test holding it was at the question-spec layer. Nothing asserted the summary reached the screen, so a renderer that stopped forwarding `summary` would leave the suite green and the disclosure gone - the same failure shape that lost the picker copy the first time. Adds four render-layer tests: two on `renderSelect` directly, one driving the real TUI select prompt end to end through `defaultConfirmSelectPromptFactory`, and one on the non-TTY numbered fallback. The TTY and non-TTY tests replay the gate question captured off `runWizardPick`, so they assert the shipped copy in the rendered bytes rather than a hand-written fixture. Test-only: no production code changed. Co-Authored-By: Claude <noreply@anthropic.com>
captureGateQuestion() mkdtemp'd a real HOME with no cleanup, so every run of this file leaked a /tmp/hypaware-pick-gate-disclosure-* dir. Register test.after cleanup at the point of creation, matching the tempRoot() helper pattern in test/core/usage-policy-symlink.test.js.
Neutral review round 1 -
|
| broken link | claimed | measured |
|---|---|---|
renderSelect stops printing o.summary |
3575 / 3 | 3575 / 3 |
tuiConfirmSelectPromptFactory stops forwarding |
3577 / 1 | 3577 / 1 |
select() stops copying into SelectState |
3577 / 1 | 3577 / 1 |
| legacy factory stops printing | 3577 / 1 | 3577 / 1 |
Each edit was verified with git diff before its run. Worth recording: the reviewer's first attempt used a python3 heredoc, python3 is absent here, the edit silently no-opped and the suite came back green at 3578 - the exact trap flagged on the last PR. It was caught because git diff --stat printed nothing. An unapplied mutation is indistinguishable from a test that misses the bug.
In every case the only failures were the new tests, which confirms nothing pre-existing covered any of the four links.
Three extra mutations probed assertion quality, all diff-verified: concatenating the summary onto its row (3 fail), painting it cyan instead of dim (1 fail), and dropping summary from pick.js entirely (3 fail). So the assertions pin position and styling, not mere presence.
The tests genuinely replay the shipped question. captureGateQuestion() runs the real runWizardPick over real discoverBundledPlugins() + buildPluginCatalog() with detect seeded so the gate opens, and captures the object handed to confirm; pick.js:420 is the only confirm( call site in that module, so there is no ambiguity. The wording is read off the live option rather than hardcoded, which is why the "drop summary from pick.js" mutation kills these tests.
No meaningful duplication. render.test.js covered only the multiselect summary branch (six-space indent); the select branch (four-space) was uncovered, which mutation 1 proves. pick.test.js pins the wording at the spec layer; this file deliberately reads it live instead of re-pinning it.
Deterministic. Colour comes solely from the passed env, never ambient process.env or FORCE_COLOR; width falls back to 80 since PassThrough has no columns, and render.js does no wrapping. Verified 4/4 across 5 consecutive runs plus NO_COLOR=1, FORCE_COLOR=3, COLUMNS=40, HYP_NO_TUI=1, and CI=true TERM=dumb.
Finding, fixed in 4e0273f
LOW - the helper leaked a temp directory per run. captureGateQuestion() called fs.mkdtemp with no cleanup, and runWizardPick genuinely writes a .hyp/hypaware-config.json into it, so 26 orphaned dirs accumulated across the review's runs. Contained inside the temp HOME so nothing escaped, and a fair number of existing test files leak the same way, but cheap to close.
Fixed by registering test.after(() => fs.rm(tmp, { recursive: true, force: true })) at the creation site, following usage-policy-symlink.test.js's tempRoot() pattern (cleanup travels with creation, which is what this file's memoized shared helper needs, since a per-test t.after would require the mkdtemp inside each test body). Verified 0 leftovers across 6 runs, including a deliberately failed run to confirm cleanup survives failure.
Noted, not fixed
Test 2 pins dim (\x1b[2m) but does not assert the absence of the row colour, so bold+cyan+dim would still pass. Cosmetic. Test 4's stdout stub replies only to chunks starting with select; changing that prefix does not hang, it surfaces as cancelledByParent with a non-zero exit, so CI still goes red, just less legibly.
Scope is one test file, zero production lines. npm test 3578 pass / 0 fail / 1 skip, tsc clean, @ref LLP 0190#pick-gate resolves.
Neutral review round 2 -
|
The pick gate's accept option carries the wizard's only side-effect disclosure on the happy path ("Configures these tools to record through HypAware; the menu rows say how."), but the only test holding it was at the question-spec layer (
test/core/cli/wizard/pick.test.js). Nothing asserted it reached the screen, so a renderer that stopped forwardingsummarywould leave the suite green and the disclosure gone: the same failure shape that lost the picker copy the first time.Test-only change. No production code was modified.
What was added
test/core/cli/pick-gate-disclosure.test.js(4 tests):renderSelect: an option summary lands on its own indented line under its row- pure frame assertion againstsrc/core/cli/tui/render.js, covering the cursor row and a non-cursor row.renderSelect: the summary line is dim, not the row colour- pins the styling in the coloured frame.TTY gate: the accept disclosure reaches the screen through the real select prompt- drivesdefaultConfirmSelectPromptFactoryover fake-TTY streams, so the assertion runs the whole chain: question spec,tuiConfirmSelectPromptFactory,select()intoSelectState,renderSelect, bytes.non-TTY gate: the numbered fallback prints the accept disclosure under its row- drives the same factory over non-TTY streams intolegacyConfirmSelectPromptFactory.The two end-to-end tests replay the gate question captured off
runWizardPickrather than a hand-written fixture, so they assert the shipped copy in the rendered bytes. A copy edit inpick.jstravels here for free; the wording itself stays pinned at the spec layer bypick.test.js, so this is not a duplicate of that assertion.Landed in a dedicated file rather than
test/core/walkthrough-prompt.test.js, which keeps it clear of open PR #648.Mutation table
Each forwarding link was broken in turn and the full suite re-run (
node scripts/run-tests.js), then reverted. Baseline: 3578 pass / 0 fail.renderSelectstops printingo.summaryrenderSelecttests + TTY gatetuiConfirmSelectPromptFactorystops forwardingsummarytui/index.jsselect()stops copyingsummaryintoSelectStatelegacyConfirmSelectPromptFactorystops printingopt.summaryNo link survived. The failure counts also show no pre-existing test covers any of these four links: every failure under every mutation is one of the new tests.
Checks
node scripts/run-tests.js: 3578 pass, 0 fail, 1 skippednpx tsc -p tsconfig.json --noEmit: cleanFixes #636
🤖 Generated with Claude Code