fix: fix tests

This commit is contained in:
hypercross 2026-04-05 10:42:38 +08:00
parent c4290841e5
commit 8c2c6dc94c
5 changed files with 51 additions and 412 deletions

View File

@ -1,171 +1,71 @@
import { describe, it, expect } from 'vitest';
import { createPart, createParts, createPartPool, mergePartPools, PartPool } from '@/core/part-factory';
describe('createPart', () => {
it('should create a part with given template and id', () => {
const part = createPart<{ player: string }>(
{ regionId: 'board', position: [1, 2], player: 'X' },
'piece-1'
);
expect(part.id).toBe('piece-1');
expect(part.regionId).toBe('board');
expect(part.position).toEqual([1, 2]);
expect(part.player).toBe('X');
});
it('should apply default values for regionId and position when not provided', () => {
const part = createPart({}, 'piece-1');
expect(part.regionId).toBe('');
expect(part.position).toEqual([]);
});
it('should allow overriding default values', () => {
const part = createPart({ regionId: 'custom', position: [0] }, 'piece-1');
expect(part.regionId).toBe('custom');
expect(part.position).toEqual([0]);
});
it('should preserve metadata fields', () => {
type TestMeta = { type: string; count: number };
const part = createPart<TestMeta>(
{ regionId: 'board', position: [0], type: 'kitten', count: 5 },
'piece-1'
);
expect(part.type).toBe('kitten');
expect(part.count).toBe(5);
});
});
import { createParts, createPartsFromTable } from '@/core/part-factory';
describe('createParts', () => {
it('should create multiple parts with auto-generated IDs', () => {
const parts = createParts(
{ regionId: 'deck', position: [] },
3,
'card'
(i) => `card-${i + 1}`,
3
);
expect(parts.length).toBe(3);
expect(parts[0].id).toBe('card-1');
expect(parts[1].id).toBe('card-2');
expect(parts[2].id).toBe('card-3');
expect(Object.keys(parts).length).toBe(3);
expect(parts['card-1']).toBeDefined();
expect(parts['card-2']).toBeDefined();
expect(parts['card-3']).toBeDefined();
});
it('should create parts with identical properties', () => {
const parts = createParts(
{ regionId: 'deck', position: [], type: 'token' },
2,
'token'
{ regionId: 'deck', position: [], type: 'token' as string },
(i) => `token-${i + 1}`,
2
);
expect(parts[0].regionId).toBe('deck');
expect(parts[1].regionId).toBe('deck');
expect(parts[0].type).toBe('token');
expect(parts[1].type).toBe('token');
expect(parts['token-1'].regionId).toBe('deck');
expect(parts['token-2'].regionId).toBe('deck');
expect(parts['token-1'].type).toBe('token');
expect(parts['token-2'].type).toBe('token');
});
it('should create zero parts when count is 0', () => {
const parts = createParts({}, 0, 'empty');
expect(parts.length).toBe(0);
it('should create one part when count is not provided', () => {
const parts = createParts({}, (i) => `item-${i}`);
expect(Object.keys(parts).length).toBe(1);
expect(parts['item-0']).toBeDefined();
});
});
describe('createPartPool', () => {
it('should create a pool with specified count', () => {
const pool = createPartPool(
{ regionId: 'supply', position: [] },
5,
'token'
describe('createPartsFromTable', () => {
it('should create parts from table entries', () => {
const items = [
{ type: 'kitten', count: 13 },
{ type: 'cat', count: 6 },
];
const parts = createPartsFromTable(
items,
(item, index) => `${item.type}-${index + 1}`,
(item) => item.count
);
expect(pool.remaining()).toBe(5);
expect(Object.keys(pool.parts).length).toBe(5);
expect(Object.keys(parts).length).toBe(19);
});
it('should generate parts with correct IDs', () => {
const pool = createPartPool({}, 3, 'piece');
expect(pool.parts['piece-1']).toBeDefined();
expect(pool.parts['piece-2']).toBeDefined();
expect(pool.parts['piece-3']).toBeDefined();
it('should create one part per item when count is not specified', () => {
const items = [{ name: 'A' }, { name: 'B' }];
const parts = createPartsFromTable(
items,
(item, index) => `${item.name}-${index}`
);
expect(Object.keys(parts).length).toBe(2);
// index is 0 for each item since count defaults to 1
expect(parts['A-0']).toBeDefined();
expect(parts['B-0']).toBeDefined();
});
it('should draw parts from the pool', () => {
const pool = createPartPool({}, 2, 'card');
const drawn = pool.draw();
expect(drawn).toBeDefined();
expect(drawn!.id).toBe('card-2');
expect(pool.remaining()).toBe(1);
});
it('should return undefined when pool is empty', () => {
const pool = createPartPool({}, 1, 'card');
pool.draw();
const drawn = pool.draw();
expect(drawn).toBeUndefined();
});
it('should return part to pool', () => {
const pool = createPartPool({ regionId: 'board', position: [0, 0] }, 1, 'card');
const drawn = pool.draw();
expect(pool.remaining()).toBe(0);
pool.return(drawn!);
expect(pool.remaining()).toBe(1);
expect(drawn!.regionId).toBe('');
expect(drawn!.position).toEqual([]);
});
it('should store parts as Record keyed by ID', () => {
const pool = createPartPool({}, 2, 'piece');
expect(typeof pool.parts).toBe('object');
expect(pool.parts['piece-1']).toBeDefined();
expect(pool.parts['piece-2']).toBeDefined();
});
});
describe('mergePartPools', () => {
it('should merge multiple pools', () => {
const pool1 = createPartPool({ regionId: 'deck1', position: [] }, 2, 'card1');
const pool2 = createPartPool({ regionId: 'deck2', position: [] }, 3, 'card2');
const merged = mergePartPools(pool1, pool2);
expect(Object.keys(merged.parts).length).toBe(5);
// Parts with regionId: '' are available; pool parts have regionId from template
// After merge, available = parts with regionId === ''
expect(merged.remaining()).toBe(0); // parts have regionId: 'deck1' or 'deck2'
expect(merged.parts['card1-1']).toBeDefined();
expect(merged.parts['card2-3']).toBeDefined();
});
it('should use first pool template', () => {
const pool1 = createPartPool({ regionId: 'deck1', position: [] }, 1, 'card1');
const pool2 = createPartPool({ regionId: 'deck2', position: [] }, 1, 'card2');
const merged = mergePartPools(pool1, pool2);
expect(merged.template.regionId).toBe('deck1');
});
it('should return empty pool when no pools provided', () => {
const merged = mergePartPools();
expect(Object.keys(merged.parts).length).toBe(0);
expect(merged.remaining()).toBe(0);
});
it('should only count available parts for remaining()', () => {
const pool1 = createPartPool({}, 2, 'card');
const pool2 = createPartPool({}, 2, 'token');
const drawn = pool1.draw();
drawn!.regionId = 'board';
drawn!.position = [0, 0];
const merged = mergePartPools(pool1, pool2);
expect(merged.remaining()).toBe(3);
});
it('should handle drawing from merged pool', () => {
const pool1 = createPartPool({}, 2, 'card');
const pool2 = createPartPool({}, 2, 'token');
const merged = mergePartPools(pool1, pool2);
const drawn = merged.draw();
expect(drawn).toBeDefined();
expect(merged.remaining()).toBe(3);
it('should use fixed count when provided as number', () => {
const items = [{ name: 'A' }, { name: 'B' }];
const parts = createPartsFromTable(
items,
(item, index) => `${item.name}-${index}`,
2
);
expect(Object.keys(parts).length).toBe(4);
});
});

View File

@ -1,6 +1,5 @@
import { describe, it, expect } from 'vitest';
import { flip, flipTo, roll, findPartById, isCellOccupied, getPartAtPosition, isCellOccupiedByRegion, getPartAtPositionInRegion, Part } from '@/core/part';
import { createRegion } from '@/core/region';
import { flip, flipTo, roll, Part } from '@/core/part';
import { createRNG } from '@/utils/rng';
function createTestPart<TMeta = {}>(overrides: Partial<Part<TMeta>> & TMeta): Part<TMeta> {
@ -92,158 +91,3 @@ describe('roll', () => {
expect(part1.side).toBe(part2.side);
});
});
describe('findPartById', () => {
it('should find part by id', () => {
const parts: Record<string, Part> = {
'part-1': createTestPart({ id: 'part-1' }),
'part-2': createTestPart({ id: 'part-2' }),
};
const result = findPartById(parts, 'part-1');
expect(result).toBeDefined();
expect(result!.id).toBe('part-1');
});
it('should return undefined for non-existent id', () => {
const parts: Record<string, Part> = {
'part-1': createTestPart({ id: 'part-1' }),
};
const result = findPartById(parts, 'part-99');
expect(result).toBeUndefined();
});
it('should work with empty record', () => {
const parts: Record<string, Part> = {};
const result = findPartById(parts, 'any');
expect(result).toBeUndefined();
});
});
describe('isCellOccupied', () => {
it('should return true for occupied cell', () => {
const parts: Record<string, Part> = {
'p1': createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] }),
};
expect(isCellOccupied(parts, 'board', [1, 2])).toBe(true);
});
it('should return false for empty cell', () => {
const parts: Record<string, Part> = {
'p1': createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] }),
};
expect(isCellOccupied(parts, 'board', [3, 4])).toBe(false);
});
it('should return false for different region', () => {
const parts: Record<string, Part> = {
'p1': createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] }),
};
expect(isCellOccupied(parts, 'hand', [1, 2])).toBe(false);
});
it('should work with empty record', () => {
const parts: Record<string, Part> = {};
expect(isCellOccupied(parts, 'board', [0, 0])).toBe(false);
});
it('should handle multi-dimensional positions', () => {
const parts: Record<string, Part> = {
'p1': createTestPart({ id: 'p1', regionId: 'board', position: [1, 2, 3] }),
};
expect(isCellOccupied(parts, 'board', [1, 2, 3])).toBe(true);
expect(isCellOccupied(parts, 'board', [1, 2])).toBe(false);
});
});
describe('getPartAtPosition', () => {
it('should return part at specified position', () => {
const part1 = createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] });
const parts: Record<string, Part> = { 'p1': part1 };
const result = getPartAtPosition(parts, 'board', [1, 2]);
expect(result).toBe(part1);
});
it('should return undefined for empty cell', () => {
const parts: Record<string, Part> = {
'p1': createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] }),
};
const result = getPartAtPosition(parts, 'board', [3, 4]);
expect(result).toBeUndefined();
});
it('should return undefined for different region', () => {
const parts: Record<string, Part> = {
'p1': createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] }),
};
const result = getPartAtPosition(parts, 'hand', [1, 2]);
expect(result).toBeUndefined();
});
it('should work with empty record', () => {
const parts: Record<string, Part> = {};
const result = getPartAtPosition(parts, 'board', [0, 0]);
expect(result).toBeUndefined();
});
});
describe('isCellOccupiedByRegion', () => {
it('should return true for occupied cell', () => {
const region = createRegion('board', [
{ name: 'x', min: 0, max: 2 },
{ name: 'y', min: 0, max: 2 },
]);
const part = createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] });
region.childIds.push(part.id);
region.partMap['1,2'] = part.id;
expect(isCellOccupiedByRegion(region, [1, 2])).toBe(true);
});
it('should return false for empty cell', () => {
const region = createRegion('board', [
{ name: 'x', min: 0, max: 2 },
{ name: 'y', min: 0, max: 2 },
]);
const part = createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] });
region.childIds.push(part.id);
region.partMap['1,2'] = part.id;
expect(isCellOccupiedByRegion(region, [0, 0])).toBe(false);
});
it('should work with empty region', () => {
const region = createRegion('board', []);
expect(isCellOccupiedByRegion(region, [0, 0])).toBe(false);
});
});
describe('getPartAtPositionInRegion', () => {
it('should return part at specified position', () => {
const region = createRegion('board', []);
const part1 = createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] });
const parts: Record<string, Part> = { 'p1': part1 };
region.childIds.push(part1.id);
region.partMap['1,2'] = part1.id;
const result = getPartAtPositionInRegion(region, parts, [1, 2]);
expect(result).toBe(part1);
});
it('should return undefined for empty cell', () => {
const region = createRegion('board', []);
const part1 = createTestPart({ id: 'p1', regionId: 'board', position: [1, 2] });
const parts: Record<string, Part> = { 'p1': part1 };
region.childIds.push(part1.id);
region.partMap['1,2'] = part1.id;
const result = getPartAtPositionInRegion(region, parts, [0, 0]);
expect(result).toBeUndefined();
});
it('should work with empty region', () => {
const region = createRegion('board', []);
const parts: Record<string, Part> = {};
const result = getPartAtPositionInRegion(region, parts, [0, 0]);
expect(result).toBeUndefined();
});
});

View File

@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import { registry, createInitialState, BoopState } from '@/samples/boop';
import { createGameContext } from '@/index';
import { createGameContext } from '@/core/game';
import type { PromptEvent } from '@/utils/command';
function createTestContext() {

View File

@ -10,7 +10,7 @@ import {
WinnerType,
PlayerType
} from '@/samples/tic-tac-toe';
import { createGameContext } from '@/index';
import { createGameContext } from '@/core/game';
import type { PromptEvent } from '@/utils/command';
function createTestContext() {

View File

@ -1,110 +1,5 @@
import { describe, it, expect } from 'vitest';
import { createEntityCollection, MutableSignal, mutableSignal } from '@/utils/mutable-signal';
type TestEntity = {
id: string;
name: string;
value: number;
};
describe('createEntityCollection', () => {
it('should create empty collection', () => {
const collection = createEntityCollection<TestEntity>();
expect(collection.collection.value).toEqual({});
});
it('should add single entity', () => {
const collection = createEntityCollection<TestEntity>();
const testEntity: TestEntity = { id: 'e1', name: 'Entity 1', value: 10 };
collection.add(testEntity);
expect(collection.collection.value).toHaveProperty('e1');
expect(collection.get('e1').value).toEqual(testEntity);
});
it('should add multiple entities', () => {
const collection = createEntityCollection<TestEntity>();
const entity1: TestEntity = { id: 'e1', name: 'Entity 1', value: 10 };
const entity2: TestEntity = { id: 'e2', name: 'Entity 2', value: 20 };
const entity3: TestEntity = { id: 'e3', name: 'Entity 3', value: 30 };
collection.add(entity1, entity2, entity3);
expect(Object.keys(collection.collection.value)).toHaveLength(3);
expect(collection.get('e1').value.name).toBe('Entity 1');
expect(collection.get('e2').value.name).toBe('Entity 2');
expect(collection.get('e3').value.name).toBe('Entity 3');
});
it('should remove single entity', () => {
const collection = createEntityCollection<TestEntity>();
const entity1: TestEntity = { id: 'e1', name: 'Entity 1', value: 10 };
const entity2: TestEntity = { id: 'e2', name: 'Entity 2', value: 20 };
collection.add(entity1, entity2);
collection.remove('e1');
expect(Object.keys(collection.collection.value)).toHaveLength(1);
expect(collection.collection.value).not.toHaveProperty('e1');
expect(collection.collection.value).toHaveProperty('e2');
});
it('should remove multiple entities', () => {
const collection = createEntityCollection<TestEntity>();
const entity1: TestEntity = { id: 'e1', name: 'Entity 1', value: 10 };
const entity2: TestEntity = { id: 'e2', name: 'Entity 2', value: 20 };
const entity3: TestEntity = { id: 'e3', name: 'Entity 3', value: 30 };
collection.add(entity1, entity2, entity3);
collection.remove('e1', 'e3');
expect(Object.keys(collection.collection.value)).toHaveLength(1);
expect(collection.collection.value).toHaveProperty('e2');
});
it('should update entity via accessor', () => {
const collection = createEntityCollection<TestEntity>();
const testEntity: TestEntity = { id: 'e1', name: 'Entity 1', value: 10 };
collection.add(testEntity);
const accessor = collection.get('e1');
accessor.value = { ...testEntity, value: 100, name: 'Updated' };
expect(collection.get('e1').value.value).toBe(100);
expect(collection.get('e1').value.name).toBe('Updated');
});
it('should return undefined for non-existent entity', () => {
const collection = createEntityCollection<TestEntity>();
expect(collection.get('nonexistent')).toBeUndefined();
});
it('should handle removing non-existent entity', () => {
const collection = createEntityCollection<TestEntity>();
const testEntity: TestEntity = { id: 'e1', name: 'Entity 1', value: 10 };
collection.add(testEntity);
collection.remove('nonexistent');
expect(Object.keys(collection.collection.value)).toHaveLength(1);
});
it('should work with reactive updates', () => {
const collection = createEntityCollection<TestEntity>();
const testEntity: TestEntity = { id: 'e1', name: 'Entity 1', value: 10 };
collection.add(testEntity);
const accessor = collection.get('e1');
expect(accessor.value.value).toBe(10);
accessor.value = { ...testEntity, value: 50 };
expect(accessor.value.value).toBe(50);
});
});
import { MutableSignal, mutableSignal } from '@/utils/mutable-signal';
describe('MutableSignal', () => {
it('should create signal with initial value', () => {