From 8c2c6dc94cda60aab27dd940dab4210f87d47be4 Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 5 Apr 2026 10:42:38 +0800 Subject: [PATCH] fix: fix tests --- tests/core/part-factory.test.ts | 194 +++++++---------------------- tests/core/part.test.ts | 158 +---------------------- tests/samples/boop.test.ts | 2 +- tests/samples/tic-tac-toe.test.ts | 2 +- tests/utils/mutable-signal.test.ts | 107 +--------------- 5 files changed, 51 insertions(+), 412 deletions(-) diff --git a/tests/core/part-factory.test.ts b/tests/core/part-factory.test.ts index be7e940..2b73f15 100644 --- a/tests/core/part-factory.test.ts +++ b/tests/core/part-factory.test.ts @@ -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( - { 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); }); }); diff --git a/tests/core/part.test.ts b/tests/core/part.test.ts index a5edaf4..1db66f8 100644 --- a/tests/core/part.test.ts +++ b/tests/core/part.test.ts @@ -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(overrides: Partial> & TMeta): Part { @@ -92,158 +91,3 @@ describe('roll', () => { expect(part1.side).toBe(part2.side); }); }); - -describe('findPartById', () => { - it('should find part by id', () => { - const parts: Record = { - '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 = { - 'part-1': createTestPart({ id: 'part-1' }), - }; - const result = findPartById(parts, 'part-99'); - expect(result).toBeUndefined(); - }); - - it('should work with empty record', () => { - const parts: Record = {}; - const result = findPartById(parts, 'any'); - expect(result).toBeUndefined(); - }); -}); - -describe('isCellOccupied', () => { - it('should return true for occupied cell', () => { - const parts: Record = { - '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 = { - '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 = { - '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 = {}; - expect(isCellOccupied(parts, 'board', [0, 0])).toBe(false); - }); - - it('should handle multi-dimensional positions', () => { - const parts: Record = { - '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 = { 'p1': part1 }; - const result = getPartAtPosition(parts, 'board', [1, 2]); - expect(result).toBe(part1); - }); - - it('should return undefined for empty cell', () => { - const parts: Record = { - '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 = { - '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 = {}; - 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 = { '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 = { '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 = {}; - const result = getPartAtPositionInRegion(region, parts, [0, 0]); - expect(result).toBeUndefined(); - }); -}); diff --git a/tests/samples/boop.test.ts b/tests/samples/boop.test.ts index 5eb94cc..56cc44c 100644 --- a/tests/samples/boop.test.ts +++ b/tests/samples/boop.test.ts @@ -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() { diff --git a/tests/samples/tic-tac-toe.test.ts b/tests/samples/tic-tac-toe.test.ts index 46892e0..53379e6 100644 --- a/tests/samples/tic-tac-toe.test.ts +++ b/tests/samples/tic-tac-toe.test.ts @@ -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() { diff --git a/tests/utils/mutable-signal.test.ts b/tests/utils/mutable-signal.test.ts index 464877b..dbfaab5 100644 --- a/tests/utils/mutable-signal.test.ts +++ b/tests/utils/mutable-signal.test.ts @@ -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(); - expect(collection.collection.value).toEqual({}); - }); - - it('should add single entity', () => { - const collection = createEntityCollection(); - 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(); - 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(); - 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(); - 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(); - 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(); - - expect(collection.get('nonexistent')).toBeUndefined(); - }); - - it('should handle removing non-existent entity', () => { - const collection = createEntityCollection(); - 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(); - 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', () => {