/** * @jest-environment jsdom */ import { Knight } from '../../../js/pieces/Knight.js'; import { Board } from '../../../js/game/Board.js'; describe('Knight', () => { let board; beforeEach(() => { board = new Board(); board.clear(); }); describe('L-Shaped Movement', () => { test('knight in center can move to all 8 positions', () => { const knight = new Knight('white', { row: 4, col: 4 }); board.setPiece(4, 4, knight); const moves = knight.getValidMoves(board); const expectedMoves = [ { row: 2, col: 3 }, // Up 2, Left 1 { row: 2, col: 5 }, // Up 2, Right 1 { row: 3, col: 2 }, // Up 1, Left 2 { row: 3, col: 6 }, // Up 1, Right 2 { row: 5, col: 2 }, // Down 1, Left 2 { row: 5, col: 6 }, // Down 1, Right 2 { row: 6, col: 3 }, // Down 2, Left 1 { row: 6, col: 5 } // Down 2, Right 1 ]; expect(moves).toHaveLength(8); expectedMoves.forEach(expected => { expect(moves).toContainEqual(expected); }); }); test('knight in corner has limited moves', () => { const knight = new Knight('white', { row: 0, col: 0 }); board.setPiece(0, 0, knight); const moves = knight.getValidMoves(board); const expectedMoves = [ { row: 1, col: 2 }, { row: 2, col: 1 } ]; expect(moves).toHaveLength(2); expectedMoves.forEach(expected => { expect(moves).toContainEqual(expected); }); }); test('knight on edge has restricted moves', () => { const knight = new Knight('white', { row: 4, col: 0 }); board.setPiece(4, 0, knight); const moves = knight.getValidMoves(board); // Can only move to right side expect(moves).toHaveLength(4); moves.forEach(move => { expect(move.col).toBeGreaterThan(0); }); }); test('knight moves exactly 2+1 squares', () => { const knight = new Knight('white', { row: 4, col: 4 }); board.setPiece(4, 4, knight); const moves = knight.getValidMoves(board); moves.forEach(move => { const rowDiff = Math.abs(move.row - 4); const colDiff = Math.abs(move.col - 4); // L-shape: either (2,1) or (1,2) const isLShape = (rowDiff === 2 && colDiff === 1) || (rowDiff === 1 && colDiff === 2); expect(isLShape).toBe(true); }); }); }); describe('Jumping Over Pieces', () => { test('knight can jump over own pieces', () => { const knight = new Knight('white', { row: 7, col: 1 }); const blockingPawn = { type: 'pawn', color: 'white', position: { row: 6, col: 1 } }; board.setPiece(7, 1, knight); board.setPiece(6, 1, blockingPawn); const moves = knight.getValidMoves(board); // Should still be able to move despite blocked adjacent squares expect(moves.length).toBeGreaterThan(0); expect(moves).toContainEqual({ row: 5, col: 0 }); expect(moves).toContainEqual({ row: 5, col: 2 }); }); test('knight can jump over opponent pieces', () => { const knight = new Knight('white', { row: 4, col: 4 }); // Surround knight with pieces board.setPiece(4, 4, knight); board.setPiece(3, 4, { type: 'pawn', color: 'black', position: { row: 3, col: 4 } }); board.setPiece(5, 4, { type: 'pawn', color: 'black', position: { row: 5, col: 4 } }); board.setPiece(4, 3, { type: 'pawn', color: 'black', position: { row: 4, col: 3 } }); board.setPiece(4, 5, { type: 'pawn', color: 'black', position: { row: 4, col: 5 } }); const moves = knight.getValidMoves(board); // Should still have all 8 moves available expect(moves).toHaveLength(8); }); test('knight trapped by own pieces on destination squares', () => { const knight = new Knight('white', { row: 7, col: 1 }); board.setPiece(7, 1, knight); // Place white pieces on all possible destinations board.setPiece(6, 3, { type: 'pawn', color: 'white', position: { row: 6, col: 3 } }); board.setPiece(5, 0, { type: 'pawn', color: 'white', position: { row: 5, col: 0 } }); board.setPiece(5, 2, { type: 'pawn', color: 'white', position: { row: 5, col: 2 } }); const moves = knight.getValidMoves(board); expect(moves).toHaveLength(0); }); }); describe('Capture Mechanics', () => { test('knight can capture opponent pieces', () => { const knight = new Knight('white', { row: 4, col: 4 }); const blackPawn = { type: 'pawn', color: 'black', position: { row: 2, col: 3 } }; board.setPiece(4, 4, knight); board.setPiece(2, 3, blackPawn); const moves = knight.getValidMoves(board); expect(moves).toContainEqual({ row: 2, col: 3 }); }); test('knight cannot capture own pieces', () => { const knight = new Knight('white', { row: 4, col: 4 }); const whitePawn = { type: 'pawn', color: 'white', position: { row: 2, col: 3 } }; board.setPiece(4, 4, knight); board.setPiece(2, 3, whitePawn); const moves = knight.getValidMoves(board); expect(moves).not.toContainEqual({ row: 2, col: 3 }); }); test('knight can capture multiple opponent pieces', () => { const knight = new Knight('white', { row: 4, col: 4 }); board.setPiece(4, 4, knight); board.setPiece(2, 3, { type: 'pawn', color: 'black', position: { row: 2, col: 3 } }); board.setPiece(2, 5, { type: 'pawn', color: 'black', position: { row: 2, col: 5 } }); board.setPiece(6, 3, { type: 'pawn', color: 'black', position: { row: 6, col: 3 } }); const moves = knight.getValidMoves(board); expect(moves).toContainEqual({ row: 2, col: 3 }); expect(moves).toContainEqual({ row: 2, col: 5 }); expect(moves).toContainEqual({ row: 6, col: 3 }); }); }); describe('Board Boundaries', () => { test('knight at a1 (bottom-left corner)', () => { const knight = new Knight('white', { row: 7, col: 0 }); board.setPiece(7, 0, knight); const moves = knight.getValidMoves(board); expect(moves).toHaveLength(2); expect(moves).toContainEqual({ row: 6, col: 2 }); expect(moves).toContainEqual({ row: 5, col: 1 }); }); test('knight at h8 (top-right corner)', () => { const knight = new Knight('black', { row: 0, col: 7 }); board.setPiece(0, 7, knight); const moves = knight.getValidMoves(board); expect(moves).toHaveLength(2); expect(moves).toContainEqual({ row: 1, col: 5 }); expect(moves).toContainEqual({ row: 2, col: 6 }); }); test('knight moves stay within board bounds', () => { // Test all board positions for (let row = 0; row < 8; row++) { for (let col = 0; col < 8; col++) { const knight = new Knight('white', { row, col }); board.clear(); board.setPiece(row, col, knight); const moves = knight.getValidMoves(board); moves.forEach(move => { expect(move.row).toBeGreaterThanOrEqual(0); expect(move.row).toBeLessThan(8); expect(move.col).toBeGreaterThanOrEqual(0); expect(move.col).toBeLessThan(8); }); } } }); }); describe('Special Positions', () => { test('knight fork - attacking two pieces simultaneously', () => { const knight = new Knight('white', { row: 4, col: 4 }); const blackKing = { type: 'king', color: 'black', position: { row: 2, col: 3 } }; const blackQueen = { type: 'queen', color: 'black', position: { row: 2, col: 5 } }; board.setPiece(4, 4, knight); board.setPiece(2, 3, blackKing); board.setPiece(2, 5, blackQueen); const moves = knight.getValidMoves(board); // Knight can attack both king and queen expect(moves).toContainEqual({ row: 2, col: 3 }); expect(moves).toContainEqual({ row: 2, col: 5 }); }); test('knight starting positions from initial board', () => { board = new Board(); board.setupInitialPosition(); const whiteKnight1 = board.getPiece(7, 1); const whiteKnight2 = board.getPiece(7, 6); const blackKnight1 = board.getPiece(0, 1); const blackKnight2 = board.getPiece(0, 6); expect(whiteKnight1.type).toBe('knight'); expect(whiteKnight2.type).toBe('knight'); expect(blackKnight1.type).toBe('knight'); expect(blackKnight2.type).toBe('knight'); // From starting position, each knight has 2 possible moves expect(whiteKnight1.getValidMoves(board)).toHaveLength(2); expect(whiteKnight2.getValidMoves(board)).toHaveLength(2); }); }); });