- STACK.md - Technologies and dependencies - ARCHITECTURE.md - System design and patterns - STRUCTURE.md - Directory layout - CONVENTIONS.md - Code style and patterns - TESTING.md - Test structure - INTEGRATIONS.md - External services - CONCERNS.md - Technical debt and issues
8.1 KiB
8.1 KiB
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:
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.jssuffix:game-logic.test.js - E2E tests:
.spec.jssuffix: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:
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:
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:
// 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:
// vitest.config.js
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: [
'node_modules/',
'dist/',
'*.config.js',
'tests/e2e/**'
]
}
View Coverage:
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
- Barrel calculation:
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):
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 fixtureawaiton page navigation and interactionspage.waitForTimeout()for animation/transition delayspage.locator()for element selection
Error Testing (Unit):
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):
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,expectavailable 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 devbefore tests
Test Execution Flow
-
Unit Tests (Vitest):
- Runs isolated game logic tests
- Uses jsdom environment
- No Phaser instance needed
- Fast execution
-
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
- Starts dev server via
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