Skip to content

test(website): cover six app surfaces that had no tests at all - #2514

Merged
kyleseaman merged 1 commit into
mainfrom
test/frontend-zero-coverage
Aug 10, 2026
Merged

test(website): cover six app surfaces that had no tests at all#2514
kyleseaman merged 1 commit into
mainfrom
test/frontend-zero-coverage

Conversation

@CrysisDeu

Copy link
Copy Markdown
Collaborator

What

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 in six new files.

component statements before after
mochi/src/renderer/ChatPanel.tsx 650 0% 88.6%
design-critique/DesignCritiquePage.tsx 568 0% 89.1%
md-notebook/MdNotebookPage.tsx 564 0% 79.6%
crew-companion/pet.tsx 310 0% 92.9%
crew-companion/GalleryPanel.tsx 282 0% 95.4%
mochi/src/renderer/PetWidget.tsx 244 0% 100%

+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.

  1. ChatPanel image extraction corrupts remote image URLs. bareRe matches the //host/path.png portion of an ordinary https URL, so a reply containing ![logo](https://example.com/logo.png) 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 ![logo](https:).
  2. MdNotebookPage does not validate the persisted auto-sync interval. Panel width is range-checked and the sort id is membership-checked; 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 until timeout.
  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 ("must not wander off while the user is picking an avatar") 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 code — 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.

Testing

Verified in the worktree, not self-reported:

  • npx vitest run332/332 pass across all six files
  • npx tsc --noEmit — clean
  • npx eslint on all six files — clean
  • npm run jscpd0 clones (relevant: 5,006 lines of new test code)
  • Coverage measured with the reporter scoped to the four app directories

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

  • A second GalleryPanel.tsx (mochi renderer, 204 statements) is still at 0% — a separate target, not in this PR.
  • PackEditor / SpriteImporter internals and petdexImport's canvas decode are stubbed at the module boundary; each has its own tests, and firstFramePreview uses new Image() + canvas, which never settles under happy-dom.
  • AnimThumbnail's Lottie branch would boot lottie-web inside happy-dom, which needs a real renderer.
  • The built-in-ghost-only blocks in DetailPanel are unreachable through the UI (finding 4), so covering them would mean fabricating a pack shape no backend produces.

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 ![logo](https:).
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.
@CrysisDeu
CrysisDeu requested a review from a team as a code owner August 10, 2026 08:51
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

Advisory UX-level review of 07b074704718b81e39178882ef8aaf9578839ab9 — updated in place on each push; does not block merge.

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

@github-actions

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 07b074704718b81e39178882ef8aaf9578839ab9 — this comment is updated in place on each push.

Review details

I've read the entire diff. This PR adds six new frontend test files (website/src/test/*.coverage.test.tsx) and modifies no production code.

Assessing against the review bar:

  • AUTOSDE rules: The backend no-test-side-effects rule is Python-only (test/**/*.py). The frontend security/lucide/emoji rules match src/**/*.tsx by pattern, but their intent is UI components — these are test harnesses (mocks, selectors, assertions), not rendered UI, and none introduce a real dangerouslySetInnerHTML, inline SVG icon, or emoji-as-icon on a UI path. No rule violation.
  • Consequence chains: Test files ship to no user. The only system-visible harm would be a hang, a leaked real timer, or cross-test pollution that flakes CI. The tests that use fake timers restore them (vi.useRealTimers() in afterEach); the pet.tsx tests unmount their captured roots and unstub globals; global fetch spies in the Mochi/DesignCritique files are restored in finally. The one leftover — the Gallery file's stubGhostBody spy isn't explicitly restored — is not asserted against by later tests and the suite is verified green, so no chain completes to a real failure.
  • The PR deliberately pins (not fixes) nine documented defects with explanatory comments; those live in untouched production files and are out of scope, and pinning current behavior is not itself a defect.

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 07b074704718b81e39178882ef8aaf9578839ab9.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 07b074704718b81e39178882ef8aaf9578839ab9: <one-sentence reason>

@github-actions

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 07b074704718b81e39178882ef8aaf9578839ab9 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 07b0747

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 07b074704718b81e39178882ef8aaf9578839ab9: <one-sentence reason>

@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Advisory design-level review of 07b074704718b81e39178882ef8aaf9578839ab9 — updated in place on each push; does not block merge.

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

  • File the nine surfaced defects (especially the MdNotebook zero-delay sync loop, deliberately left untested) as tracked issues before merge — a PR body and scattered test comments won't survive as a bug tracker.

[DESIGN-REVIEWED] 07b0747

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Aug 10, 2026
@kyleseaman
kyleseaman merged commit 7e018e1 into main Aug 10, 2026
51 checks passed
@kyleseaman
kyleseaman deleted the test/frontend-zero-coverage branch August 10, 2026 12:18
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Aug 10, 2026
encomjp pushed a commit to encomjp/kirocrew-customapi that referenced this pull request Aug 22, 2026
…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 ![logo](https:).
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants