From e745631ea7feae4d0e9e7d7efd2168517f09c85a Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Tue, 28 Apr 2026 10:03:47 +0700 Subject: [PATCH] refactor(player-board): extract auto-tick into pure helper + tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pull the bus-driven auto-tick effect body out of PlayerBoard.svelte into `src/lib/auto-tick.js` so the dedup-by-`at` invariant — the one that already caught a P0 — is unit-testable without mounting Svelte. The effect is now a thin wrapper that calls `processAutoTick()` and applies the returned `{crossed, lastHandledAt, changed}`. 8 vitest cases cover NEW draw, dedup on same `at`, re-cross after manual untick, mode=master/player ignored (timestamp still advances), off-board number, null lastDraw, and null grid. --- web/docs/codebase-summary.md | 2 + web/src/lib/PlayerBoard.svelte | 32 +++---- web/src/lib/auto-tick.js | 52 ++++++++++++ web/src/lib/auto-tick.test.js | 150 +++++++++++++++++++++++++++++++++ 4 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 web/src/lib/auto-tick.js create mode 100644 web/src/lib/auto-tick.test.js diff --git a/web/docs/codebase-summary.md b/web/docs/codebase-summary.md index 801d774..3a1ac49 100644 --- a/web/docs/codebase-summary.md +++ b/web/docs/codebase-summary.md @@ -22,6 +22,7 @@ |------|---------| | `src/lib/game-logic.js` | Stateless utilities: generateGrid (constraint-aware picker — exact 5 per row & per col, ascending-sorted columns, soft "no 3 consecutive filled cols per row" via rejection sampling), saveGrid, loadGrid, saveCrossedState, loadCrossedState, isRowComplete, getWaitingNumber. | | `src/lib/call-bus.svelte.js` | Pub/sub for master draws → player auto-tick. Reactive `bus.lastDrawn` slot (emits `{ num, at }`). Used in `mode: "both"` to auto-mark master-called numbers on player board. | +| `src/lib/auto-tick.js` | Pure `processAutoTick({grid, crossed, lastDraw, lastHandledAt, mode})` extracted from PlayerBoard's bus-driven $effect. Owns the dedup-by-`at` invariant: `lastHandledAt` advances on every NEW timestamp (even no-op draws) so reactive re-runs from `crossed`/`grid` changes never re-fire a stale draw. | | `src/lib/vietnamese-number.js` | `numberToVietnamese(n)` — pure utility mapping 0..90 to spoken Vietnamese, with tonal exceptions (15 → "mười lăm", 21 → "hai mươi mốt", 25 → "hai mươi lăm"). Out-of-range falls back to `String(n)`. | | `src/lib/voice.js` | Bundled-MP3 playback. Exports `playNumber(n)`, `playWaiting(n)` (sequences cho + N), `playBingo()`, `cancelPlayback()`. Lazy `