303 lines
11 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|