boardgame-core/tests/region.actions.test.ts

303 lines
11 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest';
import { createGameState } from '../src/core/GameState';
import { RegionType } from '../src/core/Region';
import {
createRegionAction,
getRegionAction,
removeRegionAction,
addPlacementToRegionAction,
removePlacementFromRegionAction,
setSlotAction,
getSlotAction,
clearRegionAction,
getRegionPlacementCountAction,
isRegionEmptyAction,
isRegionFullAction,
} from '../src/actions/region.actions';
import { createMeepleAction } from '../src/actions/part.actions';
import { createPlacementAction } from '../src/actions/placement.actions';
describe('Region Actions', () => {
let gameState: ReturnType<typeof createGameState>;
beforeEach(() => {
gameState = createGameState({ id: 'test-game', name: 'Test Game' });
});
describe('createRegionAction', () => {
it('should create an unkeyed region', () => {
const region = createRegionAction(gameState, {
id: 'deck',
type: RegionType.Unkeyed,
name: 'Draw Deck',
});
expect(region.id).toBe('deck');
expect(region.type).toBe(RegionType.Unkeyed);
expect(region.slots).toBeUndefined();
});
it('should create a keyed region', () => {
const region = createRegionAction(gameState, {
id: 'board',
type: RegionType.Keyed,
name: 'Game Board',
});
expect(region.id).toBe('board');
expect(region.type).toBe(RegionType.Keyed);
expect(region.slots).toBeDefined();
});
it('should create a region with capacity', () => {
const region = createRegionAction(gameState, {
id: 'hand',
type: RegionType.Unkeyed,
capacity: 5,
});
expect(region.capacity).toBe(5);
});
});
describe('getRegionAction', () => {
it('should return undefined for non-existent region', () => {
const region = getRegionAction(gameState, 'non-existent');
expect(region).toBeUndefined();
});
it('should return existing region', () => {
createRegionAction(gameState, { id: 'board', type: RegionType.Keyed });
const region = getRegionAction(gameState, 'board');
expect(region?.id).toBe('board');
});
});
describe('removeRegionAction', () => {
it('should remove a region', () => {
createRegionAction(gameState, { id: 'board', type: RegionType.Keyed });
expect(getRegionAction(gameState, 'board')).toBeDefined();
removeRegionAction(gameState, 'board');
expect(getRegionAction(gameState, 'board')).toBeUndefined();
});
});
describe('addPlacementToRegionAction (unkeyed)', () => {
it('should add a placement to an unkeyed region', () => {
createRegionAction(gameState, { id: 'deck', type: RegionType.Unkeyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
const placement = createPlacementAction(gameState, {
id: 'placement-1',
partId: 'meeple-1',
regionId: 'deck',
});
addPlacementToRegionAction(gameState, 'deck', 'placement-1');
const region = getRegionAction(gameState, 'deck');
expect(region?.placements.value).toContain('placement-1');
});
it('should throw when adding to a keyed region', () => {
createRegionAction(gameState, { id: 'board', type: RegionType.Keyed });
expect(() => {
addPlacementToRegionAction(gameState, 'board', 'placement-1');
}).toThrow('Cannot use addPlacementToRegionAction on a keyed region');
});
it('should respect capacity limit', () => {
createRegionAction(gameState, { id: 'hand', type: RegionType.Unkeyed, capacity: 2 });
createMeepleAction(gameState, 'meeple-1', 'red');
createMeepleAction(gameState, 'meeple-2', 'blue');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'hand' });
createPlacementAction(gameState, { id: 'p2', partId: 'meeple-2', regionId: 'hand' });
addPlacementToRegionAction(gameState, 'hand', 'p1');
addPlacementToRegionAction(gameState, 'hand', 'p2');
expect(() => {
addPlacementToRegionAction(gameState, 'hand', 'p3');
}).toThrow('has reached its capacity');
});
});
describe('removePlacementFromRegionAction', () => {
it('should remove a placement from an unkeyed region', () => {
createRegionAction(gameState, { id: 'deck', type: RegionType.Unkeyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'deck' });
addPlacementToRegionAction(gameState, 'deck', 'p1');
removePlacementFromRegionAction(gameState, 'deck', 'p1');
const region = getRegionAction(gameState, 'deck');
expect(region?.placements.value).not.toContain('p1');
});
it('should clear slot in keyed region', () => {
createRegionAction(gameState, { id: 'board', type: RegionType.Keyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'board' });
setSlotAction(gameState, 'board', 'A1', 'p1');
removePlacementFromRegionAction(gameState, 'board', 'p1');
const slotValue = getSlotAction(gameState, 'board', 'A1');
expect(slotValue).toBeNull();
});
});
describe('setSlotAction (keyed)', () => {
it('should set a slot in a keyed region', () => {
createRegionAction(gameState, { id: 'board', type: RegionType.Keyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'board' });
setSlotAction(gameState, 'board', 'A1', 'p1');
const slotValue = getSlotAction(gameState, 'board', 'A1');
expect(slotValue).toBe('p1');
});
it('should throw when used on unkeyed region', () => {
createRegionAction(gameState, { id: 'deck', type: RegionType.Unkeyed });
expect(() => {
setSlotAction(gameState, 'deck', 'slot1', 'p1');
}).toThrow('Cannot use setSlotAction on an unkeyed region');
});
it('should add placement to region list when setting slot', () => {
createRegionAction(gameState, { id: 'board', type: RegionType.Keyed });
createRegionAction(gameState, { id: 'other', type: RegionType.Unkeyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'other' });
setSlotAction(gameState, 'board', 'A1', 'p1');
const region = getRegionAction(gameState, 'board');
expect(region?.placements.value).toContain('p1');
});
it('should clear a slot with null', () => {
createRegionAction(gameState, { id: 'board', type: RegionType.Keyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'board' });
setSlotAction(gameState, 'board', 'A1', 'p1');
setSlotAction(gameState, 'board', 'A1', null);
const slotValue = getSlotAction(gameState, 'board', 'A1');
expect(slotValue).toBeNull();
});
});
describe('getSlotAction', () => {
it('should return null for empty slot', () => {
createRegionAction(gameState, { id: 'board', type: RegionType.Keyed });
const slotValue = getSlotAction(gameState, 'board', 'A1');
expect(slotValue).toBeNull();
});
it('should throw when used on unkeyed region', () => {
createRegionAction(gameState, { id: 'deck', type: RegionType.Unkeyed });
expect(() => {
getSlotAction(gameState, 'deck', 'slot1');
}).toThrow('Cannot use getSlotAction on an unkeyed region');
});
});
describe('clearRegionAction', () => {
it('should clear all placements from unkeyed region', () => {
createRegionAction(gameState, { id: 'deck', type: RegionType.Unkeyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'deck' });
createPlacementAction(gameState, { id: 'p2', partId: 'meeple-1', regionId: 'deck' });
addPlacementToRegionAction(gameState, 'deck', 'p1');
addPlacementToRegionAction(gameState, 'deck', 'p2');
clearRegionAction(gameState, 'deck');
const region = getRegionAction(gameState, 'deck');
expect(region?.placements.value.length).toBe(0);
});
it('should clear all slots in keyed region', () => {
createRegionAction(gameState, { id: 'board', type: RegionType.Keyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'board' });
setSlotAction(gameState, 'board', 'A1', 'p1');
setSlotAction(gameState, 'board', 'A2', 'p1');
clearRegionAction(gameState, 'board');
const region = getRegionAction(gameState, 'board');
expect(region?.placements.value.length).toBe(0);
expect(region?.slots?.value.size).toBe(0);
});
});
describe('getRegionPlacementCountAction', () => {
it('should return the count of placements', () => {
createRegionAction(gameState, { id: 'deck', type: RegionType.Unkeyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'deck' });
createPlacementAction(gameState, { id: 'p2', partId: 'meeple-1', regionId: 'deck' });
addPlacementToRegionAction(gameState, 'deck', 'p1');
addPlacementToRegionAction(gameState, 'deck', 'p2');
const count = getRegionPlacementCountAction(gameState, 'deck');
expect(count).toBe(2);
});
it('should return 0 for non-existent region', () => {
const count = getRegionPlacementCountAction(gameState, 'non-existent');
expect(count).toBe(0);
});
});
describe('isRegionEmptyAction', () => {
it('should return true for empty region', () => {
createRegionAction(gameState, { id: 'deck', type: RegionType.Unkeyed });
expect(isRegionEmptyAction(gameState, 'deck')).toBe(true);
});
it('should return false for non-empty region', () => {
createRegionAction(gameState, { id: 'deck', type: RegionType.Unkeyed });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'deck' });
addPlacementToRegionAction(gameState, 'deck', 'p1');
expect(isRegionEmptyAction(gameState, 'deck')).toBe(false);
});
});
describe('isRegionFullAction', () => {
it('should return false for region without capacity', () => {
createRegionAction(gameState, { id: 'deck', type: RegionType.Unkeyed });
expect(isRegionFullAction(gameState, 'deck')).toBe(false);
});
it('should return true when at capacity', () => {
createRegionAction(gameState, { id: 'hand', type: RegionType.Unkeyed, capacity: 2 });
const meeple = createMeepleAction(gameState, 'meeple-1', 'red');
createPlacementAction(gameState, { id: 'p1', partId: 'meeple-1', regionId: 'hand' });
createPlacementAction(gameState, { id: 'p2', partId: 'meeple-1', regionId: 'hand' });
addPlacementToRegionAction(gameState, 'hand', 'p1');
addPlacementToRegionAction(gameState, 'hand', 'p2');
expect(isRegionFullAction(gameState, 'hand')).toBe(true);
});
});
});