test(client): migrate net-stub seam tests to WS-mock, retire bespoke seams (#113) #140
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/113-netstub-migration"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Completes the bespoke-bridge debt retirement #92 began (the #80/#89/#94 lineage). Test-substrate-only — no production game-logic change; the only
src/edits remove inert?navtest-gated test seams.The net-backed versus/abort/forfeit/rematch tests lived in
nav.spec.ts's mock-solo tree behind__navTestseams that injected a fakenetclient — because the?mocktree hasnet=null, which shortcuts every net-backed branch (forfeit-send, PLAY-AGAIN mode-routing). That's the exact harness-substrate-vs-prod gap #91 surfaced. Driving the real net.ts over the #92 WS-mock removes the fake entirely: a forfeit is now an actual{type:'forfeit'}frame, an external match-end is an actualmatchEndframe, mode-routing is the real wire.Seam taxonomy (the audit — substrate-grounded, not issue-extrapolated)
The issue scoped "3 seams (#87/#91/#94)"; source enumeration found 10. Partitioned by what each does (the partition that decides each one's fate):
net?enterVersusPlaying(#87 forfeit)enterSoloPlaying(#87 solo-no-forfeit)enterSoloRematchReady(#91 PLAY-AGAIN)enterLobby(#85 lobby)forceGameover(#87 external-end)matchEndframeenterCountdownLockedPlaying(#110)enterSoloPlayAgainRace(#98)enterInitials/enterTopScore/enterPlacedNewBest(#102/#98)resetMigrated to versus.spec.ts (real net via WS-mock), each mutation-proven
net.sendForfeit()in confirmAbortmode !== 'solo'guardabortConfirm && gameover → clearinLobbyBackButtonhit-testnav.spec.tslobby/playing/abort-confirm TABLE nodes removed (versus.spec already covers them over real net — they were duplicates); #91 solo-rematch removed (the existing #92/#91 mode-routing test subsumes it). nav.spec is now purely the mock-solo tree per #92's mode-separation (AC5).The scope-boundary call (Bosun-ratified)
#110
enterCountdownLockedPlayingis deliberately KEPT despite injecting a net stub. AC1's "ALL net-stub seam tests" reads as the in-scope #87/#91/#94 net-stub-DEBT lineage, not "any seam touching net-state": #110's countdown lock is a client-LOCAL mechanism (countdownStart, applies in solo too), out of that lineage, and migrating it buys no debt-retirement (same complexity — 3s real-countdown waits or keep a release seam anyway). The taxonomy + this reasoning are preserved in the source comments so a future reader reconstructs why each seam's fate was chosen.Verification
npx tsc --noEmitclean; full client suite green (78 → 70 tests: −16 migrated/removed, +8 real-net; net −82 LOC).What this PR does NOT do
?navtesttest seams removed from main.ts.Closes #113
🤖 Generated with Claude Code
✅ APPROVED — net-stub seam migration (#113)
Reviewed at head
aa862a1(on current main2d0177f). The bespoke-bridge debt-retirement, done cleanly — and the one real risk in a test-removal refactor (silent coverage-drop) I verified definitively rather than trusting the table.Coverage-preservation — verified by mutation, not assumed
A green suite is vacuous on dropped coverage (remove a test + its target-bug protection atomically → still green). So for the one removed test without an obvious 1:1 migration — #91 (solo PLAY AGAIN → fresh solo, not versus rematch) — I didn't trust the "subsumed by the existing #92/#91 test" mapping; I reintroduced the bug. Forcing
playAgain()'s solo branch to fall through to the versussendRestartpath (the original #91 defect) redsversus.spec:297 #92/#91: solo PLAY AGAIN does NOT emit rematch (mode-routing prod path)(+ the #80 inert-during-submit test). So the coverage is live, and it's actually stronger than the seam it replaced — it observes the real absence-of-rematch-frame over net rather than the fake-net's mode/phase state. The other six removals (#85, #87×4, #97) have clear migrated equivalents with matching assertions. Refactor-invariant holds on both halves: suite passes AND each removed test's protection has a teeth-having home.Taxonomy + boundary
The seam taxonomy (10 found vs the issue's 3, partitioned by injects-fake-net? — the partition that decides each fate) is the audit-grows-direction counterpart to #88's shrink, and the partition key is the right one (injecting a fake net is exactly what makes a seam net-stub-debt). The #110 KEEP boundary is a sound call:
enterCountdownLockedPlaying's countdown lock is client-LOCAL (applies in solo too), outside the #87/#91/#94 net-stub-debt lineage, so migrating it buys no debt-retirement (you'd trade the seam for a 3s real-countdown wait or another release seam). Preserving the taxonomy + each fate's reasoning in-source is exactly what lets the next reader reconstruct why.Scope honesty
"No production game-logic change" holds — I filtered the main.ts diff and every removed line is a NAV_TEST-gated seam or its comment; nothing in the live game path moved. tsc clean, 70/70, net −82 LOC. nav.spec is now cleanly the mock-solo tree (AC5).
A debt-retirement that strengthens coverage while shrinking the harness — the best kind. Closes #113. 16/16 — milestone-close on merge. Merge-ready → Bosun.