Skip to content

test: close mutation coverage gaps for TypewriterCaption; exclude real-coding-agent-loaders.ts - #271

Merged
tbrandenburg merged 1 commit into
mainfrom
fix/mutation-gaps-typewriter-caption-real-coding-agent-loaders
Aug 31, 2026
Merged

test: close mutation coverage gaps for TypewriterCaption; exclude real-coding-agent-loaders.ts#271
tbrandenburg merged 1 commit into
mainfrom
fix/mutation-gaps-typewriter-caption-real-coding-agent-loaders

Conversation

@tbrandenburg

Copy link
Copy Markdown
Owner

Summary

Resolves 2 open mutation-gap issues.

Issue File Before After Resolution
#226 src/renderer/components/TypewriterCaption.tsx 0% (0 tests) 83.33% (30/36 killed) new test file, above the 60% floor
#269 src/main/agent/test-support/real-coding-agent-loaders.ts 0% (below floor) N/A Stryker config exclusion, same precedent as #223

TypewriterCaption.tsx test approach

This is the first timer-based component test in this repo. Uses vi.useFakeTimers() + vi.advanceTimersByTime() wrapped in act() (from @testing-library/react, matching InputDialog.test.tsx's existing convention) to deterministically drive the setInterval-based character-by-character typing animation. Covers: initial empty render + cursor presence, per-tick reveal and full completion, self-clearing behavior (verified via clearInterval spy + vi.getTimerCount(), not just visible text — .slice() naturally clamps and would mask whether the interval actually stopped), label-prop-change restart (asserting the stale interval is torn down before the new one starts), the set-working store push overriding the label prop, visible: false hiding the component, and two unmount scenarios (mid-animation cleanup, and no redundant clearInterval once the interval already self-cleared).

5 mutants remain unkilled (83.33%, still well above the 60% floor):

  • 1 NoCoverageuseState("")'s initial-value literal, overwritten synchronously by the effect (setDisplayed("")) before any assertion can observe the pre-effect state, since Testing Library flushes effects during render().
  • 4 Survived — a defensive if (intervalRef.current !== null) clearInterval(...) guard at the top of the effect (3 mutants: if(true), if(false), !=====) that's dead code under normal React effect-cleanup semantics (React always runs the previous effect's cleanup, nulling the ref, before re-running on a dependency change), plus 1 equivalent-mutant null-guard inside the interval callback that's always non-null on every reachable execution path.

These are documented as effectively-equivalent/dead-code mutants (defensive redundant guards), not missing behavioral coverage — driving them to true/hitting the "already non-null" branch would require bypassing React's own effect lifecycle guarantees, which isn't a realistic test scenario.

Why #269 got a config exclusion instead of a test

Identical pattern to the already-resolved #223: real-coding-agent-loaders.ts is a test-support re-export helper that trivially forwards real @earendil-works/pi-coding-agent symbols via a normal static import, with zero independent branching logic. Testing it would mean mocking the real library just to assert a wrapper calls through, defeating its purpose.

Validation commands run

npx vitest run src/renderer/components/TypewriterCaption.test.tsx
# 1 file, 8 tests passed

npx vitest run
# 475 passed, 15 pre-existing failures in src/main/model/** (unrelated to this change,
# same failure set confirmed unrelated in prior mutation-gap PRs #268/#270)

npx stryker run --mutate "src/renderer/components/TypewriterCaption.tsx"   # 83.33%, 30/36 killed
node -e "import('./stryker.config.mjs').then(m => console.log(m.default.mutate))"  # confirms both exclusions present

npm run check          # typecheck clean (renderer + main + workspaces)
npx oxlint <the new test file + stryker.config.mjs>   # 0 errors

Risks / follow-ups

  • Same pre-existing src/main/model/** test failures as prior rounds, confirmed unrelated (untouched by this branch's diff).
  • No further mutation-gap follow-up issues discovered this round.

Closes #226
Closes #269

…l-coding-agent-loaders.ts

Resolves 2 open mutation-gap issues:

- TypewriterCaption.tsx: 0% -> 83.33% (30/36 mutants killed, above the
  60% floor). Uses vi.useFakeTimers()/advanceTimersByTime() wrapped in
  act() to deterministically drive the character-by-character typing
  interval. Covers initial render, per-tick reveal, self-clearing on
  completion (no overshoot), label-change restart, the set-working
  store push overriding the label prop, visible:false hiding the
  component, and unmount cleanup (both mid-animation and post-completion).
  5 mutants remain unkilled: 3 are a defensive null-guard
  (intervalRef.current !== null before the effect's own clearInterval)
  that's dead code under normal React effect-cleanup semantics, 1 is an
  equivalent-mutant null-guard inside the interval callback, and 1 is
  the useState('') initial-value literal that gets overwritten
  synchronously before any assertion can observe it. All five are
  documented in the test file/PR as effectively-equivalent mutants, not
  missing behavioral coverage.
- real-coding-agent-loaders.ts: excluded from Stryker's mutate glob,
  following the exact same precedent as real-agent-core-loaders.ts
  (issue #223): a test-support re-export helper with no independent
  branching logic of its own.

Closes #226, #269
@github-actions

Copy link
Copy Markdown

⚠️ Test duration budget warning

@tbrandenburg the test suite exceeded its duration budget:

  • [SOFT] /home/runner/work/pi-desktop/pi-desktop/src/main/settings/store.test.ts (node) took 2.60s (threshold: 1.0s)

@tbrandenburg
tbrandenburg merged commit b2778f4 into main Aug 31, 2026
1 check passed
@tbrandenburg
tbrandenburg deleted the fix/mutation-gaps-typewriter-caption-real-coding-agent-loaders branch August 31, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant