# 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` ```typescript 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` ```typescript 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` ```typescript 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` ```typescript 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**: ```typescript // 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) ```typescript 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 ```typescript 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 ```typescript 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: '
' }) 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 ```bash # 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 ```yaml # .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 ```typescript // 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 ```typescript // 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 ```typescript // 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**: - [Vitest Docs](https://vitest.dev/) - [Vue Test Utils](https://test-utils.vuejs.org/) - [Playwright Docs](https://playwright.dev/) **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