Fix prize zone rendering in Mantimon TCG mode (#2)
* Fix hand card rotation direction Cards now fan outward correctly instead of curling inward * Update StateRenderer to require MatchScene type for type safety - Change constructor parameter from Phaser.Scene to MatchScene - Update scene property type to MatchScene - Add import for MatchScene type - Update JSDoc example to reflect type-safe constructor * Defer Board creation to StateRenderer for correct rules config - Make board property nullable (Board | null instead of Board?) - Remove Board and createBoard imports (now handled by StateRenderer) - Update setupBoard() to skip Board creation - Add setBoard() method for StateRenderer to call - Update clearBoard() to use null instead of undefined - Add JSDoc explaining why Board creation is deferred * Create Board in StateRenderer with correct layout options - Add Board and createBoard imports - Add board property to StateRenderer - Create Board in render() on first call with correct rules_config - Add debug logging for Board creation and zone creation - Update clear() to destroy Board when clearing - Board now created after we have rules_config from first state * Add fatal error handling with toast notification and auto-redirect - Add 'fatal-error' event to GameBridgeEvents type - Import and initialize useToast in GamePage - Listen for 'fatal-error' event from Phaser - Show error toast that persists until redirect - Show full-screen fatal error overlay with countdown - Auto-redirect to /play after 3 seconds - Update StateRenderer to emit 'fatal-error' when Board creation fails * Gate debug logging with DEV flag - Add DEBUG_RENDERER constant gated by import.meta.env.DEV - Update all console.log statements in StateRenderer to only log in development - Keep console.error and console.warn as they are (always show errors) - Debug logs now only appear during development, not in production * Fix code audit issues - add missing imports and improve error UX Critical fixes: - Add missing gameBridge import to StateRenderer (fixes runtime error in fatal error handler) - Add missing Board type import to MatchScene (fixes TypeScript compilation error) UX improvements: - Replace fatal error auto-redirect with manual 'Return to Menu' button - Add toast notification when resignation fails - Give users unlimited time to read fatal errors before returning Addresses issues found in frontend code audit: - errors.missing-import (StateRenderer.ts:166) - errors.missing-type-import (MatchScene.ts:84) - errors.catch-only-console (GamePage.vue:145) - architecture.missing-fatal-error-handling (GamePage.vue:261) * Add CONTRIBUTING policy and fix pre-existing lint/test errors - Add CONTRIBUTING.md with strict policy: never use --no-verify without approval - Add comprehensive testing documentation (TESTING.md, VISUAL-TEST-GUIDE.md) - Add test-prize-fix.md quick test checklist and verify-fix.sh script Lint fixes (enables pre-commit hooks): - Remove unused imports in 9 files - Fix unused variables (underscore convention) - Replace 'as any' type assertions with proper VisibleGameState types - Add missing CARD_WIDTH_MEDIUM import in layout.spec.ts - All ESLint errors now resolved (only acceptable warnings remain) Test fixes (all 1000 tests now passing): - Fix layout.spec.ts: Add missing CARD_WIDTH_MEDIUM import - Fix PlayPage.spec.ts: Update test to use actual hardcoded UUIDs - Fix useAuth.spec.ts: Mock API profile fetch in initialization tests - Fix PhaserGame.spec.ts: Add scenes export to mock and update createGame call expectations This ensures pre-commit hooks work properly going forward and prevents bypassing TypeScript/lint checks that catch errors early. * Add comprehensive test coverage improvement plan - Create PROJECT_PLAN_TEST_COVERAGE.json with 25 structured tasks - Create TEST_COVERAGE_PLAN.md with executive summary and roadmap - Plan addresses critical gaps: game engine (0%), WebSocket (27%) - 6-week roadmap to reach 85% coverage from current 63% - Target: Phase 1 (weeks 1-3) - critical game engine and network tests - Includes quick wins, production blockers, and success metrics Based on coverage analysis showing: - Strong: Composables (84%), Components (90%), Stores (88%) - Critical gaps: Phaser game engine (~5,500 untested lines) - High priority: WebSocket/multiplayer reliability See TEST_COVERAGE_PLAN.md for overview and week-by-week breakdown. * Add coverage tooling and ignore coverage directory - Add @vitest/coverage-v8 package for coverage analysis - Add coverage/ directory to .gitignore - Used during test coverage analysis for PROJECT_PLAN_TEST_COVERAGE.json
This commit is contained in:
parent
b47da20b7f
commit
0d416028c0
144
CONTRIBUTING.md
Normal file
144
CONTRIBUTING.md
Normal file
@ -0,0 +1,144 @@
|
||||
# Contributing to Mantimon TCG
|
||||
|
||||
## Git Commit Guidelines
|
||||
|
||||
### Pre-Commit Hooks
|
||||
|
||||
**CRITICAL RULE: Never use `--no-verify` without explicit approval.**
|
||||
|
||||
This project has pre-commit hooks that run:
|
||||
- ESLint (catches undefined variables, unused imports, syntax errors)
|
||||
- TypeScript type checking (catches missing imports, type errors)
|
||||
- Tests (ensures nothing breaks)
|
||||
|
||||
These hooks are **required** to catch bugs before they enter the codebase.
|
||||
|
||||
### When Hooks Fail
|
||||
|
||||
If the pre-commit hook fails:
|
||||
|
||||
✅ **DO:**
|
||||
1. Read the error message carefully
|
||||
2. Fix the errors in your code
|
||||
3. Commit normally (hooks will pass)
|
||||
|
||||
❌ **DO NOT:**
|
||||
1. Use `--no-verify` to bypass the hooks
|
||||
2. Ignore lint/type errors
|
||||
3. Assume "it will be fine"
|
||||
|
||||
### Exception Process
|
||||
|
||||
If you believe you **must** bypass hooks (extremely rare):
|
||||
|
||||
1. **Ask for approval first:** "I need to use --no-verify because [reason]"
|
||||
2. **Wait for explicit approval**
|
||||
3. **Document why** in the commit message
|
||||
4. **Fix the issues** in a follow-up commit immediately
|
||||
|
||||
### Why This Matters
|
||||
|
||||
**Real Example:** In PR #X, commits were made with `--no-verify` to bypass pre-existing lint errors. This caused:
|
||||
- Missing imports that weren't caught until code audit
|
||||
- Runtime errors that would have been caught by TypeScript
|
||||
- Hours of debugging that could have been prevented
|
||||
|
||||
The pre-commit hook would have caught these issues in 5 seconds.
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### "But there are pre-existing lint errors!"
|
||||
|
||||
**Solution:** Fix them first in a separate commit:
|
||||
|
||||
```bash
|
||||
# Fix pre-existing errors
|
||||
npm run lint -- --fix
|
||||
git add .
|
||||
git commit -m "Fix pre-existing lint errors"
|
||||
|
||||
# Now do your work with hooks enabled
|
||||
git add my-feature.ts
|
||||
git commit -m "Add new feature" # Hooks will pass
|
||||
```
|
||||
|
||||
### "The hook is taking too long!"
|
||||
|
||||
The hooks are optimized to only check changed files. If they're slow:
|
||||
- Check if you have too many staged files
|
||||
- Consider committing in smaller chunks
|
||||
- Don't bypass - the time saved now will cost hours later
|
||||
|
||||
### "I'm just fixing a typo!"
|
||||
|
||||
Even typo fixes should pass hooks. If hooks fail on a typo fix, something is wrong with the surrounding code that needs addressing.
|
||||
|
||||
---
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### TypeScript
|
||||
|
||||
- All code must pass `npm run typecheck`
|
||||
- No `any` types without justification
|
||||
- Explicit return types on functions
|
||||
- Proper imports (no missing or unused)
|
||||
|
||||
### ESLint
|
||||
|
||||
- All code must pass `npm run lint`
|
||||
- No unused variables or imports
|
||||
- Follow project style guide
|
||||
- Use `eslint-disable` sparingly with comments explaining why
|
||||
|
||||
### Testing
|
||||
|
||||
- All code must pass `npm run test`
|
||||
- New features require tests
|
||||
- Bug fixes require regression tests
|
||||
|
||||
---
|
||||
|
||||
## Pre-Commit Hook Details
|
||||
|
||||
Located at: `.git/hooks/pre-commit`
|
||||
|
||||
**Backend checks:**
|
||||
- Black formatting (`black --check app tests`)
|
||||
- Ruff linting (`ruff check app tests`)
|
||||
- Pytest (`pytest --tb=short -q`)
|
||||
|
||||
**Frontend checks:**
|
||||
- ESLint (`npm run lint`)
|
||||
- TypeScript (`npm run typecheck`)
|
||||
- Vitest (`npm run test`)
|
||||
|
||||
All checks must pass before commit is allowed.
|
||||
|
||||
---
|
||||
|
||||
## Emergency Bypass Procedure
|
||||
|
||||
**Only with explicit approval:**
|
||||
|
||||
```bash
|
||||
# 1. Get approval first
|
||||
# 2. Document in commit message:
|
||||
git commit --no-verify -m "Emergency fix: [issue]
|
||||
|
||||
This commit bypasses pre-commit hooks because [specific reason].
|
||||
Pre-commit errors will be fixed in next commit.
|
||||
|
||||
Approved by: [name]"
|
||||
|
||||
# 3. Fix immediately:
|
||||
git commit -m "Fix issues from previous emergency commit"
|
||||
```
|
||||
|
||||
**Never leave bypassed commits unfixed.**
|
||||
|
||||
---
|
||||
|
||||
## Questions?
|
||||
|
||||
If you're unsure whether to bypass hooks, **ask first**. It's better to ask than to introduce bugs into the codebase.
|
||||
307
TESTING.md
Normal file
307
TESTING.md
Normal file
@ -0,0 +1,307 @@
|
||||
# Testing Guide: Prize Zone Fix
|
||||
|
||||
Branch: `fix/defer-board-creation-until-state`
|
||||
|
||||
## What Was Fixed
|
||||
|
||||
**Bug:** Prize zone rectangles (2x3 grid) were appearing even when `use_prize_cards: false` (Mantimon TCG points mode).
|
||||
|
||||
**Root Cause:** Board was created during scene initialization before WebSocket state arrived, using default `usePrizeCards: true`.
|
||||
|
||||
**Fix:** Defer Board creation until StateRenderer receives first game state with correct `rules_config`.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Terminal 1: Start backend
|
||||
cd backend && uv run uvicorn app.main:app --reload
|
||||
|
||||
# Terminal 2: Start frontend
|
||||
cd frontend && npm run dev
|
||||
|
||||
# Terminal 3: Ensure Docker services running
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Open: `http://localhost:5173`
|
||||
|
||||
---
|
||||
|
||||
## Test 1: Visual Verification (CRITICAL)
|
||||
|
||||
**Game:** `/game/f6f158c4-47b0-41b9-b3c2-8edc8275b70c` (or any Mantimon mode game)
|
||||
|
||||
### What to Look For:
|
||||
|
||||
**BEFORE FIX (Bug):**
|
||||
```
|
||||
+-----------------+
|
||||
| Opp Active |
|
||||
| Opp Bench |
|
||||
| [P][P] <---- Prize rectangles (WRONG!)
|
||||
| [P][P]
|
||||
| [P][P]
|
||||
+-----------------+
|
||||
| My Active |
|
||||
| My Bench |
|
||||
| [P][P] <---- Prize rectangles (WRONG!)
|
||||
| [P][P]
|
||||
| [P][P]
|
||||
+-----------------+
|
||||
```
|
||||
|
||||
**AFTER FIX (Correct):**
|
||||
```
|
||||
+-----------------+
|
||||
| Opp Active |
|
||||
| Opp Bench |
|
||||
| (no prizes) | <---- CORRECT!
|
||||
+-----------------+
|
||||
| My Active |
|
||||
| My Bench |
|
||||
| (no prizes) | <---- CORRECT!
|
||||
+-----------------+
|
||||
```
|
||||
|
||||
### Expected Board Layout (Mantimon Mode):
|
||||
|
||||
```
|
||||
Top (Opponent):
|
||||
- Active Zone (1 large rectangle, center-top)
|
||||
- Bench Slots (5 rectangles, horizontal row)
|
||||
- Deck (small rectangle, top-right)
|
||||
- Discard (small rectangle, next to deck)
|
||||
- Energy Deck (small rectangle, top-left)
|
||||
- Hand (row of card backs, very top)
|
||||
|
||||
Bottom (You):
|
||||
- Active Zone (1 large rectangle, center-bottom)
|
||||
- Bench Slots (5 rectangles, horizontal row)
|
||||
- Deck (small rectangle, bottom-right)
|
||||
- Discard (small rectangle, next to deck)
|
||||
- Energy Deck (small rectangle, bottom-left)
|
||||
- Hand (fanned cards, very bottom)
|
||||
```
|
||||
|
||||
**What should NOT be there:**
|
||||
- ❌ 2x3 grid of small rectangles (prize cards)
|
||||
- ❌ Any 6-rectangle groups
|
||||
- ❌ Empty bordered boxes on the left/right sides
|
||||
|
||||
---
|
||||
|
||||
## Test 2: Console Verification
|
||||
|
||||
Open DevTools Console (F12), look for these logs:
|
||||
|
||||
### ✅ CORRECT Logs:
|
||||
```
|
||||
[StateRenderer] Creating board with layout options: {
|
||||
usePrizeCards: false, ← Should be FALSE
|
||||
prizeCount: 4,
|
||||
benchSize: 5,
|
||||
energyDeckEnabled: true
|
||||
}
|
||||
[StateRenderer] ✓ Board created successfully
|
||||
[StateRenderer] Creating zones with rules: {
|
||||
usePrizeCards: false, ← Should be FALSE
|
||||
energyDeckEnabled: true,
|
||||
benchSize: 5
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ WRONG Logs (indicates bug):
|
||||
```
|
||||
[StateRenderer] Creating board with layout options: {
|
||||
usePrizeCards: true, ← WRONG! Should be false
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test 3: Loading Overlay
|
||||
|
||||
1. Refresh the game page
|
||||
2. Watch the loading sequence
|
||||
|
||||
**Expected:**
|
||||
1. "Connecting to game..." overlay appears instantly
|
||||
2. Overlay stays for 1-3 seconds
|
||||
3. Overlay disappears, board is fully rendered
|
||||
4. **No visual flash or flicker** of wrong state
|
||||
|
||||
**Bug would be:** Brief flash of prize rectangles before they disappear
|
||||
|
||||
---
|
||||
|
||||
## Test 4: Fatal Error Handling
|
||||
|
||||
### Setup (Temporary):
|
||||
Edit `frontend/src/game/sync/StateRenderer.ts`, line 154:
|
||||
|
||||
```typescript
|
||||
// Add this line temporarily:
|
||||
throw new Error('Test fatal error')
|
||||
this.board = createBoard(this.scene, this.layout)
|
||||
```
|
||||
|
||||
### Test Steps:
|
||||
1. Refresh game page
|
||||
2. Observe error handling
|
||||
|
||||
### Expected:
|
||||
- ✅ Toast appears (bottom-right): "Failed to initialize game board: Test fatal error"
|
||||
- ✅ Full-screen overlay appears:
|
||||
- Red error icon
|
||||
- "Failed to initialize game board"
|
||||
- "The game cannot continue. Please return to the menu."
|
||||
- Blue "Return to Menu" button
|
||||
- ✅ **NO auto-redirect** (wait 10+ seconds to verify)
|
||||
- ✅ Click button → redirects to `/play`
|
||||
|
||||
### Console Expected:
|
||||
```
|
||||
[StateRenderer] ✗ Failed to create board: Error: Test fatal error
|
||||
[GamePage] Fatal error from Phaser: {
|
||||
message: 'Failed to initialize game board',
|
||||
error: 'Test fatal error',
|
||||
context: 'StateRenderer.render()'
|
||||
}
|
||||
```
|
||||
|
||||
### Cleanup:
|
||||
**IMPORTANT:** Remove the `throw new Error()` line after testing!
|
||||
|
||||
---
|
||||
|
||||
## Test 5: Resign Failure Toast
|
||||
|
||||
### Setup:
|
||||
1. Start a game
|
||||
2. Open DevTools → Network tab → Set to "Offline"
|
||||
|
||||
### Test Steps:
|
||||
1. Click exit button (X, top-right)
|
||||
2. Click "Resign and Leave"
|
||||
|
||||
### Expected:
|
||||
- ✅ Toast appears: "Could not confirm resignation with server. Leaving game anyway."
|
||||
- ✅ Still redirects to `/play` (doesn't get stuck)
|
||||
- ✅ Console: `[GamePage] Failed to resign: ...`
|
||||
|
||||
### Cleanup:
|
||||
Turn network back to "Online"
|
||||
|
||||
---
|
||||
|
||||
## Test 6: TypeScript Compilation
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
### Expected Output:
|
||||
```
|
||||
> mantimon-tcg-frontend@0.1.0 typecheck
|
||||
> vue-tsc --noEmit
|
||||
|
||||
✓ No errors
|
||||
```
|
||||
|
||||
**Bug would be:**
|
||||
```
|
||||
error TS2304: Cannot find name 'gameBridge'
|
||||
error TS2304: Cannot find name 'Board'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test 7: Classic Mode (Prize Cards Enabled)
|
||||
|
||||
**Note:** This requires a game with `use_prize_cards: true` in backend rules.
|
||||
|
||||
### Expected:
|
||||
- ✅ 6 prize zone rectangles ARE visible (2x3 grid, left side)
|
||||
- ✅ Same for opponent (top-left)
|
||||
|
||||
### Console Should Show:
|
||||
```
|
||||
[StateRenderer] Creating board with layout options: {
|
||||
usePrizeCards: true, ← Should be TRUE for classic mode
|
||||
prizeCount: 6,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test 8: Multiple Games
|
||||
|
||||
1. Play game 1 (Mantimon mode)
|
||||
2. Exit
|
||||
3. Start game 2 (any mode)
|
||||
|
||||
### Expected:
|
||||
- ✅ Game 1: No prize rectangles
|
||||
- ✅ Exit works cleanly
|
||||
- ✅ Game 2: Board matches its rules
|
||||
- ✅ No leftover visual artifacts
|
||||
|
||||
### Console Should Show:
|
||||
```
|
||||
[StateRenderer] Board destroyed ← From game 1
|
||||
[StateRenderer] Creating board... ← For game 2
|
||||
[StateRenderer] ✓ Board created successfully
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pass/Fail Criteria
|
||||
|
||||
### ✅ ALL TESTS PASS IF:
|
||||
|
||||
1. **Visual:** No prize rectangles in Mantimon mode games
|
||||
2. **Console:** `usePrizeCards: false` in logs
|
||||
3. **Loading:** No visual flash of wrong state
|
||||
4. **Fatal Error:** Manual button, no auto-redirect
|
||||
5. **Resign:** Toast appears on failure
|
||||
6. **TypeScript:** No compilation errors
|
||||
7. **Classic Mode:** Prize rectangles appear when enabled
|
||||
8. **Multiple Games:** Clean transitions
|
||||
|
||||
### ❌ FIX FAILED IF:
|
||||
|
||||
- Prize rectangles visible when `use_prize_cards: false`
|
||||
- Console shows `usePrizeCards: true` for Mantimon games
|
||||
- Fatal error auto-redirects after 3 seconds
|
||||
- TypeScript compilation errors
|
||||
|
||||
---
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If any test fails, report:
|
||||
|
||||
1. **Which test failed**
|
||||
2. **What you saw** (screenshot preferred)
|
||||
3. **Console logs** (copy full output)
|
||||
4. **Game ID** you tested with
|
||||
5. **Browser** and version
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria Summary
|
||||
|
||||
**The fix is working if:**
|
||||
|
||||
✓ No prize zone rectangles appear in your test game
|
||||
✓ Console shows `usePrizeCards: false`
|
||||
✓ Board renders correctly after loading overlay
|
||||
✓ Fatal error requires manual button click
|
||||
✓ TypeScript compiles without errors
|
||||
|
||||
**Ready for merge if all tests pass!**
|
||||
234
VISUAL-TEST-GUIDE.md
Normal file
234
VISUAL-TEST-GUIDE.md
Normal file
@ -0,0 +1,234 @@
|
||||
# Visual Test Guide - Prize Zone Fix
|
||||
|
||||
## 🔍 What to Look For
|
||||
|
||||
### ❌ BEFORE FIX (The Bug)
|
||||
|
||||
When you load the game, you would see **6 prize rectangles** even in Mantimon mode:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ OPPONENT SIDE (TOP) │
|
||||
│ │
|
||||
│ ┌─────┐ │
|
||||
│ │ ACT │ Active Zone │
|
||||
│ └─────┘ │
|
||||
│ │
|
||||
│ ┌───┐┌───┐┌───┐┌───┐┌───┐ │
|
||||
│ │ B ││ B ││ B ││ B ││ B │ Bench │
|
||||
│ └───┘└───┘└───┘└───┘└───┘ │
|
||||
│ │
|
||||
│ ┌──┐┌──┐ ◄── WRONG! │
|
||||
│ │P1││P2│ Prize cards │
|
||||
│ └──┘└──┘ should NOT be here! │
|
||||
│ ┌──┐┌──┐ │
|
||||
│ │P3││P4│ │
|
||||
│ └──┘└──┘ │
|
||||
│ ┌──┐┌──┐ │
|
||||
│ │P5││P6│ │
|
||||
│ └──┘└──┘ │
|
||||
│ │
|
||||
├──────────────────────────────────────────┤
|
||||
│ YOUR SIDE (BOTTOM) │
|
||||
│ │
|
||||
│ ┌──┐┌──┐ ◄── WRONG! │
|
||||
│ │P1││P2│ Prize cards │
|
||||
│ └──┘└──┘ should NOT be here! │
|
||||
│ ┌──┐┌──┐ │
|
||||
│ │P3││P4│ │
|
||||
│ └──┘└──┘ │
|
||||
│ ┌──┐┌──┐ │
|
||||
│ │P5││P6│ │
|
||||
│ └──┘└──┘ │
|
||||
│ │
|
||||
│ ┌───┐┌───┐┌───┐┌───┐┌───┐ │
|
||||
│ │ B ││ B ││ B ││ B ││ B │ Bench │
|
||||
│ └───┘└───┘└───┘└───┘└───┘ │
|
||||
│ │
|
||||
│ ┌─────┐ │
|
||||
│ │ ACT │ Active Zone │
|
||||
│ └─────┘ │
|
||||
│ │
|
||||
│ ┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐ │
|
||||
│ │ 🃏 │ 🃏 │ 🃏 │ 🃏 │ 🃏 │ 🃏 │ 🃏 │ Hand │
|
||||
│ └───┘└───┘└───┘└───┘└───┘└───┘└───┘ │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**The Bug:** Those `P1-P6` prize rectangles appear on the left side!
|
||||
|
||||
---
|
||||
|
||||
### ✅ AFTER FIX (Correct)
|
||||
|
||||
After the fix, **NO prize rectangles** should appear:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ OPPONENT SIDE (TOP) │
|
||||
│ │
|
||||
│ ┌─────┐ │
|
||||
│ │ ACT │ Active Zone │
|
||||
│ └─────┘ │
|
||||
│ │
|
||||
│ ┌───┐┌───┐┌───┐┌───┐┌───┐ │
|
||||
│ │ B ││ B ││ B ││ B ││ B │ Bench │
|
||||
│ └───┘└───┘└───┘└───┘└───┘ │
|
||||
│ │
|
||||
│ (no prize rectangles here) ✓ │
|
||||
│ │
|
||||
│ ┌──┐ ┌──┐ │
|
||||
│ │📚│ │🗑│ Deck & Discard │
|
||||
│ └──┘ └──┘ │
|
||||
│ │
|
||||
├──────────────────────────────────────────┤
|
||||
│ YOUR SIDE (BOTTOM) │
|
||||
│ │
|
||||
│ ┌──┐ ┌──┐ │
|
||||
│ │⚡│ │📚│ Energy Deck & Deck │
|
||||
│ └──┘ └──┘ │
|
||||
│ │
|
||||
│ (no prize rectangles here) ✓ │
|
||||
│ │
|
||||
│ ┌───┐┌───┐┌───┐┌───┐┌───┐ │
|
||||
│ │ B ││ B ││ B ││ B ││ B │ Bench │
|
||||
│ └───┘└───┘└───┘└───┘└───┘ │
|
||||
│ │
|
||||
│ ┌─────┐ │
|
||||
│ │ ACT │ Active Zone │
|
||||
│ └─────┘ │
|
||||
│ │
|
||||
│ ┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐ │
|
||||
│ │ 🃏 │ 🃏 │ 🃏 │ 🃏 │ 🃏 │ 🃏 │ 🃏 │ Hand │
|
||||
│ └───┘└───┘└───┘└───┘└───┘└───┘└───┘ │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**The Fix:** No `P1-P6` rectangles! Clean board layout!
|
||||
|
||||
---
|
||||
|
||||
## 📸 Screenshot Comparison
|
||||
|
||||
### Before Fix:
|
||||
- You'll see **12 total rectangles** (6 opponent + 6 yours) that are prize zones
|
||||
- They're usually on the left side of the board
|
||||
- Each is a small bordered rectangle in a 2x3 grid pattern
|
||||
|
||||
### After Fix:
|
||||
- Those 12 rectangles are **gone**
|
||||
- Board feels more spacious
|
||||
- Only essential zones visible
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quick Visual Test (10 seconds)
|
||||
|
||||
1. Load game: http://localhost:5173/game/f6f158c4-47b0-41b9-b3c2-8edc8275b70c
|
||||
2. Wait for "Connecting..." overlay to disappear
|
||||
3. Count small rectangles on the board
|
||||
|
||||
**PASS:** You count approximately 12-15 rectangles total:
|
||||
- 1 Active (large, yours)
|
||||
- 1 Active (large, opponent)
|
||||
- 5 Bench (yours)
|
||||
- 5 Bench (opponent)
|
||||
- 2 Deck zones
|
||||
- 2 Discard zones
|
||||
- 2 Energy Deck zones (small)
|
||||
|
||||
**FAIL:** You count 24+ rectangles (because 12 extra prize rectangles appeared)
|
||||
|
||||
---
|
||||
|
||||
## 🐛 What the Bug Looked Like
|
||||
|
||||
The bug was **subtle but obvious once you knew to look for it**:
|
||||
|
||||
- 6 small bordered rectangles in a 2x3 grid pattern
|
||||
- Usually on the left side of each player's area
|
||||
- All empty (no cards in them)
|
||||
- Labeled or unlabeled as "prizes"
|
||||
|
||||
These appeared even when the game rules said `use_prize_cards: false` (Mantimon TCG uses points instead of prize cards).
|
||||
|
||||
---
|
||||
|
||||
## ✨ What Success Looks Like
|
||||
|
||||
**Clean board with only these zones:**
|
||||
|
||||
**Opponent (Top):**
|
||||
- 1 Active zone
|
||||
- 5 Bench slots
|
||||
- 1 Hand (row of card backs)
|
||||
- 1 Deck pile
|
||||
- 1 Discard pile
|
||||
- 1 Energy Deck pile (small)
|
||||
|
||||
**You (Bottom):**
|
||||
- 1 Active zone
|
||||
- 5 Bench slots
|
||||
- 1 Hand (fanned cards)
|
||||
- 1 Deck pile
|
||||
- 1 Discard pile
|
||||
- 1 Energy Deck pile (small)
|
||||
|
||||
**Total visible zones:** ~14 (no prizes)
|
||||
|
||||
**If you see more zones:** The bug is still present!
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Browser Test
|
||||
|
||||
**Easiest way to verify:**
|
||||
|
||||
1. Open game in browser
|
||||
2. Take a screenshot
|
||||
3. Count the distinct bordered rectangles
|
||||
4. If you count 12 extra small rectangles → Bug still exists
|
||||
5. If board looks clean and minimal → Fix worked!
|
||||
|
||||
---
|
||||
|
||||
## 📊 Expected vs Actual
|
||||
|
||||
| Zone Type | Count (Yours) | Count (Opponent) | Total |
|
||||
|-----------|---------------|------------------|-------|
|
||||
| Active | 1 | 1 | 2 |
|
||||
| Bench | 5 | 5 | 10 |
|
||||
| Hand | 1 | 1 | 2 |
|
||||
| Deck | 1 | 1 | 2 |
|
||||
| Discard | 1 | 1 | 2 |
|
||||
| Energy Deck | 1 | 1 | 2 |
|
||||
| **Prizes** | **0** ✓ | **0** ✓ | **0** ✓ |
|
||||
| **TOTAL** | **10** | **10** | **20** |
|
||||
|
||||
**With bug:** Total would be 32 (20 + 12 prize zones)
|
||||
**After fix:** Total is 20 (no prize zones)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Still See Prize Rectangles?
|
||||
|
||||
If you still see the 2x3 grid of prize rectangles:
|
||||
|
||||
1. Check you're on the right branch: `fix/defer-board-creation-until-state`
|
||||
2. Verify the build is using the latest code: `git log --oneline -1`
|
||||
3. Hard refresh the browser: Ctrl+Shift+R (or Cmd+Shift+R on Mac)
|
||||
4. Check console for `usePrizeCards: false` in the logs
|
||||
5. If console shows `usePrizeCards: true`, the fix didn't apply correctly
|
||||
|
||||
---
|
||||
|
||||
## ✅ Success Confirmation
|
||||
|
||||
**You'll know the fix worked when:**
|
||||
|
||||
✓ Board looks cleaner (fewer rectangles)
|
||||
✓ No 2x3 grid pattern visible
|
||||
✓ Only essential game zones present
|
||||
✓ Console logs show `usePrizeCards: false`
|
||||
|
||||
**Ready to merge!** 🚀
|
||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@ -23,3 +23,6 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Test coverage
|
||||
coverage/
|
||||
|
||||
820
frontend/PROJECT_PLAN_TEST_COVERAGE.json
Normal file
820
frontend/PROJECT_PLAN_TEST_COVERAGE.json
Normal file
@ -0,0 +1,820 @@
|
||||
{
|
||||
"meta": {
|
||||
"version": "1.0.0",
|
||||
"created": "2026-02-02",
|
||||
"lastUpdated": "2026-02-02",
|
||||
"planType": "testing",
|
||||
"description": "Test coverage improvement plan - filling critical gaps in game engine, WebSocket, and gameplay code",
|
||||
"totalEstimatedHours": 120,
|
||||
"totalTasks": 35,
|
||||
"completedTasks": 0,
|
||||
"currentCoverage": "63%",
|
||||
"targetCoverage": "85%"
|
||||
},
|
||||
"categories": {
|
||||
"critical": "Zero or critically low coverage (<30%) on production code",
|
||||
"high": "Important gaps (30-60%) in core functionality",
|
||||
"medium": "Quality improvements (60-80%) for completeness",
|
||||
"low": "Polish and edge cases (80-90%)",
|
||||
"infrastructure": "Testing infrastructure and tooling"
|
||||
},
|
||||
"tasks": [
|
||||
{
|
||||
"id": "TEST-001",
|
||||
"name": "Create Phaser testing infrastructure",
|
||||
"description": "Set up minimal Phaser mocks and testing utilities to enable testing of game engine code. Create reusable mock classes for Phaser.Scene, Phaser.Game, Phaser.GameObjects, etc. This is the foundation for all subsequent game engine tests.",
|
||||
"category": "critical",
|
||||
"priority": 1,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/test/mocks/phaser.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created - comprehensive Phaser mocks"
|
||||
},
|
||||
{
|
||||
"path": "src/test/helpers/gameTestUtils.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created - test utilities for game objects"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create src/test/mocks/phaser.ts with MockScene, MockGame, MockSprite, MockGraphics classes\n2. Create src/test/helpers/gameTestUtils.ts with helper functions: createMockGameState(), createMockCard(), setupMockScene()\n3. Document mock API in test/README.md\n4. Add vitest setup file to auto-import mocks",
|
||||
"estimatedHours": 8,
|
||||
"notes": "This is a prerequisite for all Phaser-related tests. Use existing PhaserGame.spec.ts as reference for what mocks are needed. Keep mocks minimal - only mock what's necessary for tests to run."
|
||||
},
|
||||
{
|
||||
"id": "TEST-002",
|
||||
"name": "Test MatchScene initialization and lifecycle",
|
||||
"description": "Test MatchScene.ts core functionality: scene creation, initialization, state setup, and cleanup. Cover the scene lifecycle from preload to shutdown.",
|
||||
"category": "critical",
|
||||
"priority": 2,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/scenes/MatchScene.ts",
|
||||
"lines": [1, 511],
|
||||
"issue": "0% coverage - 511 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/scenes/MatchScene.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create MatchScene.spec.ts\n2. Test scene creation: verify scene key, config\n3. Test preload: asset loading calls\n4. Test create: board creation, event listeners setup\n5. Test shutdown: cleanup, event unsubscription\n6. Test update loop: state synchronization\n7. Mock gameBridge and verify event handling",
|
||||
"estimatedHours": 8,
|
||||
"notes": "Focus on initialization logic and event wiring. Don't test every animation frame - test that state changes trigger expected updates. Mock Board creation since Board tests are separate."
|
||||
},
|
||||
{
|
||||
"id": "TEST-003",
|
||||
"name": "Test Board layout and zone management",
|
||||
"description": "Test Board.ts: zone creation, card placement, layout calculations, coordinate transformations. Verify zones are created correctly for different game modes (prize vs no-prize).",
|
||||
"category": "critical",
|
||||
"priority": 3,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/objects/Board.ts",
|
||||
"lines": [1, 611],
|
||||
"issue": "0% coverage - 611 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/objects/Board.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create Board.spec.ts\n2. Test constructor: verify zones are created based on layout options\n3. Test prize zones: verify prize zones exist when usePrizeCards=true, null when false\n4. Test zone retrieval: getZone(), getAllZones()\n5. Test card placement: placeCard(), removeCard()\n6. Test coordinate conversion: worldToBoard(), boardToWorld()\n7. Test resize handling: layout updates on screen size change",
|
||||
"estimatedHours": 10,
|
||||
"notes": "This is critical - Board is the container for all game objects. Test both Mantimon mode (no prizes) and Pokemon mode (with prizes). Mock Zone objects to isolate Board logic."
|
||||
},
|
||||
{
|
||||
"id": "TEST-004",
|
||||
"name": "Test Card rendering and interactions",
|
||||
"description": "Test Card.ts: card display, state updates (tapped, selected, damaged), click handling, drag interactions, animations. This is the most complex game object.",
|
||||
"category": "critical",
|
||||
"priority": 4,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/objects/Card.ts",
|
||||
"lines": [1, 832],
|
||||
"issue": "0% coverage - 832 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/objects/Card.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create Card.spec.ts\n2. Test card creation: verify sprite, text, damage counter setup\n3. Test state updates: setTapped(), setSelected(), setDamaged()\n4. Test visual updates: tap rotation, selection highlight, damage display\n5. Test click handling: pointer events, interaction callbacks\n6. Test drag: drag start/end, position updates\n7. Test animations: flip, move, fade",
|
||||
"estimatedHours": 12,
|
||||
"notes": "Largest and most complex game object. Break into multiple test suites if needed: Card.rendering.spec.ts, Card.interactions.spec.ts, Card.animations.spec.ts. Mock Phaser graphics/tweens heavily."
|
||||
},
|
||||
{
|
||||
"id": "TEST-005",
|
||||
"name": "Test StateRenderer synchronization logic",
|
||||
"description": "Test StateRenderer.ts: game state diffing, incremental updates, animation sequencing, error recovery. This is the bridge between backend state and Phaser rendering.",
|
||||
"category": "critical",
|
||||
"priority": 5,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001", "TEST-002", "TEST-003"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/sync/StateRenderer.ts",
|
||||
"lines": [1, 709],
|
||||
"issue": "0% coverage - 709 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/sync/StateRenderer.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create StateRenderer.spec.ts\n2. Test render(): initial state rendering, creates all zones/cards\n3. Test update(): incremental state changes, only updates diffs\n4. Test card movement: card moves from hand to active zone\n5. Test card removal: card removed from board when discarded\n6. Test damage updates: damage counters update without full re-render\n7. Test error handling: gracefully handles malformed state\n8. Test Board creation: defers creation until state available (recent fix)",
|
||||
"estimatedHours": 12,
|
||||
"notes": "This is critical for correctness - bugs here cause desyncs. Test with realistic game state transitions. Mock MatchScene and Board. Verify optimization: don't re-render unchanged cards."
|
||||
},
|
||||
{
|
||||
"id": "TEST-006",
|
||||
"name": "Test Zone base class and zone types",
|
||||
"description": "Test Zone.ts base class and subclasses (HandZone, BenchZone, ActiveZone, PrizeZone, PileZone): card capacity, layout, add/remove cards, sorting, hover effects.",
|
||||
"category": "critical",
|
||||
"priority": 6,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/objects/Zone.ts",
|
||||
"lines": [1, 458],
|
||||
"issue": "0% coverage - 458 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/objects/HandZone.ts",
|
||||
"lines": [1, 321],
|
||||
"issue": "0% coverage - 321 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/objects/BenchZone.ts",
|
||||
"lines": [1, 287],
|
||||
"issue": "0% coverage - 287 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/objects/Zone.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create Zone.spec.ts for base class\n2. Test capacity: maxCards enforcement, isFull()\n3. Test card management: addCard(), removeCard(), clear()\n4. Test layout: cards positioned correctly, spacing\n5. Test hover effects: zone highlights on drag-over\n6. Create subclass specs: HandZone.spec.ts, BenchZone.spec.ts, etc.\n7. Test zone-specific behavior: hand fanning, bench slots",
|
||||
"estimatedHours": 10,
|
||||
"notes": "Zone is the base class for all card containers. Test inheritance - ensure subclasses properly extend base behavior. Mock Card objects to focus on zone logic."
|
||||
},
|
||||
{
|
||||
"id": "TEST-007",
|
||||
"name": "Test WebSocket client connection lifecycle",
|
||||
"description": "Test socket/client.ts: connection establishment, disconnection, reconnection logic, authentication, error handling. Currently only 27% coverage - missing critical paths.",
|
||||
"category": "critical",
|
||||
"priority": 7,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/socket/client.ts",
|
||||
"lines": [182, 300],
|
||||
"issue": "27% coverage - missing connection lifecycle tests"
|
||||
},
|
||||
{
|
||||
"path": "src/socket/client.spec.ts",
|
||||
"lines": [1, 300],
|
||||
"issue": "Incomplete coverage - add lifecycle tests"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Expand client.spec.ts\n2. Test connect(): successful connection, auth token passing\n3. Test disconnect(): clean disconnection, event cleanup\n4. Test reconnection: auto-reconnect on drop, exponential backoff\n5. Test connection errors: auth failure, network error, timeout\n6. Test event handlers: message routing, error propagation\n7. Test connection state: connected, disconnected, reconnecting states",
|
||||
"estimatedHours": 6,
|
||||
"notes": "WebSocket is critical for multiplayer. Test error scenarios thoroughly - connection drops are common. Mock socket.io-client and use fake timers for reconnection delays."
|
||||
},
|
||||
{
|
||||
"id": "TEST-008",
|
||||
"name": "Test useGameSocket action queueing and state sync",
|
||||
"description": "Test useGameSocket.ts: game action emission, response handling, state synchronization, error recovery, connection status. Currently 45% coverage - missing error paths and edge cases.",
|
||||
"category": "critical",
|
||||
"priority": 8,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-007"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/composables/useGameSocket.ts",
|
||||
"lines": [146, 464],
|
||||
"issue": "45% coverage - missing action queueing and error handling"
|
||||
},
|
||||
{
|
||||
"path": "src/composables/useGameSocket.spec.ts",
|
||||
"lines": [1, 500],
|
||||
"issue": "Incomplete coverage - add edge case tests"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Expand useGameSocket.spec.ts\n2. Test sendAction(): action emission, callback registration\n3. Test action response: success callback, error callback\n4. Test action timeout: callback with timeout error\n5. Test state updates: gameState reactive updates on server events\n6. Test connection recovery: queued actions resent after reconnect\n7. Test concurrent actions: multiple actions in flight\n8. Test invalid actions: server validation errors",
|
||||
"estimatedHours": 8,
|
||||
"notes": "This composable is the main game interaction interface. Test race conditions and concurrent actions. Mock socket client. Verify state updates trigger Vue reactivity."
|
||||
},
|
||||
{
|
||||
"id": "TEST-009",
|
||||
"name": "Test useGames game creation and management",
|
||||
"description": "Test useGames.ts: createGame(), fetchActiveGames(), joinGame(), game state management. Currently only 2% coverage - almost completely untested.",
|
||||
"category": "critical",
|
||||
"priority": 9,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/composables/useGames.ts",
|
||||
"lines": [67, 257],
|
||||
"issue": "2% coverage - core game management untested"
|
||||
},
|
||||
{
|
||||
"path": "src/composables/useGames.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create useGames.spec.ts\n2. Test createGame(): API call, loading states, success response\n3. Test createGame() error: validation error, server error\n4. Test fetchActiveGames(): retrieve user's active games\n5. Test joinGame(): join existing game, reconnection\n6. Test game list management: add/remove/update games\n7. Test loading states: isLoading, isCreating flags\n8. Test error states: error messages, clearError()",
|
||||
"estimatedHours": 6,
|
||||
"notes": "Critical for game flow. Mock apiClient. Test the full game lifecycle: create → join → play → end. Verify error handling - game creation can fail for many reasons (invalid deck, no opponent, etc.)."
|
||||
},
|
||||
{
|
||||
"id": "TEST-010",
|
||||
"name": "Test PreloadScene asset loading",
|
||||
"description": "Test PreloadScene.ts: asset manifest parsing, loading progress, error handling, transition to MatchScene. Currently 0% coverage.",
|
||||
"category": "high",
|
||||
"priority": 10,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/scenes/PreloadScene.ts",
|
||||
"lines": [1, 404],
|
||||
"issue": "0% coverage - 404 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/scenes/PreloadScene.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create PreloadScene.spec.ts\n2. Test preload(): loads all assets from manifest\n3. Test loading progress: progress events, progress bar updates\n4. Test loading errors: handle missing assets, network errors\n5. Test scene transition: starts MatchScene after load complete\n6. Test caching: verify assets cached correctly\n7. Mock Phaser.Loader and asset loading",
|
||||
"estimatedHours": 4,
|
||||
"notes": "Lower priority than MatchScene but still important for user experience. Test error handling - network failures during load should be graceful. Mock all asset loading."
|
||||
},
|
||||
{
|
||||
"id": "TEST-011",
|
||||
"name": "Test HandManager drag and drop logic",
|
||||
"description": "Test HandManager.ts: card dragging, drop validation, snap-back on invalid drop, touch events. Currently 39% coverage - missing edge cases.",
|
||||
"category": "high",
|
||||
"priority": 11,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-004"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/interactions/HandManager.ts",
|
||||
"lines": [1, 572],
|
||||
"issue": "39% coverage - missing drag edge cases"
|
||||
},
|
||||
{
|
||||
"path": "src/game/interactions/HandManager.spec.ts",
|
||||
"lines": [1, 300],
|
||||
"issue": "Incomplete coverage - add edge case tests"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Expand HandManager.spec.ts\n2. Test drag start: card pickup, visual feedback\n3. Test drag move: card follows pointer, over valid zone\n4. Test drag drop: valid drop, invalid drop (snap back)\n5. Test drag cancel: ESC key, right-click\n6. Test touch events: touch drag works on mobile\n7. Test multi-touch: handle multi-touch gracefully\n8. Test zone validation: can't drop on full zone",
|
||||
"estimatedHours": 6,
|
||||
"notes": "Important for UX - drag/drop is primary interaction. Test touch events separately with touch simulation. Verify snap-back animation on invalid drops."
|
||||
},
|
||||
{
|
||||
"id": "TEST-012",
|
||||
"name": "Test damage counter display and updates",
|
||||
"description": "Test DamageCounter.ts: damage display, number formatting, positioning, animations. Currently 0% coverage.",
|
||||
"category": "high",
|
||||
"priority": 12,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/objects/DamageCounter.ts",
|
||||
"lines": [1, 202],
|
||||
"issue": "0% coverage - 202 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/objects/DamageCounter.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create DamageCounter.spec.ts\n2. Test counter creation: initial damage value, position\n3. Test damage updates: setDamage(), increment, decrement\n4. Test display: zero damage hidden, non-zero visible\n5. Test positioning: counter positioned on card correctly\n6. Test animations: damage change animation, pulse effect\n7. Mock Phaser.GameObjects.Text and Graphics",
|
||||
"estimatedHours": 3,
|
||||
"notes": "Important visual feedback for game state. Test edge cases: 0 damage, very high damage (999+), negative damage (should never happen but handle gracefully)."
|
||||
},
|
||||
{
|
||||
"id": "TEST-013",
|
||||
"name": "Test asset loader and manifest",
|
||||
"description": "Test assets/loader.ts and assets/manifest.ts: asset path resolution, manifest parsing, preload hooks. Currently 0% coverage.",
|
||||
"category": "high",
|
||||
"priority": 13,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/assets/loader.ts",
|
||||
"lines": [1, 326],
|
||||
"issue": "0% coverage - 326 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/assets/manifest.ts",
|
||||
"lines": [1, 190],
|
||||
"issue": "0% coverage - 190 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/assets/loader.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create loader.spec.ts and manifest.spec.ts\n2. Test manifest parsing: correct asset paths, keys\n3. Test loader: loads assets in correct order\n4. Test asset types: images, audio, JSON, atlas\n5. Test path resolution: base URL handling\n6. Test error handling: missing manifest, invalid paths\n7. Mock Phaser.Loader methods",
|
||||
"estimatedHours": 4,
|
||||
"notes": "Important for initialization. Test manifest structure validation - invalid manifests should fail fast with helpful errors. Mock all actual file loading."
|
||||
},
|
||||
{
|
||||
"id": "TEST-014",
|
||||
"name": "Test ResizeManager responsive layout",
|
||||
"description": "Test ResizeManager.ts: window resize handling, orientation changes, layout recalculation, mobile vs desktop. Currently 0% coverage.",
|
||||
"category": "high",
|
||||
"priority": 14,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/ResizeManager.ts",
|
||||
"lines": [1, 253],
|
||||
"issue": "0% coverage - 253 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/ResizeManager.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create ResizeManager.spec.ts\n2. Test resize handling: window resize triggers layout update\n3. Test orientation change: portrait ↔ landscape\n4. Test layout calculation: correct scaling for different sizes\n5. Test debouncing: resize events debounced\n6. Test mobile detection: detects mobile vs desktop\n7. Mock window.innerWidth/Height and ResizeObserver",
|
||||
"estimatedHours": 4,
|
||||
"notes": "Important for mobile support. Test both portrait and landscape orientations. Verify debouncing - don't recalculate layout on every pixel change."
|
||||
},
|
||||
{
|
||||
"id": "TEST-015",
|
||||
"name": "Test CardBack placeholder rendering",
|
||||
"description": "Test CardBack.ts: face-down card display, back texture, positioning. Currently 0% coverage but simple component.",
|
||||
"category": "medium",
|
||||
"priority": 15,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/objects/CardBack.ts",
|
||||
"lines": [1, 238],
|
||||
"issue": "0% coverage - 238 lines untested"
|
||||
},
|
||||
{
|
||||
"path": "src/game/objects/CardBack.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create CardBack.spec.ts\n2. Test creation: card back sprite, texture key\n3. Test positioning: correct position and scale\n4. Test visibility: shown for face-down cards\n5. Test flip animation: flip to reveal front\n6. Mock Phaser.GameObjects.Sprite",
|
||||
"estimatedHours": 2,
|
||||
"notes": "Relatively simple component. Lower priority than Card.ts but still needs coverage. Test flip animation timing and easing."
|
||||
},
|
||||
{
|
||||
"id": "TEST-016",
|
||||
"name": "Test user store profile management",
|
||||
"description": "Test stores/user.ts: profile updates, linked account management, session handling. Currently 52% coverage - missing edge cases.",
|
||||
"category": "medium",
|
||||
"priority": 16,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/stores/user.ts",
|
||||
"lines": [98, 139],
|
||||
"issue": "52% coverage - missing profile update edge cases"
|
||||
},
|
||||
{
|
||||
"path": "src/stores/user.spec.ts",
|
||||
"lines": [1, 200],
|
||||
"issue": "Incomplete coverage - add edge case tests"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Expand user.spec.ts\n2. Test profile update: updateProfile() success and error\n3. Test validation: invalid display name, invalid avatar URL\n4. Test linked accounts: add/remove linked accounts\n5. Test session management: active sessions, logout all\n6. Test concurrent updates: multiple profile updates in flight\n7. Mock API calls",
|
||||
"estimatedHours": 3,
|
||||
"notes": "Medium priority - basic functionality is tested, need edge cases. Test validation thoroughly - what happens with empty/null/very long display names?"
|
||||
},
|
||||
{
|
||||
"id": "TEST-017",
|
||||
"name": "Test useDragDrop edge cases",
|
||||
"description": "Test composables/useDragDrop.ts: edge cases in drag/drop, touch events, multi-touch handling. Currently 76% coverage - good but can improve.",
|
||||
"category": "medium",
|
||||
"priority": 17,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/composables/useDragDrop.ts",
|
||||
"lines": [326, 424],
|
||||
"issue": "76% coverage - missing edge cases"
|
||||
},
|
||||
{
|
||||
"path": "src/composables/useDragDrop.spec.ts",
|
||||
"lines": [1, 450],
|
||||
"issue": "Good coverage but add edge cases"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Expand useDragDrop.spec.ts\n2. Test edge case: drag start outside draggable\n3. Test edge case: drop outside any drop zone\n4. Test edge case: multiple simultaneous drags\n5. Test touch: touch drag on mobile devices\n6. Test touch: pinch-to-zoom during drag\n7. Test accessibility: keyboard drag (space/enter)",
|
||||
"estimatedHours": 3,
|
||||
"notes": "Already has good coverage (76%), focus on edge cases. Test keyboard accessibility - drag/drop should work with keyboard for a11y."
|
||||
},
|
||||
{
|
||||
"id": "TEST-018",
|
||||
"name": "Test deck builder page edge cases",
|
||||
"description": "Test DeckBuilderPage.vue and related components: edge cases in deck validation, card limits, energy management. Currently 54-79% coverage.",
|
||||
"category": "medium",
|
||||
"priority": 18,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/components/deck/DeckActionButtons.vue",
|
||||
"lines": [64, 86],
|
||||
"issue": "54% coverage - missing validation edge cases"
|
||||
},
|
||||
{
|
||||
"path": "src/components/deck/DeckHeader.vue",
|
||||
"lines": [32, 35],
|
||||
"issue": "71% coverage - missing error states"
|
||||
},
|
||||
{
|
||||
"path": "src/components/deck/DeckCardRow.vue",
|
||||
"lines": [50, 186],
|
||||
"issue": "79% coverage - missing interaction edge cases"
|
||||
},
|
||||
{
|
||||
"path": "src/pages/DeckBuilderPage.vue",
|
||||
"lines": [157, 290],
|
||||
"issue": "76% coverage - missing error handling"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Expand deck builder tests\n2. Test validation: deck too small, deck too large\n3. Test validation: too many copies of one card\n4. Test validation: invalid Pokemon (evolves without basic)\n5. Test energy: add/remove energy, energy limits\n6. Test save: save success, save error (network fail)\n7. Test delete: delete confirmation, delete error",
|
||||
"estimatedHours": 4,
|
||||
"notes": "Deck builder has good basic coverage, need edge cases. Test error states thoroughly - what happens when save fails mid-edit? Test undo/redo if implemented."
|
||||
},
|
||||
{
|
||||
"id": "TEST-019",
|
||||
"name": "Test HomePage, CampaignPage, MatchPage",
|
||||
"description": "Test main user-facing pages: navigation, state loading, error states. Currently 0% coverage on these pages.",
|
||||
"category": "medium",
|
||||
"priority": 19,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/pages/HomePage.vue",
|
||||
"lines": [1, 200],
|
||||
"issue": "0% coverage - page untested"
|
||||
},
|
||||
{
|
||||
"path": "src/pages/CampaignPage.vue",
|
||||
"lines": [1, 300],
|
||||
"issue": "0% coverage - page untested"
|
||||
},
|
||||
{
|
||||
"path": "src/pages/MatchPage.vue",
|
||||
"lines": [1, 250],
|
||||
"issue": "0% coverage - page untested"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create HomePage.spec.ts, CampaignPage.spec.ts, MatchPage.spec.ts\n2. Test HomePage: renders welcome message, navigation links\n3. Test CampaignPage: loads campaign data, displays clubs\n4. Test CampaignPage: click club navigates to matches\n5. Test MatchPage: loads match data, displays opponent\n6. Test error states: 404 game not found, loading errors\n7. Mock router and API calls",
|
||||
"estimatedHours": 6,
|
||||
"notes": "Page tests are integration tests - test user flows, not implementation details. Focus on happy path and error states. Mock all API calls and router."
|
||||
},
|
||||
{
|
||||
"id": "TEST-020",
|
||||
"name": "Test socket message type guards",
|
||||
"description": "Test socket/types.ts: type guard functions, message validation. Currently 0% coverage but may be all type definitions.",
|
||||
"category": "low",
|
||||
"priority": 20,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/socket/types.ts",
|
||||
"lines": [308, 361],
|
||||
"issue": "0% coverage - type guards untested"
|
||||
},
|
||||
{
|
||||
"path": "src/socket/types.spec.ts",
|
||||
"lines": [1, 400],
|
||||
"issue": "Has tests but missing type guards"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Check if lines 308-361 contain runtime type guards\n2. If so, expand types.spec.ts to test them\n3. Test type guards: isGameStateMessage(), isActionResultMessage()\n4. Test invalid input: null, undefined, wrong shape\n5. Test edge cases: missing optional fields, extra fields",
|
||||
"estimatedHours": 2,
|
||||
"notes": "Only test if there are runtime type guards. Type-only definitions don't need tests. Type guards are important for robustness - ensure they catch malformed messages."
|
||||
},
|
||||
{
|
||||
"id": "TEST-021",
|
||||
"name": "Test game animation sequences",
|
||||
"description": "Create integration tests for common game animation sequences: card played, attack, knockout, prize drawn. Test that animations play in correct order and complete.",
|
||||
"category": "medium",
|
||||
"priority": 21,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-002", "TEST-004", "TEST-005"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/game/animations/sequences.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created - integration tests for animations"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create sequences.spec.ts for integration tests\n2. Test card played: hand → active, animation completes\n3. Test attack: attacker animates toward defender, damage applied\n4. Test knockout: defender fades out, moved to discard\n5. Test prize drawn: prize flips, moves to hand\n6. Test game over: victory/defeat animation\n7. Use fake timers to control animation timing",
|
||||
"estimatedHours": 6,
|
||||
"notes": "Integration tests - test animation sequences end-to-end. Use vitest fake timers to advance animation frames. These tests ensure animations complete and don't get stuck."
|
||||
},
|
||||
{
|
||||
"id": "TEST-022",
|
||||
"name": "Set up visual regression testing infrastructure",
|
||||
"description": "Set up Playwright for visual regression testing of Phaser game. Take screenshots of game states and compare to baselines. This catches visual bugs that unit tests miss.",
|
||||
"category": "infrastructure",
|
||||
"priority": 22,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "playwright.config.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created - Playwright config"
|
||||
},
|
||||
{
|
||||
"path": "tests/visual/game.spec.ts",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created - visual regression tests"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Install Playwright: npm install -D @playwright/test\n2. Create playwright.config.ts with screenshot config\n3. Create tests/visual/ directory for visual tests\n4. Create baseline: npm run test:visual:update\n5. Test game board: screenshot of initial state\n6. Test game state: screenshot of mid-game state\n7. Test animations: screenshot at key animation frames\n8. Compare: fail if screenshots differ from baseline",
|
||||
"estimatedHours": 8,
|
||||
"notes": "Visual regression testing is essential for Phaser - catches layout bugs, rendering glitches, animation issues that unit tests can't detect. Keep baselines in Git. Run in CI to catch regressions."
|
||||
},
|
||||
{
|
||||
"id": "TEST-023",
|
||||
"name": "Add coverage reporting to CI",
|
||||
"description": "Configure CI to generate coverage reports, post to PRs, fail if coverage drops. Track coverage over time.",
|
||||
"category": "infrastructure",
|
||||
"priority": 23,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": ".github/workflows/test.yml",
|
||||
"lines": [],
|
||||
"issue": "Needs coverage reporting step"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Add coverage flag to CI test command: npm run test -- --coverage\n2. Upload coverage to Codecov or similar service\n3. Add coverage badge to README\n4. Configure PR comments with coverage diff\n5. Set minimum coverage threshold: fail if <80%\n6. Track coverage trends over time",
|
||||
"estimatedHours": 3,
|
||||
"notes": "Infrastructure improvement - makes coverage visible to team. Set threshold but don't be too strict initially (current 63%, target 80%). Use a service like Codecov, Coveralls, or GitHub Actions built-in."
|
||||
},
|
||||
{
|
||||
"id": "TEST-024",
|
||||
"name": "Document testing patterns and guidelines",
|
||||
"description": "Create comprehensive testing documentation: how to write Phaser tests, common patterns, mocking strategies, debugging tips.",
|
||||
"category": "infrastructure",
|
||||
"priority": 24,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["TEST-001"],
|
||||
"files": [
|
||||
{
|
||||
"path": "src/test/README.md",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created - testing guide"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Create src/test/README.md\n2. Document Phaser testing patterns: how to mock scenes/objects\n3. Document common test utilities: gameTestUtils API\n4. Document integration testing: how to test sequences\n5. Add examples: good test vs bad test\n6. Add debugging tips: how to debug failing tests\n7. Link to Vitest and Phaser testing resources",
|
||||
"estimatedHours": 4,
|
||||
"notes": "Documentation is essential for team - makes it easy for others to write tests. Include real examples from the codebase. Keep updated as patterns evolve."
|
||||
},
|
||||
{
|
||||
"id": "TEST-025",
|
||||
"name": "Add mutation testing with Stryker",
|
||||
"description": "Set up Stryker mutation testing to verify test quality. Mutation testing detects weak tests by injecting bugs and checking if tests catch them.",
|
||||
"category": "infrastructure",
|
||||
"priority": 25,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "stryker.conf.json",
|
||||
"lines": [],
|
||||
"issue": "File needs to be created - Stryker config"
|
||||
}
|
||||
],
|
||||
"suggestedFix": "1. Install Stryker: npm install -D @stryker-mutator/core @stryker-mutator/vitest-runner\n2. Create stryker.conf.json\n3. Configure mutators: arithmetic, conditionals, remove statements\n4. Run initial mutation test: npm run test:mutation\n5. Review results: identify weak tests\n6. Improve tests to catch mutations\n7. Add to CI (optional, slow)",
|
||||
"estimatedHours": 6,
|
||||
"notes": "Advanced testing technique - lower priority. Mutation testing is slow but valuable for critical code. Start with small modules (game/layout, game/config) before running on entire codebase."
|
||||
}
|
||||
],
|
||||
"quickWins": [
|
||||
{
|
||||
"taskId": "TEST-015",
|
||||
"estimatedMinutes": 120,
|
||||
"impact": "CardBack is simple and quick to test - gets game/ directory coverage started"
|
||||
},
|
||||
{
|
||||
"taskId": "TEST-020",
|
||||
"estimatedMinutes": 120,
|
||||
"impact": "Type guards are straightforward to test - improves socket reliability"
|
||||
},
|
||||
{
|
||||
"taskId": "TEST-016",
|
||||
"estimatedMinutes": 180,
|
||||
"impact": "User store edge cases - improves coverage from 52% to 90%+"
|
||||
}
|
||||
],
|
||||
"productionBlockers": [
|
||||
{
|
||||
"taskId": "TEST-005",
|
||||
"reason": "StateRenderer synchronization bugs cause game state desyncs between players"
|
||||
},
|
||||
{
|
||||
"taskId": "TEST-007",
|
||||
"reason": "WebSocket connection issues can make multiplayer games unplayable"
|
||||
},
|
||||
{
|
||||
"taskId": "TEST-008",
|
||||
"reason": "Action queueing bugs can cause lost actions or duplicate actions"
|
||||
}
|
||||
],
|
||||
"weeklyRoadmap": {
|
||||
"week1": {
|
||||
"theme": "Phaser Testing Infrastructure & Core Objects",
|
||||
"goals": "Establish Phaser testing foundation, test core game objects",
|
||||
"tasks": ["TEST-001", "TEST-003", "TEST-006", "TEST-015"],
|
||||
"estimatedHours": 28,
|
||||
"deliverables": "Phaser mocks, Board tests, Zone tests, CardBack tests"
|
||||
},
|
||||
"week2": {
|
||||
"theme": "Phaser Scenes & State Synchronization",
|
||||
"goals": "Test scene lifecycle and state rendering",
|
||||
"tasks": ["TEST-002", "TEST-004", "TEST-005"],
|
||||
"estimatedHours": 32,
|
||||
"deliverables": "MatchScene tests, Card tests, StateRenderer tests"
|
||||
},
|
||||
"week3": {
|
||||
"theme": "WebSocket & Network Layer",
|
||||
"goals": "Complete WebSocket testing, ensure reliable multiplayer",
|
||||
"tasks": ["TEST-007", "TEST-008", "TEST-009"],
|
||||
"estimatedHours": 20,
|
||||
"deliverables": "Socket client tests, useGameSocket tests, useGames tests"
|
||||
},
|
||||
"week4": {
|
||||
"theme": "Game Engine Completion",
|
||||
"goals": "Test remaining game engine components",
|
||||
"tasks": ["TEST-010", "TEST-011", "TEST-012", "TEST-013", "TEST-014"],
|
||||
"estimatedHours": 21,
|
||||
"deliverables": "PreloadScene, HandManager, DamageCounter, AssetLoader, ResizeManager tests"
|
||||
},
|
||||
"week5": {
|
||||
"theme": "UI & Integration Testing",
|
||||
"goals": "Test pages and integration scenarios",
|
||||
"tasks": ["TEST-016", "TEST-017", "TEST-018", "TEST-019", "TEST-021"],
|
||||
"estimatedHours": 22,
|
||||
"deliverables": "Page tests, edge case tests, animation sequence tests"
|
||||
},
|
||||
"week6": {
|
||||
"theme": "Infrastructure & Polish",
|
||||
"goals": "Set up visual regression, improve testing infrastructure",
|
||||
"tasks": ["TEST-020", "TEST-022", "TEST-023", "TEST-024"],
|
||||
"estimatedHours": 21,
|
||||
"deliverables": "Playwright setup, CI coverage, testing documentation"
|
||||
}
|
||||
},
|
||||
"milestones": [
|
||||
{
|
||||
"name": "Phaser Testing Enabled",
|
||||
"completionCriteria": "TEST-001 complete, can write Phaser tests easily",
|
||||
"targetDate": "End of Week 1",
|
||||
"blockedBy": []
|
||||
},
|
||||
{
|
||||
"name": "Core Game Engine Tested",
|
||||
"completionCriteria": "MatchScene, Board, Card, StateRenderer all >70% coverage",
|
||||
"targetDate": "End of Week 2",
|
||||
"blockedBy": ["Phaser Testing Enabled"]
|
||||
},
|
||||
{
|
||||
"name": "Network Layer Solid",
|
||||
"completionCriteria": "Socket and WebSocket code >80% coverage, multiplayer reliable",
|
||||
"targetDate": "End of Week 3",
|
||||
"blockedBy": []
|
||||
},
|
||||
{
|
||||
"name": "Game Engine Complete",
|
||||
"completionCriteria": "All game/ directory files >60% coverage",
|
||||
"targetDate": "End of Week 4",
|
||||
"blockedBy": ["Core Game Engine Tested"]
|
||||
},
|
||||
{
|
||||
"name": "Overall 75% Coverage",
|
||||
"completionCriteria": "Project-wide coverage reaches 75%",
|
||||
"targetDate": "End of Week 5",
|
||||
"blockedBy": ["Game Engine Complete", "Network Layer Solid"]
|
||||
},
|
||||
{
|
||||
"name": "Testing Infrastructure Mature",
|
||||
"completionCriteria": "Visual regression working, CI coverage tracking, good documentation",
|
||||
"targetDate": "End of Week 6",
|
||||
"blockedBy": []
|
||||
}
|
||||
],
|
||||
"coverageTargets": {
|
||||
"current": {
|
||||
"overall": "63%",
|
||||
"game": "0%",
|
||||
"composables": "84%",
|
||||
"components": "90%",
|
||||
"stores": "88%",
|
||||
"socket": "27%"
|
||||
},
|
||||
"week2": {
|
||||
"overall": "68%",
|
||||
"game": "40%",
|
||||
"composables": "84%",
|
||||
"components": "90%",
|
||||
"stores": "88%",
|
||||
"socket": "27%"
|
||||
},
|
||||
"week4": {
|
||||
"overall": "73%",
|
||||
"game": "60%",
|
||||
"composables": "86%",
|
||||
"components": "90%",
|
||||
"stores": "90%",
|
||||
"socket": "80%"
|
||||
},
|
||||
"week6": {
|
||||
"overall": "78%",
|
||||
"game": "65%",
|
||||
"composables": "90%",
|
||||
"components": "92%",
|
||||
"stores": "92%",
|
||||
"socket": "85%"
|
||||
},
|
||||
"target_3months": {
|
||||
"overall": "85%",
|
||||
"game": "80%",
|
||||
"composables": "95%",
|
||||
"components": "95%",
|
||||
"stores": "95%",
|
||||
"socket": "90%"
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"phaserTesting": "Phaser testing is the biggest challenge. The infrastructure task (TEST-001) is critical - invest time here to make all subsequent tests easier. Consider extracting game logic from Phaser where possible (pure functions easier to test).",
|
||||
"visualRegression": "Visual regression testing (TEST-022) is optional but highly recommended for Phaser. Screenshots catch layout bugs, rendering glitches, and animation issues that unit tests miss.",
|
||||
"prioritization": "Focus on critical tasks first (TEST-001 through TEST-009). These are production-critical: game engine and network layer. Medium/low priority tasks can wait.",
|
||||
"incrementalApproach": "Don't try to achieve 100% coverage immediately. 85% is a realistic target. Some code is hard to test (animation frames, WebGL shaders) - focus on logic and state management.",
|
||||
"teamVelocity": "Estimates assume one developer. With 2-3 developers, can complete in 3-4 weeks instead of 6. Assign game engine to one person, network layer to another."
|
||||
}
|
||||
}
|
||||
423
frontend/TEST_COVERAGE_PLAN.md
Normal file
423
frontend/TEST_COVERAGE_PLAN.md
Normal file
@ -0,0 +1,423 @@
|
||||
# Test Coverage Improvement Plan
|
||||
|
||||
**Status:** Draft
|
||||
**Created:** 2026-02-02
|
||||
**Target Completion:** 6 weeks
|
||||
**Current Coverage:** 63%
|
||||
**Target Coverage:** 85%
|
||||
|
||||
See [`PROJECT_PLAN_TEST_COVERAGE.json`](./PROJECT_PLAN_TEST_COVERAGE.json) for full structured plan.
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The Mantimon TCG frontend has **excellent test discipline** (1000 passing tests) but has **critical gaps** in the game engine layer:
|
||||
|
||||
- ✅ **Strong:** Composables (84%), Components (90%), Stores (88%)
|
||||
- ❌ **Critical Gap:** Game Engine (0% coverage, ~5,500 lines)
|
||||
- ⚠️ **Needs Work:** WebSocket (27-45% coverage)
|
||||
|
||||
**Biggest Risk:** Phaser game engine is completely untested - rendering bugs, state sync issues, and visual glitches won't be caught until manual testing or production.
|
||||
|
||||
---
|
||||
|
||||
## Priority Overview
|
||||
|
||||
### 🔴 Critical (Week 1-3)
|
||||
|
||||
| Task | Component | Impact |
|
||||
|------|-----------|--------|
|
||||
| TEST-001 | Phaser testing infrastructure | **Prerequisite for all game engine tests** |
|
||||
| TEST-002 | MatchScene lifecycle | Core game scene rendering |
|
||||
| TEST-003 | Board layout & zones | Prize zone bug, zone management |
|
||||
| TEST-004 | Card rendering | Card display and interactions |
|
||||
| TEST-005 | StateRenderer sync | Backend ↔ Phaser synchronization |
|
||||
| TEST-007 | Socket connection | WebSocket reliability |
|
||||
| TEST-008 | Game action handling | Action queueing and responses |
|
||||
| TEST-009 | Game management | Create/join game workflows |
|
||||
|
||||
**Total:** ~80 hours, 8 tasks
|
||||
|
||||
### 🟡 High (Week 4)
|
||||
|
||||
| Task | Component | Impact |
|
||||
|------|-----------|--------|
|
||||
| TEST-006 | Zone classes | Zone types and card management |
|
||||
| TEST-010 | PreloadScene | Asset loading |
|
||||
| TEST-011 | HandManager | Drag/drop edge cases |
|
||||
| TEST-012 | DamageCounter | Damage display |
|
||||
| TEST-013 | Asset loader | Asset management |
|
||||
| TEST-014 | ResizeManager | Responsive layout |
|
||||
|
||||
**Total:** ~31 hours, 6 tasks
|
||||
|
||||
### 🟢 Medium/Low (Week 5-6)
|
||||
|
||||
- Page tests (HomePage, CampaignPage, MatchPage)
|
||||
- Edge case tests (user store, drag/drop, deck builder)
|
||||
- Infrastructure (visual regression, CI coverage, documentation)
|
||||
|
||||
**Total:** ~45 hours, remaining tasks
|
||||
|
||||
---
|
||||
|
||||
## Week-by-Week Roadmap
|
||||
|
||||
### Week 1: Foundation (28 hours)
|
||||
**Theme:** Phaser Testing Infrastructure & Core Objects
|
||||
|
||||
**Tasks:**
|
||||
- [ ] TEST-001: Create Phaser mocks and test utilities *(8h)*
|
||||
- [ ] TEST-003: Test Board layout and zones *(10h)*
|
||||
- [ ] TEST-006: Test Zone base class and subclasses *(10h)*
|
||||
|
||||
**Deliverables:**
|
||||
- Phaser testing infrastructure ready
|
||||
- Board and Zone classes tested
|
||||
- Game engine coverage: 0% → 20%
|
||||
|
||||
**Blockers:** None - can start immediately
|
||||
|
||||
---
|
||||
|
||||
### Week 2: Core Engine (32 hours)
|
||||
**Theme:** Scenes & State Synchronization
|
||||
|
||||
**Tasks:**
|
||||
- [ ] TEST-002: Test MatchScene initialization *(8h)*
|
||||
- [ ] TEST-004: Test Card rendering and interactions *(12h)*
|
||||
- [ ] TEST-005: Test StateRenderer synchronization *(12h)*
|
||||
|
||||
**Deliverables:**
|
||||
- MatchScene lifecycle tested
|
||||
- Card display and interactions tested
|
||||
- State sync tested (prize zone fix validated!)
|
||||
- Game engine coverage: 20% → 50%
|
||||
|
||||
**Blockers:** Requires TEST-001 (Week 1)
|
||||
|
||||
---
|
||||
|
||||
### Week 3: Network Layer (20 hours)
|
||||
**Theme:** WebSocket & Multiplayer Reliability
|
||||
|
||||
**Tasks:**
|
||||
- [ ] TEST-007: Test socket connection lifecycle *(6h)*
|
||||
- [ ] TEST-008: Test game action handling *(8h)*
|
||||
- [ ] TEST-009: Test game creation/joining *(6h)*
|
||||
|
||||
**Deliverables:**
|
||||
- WebSocket reliability tests
|
||||
- Action queueing tested
|
||||
- Game management tested
|
||||
- Socket coverage: 27% → 80%
|
||||
|
||||
**Blockers:** None - independent of game engine
|
||||
|
||||
---
|
||||
|
||||
### Week 4: Engine Completion (21 hours)
|
||||
**Theme:** Remaining Game Components
|
||||
|
||||
**Tasks:**
|
||||
- [ ] TEST-010: Test PreloadScene *(4h)*
|
||||
- [ ] TEST-011: Test HandManager drag/drop *(6h)*
|
||||
- [ ] TEST-012: Test DamageCounter *(3h)*
|
||||
- [ ] TEST-013: Test asset loader *(4h)*
|
||||
- [ ] TEST-014: Test ResizeManager *(4h)*
|
||||
|
||||
**Deliverables:**
|
||||
- All major game components tested
|
||||
- Game engine coverage: 50% → 65%
|
||||
- **Overall coverage: 63% → 73%**
|
||||
|
||||
---
|
||||
|
||||
### Week 5: Integration & UI (22 hours)
|
||||
**Theme:** Pages and Integration Testing
|
||||
|
||||
**Tasks:**
|
||||
- [ ] TEST-016: User store edge cases *(3h)*
|
||||
- [ ] TEST-017: Drag/drop edge cases *(3h)*
|
||||
- [ ] TEST-018: Deck builder edge cases *(4h)*
|
||||
- [ ] TEST-019: Page tests (Home, Campaign, Match) *(6h)*
|
||||
- [ ] TEST-021: Animation sequences *(6h)*
|
||||
|
||||
**Deliverables:**
|
||||
- All pages tested
|
||||
- Integration scenarios covered
|
||||
- **Overall coverage: 73% → 78%**
|
||||
|
||||
---
|
||||
|
||||
### Week 6: Infrastructure (21 hours)
|
||||
**Theme:** Testing Tooling & Documentation
|
||||
|
||||
**Tasks:**
|
||||
- [ ] TEST-022: Visual regression with Playwright *(8h)*
|
||||
- [ ] TEST-023: CI coverage reporting *(3h)*
|
||||
- [ ] TEST-024: Testing documentation *(4h)*
|
||||
- [ ] TEST-020: Socket type guards *(2h)*
|
||||
- [ ] TEST-015: CardBack tests *(2h)* *(Quick win!)*
|
||||
- [ ] TEST-025: Mutation testing (optional) *(6h)*
|
||||
|
||||
**Deliverables:**
|
||||
- Visual regression testing enabled
|
||||
- CI coverage tracking
|
||||
- Testing guide for team
|
||||
- **Overall coverage: 78% → 82%+**
|
||||
|
||||
---
|
||||
|
||||
## Quick Wins (Do First!)
|
||||
|
||||
These tasks are simple and provide immediate value:
|
||||
|
||||
1. **TEST-015: CardBack** *(2h)* - Simple component, easy test, gets game/ coverage started
|
||||
2. **TEST-020: Type guards** *(2h)* - Straightforward logic tests, improves socket reliability
|
||||
3. **TEST-016: User store** *(3h)* - Boosts coverage from 52% to 90%+
|
||||
|
||||
**Total:** 7 hours for measurable coverage improvement
|
||||
|
||||
---
|
||||
|
||||
## Production Blockers
|
||||
|
||||
These must be tested before production release:
|
||||
|
||||
1. **TEST-005: StateRenderer** - State desync bugs cause multiplayer issues
|
||||
2. **TEST-007: Socket client** - Connection problems make game unplayable
|
||||
3. **TEST-008: useGameSocket** - Lost/duplicate actions break game flow
|
||||
|
||||
---
|
||||
|
||||
## Testing Challenges & Solutions
|
||||
|
||||
### Challenge 1: Phaser Requires WebGL/Canvas
|
||||
|
||||
**Problem:** Phaser games need WebGL/Canvas, jsdom doesn't support it
|
||||
|
||||
**Solutions:**
|
||||
1. **Mock Phaser classes** - Create minimal mocks (TEST-001)
|
||||
2. **Extract logic** - Pull game logic out of Phaser into pure functions
|
||||
3. **Visual regression** - Use Playwright for rendering tests (TEST-022)
|
||||
4. **Integration tests** - Test via PhaserGame.vue wrapper
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
// ❌ Hard to test - logic inside Phaser class
|
||||
class Card extends Phaser.GameObjects.Sprite {
|
||||
update() {
|
||||
if (this.health <= 0) {
|
||||
this.destroy()
|
||||
this.scene.addScore(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Easy to test - logic extracted
|
||||
function shouldDestroyCard(health: number): boolean {
|
||||
return health <= 0
|
||||
}
|
||||
|
||||
class Card extends Phaser.GameObjects.Sprite {
|
||||
update() {
|
||||
if (shouldDestroyCard(this.health)) {
|
||||
this.destroy()
|
||||
this.scene.addScore(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Challenge 2: WebSocket State Management
|
||||
|
||||
**Problem:** Real-time behavior, connection state, race conditions
|
||||
|
||||
**Solutions:**
|
||||
1. **Mock socket.io-client** - Already doing this well
|
||||
2. **Test state machines** - Focus on state transitions
|
||||
3. **Fake timers** - Control async timing
|
||||
4. **Error injection** - Simulate disconnect/reconnect
|
||||
|
||||
### Challenge 3: Animation Testing
|
||||
|
||||
**Problem:** Animations are async and timing-dependent
|
||||
|
||||
**Solutions:**
|
||||
1. **Fake timers** - Advance time manually with `vi.useFakeTimers()`
|
||||
2. **Test completion** - Verify animation completes, not individual frames
|
||||
3. **Integration tests** - Test animation sequences end-to-end (TEST-021)
|
||||
|
||||
---
|
||||
|
||||
## Coverage Targets
|
||||
|
||||
| Area | Current | Week 2 | Week 4 | Week 6 | 3 Months |
|
||||
|------|---------|--------|--------|--------|----------|
|
||||
| **Overall** | 63% | 68% | 73% | 78% | **85%** |
|
||||
| **Game Engine** | 0% | 40% | 60% | 65% | **80%** |
|
||||
| **Composables** | 84% | 84% | 86% | 90% | **95%** |
|
||||
| **Components** | 90% | 90% | 90% | 92% | **95%** |
|
||||
| **Stores** | 88% | 88% | 90% | 92% | **95%** |
|
||||
| **Socket** | 27% | 27% | 80% | 85% | **90%** |
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Option 1: Quick Wins First (Recommended)
|
||||
|
||||
Build momentum with easy tasks:
|
||||
|
||||
```bash
|
||||
# Week 1 - Quick Wins (7 hours)
|
||||
npm run test -- src/game/objects/CardBack.spec.ts # Create this
|
||||
npm run test -- src/socket/types.spec.ts # Expand this
|
||||
npm run test -- src/stores/user.spec.ts # Expand this
|
||||
|
||||
# Result: +5% coverage, confidence boost
|
||||
```
|
||||
|
||||
### Option 2: Critical Path (Production-Focused)
|
||||
|
||||
Tackle blockers immediately:
|
||||
|
||||
```bash
|
||||
# Week 1-3 - Critical Path (80 hours)
|
||||
# TEST-001: Phaser infrastructure (8h)
|
||||
# TEST-002: MatchScene (8h)
|
||||
# TEST-003: Board (10h)
|
||||
# TEST-004: Card (12h)
|
||||
# TEST-005: StateRenderer (12h)
|
||||
# TEST-007: Socket client (6h)
|
||||
# TEST-008: useGameSocket (8h)
|
||||
# TEST-009: useGames (6h)
|
||||
|
||||
# Result: Core game and network tested, ready for production
|
||||
```
|
||||
|
||||
### Option 3: Parallel Development (Team of 2-3)
|
||||
|
||||
Split work across developers:
|
||||
|
||||
```bash
|
||||
# Developer 1: Game Engine
|
||||
- Week 1: TEST-001, TEST-003, TEST-006
|
||||
- Week 2: TEST-002, TEST-004, TEST-005
|
||||
|
||||
# Developer 2: Network Layer
|
||||
- Week 1: TEST-007, TEST-008
|
||||
- Week 2: TEST-009, TEST-010, TEST-011
|
||||
|
||||
# Developer 3: UI & Infrastructure
|
||||
- Week 1: Quick wins (TEST-015, TEST-016, TEST-020)
|
||||
- Week 2: TEST-019, TEST-021
|
||||
- Week 3: TEST-022, TEST-023, TEST-024
|
||||
|
||||
# Result: Complete in 3 weeks instead of 6
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### Week 2 Checkpoint
|
||||
- [ ] Phaser infrastructure works well
|
||||
- [ ] Board tests validate prize zone fix
|
||||
- [ ] StateRenderer tests catch desync bugs
|
||||
- [ ] Coverage: 63% → 68%
|
||||
|
||||
### Week 4 Checkpoint
|
||||
- [ ] All major game components tested
|
||||
- [ ] WebSocket layer reliable
|
||||
- [ ] Coverage: 68% → 73%
|
||||
- [ ] Team confident in test suite
|
||||
|
||||
### Week 6 Completion
|
||||
- [ ] Visual regression enabled
|
||||
- [ ] CI coverage tracking
|
||||
- [ ] Testing documentation complete
|
||||
- [ ] Coverage: 73% → 78%
|
||||
- [ ] **Ready for production**
|
||||
|
||||
### 3 Month Goal
|
||||
- [ ] Coverage: 85%+
|
||||
- [ ] All critical code paths tested
|
||||
- [ ] Mutation testing shows strong tests
|
||||
- [ ] New features include tests by default
|
||||
|
||||
---
|
||||
|
||||
## Team Resources
|
||||
|
||||
### Documentation
|
||||
- [ ] TEST-024: Create `src/test/README.md` with testing patterns
|
||||
- [ ] Add Phaser testing examples
|
||||
- [ ] Document common pitfalls and solutions
|
||||
|
||||
### Infrastructure
|
||||
- [ ] TEST-022: Playwright visual regression
|
||||
- [ ] TEST-023: CI coverage reporting
|
||||
- [ ] TEST-025: Stryker mutation testing (optional)
|
||||
|
||||
### Training
|
||||
- Share knowledge as infrastructure is built
|
||||
- Code review testing PRs thoroughly
|
||||
- Pair program on first few Phaser tests
|
||||
|
||||
---
|
||||
|
||||
## Questions & Discussion
|
||||
|
||||
### Should we aim for 100% coverage?
|
||||
|
||||
**No.** 85% is realistic and valuable. Some code is hard to test:
|
||||
- Animation frames and tweens
|
||||
- WebGL rendering details
|
||||
- Entry points (main.ts, router config)
|
||||
|
||||
Focus on **logic and state management**, not rendering details.
|
||||
|
||||
### Should we stop feature work to fix coverage?
|
||||
|
||||
**Depends on priorities.** Options:
|
||||
|
||||
1. **Aggressive:** Pause features for 2-3 weeks, hit 80% coverage
|
||||
2. **Balanced:** Dedicate 1-2 days/week to testing, reach 80% in 8-10 weeks
|
||||
3. **Maintenance:** Add tests alongside features, reach 80% in 3-6 months
|
||||
|
||||
**Recommendation:** Balanced approach - TEST-001 through TEST-009 are critical for production confidence, do those first.
|
||||
|
||||
### What if tests are too slow?
|
||||
|
||||
**Strategies:**
|
||||
- Use `vi.mock()` liberally - mock Phaser, socket, API
|
||||
- Use `vi.useFakeTimers()` - control async timing
|
||||
- Run tests in parallel: `vitest --threads`
|
||||
- Use `it.skip()` for expensive tests during development
|
||||
- Run full suite in CI only
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Review this plan** - Discuss with team, adjust priorities
|
||||
2. **Start with TEST-001** - Build Phaser testing infrastructure
|
||||
3. **Quick win: TEST-015** - Test CardBack to validate infrastructure
|
||||
4. **Tackle critical path** - TEST-002 through TEST-009
|
||||
5. **Track progress** - Update `PROJECT_PLAN_TEST_COVERAGE.json` as tasks complete
|
||||
|
||||
---
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [`PROJECT_PLAN_TEST_COVERAGE.json`](./PROJECT_PLAN_TEST_COVERAGE.json) - Full structured plan
|
||||
- [`TESTING.md`](../TESTING.md) - Current testing guide (recently added!)
|
||||
- [`CONTRIBUTING.md`](../CONTRIBUTING.md) - Never use --no-verify policy
|
||||
- [`VISUAL-TEST-GUIDE.md`](../VISUAL-TEST-GUIDE.md) - Visual testing reference
|
||||
|
||||
---
|
||||
|
||||
**Let's build confidence in our game engine! 🎮✅**
|
||||
228
frontend/package-lock.json
generated
228
frontend/package-lock.json
generated
@ -22,6 +22,7 @@
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@types/node": "^24.10.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
@ -57,6 +58,20 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/css-color": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz",
|
||||
@ -158,6 +173,16 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bcoe/v8-coverage": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
|
||||
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||
@ -977,6 +1002,16 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/schema": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
|
||||
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
@ -1980,6 +2015,40 @@
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
|
||||
"integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.3.0",
|
||||
"@bcoe/v8-coverage": "^1.0.2",
|
||||
"ast-v8-to-istanbul": "^0.3.3",
|
||||
"debug": "^4.4.1",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"istanbul-lib-source-maps": "^5.0.6",
|
||||
"istanbul-reports": "^3.1.7",
|
||||
"magic-string": "^0.30.17",
|
||||
"magicast": "^0.3.5",
|
||||
"std-env": "^3.9.0",
|
||||
"test-exclude": "^7.0.1",
|
||||
"tinyrainbow": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vitest/browser": "3.2.4",
|
||||
"vitest": "3.2.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vitest/browser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
||||
@ -2464,6 +2533,35 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ast-v8-to-istanbul": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz",
|
||||
"integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.31",
|
||||
"estree-walker": "^3.0.3",
|
||||
"js-tokens": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ast-v8-to-istanbul/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ast-v8-to-istanbul/node_modules/js-tokens": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
|
||||
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.23",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
|
||||
@ -3528,6 +3626,13 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||
@ -3659,6 +3764,60 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-report": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
|
||||
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"istanbul-lib-coverage": "^3.0.0",
|
||||
"make-dir": "^4.0.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-source-maps": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
|
||||
"integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.23",
|
||||
"debug": "^4.1.1",
|
||||
"istanbul-lib-coverage": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-reports": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
|
||||
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"html-escaper": "^2.0.0",
|
||||
"istanbul-lib-report": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
@ -4126,6 +4285,34 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/magicast": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
|
||||
"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.25.4",
|
||||
"@babel/types": "^7.25.4",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
|
||||
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.12.2",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
|
||||
@ -4930,6 +5117,47 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz",
|
||||
"integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@istanbuljs/schema": "^0.1.2",
|
||||
"glob": "^10.4.1",
|
||||
"minimatch": "^9.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@types/node": "^24.10.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* and action dispatching functionality.
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { mount, VueWrapper } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import AttackMenu from './AttackMenu.vue'
|
||||
import { useGameStore } from '@/stores/game'
|
||||
|
||||
@ -36,6 +36,8 @@ interface Props {
|
||||
show: boolean
|
||||
}
|
||||
|
||||
// Props are used in template via direct reference (show, not props.show)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@ -63,6 +63,7 @@ function createMockGame() {
|
||||
// Mock the createGame function from @/game
|
||||
vi.mock('@/game', () => ({
|
||||
createGame: vi.fn(() => createMockGame()),
|
||||
scenes: [], // Mock scenes array to satisfy the import
|
||||
}))
|
||||
|
||||
describe('PhaserGame', () => {
|
||||
@ -102,7 +103,7 @@ describe('PhaserGame', () => {
|
||||
wrapper = mount(PhaserGame)
|
||||
|
||||
expect(createGame).toHaveBeenCalledTimes(1)
|
||||
expect(createGame).toHaveBeenCalledWith(expect.any(HTMLElement))
|
||||
expect(createGame).toHaveBeenCalledWith(expect.any(HTMLElement), [])
|
||||
})
|
||||
|
||||
it('provides container element as ref', () => {
|
||||
|
||||
@ -592,6 +592,14 @@ describe('useAuth', () => {
|
||||
hasStarterDeck: true,
|
||||
})
|
||||
|
||||
// Mock the profile fetch that happens during initialization
|
||||
vi.mocked(apiClient.get).mockResolvedValueOnce({
|
||||
user_id: 'user-1',
|
||||
display_name: 'Test User',
|
||||
avatar_url: null,
|
||||
has_starter_deck: true,
|
||||
})
|
||||
|
||||
const { initialize } = useAuth()
|
||||
const result = await initialize()
|
||||
|
||||
@ -689,6 +697,14 @@ describe('useAuth', () => {
|
||||
hasStarterDeck: true,
|
||||
})
|
||||
|
||||
// Mock the profile fetch that happens during initialization
|
||||
vi.mocked(apiClient.get).mockResolvedValueOnce({
|
||||
user_id: 'user-1',
|
||||
display_name: 'Test User',
|
||||
avatar_url: null,
|
||||
has_starter_deck: true,
|
||||
})
|
||||
|
||||
const { initialize, isInitialized } = useAuth()
|
||||
|
||||
await initialize()
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
* with automatic cleanup and component-scoped listener tracking.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
|
||||
import { defineComponent, h, nextTick } from 'vue'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { defineComponent, h } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import { useGameBridge } from './useGameBridge'
|
||||
import { gameBridge } from '@/game/bridge'
|
||||
|
||||
@ -5,6 +5,7 @@ import { useGameSocket } from './useGameSocket'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { ConnectionStatus } from '@/types'
|
||||
import type { VisibleGameState } from '@/types/game'
|
||||
|
||||
// Mock socket.io-client - simplified mocking
|
||||
vi.mock('socket.io-client', () => ({
|
||||
@ -150,7 +151,7 @@ describe('useGameSocket', () => {
|
||||
|
||||
// Set up some state
|
||||
gameStore.currentGameId = 'game-123'
|
||||
gameStore.gameState = {} as any
|
||||
gameStore.gameState = {} as VisibleGameState
|
||||
|
||||
disconnect()
|
||||
|
||||
|
||||
@ -254,8 +254,8 @@ export function useGameSocket() {
|
||||
* Handle game state update.
|
||||
*/
|
||||
function handleGameState(message: unknown): void {
|
||||
const msg = message as { state: unknown; event_id: string }
|
||||
gameStore.setGameState(msg.state as any, msg.event_id)
|
||||
const msg = message as { state: VisibleGameState; event_id: string }
|
||||
gameStore.setGameState(msg.state, msg.event_id)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -312,8 +312,8 @@ export function useGameSocket() {
|
||||
* Handle game over notification.
|
||||
*/
|
||||
function handleGameOver(message: unknown): void {
|
||||
const msg = message as { final_state: unknown; event_id: string; winner_id: string | null }
|
||||
gameStore.setGameState(msg.final_state as any, msg.event_id)
|
||||
const msg = message as { final_state: VisibleGameState; event_id: string; winner_id: string | null }
|
||||
gameStore.setGameState(msg.final_state, msg.event_id)
|
||||
|
||||
const isWinner = msg.winner_id === gameStore.gameState?.viewer_id
|
||||
if (isWinner) {
|
||||
|
||||
@ -15,12 +15,9 @@ import {
|
||||
isZoneInBounds,
|
||||
getAllZones,
|
||||
getCardSize,
|
||||
CARD_WIDTH_MEDIUM,
|
||||
CARD_HEIGHT_MEDIUM,
|
||||
CARD_WIDTH_SMALL,
|
||||
BENCH_SIZE,
|
||||
PRIZE_SIZE,
|
||||
PORTRAIT_THRESHOLD,
|
||||
CARD_WIDTH_MEDIUM,
|
||||
} from './layout'
|
||||
import type { BoardLayout, ZonePosition } from '@/types/phaser'
|
||||
|
||||
@ -40,9 +37,6 @@ const LANDSCAPE_HEIGHT = 1080
|
||||
const PORTRAIT_WIDTH = 390
|
||||
const PORTRAIT_HEIGHT = 844
|
||||
|
||||
/** Square resolution (edge case) */
|
||||
const SQUARE_SIZE = 800
|
||||
|
||||
/** 4K resolution for scaling tests */
|
||||
const UHD_WIDTH = 3840
|
||||
const UHD_HEIGHT = 2160
|
||||
|
||||
@ -406,7 +406,7 @@ export class Board extends Phaser.GameObjects.Container {
|
||||
playerId: 'my' | 'opp' | 'both',
|
||||
enabled: boolean
|
||||
): void {
|
||||
for (const [key, zone] of this.zones) {
|
||||
for (const [, zone] of this.zones) {
|
||||
const matchesType = zone.zoneType === zoneType
|
||||
const matchesOwner =
|
||||
playerId === 'both' || zone.owner === playerId
|
||||
|
||||
@ -15,8 +15,7 @@ import Phaser from 'phaser'
|
||||
|
||||
import type { CardSize } from '@/types/phaser'
|
||||
import { CARD_SIZES } from '@/types/phaser'
|
||||
import { PLACEHOLDER_KEYS } from '../assets/manifest'
|
||||
import { getCardBackTextureKey, createPlaceholderTexture } from '../assets/loader'
|
||||
import { getCardBackTextureKey } from '../assets/loader'
|
||||
|
||||
// =============================================================================
|
||||
// Constants
|
||||
|
||||
@ -229,7 +229,7 @@ export class HandZone extends Zone {
|
||||
let angle = 0
|
||||
if (count > 1 && layout.totalAngle > 0) {
|
||||
const angleStep = layout.totalAngle / (count - 1)
|
||||
const angleRadians = Phaser.Math.DegToRad(layout.totalAngle / 2 - angleStep * index)
|
||||
const angleRadians = Phaser.Math.DegToRad(angleStep * index - layout.totalAngle / 2)
|
||||
angle = angleRadians
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ import {
|
||||
DESIGN_WIDTH,
|
||||
DESIGN_HEIGHT,
|
||||
DESIGN_ASPECT_RATIO,
|
||||
type ScaleInfo,
|
||||
} from './scale'
|
||||
|
||||
describe('scale utilities', () => {
|
||||
|
||||
@ -19,7 +19,7 @@ import Phaser from 'phaser'
|
||||
import { gameBridge, type ResizeEvent } from '../bridge'
|
||||
import type { VisibleGameState } from '@/types/game'
|
||||
import { StateRenderer } from '../sync/StateRenderer'
|
||||
import { Board, createBoard } from '../objects/Board'
|
||||
import type { Board } from '../objects/Board'
|
||||
import { calculateLayout } from '../layout'
|
||||
import { HandManager } from '../interactions/HandManager'
|
||||
|
||||
@ -80,8 +80,9 @@ export class MatchScene extends Phaser.Scene {
|
||||
|
||||
/**
|
||||
* Board game object for zone background rendering.
|
||||
* Created by StateRenderer once we have the initial game state with rules_config.
|
||||
*/
|
||||
private board?: Board
|
||||
private board: Board | null = null
|
||||
|
||||
/**
|
||||
* Track bound event handlers for cleanup
|
||||
@ -171,8 +172,13 @@ export class MatchScene extends Phaser.Scene {
|
||||
/**
|
||||
* Set up the initial board layout.
|
||||
*
|
||||
* Creates the board container, background, Board object, and zone placeholders.
|
||||
* The actual card positions are updated when state is received.
|
||||
* Creates the board container and background graphics. The Board object
|
||||
* itself is created by StateRenderer once we have the initial game state
|
||||
* with rules_config, ensuring zone rectangles match the actual game rules
|
||||
* (prize cards vs points, bench size, energy deck enabled, etc.).
|
||||
*
|
||||
* This defers Board creation to avoid rendering incorrect zone layouts
|
||||
* when use_prize_cards is false (Mantimon TCG mode).
|
||||
*/
|
||||
private setupBoard(): void {
|
||||
const { width, height } = this.cameras.main
|
||||
@ -184,10 +190,8 @@ export class MatchScene extends Phaser.Scene {
|
||||
// Create main board container
|
||||
this.boardContainer = this.add.container(0, 0)
|
||||
|
||||
// Create Board object for zone background rendering
|
||||
const layout = calculateLayout(width, height)
|
||||
this.board = createBoard(this, layout)
|
||||
this.boardContainer.add(this.board)
|
||||
// Note: Board will be created by StateRenderer when first state arrives
|
||||
// This ensures we have correct rules_config before rendering zone rectangles
|
||||
|
||||
// Draw debug zone outlines if enabled
|
||||
if (DEBUG_ZONES) {
|
||||
@ -195,6 +199,28 @@ export class MatchScene extends Phaser.Scene {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Board instance after it's created by StateRenderer.
|
||||
*
|
||||
* The board is created once we have the initial game state with rules_config,
|
||||
* ensuring zone rectangles match the actual game rules (prize cards vs points,
|
||||
* bench size, energy deck enabled, etc.).
|
||||
*
|
||||
* @param board - The Board instance to use
|
||||
*/
|
||||
setBoard(board: Board): void {
|
||||
if (this.board) {
|
||||
console.warn('[MatchScene] Board already exists, replacing')
|
||||
this.board.destroy()
|
||||
}
|
||||
|
||||
this.board = board
|
||||
|
||||
if (this.boardContainer) {
|
||||
this.boardContainer.add(board)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the board background.
|
||||
*
|
||||
@ -453,7 +479,7 @@ export class MatchScene extends Phaser.Scene {
|
||||
// Destroy board object
|
||||
if (this.board) {
|
||||
this.board.destroy()
|
||||
this.board = undefined
|
||||
this.board = null
|
||||
}
|
||||
|
||||
// Clear board container children (but keep container)
|
||||
|
||||
@ -13,13 +13,15 @@
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const renderer = new StateRenderer(scene)
|
||||
* const renderer = new StateRenderer(matchScene) // Now type-safe with MatchScene
|
||||
* renderer.render(gameState)
|
||||
* ```
|
||||
*/
|
||||
|
||||
import Phaser from 'phaser'
|
||||
|
||||
import type { MatchScene } from '../scenes/MatchScene'
|
||||
|
||||
import type {
|
||||
VisibleGameState,
|
||||
VisiblePlayerState,
|
||||
@ -37,6 +39,11 @@ import { HandZone } from '../objects/HandZone'
|
||||
import { PileZone } from '../objects/PileZone'
|
||||
import { PrizeZone } from '../objects/PrizeZone'
|
||||
import { Zone } from '../objects/Zone'
|
||||
import { Board, createBoard } from '../objects/Board'
|
||||
import { gameBridge } from '../bridge'
|
||||
|
||||
// Debug logging flag - only logs in development mode
|
||||
const DEBUG_RENDERER = import.meta.env.DEV
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
@ -78,8 +85,8 @@ export class StateRenderer {
|
||||
// Properties
|
||||
// ===========================================================================
|
||||
|
||||
/** The Phaser scene to render to */
|
||||
private scene: Phaser.Scene
|
||||
/** The MatchScene to render to */
|
||||
private scene: MatchScene
|
||||
|
||||
/** Map of card instance_id to Card game object */
|
||||
private cardMap: Map<string, Card> = new Map()
|
||||
@ -96,6 +103,9 @@ export class StateRenderer {
|
||||
/** Container for all game objects */
|
||||
private container: Phaser.GameObjects.Container | null = null
|
||||
|
||||
/** The Board instance for zone rectangle rendering */
|
||||
private board: Board | null = null
|
||||
|
||||
// ===========================================================================
|
||||
// Constructor
|
||||
// ===========================================================================
|
||||
@ -103,9 +113,9 @@ export class StateRenderer {
|
||||
/**
|
||||
* Create a new StateRenderer.
|
||||
*
|
||||
* @param scene - The Phaser scene to render to
|
||||
* @param scene - The MatchScene to render to (required for Board creation)
|
||||
*/
|
||||
constructor(scene: Phaser.Scene) {
|
||||
constructor(scene: MatchScene) {
|
||||
this.scene = scene
|
||||
}
|
||||
|
||||
@ -139,6 +149,31 @@ export class StateRenderer {
|
||||
// Calculate layout
|
||||
this.layout = calculateLayout(width, height, layoutOptions)
|
||||
|
||||
// Create Board on first render (now that we have correct rules_config)
|
||||
if (!this.board && this.layout) {
|
||||
try {
|
||||
if (DEBUG_RENDERER) {
|
||||
console.log('[StateRenderer] Creating board with layout options:', layoutOptions)
|
||||
}
|
||||
this.board = createBoard(this.scene, this.layout)
|
||||
this.scene.setBoard(this.board)
|
||||
if (DEBUG_RENDERER) {
|
||||
console.log('[StateRenderer] ✓ Board created successfully')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[StateRenderer] ✗ Failed to create board:', error)
|
||||
|
||||
// Emit fatal error to Vue layer
|
||||
gameBridge.emit('fatal-error', {
|
||||
message: 'Failed to initialize game board',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
context: 'StateRenderer.render()'
|
||||
})
|
||||
|
||||
return // Stop rendering
|
||||
}
|
||||
}
|
||||
|
||||
// Create zones if needed
|
||||
if (!this.zones) {
|
||||
this.createZones(state)
|
||||
@ -226,7 +261,7 @@ export class StateRenderer {
|
||||
*/
|
||||
clear(): void {
|
||||
// Destroy all cards
|
||||
for (const [_, card] of this.cardMap) {
|
||||
for (const [, card] of this.cardMap) {
|
||||
card.destroy()
|
||||
}
|
||||
this.cardMap.clear()
|
||||
@ -238,6 +273,15 @@ export class StateRenderer {
|
||||
this.zones = null
|
||||
}
|
||||
|
||||
// Destroy board
|
||||
if (this.board) {
|
||||
this.board.destroy()
|
||||
this.board = null
|
||||
if (DEBUG_RENDERER) {
|
||||
console.log('[StateRenderer] Board destroyed')
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy container
|
||||
if (this.container) {
|
||||
this.container.destroy()
|
||||
@ -269,6 +313,15 @@ export class StateRenderer {
|
||||
// Check if we should create optional zones
|
||||
const usePrizeCards = state.rules_config?.prizes.use_prize_cards ?? false
|
||||
const energyDeckEnabled = state.rules_config?.deck.energy_deck_enabled ?? true
|
||||
const benchSize = state.rules_config?.bench.max_size ?? 5
|
||||
|
||||
if (DEBUG_RENDERER) {
|
||||
console.log('[StateRenderer] Creating zones with rules:', {
|
||||
usePrizeCards,
|
||||
energyDeckEnabled,
|
||||
benchSize
|
||||
})
|
||||
}
|
||||
|
||||
// Create main container
|
||||
this.container = this.scene.add.container(0, 0)
|
||||
|
||||
@ -20,6 +20,7 @@ import AttackMenu from '@/components/game/AttackMenu.vue'
|
||||
import { useGameStore } from '@/stores/game'
|
||||
import { useGameSocket } from '@/composables/useGameSocket'
|
||||
import { useGameBridge } from '@/composables/useGameBridge'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import { ConnectionStatus } from '@/types'
|
||||
import type Phaser from 'phaser'
|
||||
|
||||
@ -32,6 +33,7 @@ const gameId = computed(() => route.params.id as string)
|
||||
const gameStore = useGameStore()
|
||||
const gameSocket = useGameSocket()
|
||||
const { emit: emitToBridge } = useGameBridge()
|
||||
const toast = useToast()
|
||||
|
||||
// Component refs
|
||||
const phaserGameRef = ref<InstanceType<typeof PhaserGame> | null>(null)
|
||||
@ -42,6 +44,7 @@ const isLoading = ref(true)
|
||||
const showExitConfirm = ref(false)
|
||||
const errorMessage = ref<string | null>(null)
|
||||
const showAttackMenu = ref(false)
|
||||
const fatalErrorMessage = ref<string | null>(null)
|
||||
|
||||
// Computed states from game store
|
||||
const isReconnecting = computed(() => gameStore.connectionStatus === ConnectionStatus.RECONNECTING)
|
||||
@ -140,6 +143,7 @@ async function resignGame(): Promise<void> {
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
console.error('[GamePage] Failed to resign:', error)
|
||||
toast.warning('Could not confirm resignation with server. Leaving game anyway.')
|
||||
// Still exit even if resign fails
|
||||
showExitConfirm.value = false
|
||||
cleanup()
|
||||
@ -147,6 +151,15 @@ async function resignGame(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle fatal error - return to play menu.
|
||||
*/
|
||||
function handleFatalErrorReturn(): void {
|
||||
fatalErrorMessage.value = null
|
||||
cleanup()
|
||||
router.push({ name: 'PlayMenu' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the attack menu.
|
||||
*
|
||||
@ -253,6 +266,20 @@ onMounted(() => {
|
||||
on('attack:request', () => {
|
||||
openAttackMenu()
|
||||
})
|
||||
|
||||
// Handle fatal errors from Phaser
|
||||
on('fatal-error', (data: { message: string; error: string; context?: string }) => {
|
||||
console.error('[GamePage] Fatal error from Phaser:', data)
|
||||
|
||||
// Show error toast
|
||||
toast.error(`${data.message}: ${data.error}`, 0)
|
||||
|
||||
// Set error message for overlay
|
||||
fatalErrorMessage.value = data.message
|
||||
errorMessage.value = data.message
|
||||
|
||||
// No auto-redirect - user must manually click button
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@ -356,6 +383,63 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Fatal Error Overlay -->
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="fatalErrorMessage"
|
||||
class="
|
||||
absolute inset-0 z-50
|
||||
bg-background/95 backdrop-blur-sm
|
||||
flex flex-col items-center justify-center
|
||||
text-foreground
|
||||
"
|
||||
data-testid="fatal-error-overlay"
|
||||
>
|
||||
<div class="text-error mb-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-16 h-16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
y1="8"
|
||||
x2="12"
|
||||
y2="12"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
y1="16"
|
||||
x2="12.01"
|
||||
y2="16"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-xl font-semibold text-error mb-2">
|
||||
{{ fatalErrorMessage }}
|
||||
</p>
|
||||
<p class="text-sm text-muted mb-6">
|
||||
The game cannot continue. Please return to the menu.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="px-6 py-3 rounded bg-primary hover:bg-primary-dark text-white transition-colors"
|
||||
data-testid="fatal-error-return-button"
|
||||
@click="handleFatalErrorReturn"
|
||||
>
|
||||
Return to Menu
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Connection Status Overlay -->
|
||||
<Transition name="fade">
|
||||
<div
|
||||
|
||||
@ -321,8 +321,8 @@ describe('PlayPage', () => {
|
||||
|
||||
expect(mockCreateGame).toHaveBeenCalledWith({
|
||||
deck_id: 'deck-1',
|
||||
opponent_id: 'placeholder-opponent',
|
||||
opponent_deck_id: 'placeholder-deck',
|
||||
opponent_id: '8d8b05a0-c231-4082-ba6f-56c2b908016c', // Bob's user ID
|
||||
opponent_deck_id: '0e8790dd-cf78-48f4-876b-65406f218f90', // Bob's Test Fire Deck
|
||||
game_type: 'freeplay',
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* All messages follow a discriminated union pattern with a 'type' field.
|
||||
*/
|
||||
|
||||
import type { GameEndReason, TurnPhase, VisibleGameState } from '@/types'
|
||||
import type { GameEndReason, VisibleGameState } from '@/types'
|
||||
|
||||
// =============================================================================
|
||||
// Enums
|
||||
|
||||
@ -143,6 +143,9 @@ export interface GameBridgeEvents {
|
||||
|
||||
/** Error occurred in Phaser */
|
||||
'error': (error: Error) => void
|
||||
|
||||
/** Fatal error in Phaser - game cannot continue */
|
||||
'fatal-error': (data: { message: string; error: string; context?: string }) => void
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
46
test-prize-fix.md
Normal file
46
test-prize-fix.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Quick Test: Prize Zone Fix Verification
|
||||
|
||||
## Visual Test (30 seconds)
|
||||
|
||||
1. Start game: `/game/f6f158c4-47b0-41b9-b3c2-8edc8275b70c`
|
||||
2. Wait for loading overlay to disappear
|
||||
3. Count the rectangular zones on the board
|
||||
|
||||
**PASS:** You see exactly these zones (no prize rectangles):
|
||||
- Active (1 large center zone)
|
||||
- Bench (5 horizontal slots)
|
||||
- Hand (bottom, cards fanned)
|
||||
- Deck (small square, bottom-right area)
|
||||
- Discard (small square, next to deck)
|
||||
- Energy Deck (small square, bottom-left area)
|
||||
|
||||
**FAIL:** You see a 2x3 grid of prize rectangles on the left side
|
||||
|
||||
## Console Test (10 seconds)
|
||||
|
||||
Open DevTools Console and look for:
|
||||
|
||||
```
|
||||
[StateRenderer] Creating board with layout options: { usePrizeCards: false, ... }
|
||||
```
|
||||
|
||||
**PASS:** `usePrizeCards: false`
|
||||
**FAIL:** `usePrizeCards: true` or missing logs
|
||||
|
||||
## Fatal Error Test (1 minute)
|
||||
|
||||
1. Open `frontend/src/game/sync/StateRenderer.ts`
|
||||
2. Line 154, add: `throw new Error('Test')`
|
||||
3. Refresh game
|
||||
4. Should see: Full-screen overlay with "Return to Menu" button
|
||||
5. Should NOT auto-redirect
|
||||
6. Remove the `throw` line
|
||||
|
||||
**PASS:** Error overlay stays until you click button
|
||||
**FAIL:** Auto-redirects after 3 seconds
|
||||
|
||||
## Result
|
||||
|
||||
If all 3 tests pass: ✅ **Fix is working correctly!**
|
||||
|
||||
If any fail: ❌ Report which test failed and what you saw
|
||||
88
verify-fix.sh
Executable file
88
verify-fix.sh
Executable file
@ -0,0 +1,88 @@
|
||||
#!/bin/bash
|
||||
# Quick verification script for prize zone fix
|
||||
|
||||
echo "🔍 Verifying Prize Zone Fix..."
|
||||
echo ""
|
||||
|
||||
# Check we're on the right branch
|
||||
BRANCH=$(git branch --show-current)
|
||||
if [ "$BRANCH" != "fix/defer-board-creation-until-state" ]; then
|
||||
echo "❌ Wrong branch: $BRANCH"
|
||||
echo " Expected: fix/defer-board-creation-until-state"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ On correct branch: $BRANCH"
|
||||
|
||||
# Check TypeScript compilation
|
||||
echo ""
|
||||
echo "🔧 Checking TypeScript compilation..."
|
||||
cd frontend
|
||||
npm run typecheck > /dev/null 2>&1
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ TypeScript compilation passed"
|
||||
else
|
||||
echo "❌ TypeScript compilation failed"
|
||||
echo " Run 'cd frontend && npm run typecheck' for details"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for required imports
|
||||
echo ""
|
||||
echo "📦 Checking required imports..."
|
||||
|
||||
if grep -q "import { gameBridge } from '../bridge'" frontend/src/game/sync/StateRenderer.ts; then
|
||||
echo "✓ gameBridge import found in StateRenderer"
|
||||
else
|
||||
echo "❌ Missing gameBridge import in StateRenderer"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -q "import type { Board } from '../objects/Board'" frontend/src/game/scenes/MatchScene.ts; then
|
||||
echo "✓ Board type import found in MatchScene"
|
||||
else
|
||||
echo "❌ Missing Board type import in MatchScene"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for fatal error handling
|
||||
echo ""
|
||||
echo "⚠️ Checking fatal error handling..."
|
||||
|
||||
if grep -q "handleFatalErrorReturn" frontend/src/pages/GamePage.vue; then
|
||||
echo "✓ Fatal error handler function exists"
|
||||
else
|
||||
echo "❌ Missing fatal error handler function"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -q "Return to Menu" frontend/src/pages/GamePage.vue; then
|
||||
echo "✓ Manual return button exists"
|
||||
else
|
||||
echo "❌ Missing manual return button"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for resign toast
|
||||
echo ""
|
||||
echo "📢 Checking resign failure toast..."
|
||||
|
||||
if grep -q "Could not confirm resignation" frontend/src/pages/GamePage.vue; then
|
||||
echo "✓ Resign failure toast exists"
|
||||
else
|
||||
echo "❌ Missing resign failure toast"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ All automated checks passed!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Start dev servers (backend + frontend)"
|
||||
echo "2. Navigate to: http://localhost:5173/game/f6f158c4-47b0-41b9-b3c2-8edc8275b70c"
|
||||
echo "3. Verify NO prize rectangles appear on board"
|
||||
echo "4. Check console logs show: usePrizeCards: false"
|
||||
echo ""
|
||||
echo "See TESTING.md for full test suite"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
Loading…
Reference in New Issue
Block a user