# 🧪 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
