import { describe, it, expect, vi } from 'vitest'; import { Item, BallItem, LineClearItem } from '../src/entities/Item.js'; import { GRID_GAP, ITEM_SIZE_RATIO } from '../src/constants.js'; describe('Item (base class)', () => { describe('constructor', () => { it('initializes with correct properties', () => { const item = new Item(3, 2, 'ball', 40); expect(item.gridX).toBe(3); expect(item.gridY).toBe(2); expect(item.type).toBe('ball'); expect(item.size).toBe(40); expect(item.collected).toBe(false); }); }); describe('getRect()', () => { it('converts grid coordinates to pixel coordinates with reduced size', () => { const size = 40; const item = new Item(2, 3, 'ball', size); const rect = item.getRect(); const itemSize = size * ITEM_SIZE_RATIO; const offset = (size - itemSize) / 2; expect(rect.x).toBe(2 * (size + GRID_GAP) + GRID_GAP + offset); expect(rect.y).toBe(3 * (size + GRID_GAP) + GRID_GAP + offset); expect(rect.width).toBe(itemSize); expect(rect.height).toBe(itemSize); }); it('returns correct rect for origin (0,0)', () => { const size = 50; const item = new Item(0, 0, 'ball', size); const rect = item.getRect(); const itemSize = size * ITEM_SIZE_RATIO; const offset = (size - itemSize) / 2; expect(rect.x).toBe(GRID_GAP + offset); expect(rect.y).toBe(GRID_GAP + offset); expect(rect.width).toBe(itemSize); expect(rect.height).toBe(itemSize); }); }); describe('shouldRemove()', () => { it('returns false by default', () => { const item = new Item(0, 0, 'ball', 40); expect(item.shouldRemove()).toBe(false); }); }); describe('moveDown()', () => { it('increments gridY by 1', () => { const item = new Item(3, 2, 'ball', 40); item.moveDown(); expect(item.gridY).toBe(3); }); }); }); describe('BallItem', () => { describe('constructor', () => { it('initializes with type "ball"', () => { const item = new BallItem(1, 2, 40); expect(item.type).toBe('ball'); expect(item.gridX).toBe(1); expect(item.gridY).toBe(2); expect(item.collected).toBe(false); }); }); describe('onCollect()', () => { it('calls game.addPendingBall() and marks collected', () => { const game = { addPendingBall: vi.fn() }; const item = new BallItem(0, 0, 40); item.onCollect(game); expect(game.addPendingBall).toHaveBeenCalledOnce(); expect(item.collected).toBe(true); }); }); describe('shouldRemove()', () => { it('returns true (ball items are removed on collect)', () => { const item = new BallItem(0, 0, 40); expect(item.shouldRemove()).toBe(true); }); }); }); describe('LineClearItem', () => { describe('constructor', () => { it('initializes with type "lineClear"', () => { const item = new LineClearItem(4, 5, 40, 'horizontal'); expect(item.type).toBe('lineClear'); expect(item.gridX).toBe(4); expect(item.gridY).toBe(5); expect(item.collected).toBe(false); expect(item.direction).toBe('horizontal'); }); }); describe('onCollect()', () => { it('marks collected and calls clearRow for horizontal direction', () => { const game = { clearRow: vi.fn(), clearColumn: vi.fn() }; const item = new LineClearItem(3, 7, 40, 'horizontal'); item.onCollect(game); expect(item.collected).toBe(true); expect(game.clearRow).toHaveBeenCalledWith(7); expect(game.clearColumn).not.toHaveBeenCalled(); }); it('marks collected and calls clearColumn for vertical direction', () => { const game = { clearRow: vi.fn(), clearColumn: vi.fn() }; const item = new LineClearItem(5, 2, 40, 'vertical'); item.onCollect(game); expect(item.collected).toBe(true); expect(game.clearColumn).toHaveBeenCalledWith(5); expect(game.clearRow).not.toHaveBeenCalled(); }); it('triggers clear each time onCollect is called', () => { const game = { clearRow: vi.fn(), clearColumn: vi.fn() }; const item = new LineClearItem(3, 2, 40, 'horizontal'); item.onCollect(game); item.onCollect(game); expect(game.clearRow).toHaveBeenCalledTimes(2); }); }); describe('shouldRemove()', () => { it('returns false before collection', () => { const item = new LineClearItem(0, 0, 40, 'horizontal'); expect(item.shouldRemove()).toBe(false); }); it('returns false after collection (stays on board)', () => { const game = { clearRow: vi.fn(), clearColumn: vi.fn() }; const item = new LineClearItem(0, 0, 40, 'horizontal'); item.onCollect(game); expect(item.shouldRemove()).toBe(false); }); }); });