test(website): cover six app surfaces that had no tests at all - #2514
Conversation
Six components under website/src/apps were at exactly 0% statement coverage -- whole features with not a single test between them. This adds 332 tests across six new files and takes them to 79.6-100%. ChatPanel.tsx (mochi renderer) 0% -> 88.6% DesignCritiquePage.tsx 0% -> 89.1% MdNotebookPage.tsx 0% -> 79.6% pet.tsx (crew-companion) 0% -> 92.9% GalleryPanel.tsx (crew-companion) 0% -> 95.4% PetWidget.tsx (mochi renderer) 0% -> 100% Net +2332 covered statements, moving frontend coverage from 63.28% to 67.11%. The frontend floor is left at 60 in this PR -- ratcheting it is a separate change so a threshold move never rides along with the tests that justify it. Because these files had never been exercised, the tests surfaced nine real defects. None are fixed here: this PR only adds tests, so that the fixes can be reviewed on their own merits rather than buried in 5000 lines of new test code. Where a test would otherwise have had to assert buggy output, it pins the current behaviour and carries a comment naming the defect, so whoever fixes it gets a failing test pointing at the explanation. Findings, worst first: 1. ChatPanel image extraction corrupts remote image URLs. bareRe matches the //host/path.png portion of an ordinary https URL, so a reply containing an https image has the URL torn out of the text and handed to LocalImage as //example.com/logo.png. The read fails, a placeholder renders, and the leftover text shows a broken . 2. MdNotebookPage does not validate the persisted auto-sync interval. Panel width is range-checked and the sort id is membership-checked, but autoSyncMins is not -- clamping lives only in the setter. A stored 0 reaches the effect and schedules setInterval(sync, 0), a zero-delay loop against the backend for the life of the tab. Confirmed empirically: the test asserting the clamped behaviour hung the runner. 3. PetWidget draws the built-in cat's peek art whatever pack is active. The render path computes the pack's own currentSource, discards it for the SVG case, and re-resolves -- returning the compiled-in peek asset with no resolver check. It compounds with peekNudgeFor, which then concludes the pack has no peek art and slides the pet 60px off-screen. 4. The built-in ghost's colour customizer and dress-up row are unreachable. The grid passes onManage only when type === 'custom' and onManage is the only thing that opens the detail sheet, so the built-in pack can only ever be applied. 5. Escape inside the PetDex import dialog closes the whole gallery window. GalleryPanel and ImportPetDialog both listen on window and neither stops propagation. 6. crew-companion's pet keeps fidgeting while the avatar gallery is open. galleryOpenRef is written by the subscription effect and never read -- it appears in no gate anywhere in the file -- so the behaviour its own comment describes does not hold. 7. apps.crewCompanion.gallery.edit carries an orphaned U+FE0F left over from a stripped emoji, rendering a stray leading space and making the button unmatchable by exact accessible name. 8. PetWidget never displays the pet state it fetches at mount. getPetState sets state, but the art renders from displayState, which only moves on an onStateChange event. 9. pet.tsx dismissRef is dead: nothing assigns a timer id, so the clearTimeout branch in dismiss cannot run. Auto-dismiss moved into Bubble's own effect and this ref was left behind. Verified in the worktree: 332/332 vitest pass, tsc --noEmit clean, eslint clean, jscpd 0 clones. Coverage measured with the reporter scoped to the four app directories.
UX Review (Fable 5) — ✅ PASSAdvisory UX-level review of This is a test-only PR — six new test files, no UI code, strings, or screenshots changed. Nothing a user experiences is altered by this diff, so the UX review is a clean pass. (The nine pinned defects the PR documents — off-screen pet slides, an unreachable customizer, Escape closing the wrong window — are real UX bugs, but they pre-exist this branch and the tests correctly pin rather than mask them.) UX-Verdict: PASS Test-only diff: six new test files, zero user-facing surface touched — nothing here changes what a user sees or does. [UX-REVIEWED] 07b0747 |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsI've read the entire diff. This PR adds six new frontend test files ( Assessing against the review bar:
Everything else the tooling owns (coverage, lint, tsc, jscpd) is excluded by the Division of Labour. No completable cause → mechanism → user/system-harm chain exists on any changed line. No findings. [OPUS-REVIEWED] 07b0747 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — ✅ PASSAdvisory design-level review of Design-Verdict: PASS Test-only, additive, behavior-driven; defect-pinning strategy is deliberate and verified against source — sound design with no contract or reversibility risk. Suggestions
[DESIGN-REVIEWED] 07b0747 |
…otdev#2514) Six components under website/src/apps were at exactly 0% statement coverage -- whole features with not a single test between them. This adds 332 tests across six new files and takes them to 79.6-100%. ChatPanel.tsx (mochi renderer) 0% -> 88.6% DesignCritiquePage.tsx 0% -> 89.1% MdNotebookPage.tsx 0% -> 79.6% pet.tsx (crew-companion) 0% -> 92.9% GalleryPanel.tsx (crew-companion) 0% -> 95.4% PetWidget.tsx (mochi renderer) 0% -> 100% Net +2332 covered statements, moving frontend coverage from 63.28% to 67.11%. The frontend floor is left at 60 in this PR -- ratcheting it is a separate change so a threshold move never rides along with the tests that justify it. Because these files had never been exercised, the tests surfaced nine real defects. None are fixed here: this PR only adds tests, so that the fixes can be reviewed on their own merits rather than buried in 5000 lines of new test code. Where a test would otherwise have had to assert buggy output, it pins the current behaviour and carries a comment naming the defect, so whoever fixes it gets a failing test pointing at the explanation. Findings, worst first: 1. ChatPanel image extraction corrupts remote image URLs. bareRe matches the //host/path.png portion of an ordinary https URL, so a reply containing an https image has the URL torn out of the text and handed to LocalImage as //example.com/logo.png. The read fails, a placeholder renders, and the leftover text shows a broken . 2. MdNotebookPage does not validate the persisted auto-sync interval. Panel width is range-checked and the sort id is membership-checked, but autoSyncMins is not -- clamping lives only in the setter. A stored 0 reaches the effect and schedules setInterval(sync, 0), a zero-delay loop against the backend for the life of the tab. Confirmed empirically: the test asserting the clamped behaviour hung the runner. 3. PetWidget draws the built-in cat's peek art whatever pack is active. The render path computes the pack's own currentSource, discards it for the SVG case, and re-resolves -- returning the compiled-in peek asset with no resolver check. It compounds with peekNudgeFor, which then concludes the pack has no peek art and slides the pet 60px off-screen. 4. The built-in ghost's colour customizer and dress-up row are unreachable. The grid passes onManage only when type === 'custom' and onManage is the only thing that opens the detail sheet, so the built-in pack can only ever be applied. 5. Escape inside the PetDex import dialog closes the whole gallery window. GalleryPanel and ImportPetDialog both listen on window and neither stops propagation. 6. crew-companion's pet keeps fidgeting while the avatar gallery is open. galleryOpenRef is written by the subscription effect and never read -- it appears in no gate anywhere in the file -- so the behaviour its own comment describes does not hold. 7. apps.crewCompanion.gallery.edit carries an orphaned U+FE0F left over from a stripped emoji, rendering a stray leading space and making the button unmatchable by exact accessible name. 8. PetWidget never displays the pet state it fetches at mount. getPetState sets state, but the art renders from displayState, which only moves on an onStateChange event. 9. pet.tsx dismissRef is dead: nothing assigns a timer id, so the clearTimeout branch in dismiss cannot run. Auto-dismiss moved into Bubble's own effect and this ref was left behind. Verified in the worktree: 332/332 vitest pass, tsc --noEmit clean, eslint clean, jscpd 0 clones. Coverage measured with the reporter scoped to the four app directories. Co-authored-by: Zezhen Xu <t@t>
What
Six components under
website/src/appswere at exactly 0% statement coverage — whole features with not a single test between them. This adds 332 tests in six new files.mochi/src/renderer/ChatPanel.tsxdesign-critique/DesignCritiquePage.tsxmd-notebook/MdNotebookPage.tsxcrew-companion/pet.tsxcrew-companion/GalleryPanel.tsxmochi/src/renderer/PetWidget.tsx+2,332 covered statements → frontend coverage 63.28% → 67.11%.
Since all six were at zero, no existing test touched these lines, so the gain is exactly additive rather than an estimate.
The frontend floor stays at 60 here. Ratcheting it belongs in its own change — a threshold move should never ride along with the tests that justify it.
Why these six
Backend coverage work has hit diminishing returns: the last two waves converted 1,040 tests into +1.35pp and then 1,883 tests into +0.44pp, because the remaining backend lines sit behind I/O a unit test cannot reach. Untested frontend surfaces convert far better — this PR is 7.0 statements per test, roughly 3x the best backend wave and ~15x the last one.
The other reason is that never-executed code is where the bugs are.
Nine defects the tests surfaced
None are fixed in this PR. It adds tests only, so the fixes can be reviewed on their own merits instead of buried in 5,000 lines of new test code. Where a test would otherwise have had to assert buggy output, it pins current behaviour and carries a comment naming the defect — so whoever fixes it gets a failing test pointing straight at the explanation. Reviewers should read those comments as filed bugs, not as endorsements.
ChatPanelimage extraction corrupts remote image URLs.bareRematches the//host/path.pngportion of an ordinaryhttpsURL, so a reply containinghas the URL torn out of the text and handed toLocalImageas//example.com/logo.png. The read fails, a placeholder renders, and the leftover text shows a broken.MdNotebookPagedoes not validate the persisted auto-sync interval. Panel width is range-checked and the sort id is membership-checked;autoSyncMinsis not — clamping lives only in the setter. A stored0reaches the effect and schedulessetInterval(sync, 0), a zero-delay loop against the backend for the life of the tab. Confirmed empirically: the test asserting the clamped behaviour hung the runner until timeout.PetWidgetdraws the built-in cat's peek art whatever pack is active. The render path computes the pack's owncurrentSource, discards it for the SVG case, and re-resolves — returning the compiled-in peek asset with no resolver check. It compounds withpeekNudgeFor, which then concludes the pack has no peek art and slides the pet 60px off-screen.onManageonly whentype === 'custom', andonManageis the only thing that opens the detail sheet, so the built-in pack can only ever be applied.GalleryPanelandImportPetDialogboth listen onwindowand neither stops propagation.galleryOpenRefis written by the subscription effect and never read — it appears in no gate anywhere in the file — so the behaviour its own comment describes ("must not wander off while the user is picking an avatar") does not hold.apps.crewCompanion.gallery.editcarries an orphanedU+FE0Fleft over from a stripped emoji, rendering a stray leading space and making the button unmatchable by exact accessible name.PetWidgetnever displays the pet state it fetches at mount.getPetStatesetsstate, but the art renders fromdisplayState, which only moves on anonStateChangeevent.pet.tsxdismissRefis dead code — nothing assigns a timer id, so theclearTimeoutbranch indismisscannot run. Auto-dismiss moved intoBubble's own effect and this ref was left behind.Testing
Verified in the worktree, not self-reported:
npx vitest run— 332/332 pass across all six filesnpx tsc --noEmit— cleannpx eslinton all six files — cleannpm run jscpd— 0 clones (relevant: 5,006 lines of new test code)No existing file is modified — the diff is six new files and nothing else. Tests avoid real network, real elapsed timers, fixed ports, and the developer's home directory; interactions are driven synchronously so nothing depends on wall-clock timing or test order.
Not covered, deliberately
GalleryPanel.tsx(mochi renderer, 204 statements) is still at 0% — a separate target, not in this PR.PackEditor/SpriteImporterinternals andpetdexImport's canvas decode are stubbed at the module boundary; each has its own tests, andfirstFramePreviewusesnew Image()+ canvas, which never settles under happy-dom.AnimThumbnail's Lottie branch would bootlottie-webinside happy-dom, which needs a real renderer.DetailPanelare unreachable through the UI (finding 4), so covering them would mean fabricating a pack shape no backend produces.