* Update claude.md to push more tests. * Archiving old plans. * fix: run DB migrations programmatically on server startup Replace unreliable drizzle-kit CLI migration with drizzle-orm's built-in migrator running in the server process before accepting connections. This ensures migrations are always applied on deploy and fails fast if they error. - Add runMigrations() to server.ts using drizzle-orm/postgres-js/migrator - Skip migrations in development (handled manually via db:migrate) - Add DATABASE_URL guard with a clear error message - Remove start:production script (now identical to start) - Update Dockerfile CMD to use npm run start Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
751 lines
19 KiB
Markdown
751 lines
19 KiB
Markdown
# 🧪 Comprehensive Testing Plan for Brackt.com
|
|
|
|
**Created:** October 20, 2025
|
|
**Status:** Planned
|
|
|
|
## **Overview**
|
|
|
|
This plan sets up a complete testing infrastructure with:
|
|
- **Vitest + React Testing Library** - Unit and component tests
|
|
- **Cypress** - End-to-end integration tests
|
|
- **Test fixtures** - Reusable test data
|
|
- **CI/CD Integration** - Automated testing in GitHub Actions
|
|
- **CI-ready** - Scripts for automated testing
|
|
|
|
---
|
|
|
|
## **Phase 1: Vitest + React Testing Library Setup**
|
|
|
|
### **1.1 Installation**
|
|
```bash
|
|
npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
|
|
```
|
|
|
|
### **1.2 Configuration Files**
|
|
|
|
**`vitest.config.ts`**
|
|
```typescript
|
|
import { defineConfig } from 'vitest/config';
|
|
import react from '@vitejs/plugin-react';
|
|
import path from 'path';
|
|
|
|
export default defineConfig({
|
|
plugins: [react()],
|
|
test: {
|
|
globals: true,
|
|
environment: 'jsdom',
|
|
setupFiles: ['./app/test/setup.ts'],
|
|
coverage: {
|
|
provider: 'v8',
|
|
reporter: ['text', 'json', 'html'],
|
|
exclude: ['node_modules/', 'app/test/'],
|
|
},
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
'~': path.resolve(__dirname, './app'),
|
|
},
|
|
},
|
|
});
|
|
```
|
|
|
|
**`app/test/setup.ts`**
|
|
```typescript
|
|
import '@testing-library/jest-dom';
|
|
import { cleanup } from '@testing-library/react';
|
|
import { afterEach } from 'vitest';
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
```
|
|
|
|
### **1.3 Example Unit Tests**
|
|
|
|
**`app/models/__tests__/league.test.ts`**
|
|
```typescript
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { createLeague, updateLeague, findLeagueById } from '../league';
|
|
|
|
// Mock database
|
|
vi.mock('~/database/context', () => ({
|
|
database: vi.fn(() => ({
|
|
insert: vi.fn().mockReturnValue({
|
|
values: vi.fn().mockReturnValue({
|
|
returning: vi.fn().mockResolvedValue([{
|
|
id: 'league-1',
|
|
name: 'Test League',
|
|
createdBy: 'user-1',
|
|
isPublicDraftBoard: false,
|
|
}]),
|
|
}),
|
|
}),
|
|
query: {
|
|
leagues: {
|
|
findFirst: vi.fn(),
|
|
},
|
|
},
|
|
})),
|
|
}));
|
|
|
|
describe('League Model', () => {
|
|
it('should create a new league', async () => {
|
|
const league = await createLeague({
|
|
name: 'Test League',
|
|
createdBy: 'user-1',
|
|
});
|
|
|
|
expect(league).toMatchObject({
|
|
name: 'Test League',
|
|
createdBy: 'user-1',
|
|
});
|
|
});
|
|
|
|
it('should validate league name length', async () => {
|
|
// Test validation logic
|
|
expect(() => {
|
|
if ('ab'.length < 3) throw new Error('Name too short');
|
|
}).toThrow('Name too short');
|
|
});
|
|
});
|
|
```
|
|
|
|
### **1.4 Component Tests**
|
|
|
|
**`app/components/__tests__/DraftGrid.test.tsx`**
|
|
```typescript
|
|
import { describe, it, expect } from 'vitest';
|
|
import { render, screen } from '@testing-library/react';
|
|
import { DraftGrid } from '../DraftGrid';
|
|
|
|
describe('DraftGrid Component', () => {
|
|
const mockSlots = [
|
|
{ id: '1', draftOrder: 1, team: { id: 't1', name: 'Team 1' } },
|
|
{ id: '2', draftOrder: 2, team: { id: 't2', name: 'Team 2' } },
|
|
];
|
|
|
|
const mockGrid = [
|
|
[
|
|
{ participant: { name: 'Player 1' }, sport: { name: 'NFL' } },
|
|
null,
|
|
],
|
|
];
|
|
|
|
it('should render team names', () => {
|
|
render(
|
|
<DraftGrid
|
|
draftSlots={mockSlots}
|
|
draftGrid={mockGrid}
|
|
currentPick={2}
|
|
/>
|
|
);
|
|
|
|
expect(screen.getByText('Team 1')).toBeInTheDocument();
|
|
expect(screen.getByText('Team 2')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should highlight current pick', () => {
|
|
render(
|
|
<DraftGrid
|
|
draftSlots={mockSlots}
|
|
draftGrid={mockGrid}
|
|
currentPick={2}
|
|
/>
|
|
);
|
|
|
|
expect(screen.getByText('On Clock')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display picked players', () => {
|
|
render(
|
|
<DraftGrid
|
|
draftSlots={mockSlots}
|
|
draftGrid={mockGrid}
|
|
currentPick={2}
|
|
/>
|
|
);
|
|
|
|
expect(screen.getByText('Player 1')).toBeInTheDocument();
|
|
expect(screen.getByText('NFL')).toBeInTheDocument();
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## **Phase 2: Cypress E2E Testing Setup**
|
|
|
|
### **2.1 Installation**
|
|
```bash
|
|
npm install -D cypress @testing-library/cypress
|
|
```
|
|
|
|
### **2.2 Configuration**
|
|
|
|
**`cypress.config.ts`**
|
|
```typescript
|
|
import { defineConfig } from 'cypress';
|
|
|
|
export default defineConfig({
|
|
e2e: {
|
|
baseUrl: 'http://localhost:3000',
|
|
setupNodeEvents(on, config) {
|
|
// implement node event listeners here
|
|
},
|
|
supportFile: 'cypress/support/e2e.ts',
|
|
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
|
|
},
|
|
env: {
|
|
TEST_USER_EMAIL: 'test@example.com',
|
|
TEST_USER_PASSWORD: 'testpassword123',
|
|
},
|
|
});
|
|
```
|
|
|
|
**`cypress/support/e2e.ts`**
|
|
```typescript
|
|
import '@testing-library/cypress/add-commands';
|
|
|
|
// Custom commands
|
|
Cypress.Commands.add('login', (email: string, password: string) => {
|
|
cy.session([email, password], () => {
|
|
cy.visit('/sign-in');
|
|
cy.get('input[name="email"]').type(email);
|
|
cy.get('input[name="password"]').type(password);
|
|
cy.get('button[type="submit"]').click();
|
|
cy.url().should('not.include', '/sign-in');
|
|
});
|
|
});
|
|
|
|
declare global {
|
|
namespace Cypress {
|
|
interface Chainable {
|
|
login(email: string, password: string): Chainable<void>;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### **2.3 E2E Test Examples**
|
|
|
|
**`cypress/e2e/league-creation.cy.ts`**
|
|
```typescript
|
|
describe('League Creation Flow', () => {
|
|
beforeEach(() => {
|
|
cy.login(Cypress.env('TEST_USER_EMAIL'), Cypress.env('TEST_USER_PASSWORD'));
|
|
});
|
|
|
|
it('should create a new league with all settings', () => {
|
|
cy.visit('/leagues/new');
|
|
|
|
// Fill in league details
|
|
cy.get('input[name="name"]').type('My Test League');
|
|
cy.get('select[name="teamCount"]').select('12');
|
|
cy.get('input[name="draftRounds"]').clear().type('20');
|
|
|
|
// Set draft speed
|
|
cy.get('select[name="draftSpeed"]').select('standard');
|
|
|
|
// Set draft date/time
|
|
cy.get('input[name="draftDate"]').type('2025-12-01');
|
|
cy.get('input[name="draftTime"]').type('19:00');
|
|
|
|
// Select sports (assuming checkboxes)
|
|
cy.findByLabelText(/NFL/i).check();
|
|
cy.findByLabelText(/NBA/i).check();
|
|
cy.findByLabelText(/MLB/i).check();
|
|
|
|
// Submit form
|
|
cy.get('button[type="submit"]').click();
|
|
|
|
// Verify redirect to league page
|
|
cy.url().should('include', '/leagues/');
|
|
cy.findByText('My Test League').should('exist');
|
|
});
|
|
|
|
it('should validate required fields', () => {
|
|
cy.visit('/leagues/new');
|
|
|
|
// Try to submit without filling anything
|
|
cy.get('button[type="submit"]').click();
|
|
|
|
// Should show validation errors
|
|
cy.findByText(/league name is required/i).should('exist');
|
|
});
|
|
});
|
|
```
|
|
|
|
**`cypress/e2e/league-settings.cy.ts`**
|
|
```typescript
|
|
describe('League Settings', () => {
|
|
beforeEach(() => {
|
|
cy.login(Cypress.env('TEST_USER_EMAIL'), Cypress.env('TEST_USER_PASSWORD'));
|
|
});
|
|
|
|
it('should update league name', () => {
|
|
// Assuming we have a test league
|
|
cy.visit('/leagues/test-league-id/settings');
|
|
|
|
cy.get('input[name="name"]').clear().type('Updated League Name');
|
|
cy.get('button[type="submit"]').contains('Save').click();
|
|
|
|
// Verify success
|
|
cy.findByText(/settings updated/i).should('exist');
|
|
cy.findByDisplayValue('Updated League Name').should('exist');
|
|
});
|
|
|
|
it('should toggle public draft board', () => {
|
|
cy.visit('/leagues/test-league-id/settings');
|
|
|
|
// Check the public draft board checkbox
|
|
cy.get('input[name="isPublicDraftBoard"]').check();
|
|
cy.get('button[type="submit"]').contains('Save').click();
|
|
|
|
// Verify it's checked after save
|
|
cy.get('input[name="isPublicDraftBoard"]').should('be.checked');
|
|
});
|
|
|
|
it('should change draft speed', () => {
|
|
cy.visit('/leagues/test-league-id/settings');
|
|
|
|
cy.get('select[name="draftSpeed"]').select('fast');
|
|
cy.get('button[type="submit"]').contains('Save').click();
|
|
|
|
// Verify selection persists
|
|
cy.get('select[name="draftSpeed"]').should('have.value', 'fast');
|
|
});
|
|
});
|
|
```
|
|
|
|
**`cypress/e2e/draft-room.cy.ts`**
|
|
```typescript
|
|
describe('Draft Room', () => {
|
|
beforeEach(() => {
|
|
cy.login(Cypress.env('TEST_USER_EMAIL'), Cypress.env('TEST_USER_PASSWORD'));
|
|
});
|
|
|
|
it('should display draft grid with all teams', () => {
|
|
cy.visit('/leagues/test-league-id/draft/test-season-id');
|
|
|
|
// Verify team headers are visible
|
|
cy.findByText('Team 1').should('exist');
|
|
cy.findByText('Team 2').should('exist');
|
|
|
|
// Verify draft grid is rendered
|
|
cy.get('[title*="Overall Pick"]').should('have.length.at.least', 12);
|
|
});
|
|
|
|
it('should show current pick highlighted', () => {
|
|
cy.visit('/leagues/test-league-id/draft/test-season-id');
|
|
|
|
// Current pick should have special styling
|
|
cy.findByText('On Clock').should('exist');
|
|
cy.findByText('On Clock')
|
|
.parent()
|
|
.should('have.class', 'border-blue-500');
|
|
});
|
|
|
|
it('should allow commissioner to start draft', () => {
|
|
cy.visit('/leagues/test-league-id/draft/test-season-id');
|
|
|
|
// Click start draft button
|
|
cy.findByRole('button', { name: /start draft/i }).click();
|
|
|
|
// Verify draft started
|
|
cy.findByText(/draft started/i).should('exist');
|
|
});
|
|
|
|
it('should make a pick', () => {
|
|
cy.visit('/leagues/test-league-id/draft/test-season-id');
|
|
|
|
// Search for a player
|
|
cy.get('input[placeholder*="Search"]').type('Aaron Judge');
|
|
|
|
// Click on player to select
|
|
cy.findByText('Aaron Judge').click();
|
|
|
|
// Confirm pick
|
|
cy.findByRole('button', { name: /confirm pick/i }).click();
|
|
|
|
// Verify pick appears in grid
|
|
cy.findByText('Aaron Judge').should('exist');
|
|
});
|
|
|
|
it('should add player to queue', () => {
|
|
cy.visit('/leagues/test-league-id/draft/test-season-id');
|
|
|
|
// Find a player and add to queue
|
|
cy.findByText('Mike Trout').parent().findByRole('button', { name: /add to queue/i }).click();
|
|
|
|
// Verify in queue section
|
|
cy.findByText(/your queue/i).parent().findByText('Mike Trout').should('exist');
|
|
});
|
|
|
|
it('should display timers for all teams', () => {
|
|
cy.visit('/leagues/test-league-id/draft/test-season-id');
|
|
|
|
// Start draft first
|
|
cy.findByRole('button', { name: /start draft/i }).click();
|
|
|
|
// Verify timers are displayed (format: MM:SS)
|
|
cy.get('[class*="font-mono"]').should('contain.text', ':');
|
|
});
|
|
|
|
it('should allow commissioner to pause/resume draft', () => {
|
|
cy.visit('/leagues/test-league-id/draft/test-season-id');
|
|
|
|
// Pause draft
|
|
cy.findByRole('button', { name: /pause draft/i }).click();
|
|
cy.findByText(/draft paused/i).should('exist');
|
|
|
|
// Resume draft
|
|
cy.findByRole('button', { name: /resume draft/i }).click();
|
|
cy.findByText(/draft resumed/i).should('exist');
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## **Phase 3: Test Fixtures & Helpers**
|
|
|
|
**`app/test/fixtures/league.ts`**
|
|
```typescript
|
|
export const mockLeague = {
|
|
id: 'league-1',
|
|
name: 'Test League',
|
|
createdBy: 'user-1',
|
|
isPublicDraftBoard: false,
|
|
createdAt: new Date('2025-01-01'),
|
|
updatedAt: new Date('2025-01-01'),
|
|
};
|
|
|
|
export const mockSeason = {
|
|
id: 'season-1',
|
|
leagueId: 'league-1',
|
|
year: 2025,
|
|
status: 'pre_draft' as const,
|
|
draftRounds: 20,
|
|
draftInitialTime: 120,
|
|
draftIncrementTime: 15,
|
|
currentPickNumber: 1,
|
|
draftPaused: false,
|
|
};
|
|
|
|
export const mockTeams = [
|
|
{ id: 'team-1', name: 'Team 1', seasonId: 'season-1', ownerId: 'user-1' },
|
|
{ id: 'team-2', name: 'Team 2', seasonId: 'season-1', ownerId: 'user-2' },
|
|
];
|
|
```
|
|
|
|
**`cypress/support/commands/database.ts`**
|
|
```typescript
|
|
// Helper to seed test database
|
|
Cypress.Commands.add('seedDatabase', () => {
|
|
cy.task('db:seed');
|
|
});
|
|
|
|
Cypress.Commands.add('cleanDatabase', () => {
|
|
cy.task('db:clean');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## **Phase 4: Package.json Scripts**
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"test": "vitest",
|
|
"test:ui": "vitest --ui",
|
|
"test:coverage": "vitest --coverage",
|
|
"test:e2e": "cypress open",
|
|
"test:e2e:headless": "cypress run",
|
|
"test:all": "npm run test && npm run test:e2e:headless"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## **Phase 5: GitHub Actions CI/CD Integration**
|
|
|
|
### **5.1 Update Deploy Workflow**
|
|
|
|
**`.github/workflows/deploy.yml`** (Updated)
|
|
```yaml
|
|
name: Deploy to Production
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
|
|
jobs:
|
|
# Run tests first
|
|
test:
|
|
name: Run Tests
|
|
runs-on: ubuntu-latest
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:15
|
|
env:
|
|
POSTGRES_USER: test
|
|
POSTGRES_PASSWORD: test
|
|
POSTGRES_DB: brackt_test
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
ports:
|
|
- 5432:5432
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Run database migrations
|
|
run: npm run db:migrate
|
|
env:
|
|
DATABASE_URL: postgresql://test:test@localhost:5432/brackt_test
|
|
|
|
- name: Run unit tests
|
|
run: npm run test -- --run
|
|
env:
|
|
DATABASE_URL: postgresql://test:test@localhost:5432/brackt_test
|
|
|
|
- name: Run E2E tests
|
|
run: npm run test:e2e:headless
|
|
env:
|
|
DATABASE_URL: postgresql://test:test@localhost:5432/brackt_test
|
|
CYPRESS_BASE_URL: http://localhost:3000
|
|
|
|
- name: Upload test coverage
|
|
uses: codecov/codecov-action@v3
|
|
if: always()
|
|
with:
|
|
files: ./coverage/coverage-final.json
|
|
flags: unittests
|
|
|
|
- name: Upload Cypress videos
|
|
uses: actions/upload-artifact@v3
|
|
if: failure()
|
|
with:
|
|
name: cypress-videos
|
|
path: cypress/videos
|
|
|
|
# Build and deploy only if tests pass
|
|
deploy:
|
|
name: Deploy to Netlify
|
|
needs: test
|
|
runs-on: ubuntu-latest
|
|
if: github.ref == 'refs/heads/main'
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Build application
|
|
run: npm run build
|
|
env:
|
|
NODE_ENV: production
|
|
|
|
- name: Deploy to Netlify
|
|
uses: nwtgck/actions-netlify@v2
|
|
with:
|
|
publish-dir: './build/client'
|
|
production-branch: main
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
deploy-message: "Deploy from GitHub Actions"
|
|
env:
|
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
|
```
|
|
|
|
### **5.2 Add PR Check Workflow**
|
|
|
|
**`.github/workflows/pr-checks.yml`** (New)
|
|
```yaml
|
|
name: PR Checks
|
|
|
|
on:
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
- develop
|
|
|
|
jobs:
|
|
lint-and-test:
|
|
name: Lint and Test
|
|
runs-on: ubuntu-latest
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:15
|
|
env:
|
|
POSTGRES_USER: test
|
|
POSTGRES_PASSWORD: test
|
|
POSTGRES_DB: brackt_test
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
ports:
|
|
- 5432:5432
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Run linter
|
|
run: npm run lint
|
|
continue-on-error: true
|
|
|
|
- name: Run type check
|
|
run: npm run typecheck
|
|
continue-on-error: true
|
|
|
|
- name: Run unit tests
|
|
run: npm run test -- --run
|
|
env:
|
|
DATABASE_URL: postgresql://test:test@localhost:5432/brackt_test
|
|
|
|
- name: Comment test results
|
|
uses: actions/github-script@v6
|
|
if: always()
|
|
with:
|
|
script: |
|
|
const fs = require('fs');
|
|
const coverage = JSON.parse(fs.readFileSync('./coverage/coverage-summary.json', 'utf8'));
|
|
const total = coverage.total;
|
|
|
|
github.rest.issues.createComment({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
body: `## Test Results\n\n` +
|
|
`✅ Tests passed!\n\n` +
|
|
`**Coverage:**\n` +
|
|
`- Lines: ${total.lines.pct}%\n` +
|
|
`- Statements: ${total.statements.pct}%\n` +
|
|
`- Functions: ${total.functions.pct}%\n` +
|
|
`- Branches: ${total.branches.pct}%`
|
|
});
|
|
```
|
|
|
|
### **5.3 Required Status Checks**
|
|
|
|
In GitHub repository settings, configure branch protection rules:
|
|
|
|
1. Go to **Settings** → **Branches** → **Branch protection rules**
|
|
2. Add rule for `main` branch
|
|
3. Enable:
|
|
- ✅ Require status checks to pass before merging
|
|
- ✅ Require branches to be up to date before merging
|
|
4. Select required checks:
|
|
- `test` (from deploy.yml)
|
|
- `lint-and-test` (from pr-checks.yml)
|
|
|
|
---
|
|
|
|
## **Implementation Checklist**
|
|
|
|
### **Phase 1: Vitest Setup**
|
|
- [ ] Install Vitest and React Testing Library dependencies
|
|
- [ ] Create `vitest.config.ts`
|
|
- [ ] Create `app/test/setup.ts`
|
|
- [ ] Create example unit tests for league model
|
|
- [ ] Create example component tests for DraftGrid
|
|
- [ ] Add test scripts to package.json
|
|
|
|
### **Phase 2: Cypress Setup**
|
|
- [ ] Install Cypress dependencies
|
|
- [ ] Create `cypress.config.ts`
|
|
- [ ] Create `cypress/support/e2e.ts` with custom commands
|
|
- [ ] Create league creation E2E tests
|
|
- [ ] Create league settings E2E tests
|
|
- [ ] Create draft room E2E tests
|
|
|
|
### **Phase 3: Test Fixtures**
|
|
- [ ] Create `app/test/fixtures/league.ts`
|
|
- [ ] Create `app/test/fixtures/season.ts`
|
|
- [ ] Create `app/test/fixtures/team.ts`
|
|
- [ ] Create Cypress database helpers
|
|
|
|
### **Phase 4: CI/CD Integration**
|
|
- [ ] Update `.github/workflows/deploy.yml` with test jobs
|
|
- [ ] Create `.github/workflows/pr-checks.yml`
|
|
- [ ] Configure GitHub branch protection rules
|
|
- [ ] Add required status checks
|
|
- [ ] Test CI pipeline with a PR
|
|
|
|
### **Phase 5: Documentation**
|
|
- [ ] Document how to run tests locally
|
|
- [ ] Document how to write new tests
|
|
- [ ] Document CI/CD pipeline
|
|
- [ ] Add testing best practices guide
|
|
|
|
---
|
|
|
|
## **Test Coverage Goals**
|
|
|
|
- **Unit Tests:** 80%+ coverage for models and utilities
|
|
- **Component Tests:** All major UI components tested
|
|
- **E2E Tests:** Critical user flows covered
|
|
- League creation and editing
|
|
- Draft room functionality
|
|
- Commissioner controls
|
|
- Public draft board access
|
|
|
|
---
|
|
|
|
## **Success Criteria**
|
|
|
|
✅ All tests pass locally
|
|
✅ CI/CD pipeline runs tests automatically
|
|
✅ PRs cannot merge without passing tests
|
|
✅ Coverage reports generated and tracked
|
|
✅ E2E tests run in headless mode
|
|
✅ Test failures block deployments
|
|
|
|
---
|
|
|
|
## **Future Enhancements**
|
|
|
|
- **Visual Regression Testing** - Percy or Chromatic
|
|
- **Performance Testing** - Lighthouse CI
|
|
- **Load Testing** - k6 for draft room Socket.IO
|
|
- **Accessibility Testing** - axe-core integration
|
|
- **Mutation Testing** - Stryker for test quality
|