boardgame-core/tests/core/game.test.ts

137 lines
4.5 KiB
TypeScript
Raw Normal View History

2026-04-02 11:21:57 +08:00
import { describe, it, expect } from 'vitest';
2026-04-02 15:59:27 +08:00
import { createGameContext, createGameCommand, createGameCommandRegistry } from '@/core/game';
import type { PromptEvent } from '@/utils/command';
2026-04-02 12:48:29 +08:00
type MyState = {
score: number;
round: number;
};
2026-04-02 11:21:57 +08:00
describe('createGameContext', () => {
2026-04-02 15:16:30 +08:00
it('should create a game context with state', () => {
const { registry } = createGameCommandRegistry();
2026-04-02 11:21:57 +08:00
const ctx = createGameContext(registry);
2026-04-02 15:16:30 +08:00
expect(ctx.state).not.toBeNull();
expect(ctx.state.value).toBeDefined();
2026-04-02 11:21:57 +08:00
});
it('should wire commands to the context', () => {
2026-04-02 15:16:30 +08:00
const { registry } = createGameCommandRegistry();
2026-04-02 11:21:57 +08:00
const ctx = createGameContext(registry);
expect(ctx.commands).not.toBeNull();
expect(ctx.commands.registry).toBe(registry);
2026-04-02 15:16:30 +08:00
expect(ctx.commands.context).toBe(ctx.state);
2026-04-02 11:21:57 +08:00
});
2026-04-02 12:48:29 +08:00
it('should accept initial state as an object', () => {
2026-04-02 15:16:30 +08:00
const { registry } = createGameCommandRegistry<MyState>();
const ctx = createGameContext<MyState>(registry, {
score: 0,
round: 1,
2026-04-02 12:48:29 +08:00
});
2026-04-02 15:16:30 +08:00
expect(ctx.state.value.score).toBe(0);
expect(ctx.state.value.round).toBe(1);
2026-04-02 12:48:29 +08:00
});
it('should accept initial state as a factory function', () => {
2026-04-02 15:16:30 +08:00
const { registry } = createGameCommandRegistry<MyState>();
const ctx = createGameContext<MyState>(registry, () => ({
score: 10,
round: 3,
2026-04-02 12:48:29 +08:00
}));
2026-04-02 15:16:30 +08:00
expect(ctx.state.value.score).toBe(10);
expect(ctx.state.value.round).toBe(3);
2026-04-02 12:48:29 +08:00
});
2026-04-02 15:16:30 +08:00
it('should forward prompt events via listener', async () => {
const { registry } = createGameCommandRegistry();
2026-04-02 11:21:57 +08:00
const ctx = createGameContext(registry);
2026-04-02 15:16:30 +08:00
createGameCommand(registry, 'test <value>', async function () {
return this.prompt('prompt <answer>');
2026-04-02 11:21:57 +08:00
});
2026-04-02 15:16:30 +08:00
const promptPromise = new Promise<PromptEvent>(resolve => {
ctx.commands.on('prompt', resolve);
});
2026-04-02 11:21:57 +08:00
const runPromise = ctx.commands.run('test hello');
2026-04-02 15:16:30 +08:00
const promptEvent = await promptPromise;
2026-04-02 11:21:57 +08:00
expect(promptEvent).not.toBeNull();
expect(promptEvent.schema.name).toBe('prompt');
promptEvent.resolve({ name: 'prompt', params: ['yes'], options: {}, flags: {} });
const result = await runPromise;
expect(result.success).toBe(true);
if (result.success) {
expect((result.result as any).params[0]).toBe('yes');
}
});
});
describe('createGameCommand', () => {
it('should run a command with access to game context', async () => {
2026-04-02 15:59:27 +08:00
const { registry } = createGameCommandRegistry<{ marker: string }>();
2026-04-02 15:16:30 +08:00
const ctx = createGameContext(registry, { marker: '' });
2026-04-02 11:21:57 +08:00
2026-04-02 15:16:30 +08:00
createGameCommand(registry, 'set-marker <id>', async function (cmd) {
2026-04-02 11:21:57 +08:00
const id = cmd.params[0] as string;
2026-04-02 15:16:30 +08:00
this.context.produce(state => {
state.marker = id;
});
2026-04-02 11:21:57 +08:00
return id;
});
2026-04-02 15:16:30 +08:00
const result = await ctx.commands.run('set-marker board');
2026-04-02 11:21:57 +08:00
expect(result.success).toBe(true);
if (result.success) {
expect(result.result).toBe('board');
}
2026-04-02 15:16:30 +08:00
expect(ctx.state.value.marker).toBe('board');
2026-04-02 11:21:57 +08:00
});
2026-04-02 12:48:29 +08:00
it('should run a typed command with extended context', async () => {
2026-04-02 15:16:30 +08:00
const { registry } = createGameCommandRegistry<MyState>();
2026-04-02 12:48:29 +08:00
2026-04-02 15:16:30 +08:00
createGameCommand<MyState, number>(
registry,
2026-04-02 12:48:29 +08:00
'add-score <amount:number>',
async function (cmd) {
const amount = cmd.params[0] as number;
2026-04-02 15:16:30 +08:00
this.context.produce(state => {
state.score += amount;
});
return this.context.value.score;
2026-04-02 12:48:29 +08:00
}
);
2026-04-02 15:16:30 +08:00
const ctx = createGameContext<MyState>(registry, () => ({
score: 0,
round: 1,
2026-04-02 12:48:29 +08:00
}));
const result = await ctx.commands.run('add-score 5');
expect(result.success).toBe(true);
if (result.success) {
expect(result.result).toBe(5);
}
2026-04-02 15:16:30 +08:00
expect(ctx.state.value.score).toBe(5);
});
it('should return error for unknown command', async () => {
const { registry } = createGameCommandRegistry();
const ctx = createGameContext(registry);
const result = await ctx.commands.run('nonexistent');
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toContain('nonexistent');
}
2026-04-02 12:48:29 +08:00
});
2026-04-02 11:21:57 +08:00
});