import { describe, it, expect, beforeEach } from 'vitest'; import { createGameState } from '../../src/core/GameState'; import { CommandExecutor } from '../../src/commands/CommandExecutor'; import { Command, CommandActionType } from '../../src/commands/Command'; import { RegionType } from '../../src/core/Region'; describe('CommandExecutor', () => { let gameState: ReturnType; let executor: CommandExecutor; beforeEach(() => { gameState = createGameState({ id: 'test-game', name: 'Test Game' }); executor = new CommandExecutor(gameState); }); describe('execute', () => { it('should execute a simple command successfully', () => { const command: Command = { id: 'test-command', name: 'Test Command', steps: [ { action: CommandActionType.CreateMeeple, params: { id: 'meeple-1', color: 'red' }, }, ], }; const result = executor.execute(command); expect(result.success).toBe(true); expect(result.executedSteps).toBe(1); expect(result.totalSteps).toBe(1); expect(gameState.getPart('meeple-1')).toBeDefined(); }); it('should execute multi-step command', () => { const command: Command = { id: 'setup-command', name: 'Setup Command', steps: [ { action: CommandActionType.CreateRegion, params: { id: 'board', type: RegionType.Keyed }, }, { action: CommandActionType.CreateMeeple, params: { id: 'meeple-1', color: 'blue' }, }, { action: CommandActionType.CreatePlacement, params: { id: 'placement-1', partId: 'meeple-1', regionId: 'board', }, }, ], }; const result = executor.execute(command); expect(result.success).toBe(true); expect(result.executedSteps).toBe(3); expect(result.totalSteps).toBe(3); expect(gameState.getRegion('board')).toBeDefined(); expect(gameState.getPart('meeple-1')).toBeDefined(); expect(gameState.getPlacement('placement-1')).toBeDefined(); }); it('should stop execution on error', () => { const command: Command = { id: 'failing-command', name: 'Failing Command', steps: [ { action: CommandActionType.CreateMeeple, params: { id: 'meeple-1', color: 'red' }, }, { action: CommandActionType.CreatePlacement, params: { id: 'placement-1', partId: 'non-existent', regionId: 'non-existent', }, }, { action: CommandActionType.CreateMeeple, params: { id: 'meeple-2', color: 'blue' }, }, ], }; const result = executor.execute(command); expect(result.success).toBe(false); expect(result.executedSteps).toBe(1); expect(result.totalSteps).toBe(3); expect(result.error).toBeDefined(); expect(gameState.getPart('meeple-1')).toBeDefined(); expect(gameState.getPart('meeple-2')).toBeUndefined(); }); it('should execute createCard command', () => { const command: Command = { id: 'create-card', name: 'Create Card', steps: [ { action: CommandActionType.CreateCard, params: { id: 'card-1', suit: 'hearts', value: 10 }, }, ], }; const result = executor.execute(command); expect(result.success).toBe(true); const card = gameState.getPart('card-1'); expect(card).toBeDefined(); expect(card?.type).toBe('card'); }); it('should execute createTile command', () => { const command: Command = { id: 'create-tile', name: 'Create Tile', steps: [ { action: CommandActionType.CreateTile, params: { id: 'tile-1', pattern: 'forest', rotation: 90 }, }, ], }; const result = executor.execute(command); expect(result.success).toBe(true); const tile = gameState.getPart('tile-1'); expect(tile).toBeDefined(); expect(tile?.type).toBe('tile'); }); it('should execute movePlacement command', () => { const setupCommand: Command = { id: 'setup', name: 'Setup', steps: [ { action: CommandActionType.CreateRegion, params: { id: 'board1', type: RegionType.Unkeyed } }, { action: CommandActionType.CreateRegion, params: { id: 'board2', type: RegionType.Unkeyed } }, { action: CommandActionType.CreateMeeple, params: { id: 'm1', color: 'red' } }, { action: CommandActionType.CreatePlacement, params: { id: 'p1', partId: 'm1', regionId: 'board1' } }, ], }; executor.execute(setupCommand); const moveCommand: Command = { id: 'move', name: 'Move', steps: [ { action: CommandActionType.MovePlacement, params: { placementId: 'p1', targetRegionId: 'board2' }, }, ], }; const result = executor.execute(moveCommand); expect(result.success).toBe(true); const placement = gameState.getPlacement('p1'); expect(placement?.regionId).toBe('board2'); }); it('should execute flipPlacement command', () => { const setupCommand: Command = { id: 'setup', name: 'Setup', steps: [ { action: CommandActionType.CreateRegion, params: { id: 'board', type: RegionType.Unkeyed } }, { action: CommandActionType.CreateMeeple, params: { id: 'm1', color: 'red' } }, { action: CommandActionType.CreatePlacement, params: { id: 'p1', partId: 'm1', regionId: 'board', faceUp: true } }, ], }; executor.execute(setupCommand); const flipCommand: Command = { id: 'flip', name: 'Flip', steps: [ { action: CommandActionType.FlipPlacement, params: { placementId: 'p1' } }, ], }; const result = executor.execute(flipCommand); expect(result.success).toBe(true); const placement = gameState.getPlacement('p1'); expect(placement?.faceUp).toBe(false); }); it('should execute setPhase command', () => { const command: Command = { id: 'set-phase', name: 'Set Phase', steps: [ { action: CommandActionType.SetPhase, params: { phase: 'midgame' } }, ], }; const result = executor.execute(command); expect(result.success).toBe(true); expect(gameState.data.value.phase).toBe('midgame'); }); it('should execute swapPlacements command', () => { const setupCommand: Command = { id: 'setup', name: 'Setup', steps: [ { action: CommandActionType.CreateRegion, params: { id: 'board', type: RegionType.Unkeyed } }, { action: CommandActionType.CreateMeeple, params: { id: 'm1', color: 'red' } }, { action: CommandActionType.CreateMeeple, params: { id: 'm2', color: 'blue' } }, { action: CommandActionType.CreatePlacement, params: { id: 'p1', partId: 'm1', regionId: 'board' } }, { action: CommandActionType.CreatePlacement, params: { id: 'p2', partId: 'm2', regionId: 'board' } }, ], }; executor.execute(setupCommand); const region = gameState.getRegion('board'); region!.placements.value = ['p1', 'p2']; const swapCommand: Command = { id: 'swap', name: 'Swap', steps: [ { action: CommandActionType.SwapPlacements, params: { placementId1: 'p1', placementId2: 'p2' } }, ], }; const result = executor.execute(swapCommand); expect(result.success).toBe(true); expect(region!.placements.value).toEqual(['p2', 'p1']); }); it('should execute clearRegion command', () => { const setupCommand: Command = { id: 'setup', name: 'Setup', steps: [ { action: CommandActionType.CreateRegion, params: { id: 'board', type: RegionType.Unkeyed } }, { action: CommandActionType.CreateMeeple, params: { id: 'm1', color: 'red' } }, { action: CommandActionType.CreatePlacement, params: { id: 'p1', partId: 'm1', regionId: 'board' } }, { action: CommandActionType.AddPlacementToRegion, params: { regionId: 'board', placementId: 'p1' } }, ], }; executor.execute(setupCommand); const clearCommand: Command = { id: 'clear', name: 'Clear', steps: [ { action: CommandActionType.ClearRegion, params: { regionId: 'board' } }, ], }; const result = executor.execute(clearCommand); expect(result.success).toBe(true); const region = gameState.getRegion('board'); expect(region?.placements.value.length).toBe(0); }); it('should execute updatePart command', () => { const setupCommand: Command = { id: 'setup', name: 'Setup', steps: [ { action: CommandActionType.CreateMeeple, params: { id: 'm1', color: 'red', name: 'Original' } }, ], }; executor.execute(setupCommand); const updateCommand: Command = { id: 'update', name: 'Update', steps: [ { action: CommandActionType.UpdatePart, params: { partId: 'm1', updates: { name: 'Updated', color: 'blue' } } }, ], }; const result = executor.execute(updateCommand); expect(result.success).toBe(true); const part = gameState.getPart('m1'); expect(part?.name).toBe('Updated'); expect(part?.color).toBe('blue'); }); it('should execute removePart command', () => { const setupCommand: Command = { id: 'setup', name: 'Setup', steps: [ { action: CommandActionType.CreateMeeple, params: { id: 'm1', color: 'red' } }, ], }; executor.execute(setupCommand); expect(gameState.getPart('m1')).toBeDefined(); const removeCommand: Command = { id: 'remove', name: 'Remove', steps: [ { action: CommandActionType.RemovePart, params: { partId: 'm1' } }, ], }; const result = executor.execute(removeCommand); expect(result.success).toBe(true); expect(gameState.getPart('m1')).toBeUndefined(); }); }); });