# Testing Patterns **Analysis Date:** 2026-02-04 ## Test Framework **Runner:** - Vitest 4.0.16 - Unit test runner - Playwright 1.57.0 - E2E test runner **Config Files:** - Unit tests: `vitest.config.js` - E2E tests: `playwright.config.js` **Run Commands:** ```bash npm run test # Run unit tests (Vitest) npm run test:ui # Run tests with Vitest UI npm run test:coverage # Run tests with coverage report npm run test:e2e # Run E2E tests (Playwright) npm run test:e2e:ui # Run E2E tests with Playwright UI npm run test:all # Run all tests (unit + E2E) ``` ## Test File Organization **Location:** - Unit tests: `tests/unit/` directory - E2E tests: `tests/e2e/` directory - Separate from source code (not co-located) **Naming:** - Unit tests: `.test.js` suffix: `game-logic.test.js` - E2E tests: `.spec.js` suffix: `game-flow.spec.js` **Structure:** ``` tests/ ├── unit/ │ └── game-logic.test.js └── e2e/ └── game-flow.spec.js ``` ## Unit Test Structure **Framework:** Vitest with globals enabled **Pattern from `tests/unit/game-logic.test.js`:** ```javascript import { describe, it, expect } from 'vitest'; describe('Game Logic - Inventory Management', () => { describe('Barrel Calculations', () => { it('should calculate correct fuel barrel count', () => { const fuel = 100; const barrelCount = Math.ceil(fuel / 10); expect(barrelCount).toBe(10); }); }); }); ``` **Test Organization:** - Top-level `describe()` blocks group by feature/system (e.g., "Game Logic - Inventory Management") - Nested `describe()` blocks organize related tests (e.g., "Barrel Calculations") - Individual `it()` blocks test single logical assertions - Test names are descriptive: "should [expected behavior]" ## E2E Test Structure **Framework:** Playwright with page object patterns **Pattern from `tests/e2e/game-flow.spec.js`:** ```javascript import { test, expect } from '@playwright/test'; test.describe('Whale Hunting Game - Main Flow', () => { test('should load the intro scene', async ({ page }) => { await page.goto('/'); await page.waitForTimeout(2000); const canvas = page.locator('canvas'); await expect(canvas).toBeVisible(); await expect(page).toHaveTitle(/Whale Hunting/); }); }); ``` **Test Organization:** - `test.describe()` blocks group related scenarios - Individual `test()` blocks test user flows - Setup patterns: `test.beforeEach()` for common navigation - Viewport configuration: `test.use()` for device-specific testing - Async/await pattern for all page interactions ## Mocking **Framework:** None configured or used **Patterns:** - No mocks found in unit tests - Tests verify pure calculation logic (Math.ceil, Math.min) - E2E tests work with actual running game instance **What to Mock:** - Not applicable; unit tests use pure functions without external dependencies - Phaser framework is not mocked (games tested in browser context) **What NOT to Mock:** - Scene transitions (tested via canvas visibility) - Phaser game instances (tested via E2E) ## Fixtures and Test Data **Test Data:** ```javascript // Direct inline values it('should calculate correct fuel barrel count', () => { const fuel = 100; const barrelCount = Math.ceil(fuel / 10); expect(barrelCount).toBe(10); }); // Inventory object template const inventory = { whaleOil: 0, fuel: 100, penguins: 0 }; ``` **Location:** - No shared fixture files; test data created inline within test functions - Inventory object structure repeated across multiple tests ## Coverage **Configuration:** ```javascript // vitest.config.js coverage: { provider: 'v8', reporter: ['text', 'html', 'lcov'], exclude: [ 'node_modules/', 'dist/', '*.config.js', 'tests/e2e/**' ] } ``` **View Coverage:** ```bash npm run test:coverage # Creates HTML report in coverage/ # View: coverage/index.html ``` **Excluded from Coverage:** - E2E tests (`tests/e2e/**`) - Config files (`*.config.js`) - Dependencies and build output ## Test Types **Unit Tests:** - Scope: Pure calculation logic (barrel counts, fuel consumption, bounds checking) - Approach: Direct function calls with known inputs - No DOM interaction; test game logic in isolation - Coverage: `tests/unit/game-logic.test.js` - Examples: - Barrel calculation: `Math.ceil(fuel / 10)` - Inventory limits: `Math.min(currentFuel + 10, maxFuel)` - Whale health: Health deduction logic - Mobile detection: User agent regex testing - Crosshair bounds: Clamp function testing **Integration Tests:** - Not explicitly used - Closest: E2E tests that verify scene transitions **E2E Tests:** - Scope: Complete user workflows (game startup to hunting) - Approach: Browser automation via Playwright - Tests: Canvas visibility, scene transitions, button clicks, viewport scaling - Coverage: `tests/e2e/game-flow.spec.js` - Desktop & mobile scenarios tested with viewport configuration - Examples: - "should load the intro scene" - "should navigate to hunting grounds" - "should work on mobile viewport" - "should scale properly on desktop" ## Common Patterns **Async Testing (E2E):** ```javascript test('should start game from intro scene', async ({ page }) => { await page.goto('/'); await page.waitForTimeout(2000); const canvas = page.locator('canvas'); await canvas.click({ position: { x: 400, y: 400 } }); await page.waitForTimeout(1000); await expect(canvas).toBeVisible(); }); ``` - `async ({ page })` destructures Playwright fixture - `await` on page navigation and interactions - `page.waitForTimeout()` for animation/transition delays - `page.locator()` for element selection **Error Testing (Unit):** ```javascript it('should not process whale without enough fuel', () => { let fuel = 1; const requiredFuel = 2; const canProcess = fuel >= requiredFuel; expect(canProcess).toBe(false); }); ``` - Boolean logic to test conditions - No exception throwing; test state validation **Mobile Testing (E2E):** ```javascript test.describe('Mobile Compatibility', () => { test.use({ viewport: { width: 375, height: 667 }, isMobile: true, }); test('should work on mobile viewport', async ({ page }) => { await page.goto('/'); await page.waitForTimeout(2000); const canvas = page.locator('canvas'); await expect(canvas).toBeVisible(); const canvasBox = await canvas.boundingBox(); expect(canvasBox?.width).toBeLessThanOrEqual(375); }); }); ``` - `test.use()` sets viewport and device context - Tap interactions: `canvas.tap()` - BoundingBox assertions: verify canvas scaling ## Test Isolation **Setup:** - E2E tests use `test.beforeEach()` for repeated navigation setup - No shared state between tests - Each test starts fresh from home page **Teardown:** - Playwright automatically closes browser context between tests - No explicit cleanup required ## Configuration Details **Vitest (`vitest.config.js`):** - Environment: jsdom (browser-like DOM) - Globals enabled: `describe`, `it`, `expect` available without imports - Include pattern: `tests/unit/**/*.test.js` - Server runs on port 51204 for Vitest UI **Playwright (`playwright.config.js`):** - Projects: Chromium and mobile (iPhone 12) - Base URL: `http://localhost:5173` (dev server) - Test directory: `tests/e2e/` - Retries: 2 in CI, 0 locally - Screenshots: Only on failure - Trace: On first retry - Server: Runs `npm run dev` before tests ## Test Execution Flow 1. **Unit Tests (Vitest):** - Runs isolated game logic tests - Uses jsdom environment - No Phaser instance needed - Fast execution 2. **E2E Tests (Playwright):** - Starts dev server via `npm run dev` - Launches Chromium and mobile browsers - Navigates to `http://localhost:5173` - Performs user interactions - Verifies game state via canvas visibility - Slower execution but tests full stack ## Coverage Gaps **Not Tested (Unit):** - Scene rendering and graphics creation - Phaser animation timing and tweens - Player input handling (mouse/touch) - Collision detection logic **Not Tested (E2E):** - Detailed game mechanics (whale health, harpoon trajectory) - Inventory state persistence - Exact positioning of game elements - Performance under load --- *Testing analysis: 2026-02-04*