# TypeScript Migration Testing Strategy

## Executive Summary

This document outlines the comprehensive testing strategy for migrating the chess game from JavaScript to TypeScript while maintaining 100% test coverage and ensuring zero regressions.

**Current State:**
- 7 test files (unit tests for pieces and game logic)
- 124 passing tests across all components
- Jest with babel-jest transformer
- Custom test matchers for chess-specific validations
- jsdom environment for DOM testing

**Migration Goal:**
- Maintain all 124 tests in passing state
- Add type safety to test code
- Implement type-level testing
- Achieve 90%+ type coverage
- Zero regression tolerance

---

## Phase 1: Jest + TypeScript Configuration

### 1.1 Dependencies Installation

```bash
npm install --save-dev \
  typescript \
  ts-jest \
  @types/jest \
  @types/node \
  @jest/globals \
  jest-mock-extended
```

**Package Purposes:**
- `typescript`: TypeScript compiler
- `ts-jest`: Jest transformer for TypeScript
- `@types/jest`: TypeScript definitions for Jest
- `@types/node`: Node.js type definitions
- `@jest/globals`: Type-safe Jest globals
- `jest-mock-extended`: Advanced mocking with type safety

### 1.2 Jest Configuration Migration

**File:** `jest.config.ts` (new)

```typescript
import type { Config } from 'jest';

const config: Config = {
  // Test environment
  testEnvironment: 'jsdom',

  // TypeScript transformation
  preset: 'ts-jest',

  // Module resolution
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],

  // Test patterns
  testMatch: [
    '**/tests/**/*.test.ts',
    '**/tests/**/*.test.tsx',
    '**/__tests__/**/*.ts',
    '**/__tests__/**/*.tsx'
  ],

  // Transform configuration
  transform: {
    '^.+\\.tsx?$': ['ts-jest', {
      tsconfig: {
        esModuleInterop: true,
        allowSyntheticDefaultImports: true,
        jsx: 'react',
        module: 'ESNext',
        target: 'ES2020',
        moduleResolution: 'node',
        resolveJsonModule: true,
        isolatedModules: true
      }
    }],
    '^.+\\.jsx?$': 'babel-jest' // Keep for transition period
  },

  // Module name mapper for path aliases
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '^@pieces/(.*)$': '<rootDir>/src/pieces/$1',
    '^@game/(.*)$': '<rootDir>/src/game/$1',
    '^@utils/(.*)$': '<rootDir>/src/utils/$1',
    '^@engine/(.*)$': '<rootDir>/src/engine/$1',
    '^@controllers/(.*)$': '<rootDir>/src/controllers/$1',
    '^@views/(.*)$': '<rootDir>/src/views/$1'
  },

  // Coverage configuration
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.test.{ts,tsx}',
    '!src/main.ts',
    '!**/node_modules/**'
  ],

  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html', 'json-summary'],

  // Restore coverage thresholds with TypeScript
  coverageThreshold: {
    global: {
      statements: 80,
      branches: 75,
      functions: 80,
      lines: 80
    }
  },

  // Test setup
  setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],

  // Globals configuration
  globals: {
    'ts-jest': {
      isolatedModules: true,
      diagnostics: {
        warnOnly: true // Don't fail on type errors during transition
      }
    }
  },

  // Verbose output
  verbose: true,

  // Test timeout
  testTimeout: 10000,

  // Clear mocks between tests
  clearMocks: true,
  restoreMocks: true,
  resetMocks: true
};

export default config;
```

### 1.3 Test Setup Migration

**File:** `tests/setup.ts` (migrated from setup.js)

```typescript
import '@testing-library/jest-dom';
import { jest } from '@jest/globals';

// Type-safe localStorage mock
interface LocalStorageMock {
  getItem: jest.MockedFunction<(key: string) => string | null>;
  setItem: jest.MockedFunction<(key: string, value: string) => void>;
  removeItem: jest.MockedFunction<(key: string) => void>;
  clear: jest.MockedFunction<() => void>;
  length: number;
  key: jest.MockedFunction<(index: number) => string | null>;
}

const createLocalStorageMock = (): LocalStorageMock => ({
  getItem: jest.fn<(key: string) => string | null>(),
  setItem: jest.fn<(key: string, value: string) => void>(),
  removeItem: jest.fn<(key: string) => void>(),
  clear: jest.fn<() => void>(),
  length: 0,
  key: jest.fn<(index: number) => string | null>()
});

global.localStorage = createLocalStorageMock() as Storage;

// Type-safe console mock
global.console = {
  ...console,
  error: jest.fn(),
  warn: jest.fn(),
  log: jest.fn(),
  info: jest.fn(),
  debug: jest.fn()
};

// Custom matchers with TypeScript types
interface ChessPosition {
  row: number;
  col: number;
}

declare global {
  namespace jest {
    interface Matchers<R> {
      toBeValidChessPosition(): R;
      toBeValidFEN(): R;
    }
  }
}

expect.extend({
  toBeValidChessPosition(received: unknown) {
    const pos = received as ChessPosition;
    const isValid =
      typeof pos === 'object' &&
      pos !== null &&
      'row' in pos &&
      'col' in pos &&
      pos.row >= 0 &&
      pos.row < 8 &&
      pos.col >= 0 &&
      pos.col < 8;

    return {
      message: () =>
        isValid
          ? `expected ${JSON.stringify(received)} not to be a valid chess position`
          : `expected ${JSON.stringify(received)} to be a valid chess position (row and col must be 0-7)`,
      pass: isValid
    };
  },

  toBeValidFEN(received: unknown) {
    const fenRegex = /^([rnbqkpRNBQKP1-8]+\/){7}[rnbqkpRNBQKP1-8]+ [wb] [KQkq-]+ ([a-h][1-8]|-) \d+ \d+$/;
    const isValid = typeof received === 'string' && fenRegex.test(received);

    return {
      message: () =>
        isValid
          ? `expected "${received}" not to be valid FEN`
          : `expected "${received}" to be valid FEN notation`,
      pass: isValid
    };
  }
});

// Reset mocks before each test
beforeEach(() => {
  jest.clearAllMocks();
});

// Clean up after each test
afterEach(() => {
  jest.restoreAllMocks();
});
```

---

## Phase 2: Test File Migration Strategy

### 2.1 Migration Approach

**Incremental Migration Pattern:**
1. Migrate source file to TypeScript
2. Immediately migrate corresponding test file
3. Ensure all tests pass before moving to next file
4. Run full test suite after each migration
5. Commit after each successful file pair migration

### 2.2 Test File Migration Order

**Priority 1: Core Types & Utilities (No Dependencies)**
1. `src/utils/Constants.ts` → `tests/unit/utils/Constants.test.ts`
2. `src/utils/Helpers.ts` → `tests/unit/utils/Helpers.test.ts`

**Priority 2: Base Classes**
3. `src/pieces/Piece.ts` → `tests/unit/pieces/Piece.test.ts`
4. `src/game/Board.ts` → `tests/unit/game/Board.test.ts` (already exists)

**Priority 3: Piece Implementations**
5. `src/pieces/Pawn.ts` → `tests/unit/pieces/Pawn.test.ts`
6. `src/pieces/Knight.ts` → `tests/unit/pieces/Knight.test.ts`
7. `src/pieces/Bishop.ts` → `tests/unit/pieces/Bishop.test.ts`
8. `src/pieces/Rook.ts` → `tests/unit/pieces/Rook.test.ts`
9. `src/pieces/Queen.ts` → `tests/unit/pieces/Queen.test.ts`
10. `src/pieces/King.ts` → `tests/unit/pieces/King.test.ts`

**Priority 4: Game Logic**
11. `src/game/GameState.ts` → `tests/unit/game/GameState.test.ts`
12. `src/engine/MoveValidator.ts` → `tests/unit/engine/MoveValidator.test.ts`
13. `src/engine/SpecialMoves.ts` → `tests/unit/engine/SpecialMoves.test.ts`

**Priority 5: Controllers & Views**
14. `src/utils/EventBus.ts` → `tests/unit/utils/EventBus.test.ts`
15. `src/views/BoardRenderer.ts` → `tests/unit/views/BoardRenderer.test.ts`
16. `src/controllers/DragDropHandler.ts` → `tests/unit/controllers/DragDropHandler.test.ts`
17. `src/controllers/GameController.ts` → `tests/integration/GameController.test.ts`

### 2.3 Test File Template

**Example: King.test.ts Migration**

```typescript
import { describe, test, expect, beforeEach } from '@jest/globals';
import { King } from '@pieces/King';
import { Board } from '@game/Board';
import type { Piece, PieceColor, Position } from '@/types';

describe('King', () => {
  let board: Board;

  beforeEach(() => {
    board = new Board();
    board.clear();
  });

  describe('One Square Movement', () => {
    test('king can move one square in any direction', () => {
      const king = new King('white', { row: 4, col: 4 });
      board.setPiece(4, 4, king);

      const moves = king.getValidMoves(board);

      const expectedMoves: Position[] = [
        { row: 3, col: 3 }, { row: 3, col: 4 }, { row: 3, col: 5 },
        { row: 4, col: 3 }, /* king here */ { row: 4, col: 5 },
        { row: 5, col: 3 }, { row: 5, col: 4 }, { row: 5, col: 5 }
      ];

      expect(moves).toHaveLength(8);
      expectedMoves.forEach((expected: Position) => {
        expect(moves).toContainEqual(expected);
      });
    });

    test('king in corner has 3 moves', () => {
      const king = new King('white', { row: 0, col: 0 });
      board.setPiece(0, 0, king);

      const moves: Position[] = king.getValidMoves(board);

      expect(moves).toHaveLength(3);
      expect(moves).toContainEqual({ row: 0, col: 1 });
      expect(moves).toContainEqual({ row: 1, col: 0 });
      expect(moves).toContainEqual({ row: 1, col: 1 });
    });
  });

  describe('Cannot Move Into Check', () => {
    test('king cannot move into attacked square', () => {
      const whiteKing = new King('white', { row: 7, col: 4 });
      const blackRook: Piece = {
        type: 'rook',
        color: 'black',
        position: { row: 0, col: 5 },
        hasMoved: false,
        getValidMoves: jest.fn()
      };

      board.setPiece(7, 4, whiteKing);
      board.setPiece(0, 5, blackRook);

      const moves = whiteKing.getValidMoves(board, board);

      // Type-safe assertions
      expect(moves).not.toContainEqual({ row: 7, col: 5 });
      expect(moves).not.toContainEqual({ row: 6, col: 5 });
    });
  });

  describe('Castling - Kingside', () => {
    test('king can castle kingside when conditions met', () => {
      const king = new King('white', { row: 7, col: 4 });
      const rook: Piece = {
        type: 'rook',
        color: 'white',
        position: { row: 7, col: 7 },
        hasMoved: false,
        getValidMoves: jest.fn()
      };

      board.setPiece(7, 4, king);
      board.setPiece(7, 7, rook);

      const gameState = { castlingRights: { whiteKingside: true } };
      const moves = king.getValidMoves(board, board, gameState);

      expect(moves).toContainEqual({ row: 7, col: 6, castling: 'kingside' });
    });
  });
});
```

---

## Phase 3: Type-Safe Test Utilities

### 3.1 Test Factory Pattern

**File:** `tests/utils/factories.ts`

```typescript
import { jest } from '@jest/globals';
import type { Piece, PieceType, PieceColor, Position } from '@/types';

export class TestPieceFactory {
  static createPiece(
    type: PieceType,
    color: PieceColor,
    position: Position,
    hasMoved: boolean = false
  ): Piece {
    return {
      type,
      color,
      position: { ...position },
      hasMoved,
      getValidMoves: jest.fn(() => [])
    };
  }

  static createPawn(color: PieceColor, position: Position): Piece {
    return this.createPiece('pawn', color, position);
  }

  static createKing(color: PieceColor, position: Position, hasMoved: boolean = false): Piece {
    return this.createPiece('king', color, position, hasMoved);
  }

  static createRook(color: PieceColor, position: Position, hasMoved: boolean = false): Piece {
    return this.createPiece('rook', color, position, hasMoved);
  }

  static createKnight(color: PieceColor, position: Position): Piece {
    return this.createPiece('knight', color, position);
  }

  static createBishop(color: PieceColor, position: Position): Piece {
    return this.createPiece('bishop', color, position);
  }

  static createQueen(color: PieceColor, position: Position): Piece {
    return this.createPiece('queen', color, position);
  }
}

export class TestBoardFactory {
  static createEmptyBoard(): Board {
    const board = new Board();
    board.clear();
    return board;
  }

  static createStartingPosition(): Board {
    const board = new Board();
    board.setupInitialPosition();
    return board;
  }

  static createCustomPosition(pieces: Array<{ piece: Piece; position: Position }>): Board {
    const board = this.createEmptyBoard();
    pieces.forEach(({ piece, position }) => {
      board.setPiece(position.row, position.col, piece);
    });
    return board;
  }
}
```

### 3.2 Type-Safe Mock Helpers

**File:** `tests/utils/mocks.ts`

```typescript
import { jest } from '@jest/globals';
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended';
import type { Board, GameState, MoveValidator } from '@/types';

export function createMockBoard(): DeepMockProxy<Board> {
  return mockDeep<Board>();
}

export function createMockGameState(): DeepMockProxy<GameState> {
  return mockDeep<GameState>();
}

export function createMockMoveValidator(): DeepMockProxy<MoveValidator> {
  return mockDeep<MoveValidator>();
}

// DOM mocks
export function createMockElement(tagName: string = 'div'): HTMLElement {
  const element = document.createElement(tagName);
  jest.spyOn(element, 'addEventListener');
  jest.spyOn(element, 'removeEventListener');
  return element;
}

export function resetAllMocks(...mocks: DeepMockProxy<any>[]): void {
  mocks.forEach(mock => mockReset(mock));
}
```

### 3.3 Custom Assertion Helpers

**File:** `tests/utils/assertions.ts`

```typescript
import type { Position, Move, Piece } from '@/types';

export function expectPositionsEqual(actual: Position, expected: Position): void {
  expect(actual.row).toBe(expected.row);
  expect(actual.col).toBe(expected.col);
}

export function expectMoveInArray(moves: Move[], expected: Move): void {
  const found = moves.some(
    move =>
      move.from.row === expected.from.row &&
      move.from.col === expected.from.col &&
      move.to.row === expected.to.row &&
      move.to.col === expected.to.col
  );
  expect(found).toBe(true);
}

export function expectValidPiece(piece: Piece | null): asserts piece is Piece {
  expect(piece).not.toBeNull();
  expect(piece).toBeDefined();
  expect(piece).toHaveProperty('type');
  expect(piece).toHaveProperty('color');
  expect(piece).toHaveProperty('position');
}

export function expectPieceType(piece: Piece, expectedType: PieceType): void {
  expectValidPiece(piece);
  expect(piece.type).toBe(expectedType);
}
```

---

## Phase 4: Testing Type Definitions

### 4.1 Type-Level Testing Strategy

**File:** `tests/types/type-tests.ts`

```typescript
import { expectType, expectError, expectAssignable } from 'tsd';
import type {
  Piece,
  PieceType,
  PieceColor,
  Position,
  Move,
  GameState,
  CastlingRights
} from '@/types';

// Test: Position type is correct
expectType<Position>({ row: 0, col: 0 });
expectError<Position>({ row: -1, col: 0 }); // Should not allow negative
expectError<Position>({ x: 0, y: 0 }); // Wrong property names

// Test: PieceColor is a union of literals
expectType<PieceColor>('white');
expectType<PieceColor>('black');
expectError<PieceColor>('red'); // Invalid color

// Test: PieceType exhaustiveness
expectType<PieceType>('pawn');
expectType<PieceType>('knight');
expectType<PieceType>('bishop');
expectType<PieceType>('rook');
expectType<PieceType>('queen');
expectType<PieceType>('king');
expectError<PieceType>('wizard'); // Invalid type

// Test: Piece interface structure
expectAssignable<Piece>({
  type: 'pawn',
  color: 'white',
  position: { row: 6, col: 4 },
  hasMoved: false,
  getValidMoves: (board) => []
});

// Test: Move type structure
expectType<Move>({
  from: { row: 6, col: 4 },
  to: { row: 4, col: 4 },
  piece: 'pawn',
  captured: null,
  promotion: null,
  castling: null,
  enPassant: false
});

// Test: CastlingRights structure
expectType<CastlingRights>({
  whiteKingside: true,
  whiteQueenside: true,
  blackKingside: false,
  blackQueenside: false
});

// Test: GameState interface
expectAssignable<GameState>({
  currentTurn: 'white',
  board: {} as Board,
  moveHistory: [],
  capturedPieces: { white: [], black: [] },
  castlingRights: {
    whiteKingside: true,
    whiteQueenside: true,
    blackKingside: true,
    blackQueenside: true
  },
  enPassantTarget: null,
  halfMoveClock: 0,
  fullMoveNumber: 1,
  inCheck: false,
  isCheckmate: false,
  isStalemate: false
});
```

### 4.2 Install Type Testing Dependencies

```bash
npm install --save-dev tsd @types/testing-library__jest-dom
```

### 4.3 Type Test Script

**Add to package.json:**

```json
{
  "scripts": {
    "test:types": "tsd",
    "test:all": "npm run test:types && npm test"
  }
}
```

---

## Phase 5: Regression Prevention Strategy

### 5.1 Snapshot Testing

**File:** `tests/unit/pieces/__snapshots__/King.test.ts.snap`

```typescript
// tests/unit/pieces/King.test.ts
describe('King Moves Snapshot', () => {
  test('king starting position moves should match snapshot', () => {
    const board = TestBoardFactory.createStartingPosition();
    const king = board.getPiece(7, 4) as King;

    const moves = king.getValidMoves(board);

    // Ensure consistent ordering for snapshot
    const sortedMoves = moves.sort((a, b) =>
      a.row === b.row ? a.col - b.col : a.row - b.row
    );

    expect(sortedMoves).toMatchSnapshot();
  });
});
```

### 5.2 Visual Regression Testing

**File:** `tests/visual/board-rendering.test.ts`

```typescript
import { BoardRenderer } from '@views/BoardRenderer';
import { Board } from '@game/Board';

describe('Board Visual Regression', () => {
  let container: HTMLElement;

  beforeEach(() => {
    container = document.createElement('div');
    container.id = 'board';
    document.body.appendChild(container);
  });

  afterEach(() => {
    document.body.removeChild(container);
  });

  test('starting position renders correctly', () => {
    const board = new Board();
    board.setupInitialPosition();

    const renderer = new BoardRenderer(board);
    renderer.render();

    // Snapshot of rendered HTML
    expect(container.innerHTML).toMatchSnapshot();
  });

  test('board after e4 move renders correctly', () => {
    const board = new Board();
    board.setupInitialPosition();
    board.movePiece(6, 4, 4, 4); // e2 to e4

    const renderer = new BoardRenderer(board);
    renderer.render();

    expect(container.innerHTML).toMatchSnapshot();
  });
});
```

### 5.3 Integration Test Suite

**File:** `tests/integration/full-game.test.ts`

```typescript
import { GameController } from '@controllers/GameController';
import { Board } from '@game/Board';
import { GameState } from '@game/GameState';

describe('Full Game Integration', () => {
  let gameController: GameController;

  beforeEach(() => {
    document.body.innerHTML = `
      <div id="board"></div>
      <div id="turn-indicator"></div>
      <div id="captured-white"></div>
      <div id="captured-black"></div>
      <div id="move-history"></div>
    `;

    gameController = new GameController();
  });

  test('Scholar\'s Mate sequence', () => {
    const moves = [
      { from: { row: 6, col: 4 }, to: { row: 4, col: 4 } }, // e4
      { from: { row: 1, col: 4 }, to: { row: 3, col: 4 } }, // e5
      { from: { row: 7, col: 5 }, to: { row: 4, col: 2 } }, // Bc4
      { from: { row: 0, col: 1 }, to: { row: 2, col: 2 } }, // Nc6
      { from: { row: 7, col: 3 }, to: { row: 3, col: 7 } }, // Qh5
      { from: { row: 0, col: 6 }, to: { row: 2, col: 5 } }, // Nf6
      { from: { row: 3, col: 7 }, to: { row: 1, col: 5 } }  // Qxf7# (checkmate)
    ];

    moves.forEach((move, index) => {
      const result = gameController.makeMove(move.from, move.to);
      expect(result.success).toBe(true);

      if (index === moves.length - 1) {
        expect(gameController.isCheckmate()).toBe(true);
      }
    });
  });

  test('castling both sides', () => {
    // Setup position for castling
    const board = gameController.getBoard();
    board.clear();

    // White pieces for castling
    const whiteKing = TestPieceFactory.createKing('white', { row: 7, col: 4 });
    const whiteRookKingside = TestPieceFactory.createRook('white', { row: 7, col: 7 });
    const whiteRookQueenside = TestPieceFactory.createRook('white', { row: 7, col: 0 });

    board.setPiece(7, 4, whiteKing);
    board.setPiece(7, 7, whiteRookKingside);
    board.setPiece(7, 0, whiteRookQueenside);

    // Test kingside castling
    const kingsideCastle = gameController.makeMove(
      { row: 7, col: 4 },
      { row: 7, col: 6 }
    );
    expect(kingsideCastle.success).toBe(true);
    expect(board.getPiece(7, 6)?.type).toBe('king');
    expect(board.getPiece(7, 5)?.type).toBe('rook');
  });
});
```

### 5.4 Regression Test Checklist

**File:** `tests/regression/CHECKLIST.md`

```markdown
# Regression Test Checklist

Run this checklist after each migration step:

## Basic Functionality
- [ ] All 124 original tests pass
- [ ] No TypeScript compilation errors
- [ ] No type errors in test files
- [ ] Test coverage remains >= 80%

## Piece Movement
- [ ] All pieces can move correctly
- [ ] Capture logic works for all pieces
- [ ] Special moves (castling, en passant, promotion) work
- [ ] Move validation prevents illegal moves

## Game State
- [ ] Turn switching works correctly
- [ ] Check detection works
- [ ] Checkmate detection works
- [ ] Stalemate detection works
- [ ] Move history is recorded

## UI Integration
- [ ] Board renders correctly
- [ ] Drag and drop works
- [ ] Piece highlighting works
- [ ] Move indicators appear correctly

## Performance
- [ ] Tests run in < 10 seconds total
- [ ] No memory leaks detected
- [ ] No infinite loops in move generation

## Type Safety
- [ ] All types are properly exported
- [ ] No `any` types in production code
- [ ] All function signatures are typed
- [ ] Interfaces are properly defined
```

---

## Phase 6: Migration Execution Plan

### 6.1 Pre-Migration Setup

**Step 1: Install Dependencies**
```bash
npm install --save-dev typescript ts-jest @types/jest @types/node @jest/globals jest-mock-extended tsd
```

**Step 2: Create TypeScript Config**
```bash
# Create tsconfig.json (use Issue #6 spec)
# Create jest.config.ts
# Migrate tests/setup.js to tests/setup.ts
```

**Step 3: Create Test Utilities**
```bash
mkdir -p tests/utils tests/types tests/integration tests/visual
# Create factories.ts, mocks.ts, assertions.ts
# Create type-tests.ts
```

**Step 4: Verify Jest Configuration**
```bash
npm test -- --listTests  # Should show existing .test.js files
```

### 6.2 Migration Workflow (Per File)

```bash
# For each source file (e.g., King.js):

# 1. Create feature branch
git checkout -b migrate/king-typescript

# 2. Migrate source file
mv js/pieces/King.js src/pieces/King.ts
# Add type annotations to King.ts

# 3. Migrate test file
mv tests/unit/pieces/King.test.js tests/unit/pieces/King.test.ts
# Add type annotations to test file

# 4. Run specific test
npm test -- King.test.ts

# 5. Fix any TypeScript errors
# Iterate until all tests pass

# 6. Run full test suite
npm test

# 7. Check coverage
npm run test:coverage

# 8. Run type tests
npm run test:types

# 9. Commit if successful
git add src/pieces/King.ts tests/unit/pieces/King.test.ts
git commit -m "feat: migrate King piece to TypeScript

- Add type annotations to King class
- Migrate tests to TypeScript
- All tests passing (124/124)
- Coverage maintained at 80%+"

# 10. Push and create PR
git push origin migrate/king-typescript
gh pr create --title "feat: Migrate King piece to TypeScript" \
  --body "Part of Issue #6 TypeScript migration"

# 11. After PR approval and merge
git checkout main
git pull origin main
```

### 6.3 Maintaining Green Tests

**Strategy: Never Break Main Branch**

1. **Feature Branches:** Each file migration is a separate branch
2. **Test-First Verification:** Tests must pass before committing
3. **Incremental Merges:** Merge one file pair at a time
4. **Automated CI Checks:** All tests run on every PR
5. **Rollback Plan:** Keep JS files in separate branch until migration complete

**Parallel File Support (Transition Period):**

```typescript
// jest.config.ts - Support both .js and .ts
testMatch: [
  '**/tests/**/*.test.ts',
  '**/tests/**/*.test.js'  // Remove after full migration
],

transform: {
  '^.+\\.tsx?$': 'ts-jest',
  '^.+\\.jsx?$': 'babel-jest'
}
```

---

## Phase 7: Type Coverage Metrics

### 7.1 Install Type Coverage Tool

```bash
npm install --save-dev type-coverage
```

### 7.2 Configure Type Coverage

**Add to package.json:**

```json
{
  "scripts": {
    "type-check": "tsc --noEmit",
    "type-coverage": "type-coverage --at-least 90 --strict",
    "test:types:all": "npm run type-check && npm run type-coverage && npm run test:types"
  },
  "typeCoverage": {
    "atLeast": 90,
    "strict": true,
    "ignoreCatch": false,
    "ignoreFiles": [
      "**/*.test.ts",
      "**/*.spec.ts",
      "**/node_modules/**"
    ]
  }
}
```

### 7.3 Type Coverage Goals

| Metric | Target | Critical |
|--------|--------|----------|
| Overall Type Coverage | 90% | Yes |
| Strict Type Coverage | 85% | Yes |
| Any Types | < 5% | Yes |
| Implicit Any | 0% | Yes |
| Untyped Functions | < 10% | No |

### 7.4 CI Integration

**File:** `.github/workflows/typescript-tests.yml`

```yaml
name: TypeScript Tests

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run type-check
      - run: npm run type-coverage

  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm test
      - run: npm run test:coverage
      - uses: codecov/codecov-action@v3
        with:
          file: ./coverage/coverage-final.json

  type-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run test:types
```

---

## Phase 8: E2E Test Compatibility

### 8.1 E2E Testing Strategy

**Current State:** No E2E tests exist

**Recommendation:** Add Playwright for E2E tests

```bash
npm install --save-dev @playwright/test
```

### 8.2 Playwright Configuration

**File:** `playwright.config.ts`

```typescript
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',

  use: {
    baseURL: 'http://localhost:8080',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure'
  },

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] }
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] }
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] }
    }
  ],

  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:8080',
    reuseExistingServer: !process.env.CI
  }
});
```

### 8.3 Sample E2E Tests

**File:** `tests/e2e/gameplay.spec.ts`

```typescript
import { test, expect } from '@playwright/test';

test.describe('Chess Game E2E', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('should load game with initial position', async ({ page }) => {
    // Check board is rendered
    const board = page.locator('#board');
    await expect(board).toBeVisible();

    // Check all pieces are present
    const pieces = page.locator('.piece');
    await expect(pieces).toHaveCount(32);

    // Check turn indicator
    const turnIndicator = page.locator('#turn-indicator');
    await expect(turnIndicator).toContainText("White's turn");
  });

  test('should allow valid pawn move', async ({ page }) => {
    // Drag white pawn from e2 to e4
    const e2Pawn = page.locator('[data-position="6-4"]');
    const e4Square = page.locator('[data-square="4-4"]');

    await e2Pawn.dragTo(e4Square);

    // Verify move was made
    await expect(e4Square.locator('.piece')).toBeVisible();
    await expect(e4Square.locator('.piece')).toHaveAttribute('data-piece', 'pawn');

    // Verify turn switched
    const turnIndicator = page.locator('#turn-indicator');
    await expect(turnIndicator).toContainText("Black's turn");
  });

  test('should prevent invalid move', async ({ page }) => {
    // Try to move white pawn from e2 to e5 (invalid)
    const e2Pawn = page.locator('[data-position="6-4"]');
    const e5Square = page.locator('[data-square="3-4"]');

    await e2Pawn.dragTo(e5Square);

    // Verify move was rejected
    await expect(e5Square.locator('.piece')).not.toBeVisible();

    // Verify pawn is still at e2
    const e2Square = page.locator('[data-square="6-4"]');
    await expect(e2Square.locator('.piece')).toBeVisible();

    // Turn should still be white
    const turnIndicator = page.locator('#turn-indicator');
    await expect(turnIndicator).toContainText("White's turn");
  });

  test('should detect checkmate', async ({ page }) => {
    // Play Scholar's Mate using page.evaluate to directly manipulate game state
    await page.evaluate(() => {
      const moves = [
        { from: { row: 6, col: 4 }, to: { row: 4, col: 4 } }, // e4
        { from: { row: 1, col: 4 }, to: { row: 3, col: 4 } }, // e5
        { from: { row: 7, col: 5 }, to: { row: 4, col: 2 } }, // Bc4
        { from: { row: 0, col: 1 }, to: { row: 2, col: 2 } }, // Nc6
        { from: { row: 7, col: 3 }, to: { row: 3, col: 7 } }, // Qh5
        { from: { row: 0, col: 6 }, to: { row: 2, col: 5 } }, // Nf6
        { from: { row: 3, col: 7 }, to: { row: 1, col: 5 } }  // Qxf7# checkmate
      ];

      moves.forEach(move => {
        (window as any).gameController.makeMove(move.from, move.to);
      });
    });

    // Check for checkmate message
    const gameMessage = page.locator('#game-message');
    await expect(gameMessage).toContainText('Checkmate');
    await expect(gameMessage).toContainText('White wins');
  });
});
```

**Add to package.json:**

```json
{
  "scripts": {
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "test:e2e:debug": "playwright test --debug"
  }
}
```

---

## Success Metrics & Goals

### Test Coverage Goals

| Metric | Current | Target | Critical |
|--------|---------|--------|----------|
| Total Tests | 124 | 150+ | Yes |
| Test Pass Rate | 100% | 100% | Yes |
| Code Coverage | ~80% | 85% | Yes |
| Type Coverage | 0% | 90% | Yes |
| Branch Coverage | ~75% | 80% | No |

### Quality Gates

**Before Any PR Merge:**
1. ✅ All tests pass (124/124 minimum)
2. ✅ No TypeScript compilation errors
3. ✅ No type errors (strict mode)
4. ✅ Code coverage >= 80%
5. ✅ Type coverage >= 90%
6. ✅ No `any` types without explicit justification
7. ✅ All ESLint rules pass
8. ✅ Prettier formatting applied

**Before Final Migration Complete:**
1. ✅ All 17 source files migrated
2. ✅ All 7+ test files migrated
3. ✅ E2E tests implemented and passing
4. ✅ Type tests passing
5. ✅ Visual regression tests passing
6. ✅ Integration tests passing
7. ✅ CI/CD pipeline green
8. ✅ Documentation updated

### Performance Goals

| Metric | Target |
|--------|--------|
| Unit Test Suite | < 10 seconds |
| Integration Tests | < 5 seconds |
| E2E Tests | < 30 seconds |
| Type Check | < 5 seconds |
| Total CI Time | < 2 minutes |

---

## Risk Mitigation

### Risk 1: Test Failures During Migration

**Mitigation:**
- One file at a time approach
- Keep JS files until full migration
- Automated rollback via Git
- Comprehensive regression tests

### Risk 2: Type Errors Breaking Tests

**Mitigation:**
- Use `diagnostics.warnOnly` during transition
- Incremental strict mode adoption
- Type-safe test utilities from day 1
- Extensive type testing

### Risk 3: Performance Degradation

**Mitigation:**
- Benchmark tests before/after
- Use isolated modules for faster compilation
- Cache type checking results
- Monitor CI execution time

### Risk 4: Coverage Drop

**Mitigation:**
- Enforce coverage thresholds in CI
- Track coverage per-file
- Add tests for edge cases revealed by types
- Visual coverage reports

---

## Timeline & Milestones

### Week 1: Setup & Foundation
- Day 1-2: Install dependencies, configure Jest + TypeScript
- Day 3: Migrate test utilities and setup files
- Day 4-5: Create factories, mocks, and assertions
- Milestone: Green test suite with TypeScript configuration

### Week 2: Core Types & Base Classes
- Day 1: Migrate Constants and Helpers + tests
- Day 2-3: Migrate Piece base class + tests
- Day 4-5: Migrate Board class + tests
- Milestone: Core infrastructure in TypeScript

### Week 3: Piece Implementations
- Day 1: Pawn + tests
- Day 2: Knight & Bishop + tests
- Day 3: Rook & Queen + tests
- Day 4-5: King + tests (complex, castling logic)
- Milestone: All pieces in TypeScript

### Week 4: Game Logic & Engine
- Day 1-2: GameState + MoveValidator + tests
- Day 3: SpecialMoves + tests
- Day 4-5: Integration tests
- Milestone: Complete game logic in TypeScript

### Week 5: Controllers, Views & E2E
- Day 1: EventBus + tests
- Day 2-3: BoardRenderer + DragDropHandler + tests
- Day 4: GameController + integration tests
- Day 5: E2E tests with Playwright
- Milestone: Full application in TypeScript

### Week 6: Polish & Verification
- Day 1-2: Type coverage optimization (reach 90%)
- Day 3: Visual regression tests
- Day 4: Performance benchmarking
- Day 5: Final verification, documentation
- Milestone: Migration complete, all quality gates passed

---

## Conclusion

This comprehensive testing strategy ensures:

1. **Zero Regression:** All 124 tests remain passing throughout migration
2. **Type Safety:** 90%+ type coverage with strict mode enabled
3. **Maintainability:** Type-safe test utilities and factories
4. **Confidence:** Multi-layer testing (unit, integration, E2E, type-level)
5. **Quality:** Automated CI checks enforce quality gates
6. **Speed:** Incremental migration allows continuous deployment

**Next Steps:**
1. Review and approve this strategy
2. Set up initial TypeScript + Jest configuration
3. Begin migration with Constants/Helpers (lowest risk)
4. Establish CI pipeline
5. Execute migration plan file-by-file

**Success Criteria:**
- ✅ 100% test pass rate maintained
- ✅ 90%+ type coverage achieved
- ✅ Zero production bugs introduced
- ✅ CI pipeline running in < 2 minutes
- ✅ All quality gates passing on every PR
