boardgame-core/tests/commands/command.executor.test.ts

330 lines
10 KiB
TypeScript

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<typeof createGameState>;
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();
});
});
});