This document describes the testing infrastructure and how to run tests for the Repstack application.
- Unit Testing: Vitest - Fast unit test framework with great TypeScript support
- E2E Testing: Playwright - End-to-end testing across browsers
- Code Coverage: Vitest with v8 coverage provider
- Database Mocking: fake-indexeddb for IndexedDB operations
# Run all unit tests
npm test
# Run tests in watch mode (great for development)
npm run test:ui
# Run tests once (for CI)
npm run test:run
# Run tests with coverage report
npm run test:coverage# Run E2E tests (builds app first)
npm run test:e2e
# Run E2E tests with UI (interactive mode)
npm run test:e2e:ui
# Run E2E tests in headed mode (see browser)
npm run test:e2e:headedsrc/
├── lib/
│ ├── validation.ts # Business logic
│ ├── validation.test.ts # Unit tests
│ ├── progressTracking.ts
│ ├── progressTracking.test.ts
│ ├── mesocycleUtils.ts
│ └── mesocycleUtils.test.ts
├── db/
│ ├── service.ts # Database operations
│ └── service.test.ts # Integration tests
└── test/
└── setup.ts # Test configuration
e2e/
├── accessibility.spec.ts # Accessibility tests
├── mesocycle.spec.ts # Mesocycle workflow tests
└── offline.spec.ts # Offline functionality tests
Validation Tests (validation.test.ts) - 74 tests
- Type guards for data types
- Data validation for all models
- Input sanitization
- Edge cases and error conditions
Progress Tracking Tests (progressTracking.test.ts) - 36 tests
- 1RM calculations (Epley & Brzycki formulas)
- Volume calculations (set, exercise, workout)
- Personal records tracking
- Training statistics
- Progress trends
Mesocycle Utils Tests (mesocycleUtils.test.ts) - 39 tests
- Week calculations
- Progress updates
- Mesocycle completion checks
- Split day rotation
- Database integration
Database Service Tests (service.test.ts) - 28 tests
- CRUD operations for all entities
- Data validation integration
- Transaction flows
- Error handling
Integration tests are included within the service tests and test the complete flow of data through the application layers.
Accessibility Tests (accessibility.spec.ts)
- WCAG compliance checks
- Keyboard navigation
- Screen reader compatibility
- Focus management
Mesocycle Tests (mesocycle.spec.ts)
- Complete mesocycle creation flow
- Exercise configuration
- Workout logging
- Progress tracking
Offline Tests (offline.spec.ts)
- Service worker functionality
- Offline data access
- Data persistence
We maintain the following coverage thresholds for core business logic:
- Lines: 70%
- Functions: 70%
- Branches: 70%
- Statements: 70%
Current coverage for core business logic (src/lib):
- Overall: 95%+ ✅
Note: Coverage requirements exclude UI components, test files, and configuration files.
import { describe, it, expect } from 'vitest';
import { calculateOneRepMax } from '../lib/progressTracking';
describe('calculateOneRepMax', () => {
it('should calculate 1RM using Epley formula', () => {
// 100kg for 5 reps should be ~116.67kg 1RM
expect(calculateOneRepMax(100, 5)).toBeCloseTo(116.67, 1);
});
it('should return 0 for invalid inputs', () => {
expect(calculateOneRepMax(0, 5)).toBe(0);
expect(calculateOneRepMax(100, 0)).toBe(0);
});
});import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { db } from '../db';
import { createExercise, getExercise } from '../db/service';
describe('Exercise CRUD', () => {
beforeEach(async () => {
await db.delete();
await db.open();
});
afterEach(async () => {
await db.delete();
});
it('should create and retrieve an exercise', async () => {
const id = await createExercise({
name: 'Bench Press',
category: 'barbell',
muscleGroups: ['chest', 'triceps'],
isCustom: true,
});
const exercise = await getExercise(id);
expect(exercise?.name).toBe('Bench Press');
});
});import { test, expect } from '@playwright/test';
test('should create a new exercise', async ({ page }) => {
await page.goto('/');
await page.click('text=Exercises');
await page.click('text=Add Exercise');
await page.fill('[name="name"]', 'Squat');
await page.selectOption('[name="category"]', 'barbell');
await page.check('[value="quads"]');
await page.click('text=Save');
await expect(page.locator('text=Squat')).toBeVisible();
});Tests run automatically on:
- Every pull request
- Every push to main branch
- Before deployment
- Install dependencies
- Run linter
- Check formatting
- Type check
- Run unit tests
- Generate coverage report
- Build application
- Run E2E tests (after deployment)
Coverage reports are uploaded to Codecov for tracking over time.
- Test one thing per test case
- Use descriptive test names
- Follow AAA pattern (Arrange, Act, Assert)
- Mock external dependencies
- Test edge cases and error conditions
- Test critical user paths
- Keep tests independent
- Use data-testid for stable selectors
- Clean up test data
- Test offline scenarios
- Always clean up before/after tests
- Test complete flows, not just happy paths
- Verify data integrity
- Test concurrent operations
# Run specific test file
npm test validation.test.ts
# Run tests matching pattern
npm test -- --grep "1RM"
# Debug with UI
npm run test:ui# Debug with Playwright Inspector
npm run test:e2e:headed
# Use Playwright UI mode
npm run test:e2e:ui
# Generate trace for failed tests
npm run test:e2e -- --trace on- Run specific test files instead of all tests
- Use
test.skip()to temporarily disable slow tests - Check for unnecessary async operations
- Make sure
fake-indexeddbis properly set up in test config - Clean up database in
beforeEach/afterEachhooks - Check for hanging promises
- Increase timeouts for slow operations
- Use proper wait conditions (
page.waitForSelector) - Check for race conditions
- Ensure test data is properly set up