import { describe, it, expect } from "vitest"; import { createGameContext, createGameCommandRegistry, createPromptDef, IGameContext, PromptDef, } from "@/core/game"; import type { PromptEvent, Command, CommandRunnerContext, } from "@/utils/command"; type MyState = { score: number; round: number; }; describe("createGameContext", () => { it("should create a game context with state", () => { const registry = createGameCommandRegistry(); const ctx = createGameContext(registry); expect(ctx._state).not.toBeNull(); expect(ctx._state.value).toBeDefined(); }); it("should wire commands to the context", () => { const registry = createGameCommandRegistry(); const ctx = createGameContext(registry); expect(ctx._commands).not.toBeNull(); expect(ctx._commands.registry).toBe(registry); }); it("should accept initial state as an object", () => { const registry = createGameCommandRegistry(); const ctx = createGameContext(registry, { score: 0, round: 1, }); expect(ctx._state.value.score).toBe(0); expect(ctx._state.value.round).toBe(1); }); it("should accept initial state as a factory function", () => { const registry = createGameCommandRegistry(); const ctx = createGameContext(registry, () => ({ score: 10, round: 3, })); expect(ctx._state.value.score).toBe(10); expect(ctx._state.value.round).toBe(3); }); it("should forward prompt events via listener", async () => { const registry = createGameCommandRegistry(); const ctx = createGameContext(registry); registry.register( "test ", async function (this: CommandRunnerContext, _ctx, value) { return this.prompt( createPromptDef("prompt "), (answer) => answer, ); }, ); const promptPromise = new Promise((resolve) => { ctx._commands.on("prompt", resolve); }); const runPromise = ctx.run("test hello"); const promptEvent = await promptPromise; expect(promptEvent).not.toBeNull(); expect(promptEvent.schema.name).toBe("prompt"); const error = promptEvent.tryCommit({ name: "prompt", params: ["yes"], options: {}, flags: {}, }); expect(error).toBeNull(); const result = await runPromise; expect(result.success).toBe(true); }); }); describe("createGameCommand", () => { it("should run a command with access to game context", async () => { const registry = createGameCommandRegistry<{ marker: string }>(); registry.register("set-marker ", async function (ctx, id) { ctx.produce((state) => { state.marker = id; }); return id; }); 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); if (result.success) { expect(result.result).toBe("board"); } expect(ctx._state.value.marker).toBe("board"); }); it("should run a typed command with extended context", async () => { const registry = createGameCommandRegistry(); registry.register( "add-score ", async function (ctx, amount) { ctx.produce((state) => { state.score += amount; }); return ctx.value.score; }, ); const ctx = createGameContext(registry, () => ({ score: 0, round: 1, })); const result = await ctx.run("add-score 5"); expect(result.success).toBe(true); if (result.success) { expect(result.result).toBe(5); } 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.run("nonexistent"); expect(result.success).toBe(false); if (!result.success) { expect(result.error).toContain("nonexistent"); } }); }); describe("createPromptDef", () => { it("should create a PromptDef with string schema", () => { const promptDef = createPromptDef<[string, number]>( "play ", ); expect(promptDef).toBeDefined(); expect(promptDef.schema.name).toBe("play"); expect(promptDef.schema.params).toHaveLength(2); expect(promptDef.schema.params[0].name).toBe("player"); expect(promptDef.schema.params[1].name).toBe("score"); }); it("should create a PromptDef with CommandSchema object", () => { const schemaObj = { name: "test", params: [], options: {}, flags: {}, }; const promptDef = createPromptDef<[]>(schemaObj); expect(promptDef.schema).toEqual(schemaObj); }); it("should be usable with game.prompt", async () => { const registry = createGameCommandRegistry<{ score: number }>(); registry.register("test-prompt", async function (ctx) { const promptDef = createPromptDef<[number]>("input "); const result = await ctx.prompt(promptDef, (value) => { if (value < 0) throw "Value must be positive"; return value; }); return result; }); const ctx = createGameContext(registry, { score: 0 }); const promptPromise = new Promise((resolve) => { ctx._commands.on("prompt", resolve); }); const runPromise = ctx.run("test-prompt"); const promptEvent = await promptPromise; expect(promptEvent.schema.name).toBe("input"); const error = promptEvent.tryCommit({ name: "input", params: [42], options: {}, flags: {}, }); expect(error).toBeNull(); const result = await runPromise; expect(result.success).toBe(true); if (result.success) { expect(result.result).toBe(42); } }); });