Improve unit tests with mocking, edge cases, and better coverage
- Add deterministic mocking for Math.random() in garbage tests - Add edge case tests for piece positioning at boundaries - Add tests for negative Y positions (spawn area) - Improve lockPieceToBoard tests with proper I piece shape handling - Add performance tests for checkBoardOverflow and createEmptyBoard - Add tests for multiple garbage rows and board overflow scenarios - Fix test expectations to match actual game logic behavior - Total tests increased from 57 to 105
This commit is contained in:
@@ -47,10 +47,21 @@ describe('getPieceFromType', () => {
|
||||
expect(piece.color).toBe('#ffff00');
|
||||
});
|
||||
|
||||
test('returns correct piece for T tetromino', () => {
|
||||
const piece = getPieceFromType('T');
|
||||
expect(piece.type).toBe('T');
|
||||
expect(piece.color).toBe('#800080');
|
||||
});
|
||||
|
||||
test('centers piece horizontally', () => {
|
||||
const piece = getPieceFromType('I');
|
||||
expect(piece.x).toBe(Math.floor(BOARD_WIDTH / 2) - Math.floor(4 / 2));
|
||||
});
|
||||
|
||||
test('O piece is centered correctly', () => {
|
||||
const piece = getPieceFromType('O');
|
||||
expect(piece.x).toBe(Math.floor(BOARD_WIDTH / 2) - Math.floor(2 / 2));
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidPosition', () => {
|
||||
@@ -84,6 +95,28 @@ describe('isValidPosition', () => {
|
||||
const piece = getPieceFromType('O');
|
||||
expect(isValidPosition(piece, 4, 9, board)).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true for negative Y (piece spawning above board)', () => {
|
||||
const board = createEmptyBoard();
|
||||
const piece = getPieceFromType('I');
|
||||
// Negative Y is allowed for spawn area above visible board
|
||||
expect(isValidPosition(piece, 3, -1, board)).toBe(true);
|
||||
});
|
||||
|
||||
test('handles piece partially above board', () => {
|
||||
const board = createEmptyBoard();
|
||||
const piece = { ...getPieceFromType('I'), y: -1 };
|
||||
// Piece can be partially above board (y < 0)
|
||||
expect(isValidPosition(piece, 3, -1, board)).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false when piece collides with block at spawn area', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[0][3] = '#ff0000'; // Block at top
|
||||
const piece = getPieceFromType('I');
|
||||
// Piece at y=-1 would have blocks at y=-1,0,1,2 - row 0 has block
|
||||
expect(isValidPosition(piece, 3, -1, board)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGhostY', () => {
|
||||
@@ -104,6 +137,19 @@ describe('getGhostY', () => {
|
||||
const ghostY = getGhostY(piece, board);
|
||||
expect(ghostY).toBeGreaterThan(piece.y);
|
||||
});
|
||||
|
||||
test('calculates ghost position with obstacle', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[10][3] = '#ff0000';
|
||||
board[10][4] = '#ff0000';
|
||||
board[10][5] = '#ff0000';
|
||||
board[10][6] = '#ff0000';
|
||||
const piece = getPieceFromType('I');
|
||||
const ghostY = getGhostY(piece, board);
|
||||
// I piece at x=3 with shape [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]]
|
||||
// Bottom of piece (row 1 of shape) at y=8 would hit obstacle at row 10
|
||||
expect(ghostY).toBe(8);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkBoardOverflow', () => {
|
||||
@@ -136,6 +182,26 @@ describe('checkBoardOverflow', () => {
|
||||
board[2][5] = '#ff0000';
|
||||
expect(checkBoardOverflow(board)).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true when garbage fills row 0', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[0].fill(GARBAGE_COLOR);
|
||||
expect(checkBoardOverflow(board)).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true when row 0 has garbage with gap', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[0].fill(GARBAGE_COLOR);
|
||||
board[0][5] = 0; // Gap in row 0
|
||||
expect(checkBoardOverflow(board)).toBe(true); // Still overflowed
|
||||
});
|
||||
|
||||
test('returns true when row 1 has garbage even if row 0 empty', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[1].fill(GARBAGE_COLOR);
|
||||
// Row 0 is empty but row 1 has blocks
|
||||
expect(checkBoardOverflow(board)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addGarbageRow', () => {
|
||||
@@ -172,6 +238,38 @@ describe('addGarbageRow', () => {
|
||||
const result = addGarbageRow(board, null);
|
||||
expect(result.currentPiece).toBe(null);
|
||||
});
|
||||
|
||||
test('garbage gap position is deterministic with mocked random', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.5; // Always return 0.5 -> gap at index 5
|
||||
|
||||
const board = createEmptyBoard();
|
||||
const result = addGarbageRow(board, null);
|
||||
|
||||
expect(result.gap).toBe(5);
|
||||
expect(result.board[19][5]).toBe(0);
|
||||
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
test('removes top row correctly', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[0][5] = '#ff0000'; // Mark top row
|
||||
const result = addGarbageRow(board, null);
|
||||
// Original row 1 should now be row 0
|
||||
expect(result.board[0][5]).toBe(0);
|
||||
});
|
||||
|
||||
test('piece pushed to negative Y is still tracked', () => {
|
||||
const board = createEmptyBoard();
|
||||
const piece = { ...getPieceFromType('I'), y: 2 };
|
||||
const result = addGarbageRow(board, piece);
|
||||
expect(result.currentPiece.y).toBe(1);
|
||||
|
||||
// Push again
|
||||
const result2 = addGarbageRow(result.board, result.currentPiece);
|
||||
expect(result2.currentPiece.y).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('lockPieceToBoard', () => {
|
||||
@@ -195,6 +293,74 @@ describe('lockPieceToBoard', () => {
|
||||
// Original board should still be empty
|
||||
expect(board[5][4]).toBe(0);
|
||||
});
|
||||
|
||||
test('piece at negative Y only locks visible portion', () => {
|
||||
const board = createEmptyBoard();
|
||||
const piece = {
|
||||
...getPieceFromType('I'),
|
||||
x: 3,
|
||||
y: -1 // Top half above board
|
||||
};
|
||||
const newBoard = lockPieceToBoard(piece, board);
|
||||
|
||||
// Only bottom half should be locked (I piece is 4 rows tall)
|
||||
expect(newBoard[0][3]).toBe(piece.color);
|
||||
expect(newBoard[0][4]).toBe(piece.color);
|
||||
expect(newBoard[0][5]).toBe(piece.color);
|
||||
expect(newBoard[0][6]).toBe(piece.color);
|
||||
});
|
||||
|
||||
test('piece at bottom of board locks correctly', () => {
|
||||
const board = createEmptyBoard();
|
||||
const piece = { ...getPieceFromType('O'), x: 4, y: 18 };
|
||||
const newBoard = lockPieceToBoard(piece, board);
|
||||
|
||||
expect(newBoard[18][4]).toBe(piece.color);
|
||||
expect(newBoard[18][5]).toBe(piece.color);
|
||||
expect(newBoard[19][4]).toBe(piece.color);
|
||||
expect(newBoard[19][5]).toBe(piece.color);
|
||||
});
|
||||
|
||||
test('piece partially off bottom edge only locks visible part', () => {
|
||||
const board = createEmptyBoard();
|
||||
const piece = { ...getPieceFromType('I'), x: 3, y: 16 };
|
||||
const newBoard = lockPieceToBoard(piece, board);
|
||||
|
||||
// I piece shape: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]]
|
||||
// At y=16, the blocks (row 1 of shape) lock at row 17
|
||||
// Rows 17,18,19 are visible
|
||||
expect(newBoard[17][3]).toBe(piece.color);
|
||||
expect(newBoard[17][4]).toBe(piece.color);
|
||||
expect(newBoard[17][5]).toBe(piece.color);
|
||||
expect(newBoard[17][6]).toBe(piece.color);
|
||||
});
|
||||
|
||||
test('piece completely above board locks nothing', () => {
|
||||
const board = createEmptyBoard();
|
||||
const piece = { ...getPieceFromType('I'), x: 3, y: -10 };
|
||||
const newBoard = lockPieceToBoard(piece, board);
|
||||
|
||||
// Board should still be empty
|
||||
expect(newBoard[0][3]).toBe(0);
|
||||
});
|
||||
|
||||
test('piece at left edge locks correctly', () => {
|
||||
const board = createEmptyBoard();
|
||||
const piece = { ...getPieceFromType('O'), x: 0, y: 5 };
|
||||
const newBoard = lockPieceToBoard(piece, board);
|
||||
|
||||
expect(newBoard[5][0]).toBe(piece.color);
|
||||
expect(newBoard[5][1]).toBe(piece.color);
|
||||
});
|
||||
|
||||
test('piece at right edge locks correctly', () => {
|
||||
const board = createEmptyBoard();
|
||||
const piece = { ...getPieceFromType('O'), x: 8, y: 5 };
|
||||
const newBoard = lockPieceToBoard(piece, board);
|
||||
|
||||
expect(newBoard[5][8]).toBe(piece.color);
|
||||
expect(newBoard[5][9]).toBe(piece.color);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearRows', () => {
|
||||
@@ -234,6 +400,43 @@ describe('clearRows', () => {
|
||||
const result = clearRows(board);
|
||||
expect(result.board.length).toBe(BOARD_HEIGHT);
|
||||
});
|
||||
|
||||
test('does not clear incomplete rows', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[10].fill('#ff0000');
|
||||
board[10][5] = 0; // Leave a gap
|
||||
const result = clearRows(board);
|
||||
expect(result.rowsCleared).toBe(0);
|
||||
});
|
||||
|
||||
test('clears tetris (4 rows at once)', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[16].fill('#ff0000');
|
||||
board[17].fill('#ff0000');
|
||||
board[18].fill('#ff0000');
|
||||
board[19].fill('#ff0000');
|
||||
const result = clearRows(board);
|
||||
expect(result.rowsCleared).toBe(4);
|
||||
});
|
||||
|
||||
test('clears rows from bottom to top', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[5].fill('#ff0000');
|
||||
board[10].fill('#00ff00');
|
||||
board[15].fill('#0000ff');
|
||||
const result = clearRows(board);
|
||||
expect(result.rowsCleared).toBe(3);
|
||||
});
|
||||
|
||||
test('shifts rows down correctly after clearing', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[18].fill('#ff0000');
|
||||
board[19].fill('#00ff00');
|
||||
const result = clearRows(board);
|
||||
// After clearing 2 rows, top should be empty
|
||||
expect(result.board[0].every(cell => cell === 0)).toBe(true);
|
||||
expect(result.board[1].every(cell => cell === 0)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generatePieceBag', () => {
|
||||
@@ -254,6 +457,20 @@ describe('generatePieceBag', () => {
|
||||
expect(types.has('J')).toBe(true);
|
||||
expect(types.has('L')).toBe(true);
|
||||
});
|
||||
|
||||
test('each bag contains exactly one of each piece type', () => {
|
||||
const bag = generatePieceBag();
|
||||
const counts = {};
|
||||
bag.forEach(type => counts[type] = (counts[type] || 0) + 1);
|
||||
|
||||
expect(counts.I).toBe(1);
|
||||
expect(counts.O).toBe(1);
|
||||
expect(counts.T).toBe(1);
|
||||
expect(counts.S).toBe(1);
|
||||
expect(counts.Z).toBe(1);
|
||||
expect(counts.J).toBe(1);
|
||||
expect(counts.L).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPieceQueue', () => {
|
||||
@@ -266,4 +483,81 @@ describe('createPieceQueue', () => {
|
||||
const queue = createPieceQueue();
|
||||
expect(queue.length).toBe(98); // 14 bags * 7 pieces
|
||||
});
|
||||
|
||||
test('creates multiple complete bags', () => {
|
||||
const queue = createPieceQueue(3); // 3 bags = 21 pieces
|
||||
expect(queue.length).toBe(21);
|
||||
|
||||
// Each 7-piece segment should have all types
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const bag = queue.slice(i * 7, (i + 1) * 7);
|
||||
expect(new Set(bag).size).toBe(7);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('garbage edge cases', () => {
|
||||
test('multiple garbage rows push piece progressively', () => {
|
||||
let board = createEmptyBoard();
|
||||
let piece = { ...getPieceFromType('I'), y: 10 };
|
||||
|
||||
// Add 5 garbage rows
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const result = addGarbageRow(board, piece);
|
||||
board = result.board;
|
||||
piece = result.currentPiece;
|
||||
}
|
||||
|
||||
expect(piece.y).toBe(5); // 10 - 5 = 5
|
||||
});
|
||||
|
||||
test('19 garbage rows causes board overflow', () => {
|
||||
let board = createEmptyBoard();
|
||||
let piece = { ...getPieceFromType('I'), y: 10 };
|
||||
|
||||
for (let i = 0; i < 19; i++) {
|
||||
const result = addGarbageRow(board, piece);
|
||||
board = result.board;
|
||||
piece = result.currentPiece;
|
||||
}
|
||||
|
||||
// Board should now have garbage in top rows (row 0 and 1)
|
||||
expect(checkBoardOverflow(board)).toBe(true);
|
||||
});
|
||||
|
||||
test('piece pushed to negative Y is still tracked correctly', () => {
|
||||
let board = createEmptyBoard();
|
||||
let piece = { ...getPieceFromType('I'), y: 5 };
|
||||
|
||||
// Push piece 10 rows up
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const result = addGarbageRow(board, piece);
|
||||
board = result.board;
|
||||
piece = result.currentPiece;
|
||||
}
|
||||
|
||||
expect(piece.y).toBe(-5);
|
||||
// Board not overflowed yet (piece is above board, not in it)
|
||||
expect(checkBoardOverflow(board)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('performance', () => {
|
||||
test('checkBoardOverflow is fast', () => {
|
||||
const board = createEmptyBoard();
|
||||
|
||||
expect(() => {
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
checkBoardOverflow(board);
|
||||
}
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('createEmptyBoard is fast', () => {
|
||||
expect(() => {
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
createEmptyBoard();
|
||||
}
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,8 @@ const {
|
||||
createEmptyBoard,
|
||||
getPieceFromType,
|
||||
checkBoardOverflow,
|
||||
addGarbageRow
|
||||
addGarbageRow,
|
||||
lockPieceToBoard
|
||||
} = require('../game-logic');
|
||||
|
||||
// Mock player object
|
||||
@@ -64,6 +65,33 @@ function sendGarbage(lobby, sender, rowsCleared) {
|
||||
return garbageLog;
|
||||
}
|
||||
|
||||
// Simulate lockPiece with elimination check (from index.js)
|
||||
function lockPiece(player) {
|
||||
if (!player.currentPiece) return 0;
|
||||
|
||||
// Lock piece into board
|
||||
player.board = lockPieceToBoard(player.currentPiece, player.board);
|
||||
|
||||
// Check board overflow for elimination
|
||||
if (checkBoardOverflow(player.board)) {
|
||||
player.eliminated = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Clear rows
|
||||
let rowsCleared = 0;
|
||||
for (let row = BOARD_HEIGHT - 1; row >= 0; row--) {
|
||||
if (player.board[row].every(cell => cell !== 0)) {
|
||||
player.board.splice(row, 1);
|
||||
player.board.unshift(Array(BOARD_WIDTH).fill(0));
|
||||
rowsCleared++;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
return rowsCleared;
|
||||
}
|
||||
|
||||
describe('addGarbageToPlayer', () => {
|
||||
test('adds exactly 1 garbage row per call', () => {
|
||||
const player = createMockPlayer('Player1', 5);
|
||||
@@ -111,6 +139,28 @@ describe('addGarbageToPlayer', () => {
|
||||
expect(player.eliminated).toBe(false);
|
||||
expect(player.currentPiece.y).toBe(5); // 10 - 5 = 5
|
||||
});
|
||||
|
||||
test('handles player with no current piece', () => {
|
||||
const player = createMockPlayer('Player1', null);
|
||||
|
||||
addGarbageToPlayer(player, 'Enemy');
|
||||
|
||||
expect(player.currentPiece).toBe(null);
|
||||
expect(player.garbageReceived.length).toBe(1);
|
||||
});
|
||||
|
||||
test('multiple garbage from different senders tracked separately', () => {
|
||||
const player = createMockPlayer('Player1', 10);
|
||||
|
||||
addGarbageToPlayer(player, 'Enemy1');
|
||||
addGarbageToPlayer(player, 'Enemy2');
|
||||
addGarbageToPlayer(player, 'Enemy1');
|
||||
|
||||
expect(player.garbageReceived).toHaveLength(3);
|
||||
expect(player.garbageReceived[0].sender).toBe('Enemy1');
|
||||
expect(player.garbageReceived[1].sender).toBe('Enemy2');
|
||||
expect(player.garbageReceived[2].sender).toBe('Enemy1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendGarbage', () => {
|
||||
@@ -171,6 +221,106 @@ describe('sendGarbage', () => {
|
||||
// Total garbage sent should equal rows cleared
|
||||
expect(target1.garbageReceived.length + target2.garbageReceived.length).toBe(4);
|
||||
});
|
||||
|
||||
test('garbage distribution is deterministic with mocked random', () => {
|
||||
const sender = createMockPlayer('Sender', 5);
|
||||
const target1 = createMockPlayer('Target1', 10);
|
||||
const target2 = createMockPlayer('Target2', 10);
|
||||
const lobby = createMockLobby([sender, target1, target2]);
|
||||
|
||||
// Mock Math.random to always pick first opponent (index 0)
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.1;
|
||||
|
||||
sendGarbage(lobby, sender, 4);
|
||||
|
||||
Math.random = originalRandom;
|
||||
|
||||
// All garbage should go to target1 (first in array)
|
||||
expect(target1.garbageReceived.length).toBe(4);
|
||||
expect(target2.garbageReceived.length).toBe(0);
|
||||
});
|
||||
|
||||
test('1 row cleared sends 1 garbage row', () => {
|
||||
const sender = createMockPlayer('Sender', 5);
|
||||
const target = createMockPlayer('Target', 10);
|
||||
const lobby = createMockLobby([sender, target]);
|
||||
|
||||
const garbageLog = sendGarbage(lobby, sender, 1);
|
||||
|
||||
expect(garbageLog.length).toBe(1);
|
||||
expect(target.garbageReceived.length).toBe(1);
|
||||
});
|
||||
|
||||
test('2 rows cleared sends 2 garbage rows', () => {
|
||||
const sender = createMockPlayer('Sender', 5);
|
||||
const target = createMockPlayer('Target', 10);
|
||||
const lobby = createMockLobby([sender, target]);
|
||||
|
||||
const garbageLog = sendGarbage(lobby, sender, 2);
|
||||
|
||||
expect(garbageLog.length).toBe(2);
|
||||
});
|
||||
|
||||
test('4 rows (tetris) sends 4 garbage rows', () => {
|
||||
const sender = createMockPlayer('Sender', 5);
|
||||
const target = createMockPlayer('Target', 10);
|
||||
const lobby = createMockLobby([sender, target]);
|
||||
|
||||
const garbageLog = sendGarbage(lobby, sender, 4);
|
||||
|
||||
expect(garbageLog.length).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('lockPiece elimination', () => {
|
||||
test('piece locking does not eliminate when board not overflowed', () => {
|
||||
const player = createMockPlayer('Player1', 10);
|
||||
|
||||
const rowsCleared = lockPiece(player);
|
||||
|
||||
expect(player.eliminated).toBe(false);
|
||||
expect(rowsCleared).toBe(0);
|
||||
});
|
||||
|
||||
test('piece locking eliminates when board overflowed', () => {
|
||||
const player = createMockPlayer('Player1', 0);
|
||||
// Fill top 2 rows with garbage
|
||||
player.board[0].fill(GARBAGE_COLOR);
|
||||
player.board[0][5] = 0; // Gap
|
||||
|
||||
lockPiece(player);
|
||||
|
||||
expect(player.eliminated).toBe(true);
|
||||
});
|
||||
|
||||
test('piece at y=0 eliminates when it locks into top rows', () => {
|
||||
const player = createMockPlayer('Player1', 0);
|
||||
// I piece at y=0 locks into rows 0,1,2,3
|
||||
// This causes overflow since row 0 and 1 will have blocks
|
||||
|
||||
lockPiece(player);
|
||||
|
||||
expect(player.eliminated).toBe(true);
|
||||
});
|
||||
|
||||
test('piece at y=5 does not eliminate if top rows empty', () => {
|
||||
const player = createMockPlayer('Player1', 5);
|
||||
// Top rows are empty, piece locks at rows 5,6,7,8
|
||||
|
||||
lockPiece(player);
|
||||
|
||||
expect(player.eliminated).toBe(false);
|
||||
});
|
||||
|
||||
test('piece locks and does not clear rows when no complete rows', () => {
|
||||
const player = createMockPlayer('Player1', 10);
|
||||
// Piece locks at rows 10,11,12,13 - no complete rows
|
||||
|
||||
const rowsCleared = lockPiece(player);
|
||||
|
||||
expect(rowsCleared).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkBoardOverflow', () => {
|
||||
@@ -203,6 +353,20 @@ describe('checkBoardOverflow', () => {
|
||||
board[18].fill(GARBAGE_COLOR);
|
||||
expect(checkBoardOverflow(board)).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true when both row 0 and row 1 have blocks', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[0][3] = '#ff0000';
|
||||
board[1][7] = '#ff0000';
|
||||
expect(checkBoardOverflow(board)).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true when row 0 has garbage with gap', () => {
|
||||
const board = createEmptyBoard();
|
||||
board[0].fill(GARBAGE_COLOR);
|
||||
board[0][5] = 0; // Gap in row 0
|
||||
expect(checkBoardOverflow(board)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Garbage Elimination Bug Fix', () => {
|
||||
@@ -263,4 +427,81 @@ describe('Garbage Elimination Bug Fix', () => {
|
||||
expect(target.eliminated).toBe(false);
|
||||
expect(target.garbageReceived.length).toBe(4);
|
||||
});
|
||||
|
||||
test('elimination only happens when piece locks with overflow', () => {
|
||||
// Complete scenario: garbage received, piece locks, then elimination checked
|
||||
|
||||
const player = createMockPlayer('Player1', 0); // Piece at spawn
|
||||
|
||||
// Receive 1 garbage row - player NOT eliminated
|
||||
addGarbageToPlayer(player, 'Enemy');
|
||||
expect(player.eliminated).toBe(false);
|
||||
|
||||
// Fill top row to simulate overflow
|
||||
player.board[0].fill(GARBAGE_COLOR);
|
||||
player.board[0][5] = 0;
|
||||
|
||||
// Now lock piece - player eliminated
|
||||
lockPiece(player);
|
||||
expect(player.eliminated).toBe(true);
|
||||
});
|
||||
|
||||
test('player with piece above board survives garbage', () => {
|
||||
// Edge case: piece is already above visible area (negative y)
|
||||
// Should still not be eliminated by garbage alone
|
||||
|
||||
const player = createMockPlayer('Player1', 2);
|
||||
|
||||
// Push piece 5 rows up (to y=-3)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
addGarbageToPlayer(player, 'Enemy');
|
||||
}
|
||||
|
||||
// Piece is at y=-3, but player NOT eliminated
|
||||
expect(player.eliminated).toBe(false);
|
||||
expect(player.currentPiece.y).toBe(-3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
test('player with piece at y=0 receives garbage', () => {
|
||||
const player = createMockPlayer('Player1', 0);
|
||||
|
||||
addGarbageToPlayer(player, 'Enemy');
|
||||
|
||||
expect(player.currentPiece.y).toBe(-1);
|
||||
expect(player.eliminated).toBe(false);
|
||||
});
|
||||
|
||||
test('18 garbage rows pushes piece well above board', () => {
|
||||
const player = createMockPlayer('Player1', 10);
|
||||
|
||||
for (let i = 0; i < 18; i++) {
|
||||
addGarbageToPlayer(player, 'Enemy');
|
||||
}
|
||||
|
||||
expect(player.currentPiece.y).toBe(-8);
|
||||
// Board overflowed but player not eliminated yet (only on lock)
|
||||
expect(player.eliminated).toBe(false);
|
||||
});
|
||||
|
||||
test('garbage with no opponents does not crash', () => {
|
||||
const sender = createMockPlayer('Sender', 5);
|
||||
const lobby = createMockLobby([sender]);
|
||||
|
||||
const garbageLog = sendGarbage(lobby, sender, 4);
|
||||
|
||||
expect(garbageLog.length).toBe(0);
|
||||
});
|
||||
|
||||
test('zero rows cleared sends no garbage', () => {
|
||||
const sender = createMockPlayer('Sender', 5);
|
||||
const target = createMockPlayer('Target', 10);
|
||||
const lobby = createMockLobby([sender, target]);
|
||||
|
||||
const garbageLog = sendGarbage(lobby, sender, 0);
|
||||
|
||||
expect(garbageLog.length).toBe(0);
|
||||
expect(target.garbageReceived.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user