fix: fix tests
This commit is contained in:
parent
a02403d2c7
commit
b9105efd03
|
|
@ -19,7 +19,7 @@ function createTestHost() {
|
||||||
|
|
||||||
function waitForPromptEvent(host: GameHost<any>): Promise<PromptEvent> {
|
function waitForPromptEvent(host: GameHost<any>): Promise<PromptEvent> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
host.commands.on('prompt', resolve);
|
host.context._commands.on('prompt', resolve);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,10 +28,10 @@ describe('GameHost', () => {
|
||||||
it('should create host with initial state', () => {
|
it('should create host with initial state', () => {
|
||||||
const { host } = createTestHost();
|
const { host } = createTestHost();
|
||||||
|
|
||||||
expect(host.state.value.currentPlayer).toBe('X');
|
expect(host.context._state.value.currentPlayer).toBe('X');
|
||||||
expect(host.state.value.winner).toBeNull();
|
expect(host.context._state.value.winner).toBeNull();
|
||||||
expect(host.state.value.turn).toBe(0);
|
expect(host.context._state.value.turn).toBe(0);
|
||||||
expect(Object.keys(host.state.value.parts).length).toBe(0);
|
expect(Object.keys(host.context._state.value.parts).length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have status "created" by default', () => {
|
it('should have status "created" by default', () => {
|
||||||
|
|
@ -59,7 +59,7 @@ describe('GameHost', () => {
|
||||||
const { host } = createTestHost();
|
const { host } = createTestHost();
|
||||||
|
|
||||||
const promptPromise = waitForPromptEvent(host);
|
const promptPromise = waitForPromptEvent(host);
|
||||||
const runPromise = host.commands.run('setup');
|
const runPromise = host.context._commands.run('setup');
|
||||||
|
|
||||||
const promptEvent = await promptPromise;
|
const promptEvent = await promptPromise;
|
||||||
expect(promptEvent.schema.name).toBe('play');
|
expect(promptEvent.schema.name).toBe('play');
|
||||||
|
|
@ -81,7 +81,7 @@ describe('GameHost', () => {
|
||||||
const { host } = createTestHost();
|
const { host } = createTestHost();
|
||||||
|
|
||||||
const promptPromise = waitForPromptEvent(host);
|
const promptPromise = waitForPromptEvent(host);
|
||||||
const runPromise = host.commands.run('setup');
|
const runPromise = host.context._commands.run('setup');
|
||||||
|
|
||||||
const promptEvent = await promptPromise;
|
const promptEvent = await promptPromise;
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ describe('GameHost', () => {
|
||||||
const { host } = createTestHost();
|
const { host } = createTestHost();
|
||||||
|
|
||||||
const promptPromise = waitForPromptEvent(host);
|
const promptPromise = waitForPromptEvent(host);
|
||||||
const runPromise = host.commands.run('setup');
|
const runPromise = host.context._commands.run('setup');
|
||||||
|
|
||||||
const promptEvent = await promptPromise;
|
const promptEvent = await promptPromise;
|
||||||
const schema = host.activePromptSchema.value;
|
const schema = host.activePromptSchema.value;
|
||||||
|
|
@ -131,7 +131,7 @@ describe('GameHost', () => {
|
||||||
|
|
||||||
// First setup - make one move
|
// First setup - make one move
|
||||||
let promptPromise = waitForPromptEvent(host);
|
let promptPromise = waitForPromptEvent(host);
|
||||||
let runPromise = host.commands.run('setup');
|
let runPromise = host.context._commands.run('setup');
|
||||||
let promptEvent = await promptPromise;
|
let promptEvent = await promptPromise;
|
||||||
|
|
||||||
// Make a move
|
// Make a move
|
||||||
|
|
@ -144,7 +144,7 @@ describe('GameHost', () => {
|
||||||
|
|
||||||
let result = await runPromise;
|
let result = await runPromise;
|
||||||
expect(result.success).toBe(false); // Cancelled
|
expect(result.success).toBe(false); // Cancelled
|
||||||
expect(Object.keys(host.state.value.parts).length).toBe(1);
|
expect(Object.keys(host.context._state.value.parts).length).toBe(1);
|
||||||
|
|
||||||
// Setup listener before calling setup
|
// Setup listener before calling setup
|
||||||
const newPromptPromise = waitForPromptEvent(host);
|
const newPromptPromise = waitForPromptEvent(host);
|
||||||
|
|
@ -153,10 +153,10 @@ describe('GameHost', () => {
|
||||||
await host.setup('setup');
|
await host.setup('setup');
|
||||||
|
|
||||||
// State should be back to initial
|
// State should be back to initial
|
||||||
expect(host.state.value.currentPlayer).toBe('X');
|
expect(host.context._state.value.currentPlayer).toBe('X');
|
||||||
expect(host.state.value.winner).toBeNull();
|
expect(host.context._state.value.winner).toBeNull();
|
||||||
expect(host.state.value.turn).toBe(0);
|
expect(host.context._state.value.turn).toBe(0);
|
||||||
expect(Object.keys(host.state.value.parts).length).toBe(0);
|
expect(Object.keys(host.context._state.value.parts).length).toBe(0);
|
||||||
|
|
||||||
// New game should be running and prompting
|
// New game should be running and prompting
|
||||||
const newPrompt = await newPromptPromise;
|
const newPrompt = await newPromptPromise;
|
||||||
|
|
@ -168,7 +168,7 @@ describe('GameHost', () => {
|
||||||
const { host } = createTestHost();
|
const { host } = createTestHost();
|
||||||
|
|
||||||
const promptPromise = waitForPromptEvent(host);
|
const promptPromise = waitForPromptEvent(host);
|
||||||
const runPromise = host.commands.run('setup');
|
const runPromise = host.context._commands.run('setup');
|
||||||
|
|
||||||
await promptPromise;
|
await promptPromise;
|
||||||
|
|
||||||
|
|
@ -184,8 +184,8 @@ describe('GameHost', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// State should be reset
|
// State should be reset
|
||||||
expect(host.state.value.currentPlayer).toBe('X');
|
expect(host.context._state.value.currentPlayer).toBe('X');
|
||||||
expect(host.state.value.turn).toBe(0);
|
expect(host.context._state.value.turn).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw error when disposed', async () => {
|
it('should throw error when disposed', async () => {
|
||||||
|
|
@ -208,7 +208,7 @@ describe('GameHost', () => {
|
||||||
const { host } = createTestHost();
|
const { host } = createTestHost();
|
||||||
|
|
||||||
const promptPromise = waitForPromptEvent(host);
|
const promptPromise = waitForPromptEvent(host);
|
||||||
const runPromise = host.commands.run('setup');
|
const runPromise = host.context._commands.run('setup');
|
||||||
|
|
||||||
await promptPromise;
|
await promptPromise;
|
||||||
|
|
||||||
|
|
@ -289,12 +289,12 @@ describe('GameHost', () => {
|
||||||
const { host } = createTestHost();
|
const { host } = createTestHost();
|
||||||
|
|
||||||
// Initial state
|
// Initial state
|
||||||
expect(host.state.value.currentPlayer).toBe('X');
|
expect(host.context._state.value.currentPlayer).toBe('X');
|
||||||
expect(host.state.value.turn).toBe(0);
|
expect(host.context._state.value.turn).toBe(0);
|
||||||
|
|
||||||
// Make a move
|
// Make a move
|
||||||
const promptPromise = waitForPromptEvent(host);
|
const promptPromise = waitForPromptEvent(host);
|
||||||
const runPromise = host.commands.run('setup');
|
const runPromise = host.context._commands.run('setup');
|
||||||
|
|
||||||
const promptEvent = await promptPromise;
|
const promptEvent = await promptPromise;
|
||||||
promptEvent.tryCommit({ name: 'play', params: ['X', 1, 1], options: {}, flags: {} });
|
promptEvent.tryCommit({ name: 'play', params: ['X', 1, 1], options: {}, flags: {} });
|
||||||
|
|
@ -307,9 +307,9 @@ describe('GameHost', () => {
|
||||||
const result = await runPromise;
|
const result = await runPromise;
|
||||||
expect(result.success).toBe(false); // Cancelled
|
expect(result.success).toBe(false); // Cancelled
|
||||||
|
|
||||||
expect(host.state.value.currentPlayer).toBe('O');
|
expect(host.context._state.value.currentPlayer).toBe('O');
|
||||||
expect(host.state.value.turn).toBe(1);
|
expect(host.context._state.value.turn).toBe(1);
|
||||||
expect(Object.keys(host.state.value.parts).length).toBe(1);
|
expect(Object.keys(host.context._state.value.parts).length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update activePromptSchema reactively', async () => {
|
it('should update activePromptSchema reactively', async () => {
|
||||||
|
|
@ -320,7 +320,7 @@ describe('GameHost', () => {
|
||||||
|
|
||||||
// Start a command that triggers prompt
|
// Start a command that triggers prompt
|
||||||
const promptPromise = waitForPromptEvent(host);
|
const promptPromise = waitForPromptEvent(host);
|
||||||
const runPromise = host.commands.run('setup');
|
const runPromise = host.context._commands.run('setup');
|
||||||
|
|
||||||
await promptPromise;
|
await promptPromise;
|
||||||
|
|
||||||
|
|
@ -330,7 +330,7 @@ describe('GameHost', () => {
|
||||||
|
|
||||||
// Cancel and wait
|
// Cancel and wait
|
||||||
const cancelEvent = host.activePromptSchema.value;
|
const cancelEvent = host.activePromptSchema.value;
|
||||||
host.commands._cancel();
|
host.context._commands._cancel();
|
||||||
try {
|
try {
|
||||||
await runPromise;
|
await runPromise;
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -347,10 +347,10 @@ describe('GameHost', () => {
|
||||||
const { host } = createTestHost();
|
const { host } = createTestHost();
|
||||||
|
|
||||||
// Initial state
|
// Initial state
|
||||||
expect(host.state.value.currentPlayer).toBe('X');
|
expect(host.context._state.value.currentPlayer).toBe('X');
|
||||||
expect(host.state.value.winner).toBeNull();
|
expect(host.context._state.value.winner).toBeNull();
|
||||||
expect(host.state.value.turn).toBe(0);
|
expect(host.context._state.value.turn).toBe(0);
|
||||||
expect(Object.keys(host.state.value.parts).length).toBe(0);
|
expect(Object.keys(host.context._state.value.parts).length).toBe(0);
|
||||||
|
|
||||||
// X wins diagonally: (0,0), (1,1), (2,2)
|
// X wins diagonally: (0,0), (1,1), (2,2)
|
||||||
// O plays: (0,1), (2,1)
|
// O plays: (0,1), (2,1)
|
||||||
|
|
@ -364,12 +364,12 @@ describe('GameHost', () => {
|
||||||
|
|
||||||
// Track prompt events in a queue
|
// Track prompt events in a queue
|
||||||
const promptEvents: PromptEvent[] = [];
|
const promptEvents: PromptEvent[] = [];
|
||||||
host.commands.on('prompt', (e) => {
|
host.context._commands.on('prompt', (e) => {
|
||||||
promptEvents.push(e);
|
promptEvents.push(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start setup command (runs game loop until completion)
|
// Start setup command (runs game loop until completion)
|
||||||
const setupPromise = host.commands.run('setup');
|
const setupPromise = host.context._commands.run('setup');
|
||||||
|
|
||||||
for (let i = 0; i < moves.length; i++) {
|
for (let i = 0; i < moves.length; i++) {
|
||||||
// Wait until the next prompt event arrives
|
// Wait until the next prompt event arrives
|
||||||
|
|
@ -393,12 +393,12 @@ describe('GameHost', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final state checks
|
// Final state checks
|
||||||
expect(host.state.value.winner).toBe('X');
|
expect(host.context._state.value.winner).toBe('X');
|
||||||
expect(host.state.value.currentPlayer).toBe('X');
|
expect(host.context._state.value.currentPlayer).toBe('X');
|
||||||
expect(Object.keys(host.state.value.parts).length).toBe(5);
|
expect(Object.keys(host.context._state.value.parts).length).toBe(5);
|
||||||
|
|
||||||
// Verify winning diagonal
|
// Verify winning diagonal
|
||||||
const parts = Object.values(host.state.value.parts);
|
const parts = Object.values(host.context._state.value.parts);
|
||||||
const xPieces = parts.filter(p => p.player === 'X');
|
const xPieces = parts.filter(p => p.player === 'X');
|
||||||
expect(xPieces).toHaveLength(3);
|
expect(xPieces).toHaveLength(3);
|
||||||
expect(xPieces.some(p => JSON.stringify(p.position) === JSON.stringify([0, 0]))).toBe(true);
|
expect(xPieces.some(p => JSON.stringify(p.position) === JSON.stringify([0, 0]))).toBe(true);
|
||||||
|
|
@ -415,7 +415,7 @@ describe('GameHost', () => {
|
||||||
const { host } = createTestHost();
|
const { host } = createTestHost();
|
||||||
|
|
||||||
const promptPromise = waitForPromptEvent(host);
|
const promptPromise = waitForPromptEvent(host);
|
||||||
const runPromise = host.commands.run('setup');
|
const runPromise = host.context._commands.run('setup');
|
||||||
|
|
||||||
const promptEvent = await promptPromise;
|
const promptEvent = await promptPromise;
|
||||||
expect(promptEvent.currentPlayer).toBe('X');
|
expect(promptEvent.currentPlayer).toBe('X');
|
||||||
|
|
@ -433,7 +433,7 @@ describe('GameHost', () => {
|
||||||
|
|
||||||
// First prompt - X's turn
|
// First prompt - X's turn
|
||||||
let promptPromise = waitForPromptEvent(host);
|
let promptPromise = waitForPromptEvent(host);
|
||||||
let runPromise = host.commands.run('setup');
|
let runPromise = host.context._commands.run('setup');
|
||||||
let promptEvent = await promptPromise;
|
let promptEvent = await promptPromise;
|
||||||
expect(promptEvent.currentPlayer).toBe('X');
|
expect(promptEvent.currentPlayer).toBe('X');
|
||||||
expect(host.activePromptPlayer.value).toBe('X');
|
expect(host.activePromptPlayer.value).toBe('X');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { createGameContext, createGameCommand, createGameCommandRegistry } from '@/core/game';
|
import { createGameContext, createGameCommandRegistry, IGameContext } from '@/core/game';
|
||||||
import type { PromptEvent } from '@/utils/command';
|
import type { PromptEvent, Command } from '@/utils/command';
|
||||||
|
|
||||||
type MyState = {
|
type MyState = {
|
||||||
score: number;
|
score: number;
|
||||||
|
|
@ -9,56 +9,55 @@ type MyState = {
|
||||||
|
|
||||||
describe('createGameContext', () => {
|
describe('createGameContext', () => {
|
||||||
it('should create a game context with state', () => {
|
it('should create a game context with state', () => {
|
||||||
const { registry } = createGameCommandRegistry();
|
const registry = createGameCommandRegistry();
|
||||||
const ctx = createGameContext(registry);
|
const ctx = createGameContext(registry);
|
||||||
|
|
||||||
expect(ctx.state).not.toBeNull();
|
expect(ctx._state).not.toBeNull();
|
||||||
expect(ctx.state.value).toBeDefined();
|
expect(ctx._state.value).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should wire commands to the context', () => {
|
it('should wire commands to the context', () => {
|
||||||
const { registry } = createGameCommandRegistry();
|
const registry = createGameCommandRegistry();
|
||||||
const ctx = createGameContext(registry);
|
const ctx = createGameContext(registry);
|
||||||
|
|
||||||
expect(ctx.commands).not.toBeNull();
|
expect(ctx._commands).not.toBeNull();
|
||||||
expect(ctx.commands.registry).toBe(registry);
|
expect(ctx._commands.registry).toBe(registry);
|
||||||
expect(ctx.commands.context).toBe(ctx.state);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accept initial state as an object', () => {
|
it('should accept initial state as an object', () => {
|
||||||
const { registry } = createGameCommandRegistry<MyState>();
|
const registry = createGameCommandRegistry<MyState>();
|
||||||
const ctx = createGameContext<MyState>(registry, {
|
const ctx = createGameContext<MyState>(registry, {
|
||||||
score: 0,
|
score: 0,
|
||||||
round: 1,
|
round: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(ctx.state.value.score).toBe(0);
|
expect(ctx._state.value.score).toBe(0);
|
||||||
expect(ctx.state.value.round).toBe(1);
|
expect(ctx._state.value.round).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accept initial state as a factory function', () => {
|
it('should accept initial state as a factory function', () => {
|
||||||
const { registry } = createGameCommandRegistry<MyState>();
|
const registry = createGameCommandRegistry<MyState>();
|
||||||
const ctx = createGameContext<MyState>(registry, () => ({
|
const ctx = createGameContext<MyState>(registry, () => ({
|
||||||
score: 10,
|
score: 10,
|
||||||
round: 3,
|
round: 3,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(ctx.state.value.score).toBe(10);
|
expect(ctx._state.value.score).toBe(10);
|
||||||
expect(ctx.state.value.round).toBe(3);
|
expect(ctx._state.value.round).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should forward prompt events via listener', async () => {
|
it('should forward prompt events via listener', async () => {
|
||||||
const { registry } = createGameCommandRegistry();
|
const registry = createGameCommandRegistry();
|
||||||
const ctx = createGameContext(registry);
|
const ctx = createGameContext(registry);
|
||||||
|
|
||||||
createGameCommand(registry, 'test <value>', async function () {
|
registry.register('test <value>', async function (_ctx, value) {
|
||||||
return this.prompt('prompt <answer>');
|
return this.prompt<string>('prompt <answer>', () => 'ok');
|
||||||
});
|
});
|
||||||
|
|
||||||
const promptPromise = new Promise<PromptEvent>(resolve => {
|
const promptPromise = new Promise<PromptEvent>(resolve => {
|
||||||
ctx.commands.on('prompt', resolve);
|
ctx._commands.on('prompt', resolve);
|
||||||
});
|
});
|
||||||
const runPromise = ctx.commands.run('test hello');
|
const runPromise = ctx.run('test hello');
|
||||||
|
|
||||||
const promptEvent = await promptPromise;
|
const promptEvent = await promptPromise;
|
||||||
expect(promptEvent).not.toBeNull();
|
expect(promptEvent).not.toBeNull();
|
||||||
|
|
@ -69,45 +68,43 @@ describe('createGameContext', () => {
|
||||||
|
|
||||||
const result = await runPromise;
|
const result = await runPromise;
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
if (result.success) {
|
|
||||||
expect((result.result as any).params[0]).toBe('yes');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createGameCommand', () => {
|
describe('createGameCommand', () => {
|
||||||
it('should run a command with access to game context', async () => {
|
it('should run a command with access to game context', async () => {
|
||||||
const { registry } = createGameCommandRegistry<{ marker: string }>();
|
const registry = createGameCommandRegistry<{ marker: string }>();
|
||||||
const ctx = createGameContext(registry, { marker: '' });
|
|
||||||
|
registry.register('set-marker <id>', async function (ctx, id) {
|
||||||
createGameCommand(registry, 'set-marker <id>', async function (cmd) {
|
ctx.produce(state => {
|
||||||
const id = cmd.params[0] as string;
|
|
||||||
this.context.produce(state => {
|
|
||||||
state.marker = id;
|
state.marker = id;
|
||||||
});
|
});
|
||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await ctx.commands.run('set-marker board');
|
const ctx = createGameContext(registry, { marker: '' });
|
||||||
|
|
||||||
|
const result = await ctx.run('set-marker board');
|
||||||
|
if (!result.success) {
|
||||||
|
console.error('Error:', result.error);
|
||||||
|
}
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
expect(result.result).toBe('board');
|
expect(result.result).toBe('board');
|
||||||
}
|
}
|
||||||
expect(ctx.state.value.marker).toBe('board');
|
expect(ctx._state.value.marker).toBe('board');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run a typed command with extended context', async () => {
|
it('should run a typed command with extended context', async () => {
|
||||||
const { registry } = createGameCommandRegistry<MyState>();
|
const registry = createGameCommandRegistry<MyState>();
|
||||||
|
|
||||||
createGameCommand<MyState, number>(
|
registry.register(
|
||||||
registry,
|
|
||||||
'add-score <amount:number>',
|
'add-score <amount:number>',
|
||||||
async function (cmd) {
|
async function (ctx, amount) {
|
||||||
const amount = cmd.params[0] as number;
|
ctx.produce(state => {
|
||||||
this.context.produce(state => {
|
|
||||||
state.score += amount;
|
state.score += amount;
|
||||||
});
|
});
|
||||||
return this.context.value.score;
|
return ctx.value.score;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -116,19 +113,19 @@ describe('createGameCommand', () => {
|
||||||
round: 1,
|
round: 1,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const result = await ctx.commands.run('add-score 5');
|
const result = await ctx.run('add-score 5');
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
expect(result.result).toBe(5);
|
expect(result.result).toBe(5);
|
||||||
}
|
}
|
||||||
expect(ctx.state.value.score).toBe(5);
|
expect(ctx._state.value.score).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return error for unknown command', async () => {
|
it('should return error for unknown command', async () => {
|
||||||
const { registry } = createGameCommandRegistry();
|
const registry = createGameCommandRegistry();
|
||||||
const ctx = createGameContext(registry);
|
const ctx = createGameContext(registry);
|
||||||
|
|
||||||
const result = await ctx.commands.run('nonexistent');
|
const result = await ctx.run('nonexistent');
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
expect(result.error).toContain('nonexistent');
|
expect(result.error).toContain('nonexistent');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { createRegion, applyAlign, shuffle, moveToRegion, moveToRegionAll, removeFromRegion, type Region, type RegionAxis } from '@/core/region';
|
import { createRegion, applyAlign, shuffle, moveToRegion, type Region, type RegionAxis } from '@/core/region';
|
||||||
import { createRNG } from '@/utils/rng';
|
import { createRNG } from '@/utils/rng';
|
||||||
import { type Part } from '@/core/part';
|
import { type Part } from '@/core/part';
|
||||||
|
|
||||||
|
|
@ -303,76 +303,4 @@ describe('Region', () => {
|
||||||
expect(part.position).toEqual([3]);
|
expect(part.position).toEqual([3]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('moveToRegionAll', () => {
|
|
||||||
it('should move multiple parts to a target region', () => {
|
|
||||||
const sourceRegion = createRegion('source', [{ name: 'x' }]);
|
|
||||||
const targetRegion = createRegion('target', [{ name: 'x' }]);
|
|
||||||
|
|
||||||
const parts = {
|
|
||||||
p1: { id: 'p1', regionId: 'source', position: [0] } as Part,
|
|
||||||
p2: { id: 'p2', regionId: 'source', position: [1] } as Part,
|
|
||||||
p3: { id: 'p3', regionId: 'source', position: [2] } as Part,
|
|
||||||
};
|
|
||||||
sourceRegion.childIds.push('p1', 'p2', 'p3');
|
|
||||||
sourceRegion.partMap = { '0': 'p1', '1': 'p2', '2': 'p3' };
|
|
||||||
|
|
||||||
moveToRegionAll([parts.p1, parts.p2, parts.p3], sourceRegion, targetRegion, [[0], [1], [2]]);
|
|
||||||
|
|
||||||
expect(sourceRegion.childIds).toHaveLength(0);
|
|
||||||
expect(targetRegion.childIds).toHaveLength(3);
|
|
||||||
expect(parts.p1.position).toEqual([0]);
|
|
||||||
expect(parts.p2.position).toEqual([1]);
|
|
||||||
expect(parts.p3.position).toEqual([2]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep existing positions if no positions provided', () => {
|
|
||||||
const sourceRegion = createRegion('source', [{ name: 'x' }]);
|
|
||||||
const targetRegion = createRegion('target', [{ name: 'x' }]);
|
|
||||||
|
|
||||||
const parts = {
|
|
||||||
p1: { id: 'p1', regionId: 'source', position: [5] } as Part,
|
|
||||||
p2: { id: 'p2', regionId: 'source', position: [8] } as Part,
|
|
||||||
};
|
|
||||||
sourceRegion.childIds.push('p1', 'p2');
|
|
||||||
sourceRegion.partMap = { '5': 'p1', '8': 'p2' };
|
|
||||||
|
|
||||||
moveToRegionAll([parts.p1, parts.p2], sourceRegion, targetRegion);
|
|
||||||
|
|
||||||
expect(parts.p1.position).toEqual([5]);
|
|
||||||
expect(parts.p2.position).toEqual([8]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('removeFromRegion', () => {
|
|
||||||
it('should remove a part from its region', () => {
|
|
||||||
const region = createRegion('region1', [{ name: 'x' }]);
|
|
||||||
|
|
||||||
const part: Part = { id: 'p1', regionId: 'region1', position: [2] };
|
|
||||||
const parts: Record<string, Part> = { p1: part };
|
|
||||||
region.childIds.push('p1');
|
|
||||||
region.partMap['2'] = 'p1';
|
|
||||||
|
|
||||||
expect(region.childIds).toHaveLength(1);
|
|
||||||
|
|
||||||
removeFromRegion(part, region);
|
|
||||||
|
|
||||||
expect(region.childIds).toHaveLength(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should leave other parts unaffected', () => {
|
|
||||||
const region = createRegion('region1', [{ name: 'x' }]);
|
|
||||||
|
|
||||||
const p1 = { id: 'p1', regionId: 'region1', position: [0] } as Part;
|
|
||||||
const p2 = { id: 'p2', regionId: 'region1', position: [1] } as Part;
|
|
||||||
const p3 = { id: 'p3', regionId: 'region1', position: [2] } as Part;
|
|
||||||
region.childIds.push('p1', 'p2', 'p3');
|
|
||||||
region.partMap = { '0': 'p1', '1': 'p2', '2': 'p3' };
|
|
||||||
|
|
||||||
removeFromRegion(p2, region);
|
|
||||||
|
|
||||||
expect(region.childIds).toHaveLength(2);
|
|
||||||
expect(region.childIds).toEqual(['p1', 'p3']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -141,4 +141,394 @@ describe('Boop Game', () => {
|
||||||
expect(stateAfterWhite.regions.board.partMap['2,2']).toBeDefined();
|
expect(stateAfterWhite.regions.board.partMap['2,2']).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Kitten vs Cat Hierarchy', () => {
|
||||||
|
it('should not boop cats when placing a kitten', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
|
||||||
|
// White places a kitten at 2,2
|
||||||
|
let promptPromise = waitForPrompt(ctx);
|
||||||
|
let runPromise = ctx.run('turn white');
|
||||||
|
let prompt = await promptPromise;
|
||||||
|
let error = prompt.tryCommit({ name: 'play', params: ['white', 2, 2, 'kitten'], options: {}, flags: {} });
|
||||||
|
expect(error).toBeNull();
|
||||||
|
let result = await runPromise;
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
// Manually move white's kitten to box and replace with a cat (for testing)
|
||||||
|
ctx.produce(state => {
|
||||||
|
const whiteKitten = state.pieces['white-kitten-1'];
|
||||||
|
if (whiteKitten && whiteKitten.regionId === 'board') {
|
||||||
|
whiteKitten.type = 'cat';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Black places a kitten at 2,3 (adjacent to the cat)
|
||||||
|
promptPromise = waitForPrompt(ctx);
|
||||||
|
runPromise = ctx.run('turn black');
|
||||||
|
prompt = await promptPromise;
|
||||||
|
error = prompt.tryCommit({ name: 'play', params: ['black', 2, 3, 'kitten'], options: {}, flags: {} });
|
||||||
|
expect(error).toBeNull();
|
||||||
|
result = await runPromise;
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
const state = ctx.value;
|
||||||
|
// White's cat should still be at 2,2 (not booped)
|
||||||
|
expect(state.regions.board.partMap['2,2']).toBe('white-kitten-1');
|
||||||
|
// Black's kitten should be at 2,3
|
||||||
|
expect(state.regions.board.partMap['2,3']).toBe('black-kitten-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should boop both kittens and cats when placing a cat', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
|
||||||
|
// Manually set up: white cat at 2,3, black cat at 3,2
|
||||||
|
// First move cats to white and black supplies
|
||||||
|
ctx.produce(state => {
|
||||||
|
const whiteCat = state.pieces['white-cat-1'];
|
||||||
|
const blackCat = state.pieces['black-cat-1'];
|
||||||
|
if (whiteCat && whiteCat.regionId === '') {
|
||||||
|
whiteCat.regionId = 'white';
|
||||||
|
state.regions.white.childIds.push(whiteCat.id);
|
||||||
|
}
|
||||||
|
if (blackCat && blackCat.regionId === '') {
|
||||||
|
blackCat.regionId = 'black';
|
||||||
|
state.regions.black.childIds.push(blackCat.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now move them to the board
|
||||||
|
ctx.produce(state => {
|
||||||
|
const whiteCat = state.pieces['white-cat-1'];
|
||||||
|
const blackCat = state.pieces['black-cat-1'];
|
||||||
|
if (whiteCat && whiteCat.regionId === 'white') {
|
||||||
|
whiteCat.regionId = 'board';
|
||||||
|
whiteCat.position = [2, 3];
|
||||||
|
state.regions.board.partMap['2,3'] = whiteCat.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== whiteCat.id);
|
||||||
|
}
|
||||||
|
if (blackCat && blackCat.regionId === 'black') {
|
||||||
|
blackCat.regionId = 'board';
|
||||||
|
blackCat.position = [3, 2];
|
||||||
|
state.regions.board.partMap['3,2'] = blackCat.id;
|
||||||
|
state.regions.black.childIds = state.regions.black.childIds.filter(id => id !== blackCat.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give white another cat for placement
|
||||||
|
ctx.produce(state => {
|
||||||
|
const whiteCat2 = state.pieces['white-cat-2'];
|
||||||
|
if (whiteCat2 && whiteCat2.regionId === '') {
|
||||||
|
whiteCat2.regionId = 'white';
|
||||||
|
state.regions.white.childIds.push(whiteCat2.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// White places a cat at 2,2 (should boop black's cat at 3,2 to 4,2)
|
||||||
|
const promptPromise = waitForPrompt(ctx);
|
||||||
|
const runPromise = ctx.run('turn white');
|
||||||
|
const prompt = await promptPromise;
|
||||||
|
const error = prompt.tryCommit({ name: 'play', params: ['white', 2, 2, 'cat'], options: {}, flags: {} });
|
||||||
|
expect(error).toBeNull();
|
||||||
|
const result = await runPromise;
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
const state = ctx.value;
|
||||||
|
// Black's cat should have been booped to 4,2
|
||||||
|
expect(state.regions.board.partMap['4,2']).toBeDefined();
|
||||||
|
const pieceAt42 = state.pieces[state.regions.board.partMap['4,2']];
|
||||||
|
expect(pieceAt42?.player).toBe('black');
|
||||||
|
expect(pieceAt42?.type).toBe('cat');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Boop Obstructions', () => {
|
||||||
|
it('should boop pieces to empty positions', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
|
||||||
|
// White places at 2,2
|
||||||
|
let promptPromise = waitForPrompt(ctx);
|
||||||
|
let runPromise = ctx.run('turn white');
|
||||||
|
let prompt = await promptPromise;
|
||||||
|
let error = prompt.tryCommit({ name: 'play', params: ['white', 2, 2, 'kitten'], options: {}, flags: {} });
|
||||||
|
expect(error).toBeNull();
|
||||||
|
let result = await runPromise;
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
// Check board has 1 piece after first placement
|
||||||
|
let state = ctx.value;
|
||||||
|
expect(Object.keys(state.regions.board.partMap).length).toBe(1);
|
||||||
|
|
||||||
|
// Black places at 3,3
|
||||||
|
promptPromise = waitForPrompt(ctx);
|
||||||
|
runPromise = ctx.run('turn black');
|
||||||
|
prompt = await promptPromise;
|
||||||
|
error = prompt.tryCommit({ name: 'play', params: ['black', 3, 3, 'kitten'], options: {}, flags: {} });
|
||||||
|
expect(error).toBeNull();
|
||||||
|
result = await runPromise;
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
state = ctx.value;
|
||||||
|
expect(Object.keys(state.regions.board.partMap).length).toBe(2);
|
||||||
|
|
||||||
|
// Verify the pieces are on the board (positions may vary due to boop)
|
||||||
|
const boardPieces = Object.entries(state.regions.board.partMap);
|
||||||
|
expect(boardPieces.length).toBe(2);
|
||||||
|
|
||||||
|
// Find black's piece
|
||||||
|
const blackPiece = boardPieces.find(([pos, id]) => state.pieces[id]?.player === 'black');
|
||||||
|
expect(blackPiece).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep both pieces in place when boop is blocked', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
|
||||||
|
// Setup: place white at 2,2 and 4,4, black at 3,3
|
||||||
|
await ctx._commands.run('place 2 2 white kitten');
|
||||||
|
await ctx._commands.run('place 3 3 black kitten');
|
||||||
|
await ctx._commands.run('place 4 4 white kitten');
|
||||||
|
|
||||||
|
const stateBefore = ctx.value;
|
||||||
|
// Verify setup - 3 pieces on board
|
||||||
|
const boardPiecesBefore = Object.keys(stateBefore.regions.board.partMap);
|
||||||
|
expect(boardPiecesBefore.length).toBe(3);
|
||||||
|
expect(stateBefore.regions.board.partMap['2,2']).toBeDefined();
|
||||||
|
expect(stateBefore.regions.board.partMap['3,3']).toBeDefined();
|
||||||
|
expect(stateBefore.regions.board.partMap['4,4']).toBeDefined();
|
||||||
|
|
||||||
|
// Black places at 2,3 - should try to boop piece at 3,3 to 4,4
|
||||||
|
// but 4,4 is occupied, so both should stay
|
||||||
|
await ctx._commands.run('place 2 3 black kitten');
|
||||||
|
|
||||||
|
const state = ctx.value;
|
||||||
|
// Should now have 4 pieces on board
|
||||||
|
const boardPiecesAfter = Object.keys(state.regions.board.partMap);
|
||||||
|
expect(boardPiecesAfter.length).toBe(4);
|
||||||
|
// 3,3 should still have the same piece (not booped)
|
||||||
|
expect(state.regions.board.partMap['3,3']).toBeDefined();
|
||||||
|
// 4,4 should still be occupied
|
||||||
|
expect(state.regions.board.partMap['4,4']).toBeDefined();
|
||||||
|
// 2,3 should have black's new piece
|
||||||
|
expect(state.regions.board.partMap['2,3']).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Graduation Mechanic', () => {
|
||||||
|
it('should graduate three kittens in a row to cats', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
|
||||||
|
// Manually place three white kittens in a row
|
||||||
|
ctx.produce(state => {
|
||||||
|
const k1 = state.pieces['white-kitten-1'];
|
||||||
|
const k2 = state.pieces['white-kitten-2'];
|
||||||
|
const k3 = state.pieces['white-kitten-3'];
|
||||||
|
|
||||||
|
if (k1) {
|
||||||
|
k1.regionId = 'board';
|
||||||
|
k1.position = [0, 0];
|
||||||
|
state.regions.board.partMap['0,0'] = k1.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== k1.id);
|
||||||
|
}
|
||||||
|
if (k2) {
|
||||||
|
k2.regionId = 'board';
|
||||||
|
k2.position = [0, 1];
|
||||||
|
state.regions.board.partMap['0,1'] = k2.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== k2.id);
|
||||||
|
}
|
||||||
|
if (k3) {
|
||||||
|
k3.regionId = 'board';
|
||||||
|
k3.position = [0, 2];
|
||||||
|
state.regions.board.partMap['0,2'] = k3.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== k3.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stateBefore = ctx.value;
|
||||||
|
// Verify three kittens on board
|
||||||
|
expect(stateBefore.regions.board.partMap['0,0']).toBeDefined();
|
||||||
|
expect(stateBefore.regions.board.partMap['0,1']).toBeDefined();
|
||||||
|
expect(stateBefore.regions.board.partMap['0,2']).toBeDefined();
|
||||||
|
|
||||||
|
// Count cats in white supply before graduation
|
||||||
|
const catsInWhiteSupplyBefore = stateBefore.regions.white.childIds.filter(
|
||||||
|
id => stateBefore.pieces[id].type === 'cat'
|
||||||
|
);
|
||||||
|
expect(catsInWhiteSupplyBefore.length).toBe(0);
|
||||||
|
|
||||||
|
// Run check-graduates command
|
||||||
|
const result = await ctx._commands.run('check-graduates');
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
const state = ctx.value;
|
||||||
|
// The three positions on board should now be empty (kittens removed)
|
||||||
|
expect(state.regions.board.partMap['0,0']).toBeUndefined();
|
||||||
|
expect(state.regions.board.partMap['0,1']).toBeUndefined();
|
||||||
|
expect(state.regions.board.partMap['0,2']).toBeUndefined();
|
||||||
|
|
||||||
|
// White's supply should now have 3 cats (graduated)
|
||||||
|
const catsInWhiteSupply = state.regions.white.childIds.filter(
|
||||||
|
id => state.pieces[id].type === 'cat'
|
||||||
|
);
|
||||||
|
expect(catsInWhiteSupply.length).toBe(3);
|
||||||
|
|
||||||
|
// White's supply should have 5 kittens left (8 - 3 graduated)
|
||||||
|
const kittensInWhiteSupply = state.regions.white.childIds.filter(
|
||||||
|
id => state.pieces[id].type === 'kitten'
|
||||||
|
);
|
||||||
|
expect(kittensInWhiteSupply.length).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Win Detection', () => {
|
||||||
|
it('should detect horizontal win with three cats', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
|
||||||
|
// Manually set up a winning scenario for white
|
||||||
|
ctx.produce(state => {
|
||||||
|
const k1 = state.pieces['white-kitten-1'];
|
||||||
|
const k2 = state.pieces['white-kitten-2'];
|
||||||
|
const k3 = state.pieces['white-kitten-3'];
|
||||||
|
|
||||||
|
if (k1) {
|
||||||
|
k1.type = 'cat';
|
||||||
|
k1.regionId = 'board';
|
||||||
|
k1.position = [0, 0];
|
||||||
|
state.regions.board.partMap['0,0'] = k1.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== k1.id);
|
||||||
|
}
|
||||||
|
if (k2) {
|
||||||
|
k2.type = 'cat';
|
||||||
|
k2.regionId = 'board';
|
||||||
|
k2.position = [0, 1];
|
||||||
|
state.regions.board.partMap['0,1'] = k2.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== k2.id);
|
||||||
|
}
|
||||||
|
if (k3) {
|
||||||
|
k3.type = 'cat';
|
||||||
|
k3.regionId = 'board';
|
||||||
|
k3.position = [0, 2];
|
||||||
|
state.regions.board.partMap['0,2'] = k3.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== k3.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run check-win command
|
||||||
|
const result = await ctx._commands.run('check-win');
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.result).toBe('white');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect vertical win with three cats', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
|
||||||
|
// Manually set up a vertical winning scenario for black
|
||||||
|
ctx.produce(state => {
|
||||||
|
const k1 = state.pieces['black-kitten-1'];
|
||||||
|
const k2 = state.pieces['black-kitten-2'];
|
||||||
|
const k3 = state.pieces['black-kitten-3'];
|
||||||
|
|
||||||
|
if (k1) {
|
||||||
|
k1.type = 'cat';
|
||||||
|
k1.regionId = 'board';
|
||||||
|
k1.position = [0, 0];
|
||||||
|
state.regions.board.partMap['0,0'] = k1.id;
|
||||||
|
state.regions.black.childIds = state.regions.black.childIds.filter(id => id !== k1.id);
|
||||||
|
}
|
||||||
|
if (k2) {
|
||||||
|
k2.type = 'cat';
|
||||||
|
k2.regionId = 'board';
|
||||||
|
k2.position = [1, 0];
|
||||||
|
state.regions.board.partMap['1,0'] = k2.id;
|
||||||
|
state.regions.black.childIds = state.regions.black.childIds.filter(id => id !== k2.id);
|
||||||
|
}
|
||||||
|
if (k3) {
|
||||||
|
k3.type = 'cat';
|
||||||
|
k3.regionId = 'board';
|
||||||
|
k3.position = [2, 0];
|
||||||
|
state.regions.board.partMap['2,0'] = k3.id;
|
||||||
|
state.regions.black.childIds = state.regions.black.childIds.filter(id => id !== k3.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run check-win command
|
||||||
|
const result = await ctx._commands.run('check-win');
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.result).toBe('black');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect diagonal win with three cats', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
|
||||||
|
// Manually set up a diagonal winning scenario for white
|
||||||
|
ctx.produce(state => {
|
||||||
|
const k1 = state.pieces['white-kitten-1'];
|
||||||
|
const k2 = state.pieces['white-kitten-2'];
|
||||||
|
const k3 = state.pieces['white-kitten-3'];
|
||||||
|
|
||||||
|
if (k1) {
|
||||||
|
k1.type = 'cat';
|
||||||
|
k1.regionId = 'board';
|
||||||
|
k1.position = [0, 0];
|
||||||
|
state.regions.board.partMap['0,0'] = k1.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== k1.id);
|
||||||
|
}
|
||||||
|
if (k2) {
|
||||||
|
k2.type = 'cat';
|
||||||
|
k2.regionId = 'board';
|
||||||
|
k2.position = [1, 1];
|
||||||
|
state.regions.board.partMap['1,1'] = k2.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== k2.id);
|
||||||
|
}
|
||||||
|
if (k3) {
|
||||||
|
k3.type = 'cat';
|
||||||
|
k3.regionId = 'board';
|
||||||
|
k3.position = [2, 2];
|
||||||
|
state.regions.board.partMap['2,2'] = k3.id;
|
||||||
|
state.regions.white.childIds = state.regions.white.childIds.filter(id => id !== k3.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run check-win command
|
||||||
|
const result = await ctx._commands.run('check-win');
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.result).toBe('white');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Placing Cats', () => {
|
||||||
|
it('should allow placing a cat from supply', async () => {
|
||||||
|
const { ctx } = createTestContext();
|
||||||
|
|
||||||
|
// Manually give a cat to white's supply
|
||||||
|
ctx.produce(state => {
|
||||||
|
const cat = state.pieces['white-cat-1'];
|
||||||
|
if (cat && cat.regionId === '') {
|
||||||
|
cat.regionId = 'white';
|
||||||
|
state.regions.white.childIds.push(cat.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// White places a cat at 2,2
|
||||||
|
const promptPromise = waitForPrompt(ctx);
|
||||||
|
const runPromise = ctx.run('turn white');
|
||||||
|
const prompt = await promptPromise;
|
||||||
|
const error = prompt.tryCommit({ name: 'play', params: ['white', 2, 2, 'cat'], options: {}, flags: {} });
|
||||||
|
expect(error).toBeNull();
|
||||||
|
const result = await runPromise;
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
|
||||||
|
const state = ctx.value;
|
||||||
|
// Cat should be on the board
|
||||||
|
expect(state.regions.board.partMap['2,2']).toBe('white-cat-1');
|
||||||
|
// Cat should no longer be in supply
|
||||||
|
const whiteCatsInSupply = state.regions.white.childIds.filter(id => state.pieces[id].type === 'cat');
|
||||||
|
expect(whiteCatsInSupply.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -246,8 +246,8 @@ describe('prompt', () => {
|
||||||
const chooseRunner: CommandRunner<TestContext, string> = {
|
const chooseRunner: CommandRunner<TestContext, string> = {
|
||||||
schema: parseCommandSchema('choose'),
|
schema: parseCommandSchema('choose'),
|
||||||
run: async function () {
|
run: async function () {
|
||||||
const result = await this.prompt('select <card>');
|
const result = await this.prompt('select <card>', (cmd) => cmd.params[0] as string);
|
||||||
return result.params[0] as string;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -266,6 +266,9 @@ describe('prompt', () => {
|
||||||
await new Promise((r) => setTimeout(r, 0));
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
expect(promptEvent).not.toBeNull();
|
expect(promptEvent).not.toBeNull();
|
||||||
expect(promptEvent!.schema.name).toBe('select');
|
expect(promptEvent!.schema.name).toBe('select');
|
||||||
|
|
||||||
|
promptEvent!.cancel('test cleanup');
|
||||||
|
await runPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve prompt with valid input', async () => {
|
it('should resolve prompt with valid input', async () => {
|
||||||
|
|
@ -274,9 +277,9 @@ describe('prompt', () => {
|
||||||
const chooseRunner: CommandRunner<TestContext, string> = {
|
const chooseRunner: CommandRunner<TestContext, string> = {
|
||||||
schema: parseCommandSchema('choose'),
|
schema: parseCommandSchema('choose'),
|
||||||
run: async function () {
|
run: async function () {
|
||||||
const result = await this.prompt('select <card>');
|
const result = await this.prompt('select <card>', (cmd) => cmd.params[0] as string);
|
||||||
this.context.log.push(`selected ${result.params[0]}`);
|
this.context.log.push(`selected ${result}`);
|
||||||
return result.params[0] as string;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -314,7 +317,7 @@ describe('prompt', () => {
|
||||||
schema: parseCommandSchema('choose'),
|
schema: parseCommandSchema('choose'),
|
||||||
run: async function () {
|
run: async function () {
|
||||||
try {
|
try {
|
||||||
await this.prompt('select <card>');
|
await this.prompt('select <card>', (cmd) => cmd.params[0] as string);
|
||||||
return 'unexpected success';
|
return 'unexpected success';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return (e as Error).message;
|
return (e as Error).message;
|
||||||
|
|
@ -353,8 +356,8 @@ describe('prompt', () => {
|
||||||
const pickRunner: CommandRunner<TestContext, string> = {
|
const pickRunner: CommandRunner<TestContext, string> = {
|
||||||
schema: parseCommandSchema('pick'),
|
schema: parseCommandSchema('pick'),
|
||||||
run: async function () {
|
run: async function () {
|
||||||
const result = await this.prompt(schema);
|
const result = await this.prompt(schema, (cmd) => cmd.params[0] as string);
|
||||||
return result.params[0] as string;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -390,9 +393,9 @@ describe('prompt', () => {
|
||||||
const multiPromptRunner: CommandRunner<TestContext, string[]> = {
|
const multiPromptRunner: CommandRunner<TestContext, string[]> = {
|
||||||
schema: parseCommandSchema('multi'),
|
schema: parseCommandSchema('multi'),
|
||||||
run: async function () {
|
run: async function () {
|
||||||
const first = await this.prompt('first <a>');
|
const first = await this.prompt('first <a>', (cmd) => cmd.params[0] as string);
|
||||||
const second = await this.prompt('second <b>');
|
const second = await this.prompt('second <b>', (cmd) => cmd.params[0] as string);
|
||||||
return [first.params[0] as string, second.params[0] as string];
|
return [first, second];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -440,12 +443,12 @@ describe('prompt', () => {
|
||||||
(cmd) => {
|
(cmd) => {
|
||||||
const card = cmd.params[0] as string;
|
const card = cmd.params[0] as string;
|
||||||
if (!['Ace', 'King', 'Queen'].includes(card)) {
|
if (!['Ace', 'King', 'Queen'].includes(card)) {
|
||||||
return `Invalid card: ${card}. Must be Ace, King, or Queen.`;
|
throw `Invalid card: ${card}. Must be Ace, King, or Queen.`;
|
||||||
}
|
}
|
||||||
return null;
|
return card;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return result.params[0] as string;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -486,7 +489,7 @@ describe('prompt', () => {
|
||||||
schema: parseCommandSchema('choose'),
|
schema: parseCommandSchema('choose'),
|
||||||
run: async function () {
|
run: async function () {
|
||||||
try {
|
try {
|
||||||
await this.prompt('select <card>');
|
await this.prompt('select <card>', (cmd) => cmd.params[0] as string);
|
||||||
return 'unexpected success';
|
return 'unexpected success';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return (e as Error).message;
|
return (e as Error).message;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue