strat-gameplay-webapp/.claude/implementation/testing-strategy.md
Cal Corum eab61ad966 CLAUDE: Phases 3.5, F1-F5 Complete - Statistics & Frontend Components
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>
2025-11-14 09:52:30 -06:00

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

  1. Test-Driven Quality: Write tests as you build features, not after
  2. Fast Feedback: Unit tests run in milliseconds, catch bugs immediately
  3. Confidence: High test coverage allows refactoring without fear
  4. Documentation: Tests serve as executable documentation
  5. 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 workflow
  • game-creation.e2e.ts - Create and join game
  • gameplay-flow.e2e.ts - Full game from start to finish
  • substitution-flow.e2e.ts - Player substitutions
  • spectator-flow.e2e.ts - Spectator mode
  • mobile-responsive.e2e.ts - Mobile UX
  • websocket-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