Skip to content

Test coverage analysis: gaps and proposed improvements #139

@jacuzzicoding

Description

@jacuzzicoding

Summary

True coverage across src/ is 20.7% statements / 46.9% functions (129 tests across 9 files). The vitest.config.ts coverage.include is scoped to src/lib/**, which inflates the headline number and hides large gaps.

Re-running with full scope:

npx vitest run --coverage \
  --coverage.include='src/**' \
  --coverage.exclude='src/lib/types.ts' \
  --coverage.exclude='src/**/*.test.*' \
  --coverage.exclude='src/test/**' \
  --coverage.exclude='src/main.tsx' \
  --coverage.exclude='src/vite-env.d.ts'
Area Statements
src/lib 45.7%
src/components 16.6%
src/hooks 0%
src/App.tsx 0%
All files 20.7%

Highest-leverage gaps (ranked by blast radius)

1. Critical data-mutation gaps in src/lib/store.ts (43% covered)

The covered half is addDonation / removeDonation / createTown / basic CRUD. The uncovered half is the dangerous half:

  • applyImportedSave (lines 188–284) — all three branches (create / replace / merge) are untested in the store layer. saveFileReconcile.test.ts covers planning, but the action that actually writes to state is not exercised. A regression here silently corrupts or overwrites a user's town.
  • resetActiveTownDonations and resetAll (lines 177–186, 286–298) — Settings "Danger zone" wipes. Zero tests. resetAll also clears ac-curator-search-history; the try/catch fallback for unavailable localStorage is unverified.
  • updateTown — Decision 1 says gameId is intentionally not patchable post-create; no test asserts the patch ignores a gameId field if one sneaks through.

2. src/lib/storeMigrations.ts — 2.5% covered

This is the v1→v2 (gameId backfill) and v2→v3 (hemisphere backfill) migrate callback. A bug here silently breaks every existing user's localStorage on app load, with no recovery. Tests should feed legacy v1 and v2 persisted shapes through migrate() and assert the output schema. Same for bootstrapMigration.ts (0% — the one-shot ac-web:v1 → ac-web key rename).

3. src/lib/csvExport.ts — 0% covered (297 lines)

The Sidebar "Download report" button is entirely untested. Easy wins: fixture-driven tests per game, plus edge cases (empty donations, missing donatedAt timestamps, items present in master data but not yet donated, hemisphere-aware month columns for ACNH).

4. Hooks — 0% covered

All four are pure-ish and worth testing with @testing-library/react + renderHook:

  • useMuseumData — game-aware fetch from /data/<gameId>/. Mock fetch, verify it re-fetches when the active town's game changes, and verify the sea-creatures branch fires only for ACNL/ACNH. No regression net for the bug the v0.7 changelog mentions (hardcoded ACGCN paths).
  • useCategoryStats — memoized counts; trivial to test with a fixture state.
  • useJumpToRow — pushes /town/:townId/:tab and sets highlightId. Phase 6 navigation contract.
  • useHydrationonFinishHydration gating.

5. Save-file import edges — saveFileImport.ts 71.5%

Uncovered lines 65–173 and 226–240 are the validation/error paths: bad schemaVersion, unknown gameId, ACNH save missing hemisphere, malformed donation rows being dropped with a count. These are the exact paths that protect against user-supplied bad JSON.

6. ErrorBoundary.tsx (40 lines, 0%)

Top-level React error boundary — if its render path regresses, the whole app whitescreens. One test that throws inside a child and asserts ErrorState mounts is high-value-per-line.

7. Component logic worth covering (behavior only, not snapshots)

In rough priority order:

  • ImportSaveModal.tsx (465 lines, 0%) — orchestrates the destructive Replace confirmation gate. RTL tests for: gate appears only when target town has data; cancel reverts to mode picker; Merge/Create paths call applyImportedSave with the right plan kind.
  • CategoryTab.tsx (209 lines, 0%) — Phase 7 sectioning (Leaving / Available / Out of season / Already donated) and the highlightId → open-row + scroll effect (Decision 10) have no regression net.
  • HomeTab.tsx (419 lines, 0%) — pure derivations (leaving-soon, just-arrived) could be extracted to a helper module and unit-tested without a DOM, similar to how progressMeterUtils.ts was split out.
  • GlobalSearchDropdown.tsx (405 lines, 0%) — keyboard nav (↑↓↵esc), history persistence at ac-curator-search-history (max 8, dedup).

8. Fix the coverage config itself

Update vitest.config.ts:

coverage: {
  provider: 'v8',
  include: ['src/**'],
  exclude: [
    'src/lib/types.ts',
    'src/test/**',
    'src/**/*.test.*',
    'src/main.tsx',
    'src/vite-env.d.ts',
  ],
  thresholds: { statements: 50, functions: 60, branches: 70 },
}

Real numbers will sting at first, but the scoped config gives a misleading "we're at 71%" signal.

Suggested order

  1. storeMigrations + applyImportedSave / resetAll in store.ts — protects user data, low LOC.
  2. csvExport — pure function, fixture-driven, fast win.
  3. Hooks (useMuseumData first — the one with prior regression history).
  4. Fix coverage config so the dashboard reflects reality.
  5. ImportSaveModal + ErrorBoundary — the only two UI components where the cost of a silent regression is high enough to justify RTL setup.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions