This commit captures work from multiple sessions building the statistics system and frontend component library. Backend - Phase 3.5: Statistics System - Box score statistics with materialized views - Play stat calculator for real-time updates - Stat view refresher service - Alembic migration for materialized views - Test coverage: 41 new tests (all passing) Frontend - Phase F1: Foundation - Composables: useGameState, useGameActions, useWebSocket - Type definitions and interfaces - Store setup with Pinia Frontend - Phase F2: Game Display - ScoreBoard, GameBoard, CurrentSituation, PlayByPlay components - Demo page at /demo Frontend - Phase F3: Decision Inputs - DefensiveSetup, OffensiveApproach, StolenBaseInputs components - DecisionPanel orchestration - Demo page at /demo-decisions - Test coverage: 213 tests passing Frontend - Phase F4: Dice & Manual Outcome - DiceRoller component - ManualOutcomeEntry with validation - PlayResult display - GameplayPanel orchestration - Demo page at /demo-gameplay - Test coverage: 119 tests passing Frontend - Phase F5: Substitutions - PinchHitterSelector, DefensiveReplacementSelector, PitchingChangeSelector - SubstitutionPanel with tab navigation - Demo page at /demo-substitutions - Test coverage: 114 tests passing Documentation: - PHASE_3_5_HANDOFF.md - Statistics system handoff - PHASE_F2_COMPLETE.md - Game display completion - Frontend phase planning docs - NEXT_SESSION.md updated for Phase F6 Configuration: - Package updates (Nuxt 4 fixes) - Tailwind config enhancements - Game store updates Test Status: - Backend: 731/731 passing (100%) - Frontend: 446/446 passing (100%) - Total: 1,177 tests passing Next Phase: F6 - Integration (wire all components into game page) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
16 KiB
Frontend Testing Strategy
Project: Paper Dynasty Real-Time Game Engine - SBA Frontend Framework: Vue 3 + Nuxt 3 + TypeScript Testing Philosophy: Test early, test often - unit tests in every phase Created: 2025-01-10
Overview
This document outlines our comprehensive testing strategy for the SBA frontend. Unlike the original plan to defer testing to Phase F9, we now write unit tests alongside feature development in each phase (F1-F8), with E2E and performance testing in Phase F9.
Testing Philosophy
Core Principles
- Test-Driven Quality: Write tests as you build features, not after
- Fast Feedback: Unit tests run in milliseconds, catch bugs immediately
- Confidence: High test coverage allows refactoring without fear
- Documentation: Tests serve as executable documentation
- Regression Prevention: Tests prevent reintroducing fixed bugs
Why Test in Each Phase?
✅ Immediate Feedback: Catch bugs before they compound ✅ Better Design: Writing testable code forces better architecture ✅ Faster Development: Less debugging time overall ✅ Prevent Regressions: Safe refactoring as project grows ✅ Easier Onboarding: Tests document expected behavior
❌ Without Tests: Tech debt accumulates, bugs multiply, refactoring becomes risky
Testing Pyramid
/\
/E2\ E2E Tests (Phase F9)
/____\ - Full user flows
/ \ - Cross-browser testing
/ INTEG \ - Real WebSocket connections
/ RATION \
/____________\
/ \ Integration Tests (Light, as needed)
/ UNIT \ - API mocking
/ TESTS \ - Store + composable integration
/____________________\
80% of tests Unit Tests (Every Phase)
- Composables, stores, utils
- Fast, isolated, deterministic
Target Distribution:
- 80%: Unit tests (fast, isolated, many)
- 15%: Integration tests (medium speed, some mocking)
- 5%: E2E tests (slow, expensive, critical paths only)
Testing Tools
Unit Testing
Vitest - Modern, fast test runner
- Native ESM support
- Compatible with Vue and Nuxt
- Watch mode with instant feedback
- Built-in coverage reporting
@vue/test-utils - Official Vue testing library
- Mount Vue components
- Simulate user interactions
- Test props, emits, slots
happy-dom - Lightweight DOM implementation
- Faster than jsdom
- Sufficient for most unit tests
- Integrates with Vitest
E2E Testing (Phase F9)
Playwright - Modern E2E testing
- Cross-browser (Chromium, Firefox, WebKit)
- Mobile emulation
- Network interception
- Video/screenshot capture
What to Test (By Layer)
Composables (Critical - 90%+ coverage)
Why: Composables contain business logic and are highly reusable.
Test:
- ✅ Return values and reactive refs
- ✅ Side effects (API calls, WebSocket emits)
- ✅ Error handling and edge cases
- ✅ Lifecycle hooks (onMounted, onUnmounted)
- ✅ Integration with other composables/stores
Example: useWebSocket.spec.ts
describe('useWebSocket', () => {
it('connects with JWT auth on mount', () => { ... })
it('reconnects with exponential backoff', () => { ... })
it('emits correct events', () => { ... })
it('handles connection errors gracefully', () => { ... })
})
Stores (Critical - 90%+ coverage)
Why: Stores hold application state and business logic.
Test:
- ✅ State initialization
- ✅ Actions modify state correctly
- ✅ Computed properties calculate correctly
- ✅ Persistence (localStorage integration)
- ✅ Multiple action calls (state transitions)
Example: auth.spec.ts
describe('useAuthStore', () => {
it('logs in user with Discord OAuth', () => { ... })
it('persists token to localStorage', () => { ... })
it('refreshes expired tokens', () => { ... })
it('logs out and clears state', () => { ... })
})
Components (Important - 70%+ coverage)
Why: Components are the user interface layer.
Test:
- ✅ Props render correctly
- ✅ User interactions trigger emits
- ✅ Conditional rendering (v-if, v-show)
- ✅ Computed properties and methods
- ⚠️ Not: Visual appearance (use Storybook for that)
Example: ScoreBoard.spec.ts
describe('ScoreBoard', () => {
it('displays correct score and inning', () => { ... })
it('highlights current inning', () => { ... })
it('shows correct inning half (Top/Bottom)', () => { ... })
})
Utilities (Critical - 100% coverage)
Why: Pure functions are easiest to test and should be thoroughly covered.
Test:
- ✅ Input/output correctness
- ✅ Edge cases (null, undefined, empty)
- ✅ Error handling
- ✅ Type safety
Example: formatters.spec.ts
describe('formatInning', () => {
it('formats top of 1st inning', () => { ... })
it('formats bottom of 9th inning', () => { ... })
it('handles extra innings', () => { ... })
})
Testing Patterns
Mocking Strategy
Mock External Dependencies:
// Mock Socket.io
vi.mock('socket.io-client', () => ({
io: vi.fn(() => ({
on: vi.fn(),
emit: vi.fn(),
connect: vi.fn(),
disconnect: vi.fn(),
}))
}))
// Mock Nuxt composables
vi.mock('#app', () => ({
useRuntimeConfig: vi.fn(() => ({
public: {
apiUrl: 'http://localhost:8000',
wsUrl: 'http://localhost:8000'
}
}))
}))
Don't Mock What You Own:
- ❌ Don't mock stores in composable tests (test integration)
- ❌ Don't mock child components (use shallow mount if needed)
- ✅ Mock external APIs, WebSocket, localStorage
Test Structure (AAA Pattern)
describe('Feature', () => {
it('does something specific', () => {
// Arrange: Set up test data and mocks
const store = useAuthStore()
const mockToken = 'jwt-token-123'
// Act: Perform the action being tested
store.setToken(mockToken)
// Assert: Verify the outcome
expect(store.token).toBe(mockToken)
expect(localStorage.setItem).toHaveBeenCalledWith('token', mockToken)
})
})
Testing Async Code
it('fetches user data on login', async () => {
const store = useAuthStore()
// Mock API response
vi.mocked($fetch).mockResolvedValue({
id: '123',
username: 'testuser'
})
// Act
await store.login('code', 'state')
// Assert
expect(store.currentUser).toEqual({
id: '123',
username: 'testuser'
})
})
Testing Composables
import { mount } from '@vue/test-utils'
import { defineComponent } from 'vue'
it('useGameActions emits correct events', () => {
const mockSocket = {
emit: vi.fn(),
connected: true
}
// Create wrapper component to test composable
const TestComponent = defineComponent({
setup() {
const actions = useGameActions('game-123')
return { actions }
},
template: '<div></div>'
})
const wrapper = mount(TestComponent, {
global: {
provide: {
socket: mockSocket
}
}
})
// Act
wrapper.vm.actions.rollDice()
// Assert
expect(mockSocket.emit).toHaveBeenCalledWith('roll_dice', {
game_id: 'game-123'
})
})
Phase-by-Phase Testing Plan
Phase F1: Core Infrastructure (20+ tests)
Focus: Composables and stores (most critical)
Tests:
-
useWebSocket.spec.ts(8+ tests)- Connection/disconnection
- Exponential backoff
- JWT authentication
- Event handler registration
-
useGameActions.spec.ts(6+ tests)- Validation logic
- Emit parameter construction
- Error handling
-
auth.spec.ts(3+ tests)- Login/logout flow
- Token management
- localStorage persistence
-
game.spec.ts(2+ tests)- State updates
- Play history management
-
ui.spec.ts(2+ tests)- Toast lifecycle
- Modal stack
Estimated Time: 3-4 hours
Phase F2: Game State Display (10+ tests)
Focus: Display components and state sync
Tests:
ScoreBoard.spec.ts(2+ tests)GameBoard.spec.ts(2+ tests)PlayByPlay.spec.ts(2+ tests)CurrentBatter.spec.ts(2+ tests)- State synchronization logic (2+ tests)
Estimated Time: 2-3 hours
Phase F3: Decision Input Workflow (8+ tests)
Focus: Decision components and validation
Tests:
DefensiveSetup.spec.ts(2+ tests)OffensiveApproach.spec.ts(2+ tests)StolenBaseInputs.spec.ts(2+ tests)- Decision validation logic (2+ tests)
Estimated Time: 2 hours
Phase F4: Manual Outcome Workflow (8+ tests)
Focus: Dice roll and outcome validation
Tests:
DiceRollButton.spec.ts(2+ tests)OutcomeInputForm.spec.ts(3+ tests)- Outcome validation logic (3+ tests)
Estimated Time: 2 hours
Phase F5: Substitutions (8+ tests)
Focus: Substitution logic and components
Tests:
SubstitutionModal.spec.ts(2+ tests)PinchHitterSelect.spec.ts(2+ tests)- Substitution validation (4+ tests)
Estimated Time: 2 hours
Phase F6: Game Management (10+ tests)
Focus: Game API and management features
Tests:
useGameApi.spec.ts(4+ tests)GameCreationForm.spec.ts(2+ tests)GamesList.spec.ts(2+ tests)GameFilters.spec.ts(2+ tests)
Estimated Time: 2-3 hours
Phase F7: Spectator Mode (5+ tests)
Focus: Spectator permissions and UI
Tests:
- Spectator role validation (2+ tests)
SpectatorGameView.spec.ts(2+ tests)- Spectator WebSocket events (1+ test)
Estimated Time: 1-2 hours
Phase F8: Polish & Animation (5+ tests)
Focus: Animation triggers and timing
Tests:
- Animation trigger logic (3+ tests)
- Toast timing and auto-dismiss (2+ tests)
Estimated Time: 1-2 hours
Phase F9: E2E & Performance (10+ tests)
Focus: End-to-end user flows and performance
E2E Tests:
auth-flow.e2e.ts- Complete auth workflowgame-creation.e2e.ts- Create and join gamegameplay-flow.e2e.ts- Full game from start to finishsubstitution-flow.e2e.ts- Player substitutionsspectator-flow.e2e.ts- Spectator modemobile-responsive.e2e.ts- Mobile UXwebsocket-recovery.e2e.ts- Connection recovery
Performance Tests:
- Lighthouse audits (automated)
- Bundle size monitoring
- Memory leak detection
Estimated Time: 1 week
Running Tests
Development Workflow
# Run all tests (once)
npm run test
# Watch mode (re-run on file changes)
npm run test:watch
# Run specific test file
npm run test useWebSocket.spec.ts
# Run tests matching pattern
npm run test --grep "WebSocket"
# Coverage report
npm run test:coverage
# UI mode (interactive)
npm run test:ui
CI/CD Pipeline
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm ci
- run: npm run test:coverage
- uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npx playwright install
- run: npm run test:e2e
Coverage Goals
Minimum Coverage Targets
| Layer | Target | Priority |
|---|---|---|
| Composables | 90% | Critical |
| Stores | 90% | Critical |
| Utilities | 100% | Critical |
| Components | 70% | Important |
| Overall | 80% | Required |
Coverage Exemptions
Don't need 100% coverage for:
- UI-only components (visual polish)
- Third-party library wrappers
- Configuration files
- Type definitions
Do need high coverage for:
- Business logic (composables, stores)
- Validation and error handling
- WebSocket communication
- Authentication and security
Best Practices
DO ✅
- Write tests alongside feature code
- Test behavior, not implementation
- Use descriptive test names
- Keep tests independent (no shared state)
- Mock external dependencies
- Test edge cases and errors
- Run tests before committing
DON'T ❌
- Skip tests to "save time" (you'll pay later)
- Test implementation details
- Create test dependencies (test A needs test B)
- Mock everything (test real integration)
- Write slow unit tests (use E2E instead)
- Ignore failing tests
- Commit code that breaks tests
Troubleshooting
Tests Run Slowly
Problem: Unit tests take > 5 seconds Solution:
- Check for unmocked API calls
- Use happy-dom instead of jsdom
- Avoid unnecessary component mounts
- Run tests in parallel
Flaky Tests
Problem: Tests pass sometimes, fail other times Solution:
- Fix race conditions (use
waitFor) - Clear mocks between tests
- Avoid relying on timing
- Check for shared state
Coverage Not Updating
Problem: Coverage report doesn't reflect new tests Solution:
- Delete
coverage/directory - Re-run
npm run test:coverage - Check if files are ignored in config
Example Test Suites
Composable Test Template
// tests/unit/composables/useExample.spec.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { useExample } from '~/composables/useExample'
describe('useExample', () => {
beforeEach(() => {
// Setup: Create fresh instances, mock dependencies
})
afterEach(() => {
// Cleanup: Clear mocks, restore state
vi.clearAllMocks()
})
describe('initialization', () => {
it('initializes with default state', () => {
// Test default values
})
})
describe('actions', () => {
it('performs action successfully', async () => {
// Test happy path
})
it('handles errors gracefully', async () => {
// Test error case
})
})
describe('edge cases', () => {
it('handles null input', () => {
// Test edge case
})
})
})
Store Test Template
// tests/unit/store/example.spec.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useExampleStore } from '~/store/example'
describe('useExampleStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('initializes with default state', () => {
const store = useExampleStore()
expect(store.someState).toBe(null)
})
it('updates state via action', () => {
const store = useExampleStore()
store.someAction('value')
expect(store.someState).toBe('value')
})
it('computes derived state correctly', () => {
const store = useExampleStore()
store.someState = 'test'
expect(store.computedValue).toBe('TEST')
})
})
Component Test Template
// tests/unit/components/Example.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import ExampleComponent from '~/components/Example.vue'
describe('ExampleComponent', () => {
it('renders props correctly', () => {
const wrapper = mount(ExampleComponent, {
props: {
title: 'Test Title'
}
})
expect(wrapper.text()).toContain('Test Title')
})
it('emits event on button click', async () => {
const wrapper = mount(ExampleComponent)
await wrapper.find('button').trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
expect(wrapper.emitted('click')?.[0]).toEqual(['expected-value'])
})
it('conditionally renders based on prop', () => {
const wrapper = mount(ExampleComponent, {
props: {
show: false
}
})
expect(wrapper.find('.conditional-element').exists()).toBe(false)
})
})
Success Metrics
Phase Completion Criteria:
- ✅ All new code has corresponding tests
- ✅ All tests passing (no skipped tests)
- ✅ Coverage meets phase target (> 80%)
- ✅ No flaky tests
- ✅ Tests run in < 10 seconds (unit tests)
Project Completion Criteria:
- ✅ 80%+ overall coverage
- ✅ 90%+ coverage on critical paths
- ✅ All E2E tests passing
- ✅ Performance benchmarks met
- ✅ Zero known failing tests
Resources
Documentation:
Backend Tests (Inspiration):
/mnt/NV2/Development/strat-gameplay-webapp/backend/tests/unit/- 730+ tests with 99.9% coverage - excellent patterns to learn from
Document Version: 1.0 Last Updated: 2025-01-10 Status: Active - guiding all frontend development