test: reformat and expand test coverage

This commit is contained in:
hypercross 2026-04-20 15:37:15 +08:00
parent b83ff28f60
commit 974c1a828c
3 changed files with 495 additions and 448 deletions

View File

@ -63,7 +63,10 @@ describe("createGameContext", () => {
registry.register( registry.register(
"test <value>", "test <value>",
async function (this: CommandRunnerContext<IGameContext>, _ctx, value) { async function (this: CommandRunnerContext<IGameContext>, _ctx, value) {
return this.prompt(createPromptDef("prompt <answer>"), () => "ok"); return this.prompt(
createPromptDef("prompt <answer>"),
(answer) => answer,
);
}, },
); );

View File

@ -23,7 +23,7 @@ function createTestItem(
id: string, id: string,
shapeStr: string, shapeStr: string,
transform = IDENTITY_TRANSFORM, transform = IDENTITY_TRANSFORM,
): InventoryItem { ): InventoryItem<Record<string, never>> {
const shape = parseShapeString(shapeStr); const shape = parseShapeString(shapeStr);
return { return {
id, id,

View File

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from "vitest";
import { parseCommandSchema } from '@/utils/command/schema-parse'; import { parseCommandSchema } from "@/utils/command/schema-parse";
import { import {
createCommandRegistry, createCommandRegistry,
registerCommand, registerCommand,
@ -10,109 +10,120 @@ import {
createCommandRunnerContext, createCommandRunnerContext,
type CommandRegistry, type CommandRegistry,
type CommandRunnerContextExport, type CommandRunnerContextExport,
} from '@/utils/command/command-registry'; } from "@/utils/command/command-registry";
import type { CommandRunner, PromptEvent } from '@/utils/command/command-runner'; import type {
CommandRunner,
PromptEvent,
} from "@/utils/command/command-runner";
type TestContext = { type TestContext = {
counter: number; counter: number;
log: string[]; log: string[];
}; };
describe('CommandRegistry', () => { describe("CommandRegistry", () => {
it('should create an empty registry', () => { it("should create an empty registry", () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
expect(registry.size).toBe(0); expect(registry.size).toBe(0);
}); });
it('should register a command', () => { it("should register a command", () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const runner: CommandRunner<TestContext, number> = { const runner: CommandRunner<TestContext, number> = {
schema: parseCommandSchema('add <a> <b>'), schema: parseCommandSchema("add <a> <b>"),
run: async function (cmd) { run: async function (cmd) {
return Number(cmd.params[0]) + Number(cmd.params[1]); return Number(cmd.params[0]) + Number(cmd.params[1]);
}, },
}; };
registerCommand(registry, runner); registerCommand(registry, runner);
expect(registry.size).toBe(1); expect(registry.size).toBe(1);
expect(hasCommand(registry, 'add')).toBe(true); expect(hasCommand(registry, "add")).toBe(true);
}); });
it('should unregister a command', () => { it("should unregister a command", () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const runner: CommandRunner<TestContext> = { const runner: CommandRunner<TestContext> = {
schema: parseCommandSchema('remove'), schema: parseCommandSchema("remove"),
run: async () => {}, run: async () => {},
}; };
registerCommand(registry, runner); registerCommand(registry, runner);
expect(hasCommand(registry, 'remove')).toBe(true); expect(hasCommand(registry, "remove")).toBe(true);
unregisterCommand(registry, 'remove'); unregisterCommand(registry, "remove");
expect(hasCommand(registry, 'remove')).toBe(false); expect(hasCommand(registry, "remove")).toBe(false);
}); });
it('should get a command runner', () => { it("should get a command runner", () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const runner: CommandRunner<TestContext> = { const runner: CommandRunner<TestContext> = {
schema: parseCommandSchema('get'), schema: parseCommandSchema("get"),
run: async () => {}, run: async () => {},
}; };
registerCommand(registry, runner); registerCommand(registry, runner);
const retrieved = getCommand(registry, 'get'); const retrieved = getCommand(registry, "get");
expect(retrieved).toBe(runner); expect(retrieved).toBe(runner);
}); });
it('should return undefined for unknown command', () => { it("should return undefined for unknown command", () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const retrieved = getCommand(registry, 'unknown'); const retrieved = getCommand(registry, "unknown");
expect(retrieved).toBeUndefined(); expect(retrieved).toBeUndefined();
}); });
}); });
describe('runCommand', () => { describe("runCommand", () => {
it('should run a command successfully', async () => { it("should run a command successfully", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const runner: CommandRunner<TestContext, number> = { const runner: CommandRunner<TestContext, number> = {
schema: parseCommandSchema('add <a> <b>'), schema: parseCommandSchema("add <a> <b>"),
run: async function (cmd) { run: async function (cmd) {
return Number(cmd.params[0]) + Number(cmd.params[1]); return Number(cmd.params[0]) + Number(cmd.params[1]);
}, },
}; };
registerCommand(registry, runner); registerCommand(registry, runner);
const result = await runCommand(registry, { counter: 0, log: [] }, 'add 1 2'); const result = await runCommand(
registry,
{ counter: 0, log: [] },
"add 1 2",
);
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toBe(3); expect(result.result).toBe(3);
} }
}); });
it('should fail for unknown command', async () => { it("should fail for unknown command", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const result = await runCommand(registry, { counter: 0, log: [] }, 'unknown'); const result = await runCommand(
registry,
{ counter: 0, log: [] },
"unknown",
);
expect(result.success).toBe(false); expect(result.success).toBe(false);
if (!result.success) { if (!result.success) {
expect(result.error).toContain('Unknown command'); expect(result.error).toContain("Unknown command");
} }
}); });
it('should fail for invalid command params', async () => { it("should fail for invalid command params", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const runner: CommandRunner<TestContext> = { const runner: CommandRunner<TestContext> = {
schema: parseCommandSchema('add <a> <b>'), schema: parseCommandSchema("add <a> <b>"),
run: async () => {}, run: async () => {},
}; };
registerCommand(registry, runner); registerCommand(registry, runner);
const result = await runCommand(registry, { counter: 0, log: [] }, 'add 1'); const result = await runCommand(registry, { counter: 0, log: [] }, "add 1");
expect(result.success).toBe(false); expect(result.success).toBe(false);
if (!result.success) { if (!result.success) {
expect(result.error).toContain('参数不足'); expect(result.error).toContain("参数不足");
} }
}); });
it('should access context via this.context', async () => { it("should access context via this.context", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const runner: CommandRunner<TestContext, number> = { const runner: CommandRunner<TestContext, number> = {
schema: parseCommandSchema('increment'), schema: parseCommandSchema("increment"),
run: async function () { run: async function () {
this.context.counter++; this.context.counter++;
return this.context.counter; return this.context.counter;
@ -121,30 +132,30 @@ describe('runCommand', () => {
registerCommand(registry, runner); registerCommand(registry, runner);
const ctx = { counter: 0, log: [] }; const ctx = { counter: 0, log: [] };
await runCommand(registry, ctx, 'increment'); await runCommand(registry, ctx, "increment");
expect(ctx.counter).toBe(1); expect(ctx.counter).toBe(1);
}); });
it('should handle async errors', async () => { it("should handle async errors", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const runner: CommandRunner<TestContext> = { const runner: CommandRunner<TestContext> = {
schema: parseCommandSchema('fail'), schema: parseCommandSchema("fail"),
run: async () => { run: async () => {
throw new Error('Something went wrong'); throw new Error("Something went wrong");
}, },
}; };
registerCommand(registry, runner); registerCommand(registry, runner);
const result = await runCommand(registry, { counter: 0, log: [] }, 'fail'); const result = await runCommand(registry, { counter: 0, log: [] }, "fail");
expect(result.success).toBe(false); expect(result.success).toBe(false);
if (!result.success) { if (!result.success) {
expect(result.error).toBe('Something went wrong'); expect(result.error).toBe("Something went wrong");
} }
}); });
}); });
describe('CommandRunnerContext', () => { describe("CommandRunnerContext", () => {
it('should create a runner context', () => { it("should create a runner context", () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const ctx = { counter: 0, log: [] }; const ctx = { counter: 0, log: [] };
const runnerCtx = createCommandRunnerContext(registry, ctx); const runnerCtx = createCommandRunnerContext(registry, ctx);
@ -152,10 +163,10 @@ describe('CommandRunnerContext', () => {
expect(runnerCtx.context).toBe(ctx); expect(runnerCtx.context).toBe(ctx);
}); });
it('should run commands via runner context', async () => { it("should run commands via runner context", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const runner: CommandRunner<TestContext, string> = { const runner: CommandRunner<TestContext, string> = {
schema: parseCommandSchema('greet <name>'), schema: parseCommandSchema("greet <name>"),
run: async function (cmd) { run: async function (cmd) {
this.context.log.push(`Hello, ${cmd.params[0]}!`); this.context.log.push(`Hello, ${cmd.params[0]}!`);
return `Hello, ${cmd.params[0]}!`; return `Hello, ${cmd.params[0]}!`;
@ -165,19 +176,19 @@ describe('CommandRunnerContext', () => {
const ctx = { counter: 0, log: [] }; const ctx = { counter: 0, log: [] };
const runnerCtx = createCommandRunnerContext(registry, ctx); const runnerCtx = createCommandRunnerContext(registry, ctx);
const result = await runnerCtx.run('greet World'); const result = await runnerCtx.run("greet World");
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toBe('Hello, World!'); expect(result.result).toBe("Hello, World!");
} }
expect(ctx.log).toEqual(['Hello, World!']); expect(ctx.log).toEqual(["Hello, World!"]);
}); });
it('should allow commands to call other commands via this.run', async () => { it("should allow commands to call other commands via this.run", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const addRunner: CommandRunner<TestContext, number> = { const addRunner: CommandRunner<TestContext, number> = {
schema: parseCommandSchema('add <a> <b>'), schema: parseCommandSchema("add <a> <b>"),
run: async function (cmd) { run: async function (cmd) {
return Number(cmd.params[0]) + Number(cmd.params[1]); return Number(cmd.params[0]) + Number(cmd.params[1]);
}, },
@ -186,12 +197,12 @@ describe('CommandRunnerContext', () => {
registerCommand(registry, addRunner); registerCommand(registry, addRunner);
const multiplyRunner: CommandRunner<TestContext, number> = { const multiplyRunner: CommandRunner<TestContext, number> = {
schema: parseCommandSchema('multiply <a> <b>'), schema: parseCommandSchema("multiply <a> <b>"),
run: async function (cmd) { run: async function (cmd) {
const a = Number(cmd.params[0]); const a = Number(cmd.params[0]);
const b = Number(cmd.params[1]); const b = Number(cmd.params[1]);
const addResult = await this.run(`add ${a} ${a}`); const addResult = await this.run(`add ${a} ${a}`);
if (!addResult.success) throw new Error('add failed'); if (!addResult.success) throw new Error("add failed");
return (addResult.result as number) * b; return (addResult.result as number) * b;
}, },
}; };
@ -199,18 +210,18 @@ describe('CommandRunnerContext', () => {
registerCommand(registry, multiplyRunner); registerCommand(registry, multiplyRunner);
const ctx = { counter: 0, log: [] }; const ctx = { counter: 0, log: [] };
const result = await runCommand(registry, ctx, 'multiply 3 4'); const result = await runCommand(registry, ctx, "multiply 3 4");
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toBe(24); expect(result.result).toBe(24);
} }
}); });
it('should allow commands to call other commands via this.runParsed', async () => { it("should allow commands to call other commands via this.runParsed", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const doubleRunner: CommandRunner<TestContext, number> = { const doubleRunner: CommandRunner<TestContext, number> = {
schema: parseCommandSchema('double <n>'), schema: parseCommandSchema("double <n>"),
run: async function (cmd) { run: async function (cmd) {
return Number(cmd.params[0]) * 2; return Number(cmd.params[0]) * 2;
}, },
@ -219,11 +230,16 @@ describe('CommandRunnerContext', () => {
registerCommand(registry, doubleRunner); registerCommand(registry, doubleRunner);
const quadrupleRunner: CommandRunner<TestContext, number> = { const quadrupleRunner: CommandRunner<TestContext, number> = {
schema: parseCommandSchema('quadruple <n>'), schema: parseCommandSchema("quadruple <n>"),
run: async function (cmd) { run: async function (cmd) {
const n = Number(cmd.params[0]); const n = Number(cmd.params[0]);
const doubleResult = await this.runParsed({ name: 'double', params: [String(n)], options: {}, flags: {} }); const doubleResult = await this.runParsed({
if (!doubleResult.success) throw new Error('double failed'); name: "double",
params: [String(n)],
options: {},
flags: {},
});
if (!doubleResult.success) throw new Error("double failed");
return (doubleResult.result as number) * 2; return (doubleResult.result as number) * 2;
}, },
}; };
@ -231,7 +247,7 @@ describe('CommandRunnerContext', () => {
registerCommand(registry, quadrupleRunner); registerCommand(registry, quadrupleRunner);
const ctx = { counter: 0, log: [] }; const ctx = { counter: 0, log: [] };
const result = await runCommand(registry, ctx, 'quadruple 5'); const result = await runCommand(registry, ctx, "quadruple 5");
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toBe(20); expect(result.result).toBe(20);
@ -239,14 +255,17 @@ describe('CommandRunnerContext', () => {
}); });
}); });
describe('prompt', () => { describe("prompt", () => {
it('should dispatch prompt event with string schema', async () => { it("should dispatch prompt event with string schema", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
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>', (cmd) => cmd.params[0] as string); const result = await this.prompt(
parseCommandSchema("select <card>"),
(cmd) => cmd.params[0] as string,
);
return result; return result;
}, },
}; };
@ -257,27 +276,30 @@ describe('prompt', () => {
let promptEvent: PromptEvent | null = null; let promptEvent: PromptEvent | null = null;
const runnerCtx = createCommandRunnerContext(registry, ctx); const runnerCtx = createCommandRunnerContext(registry, ctx);
runnerCtx.on('prompt', (e) => { runnerCtx.on("prompt", (e) => {
promptEvent = e; promptEvent = e;
}); });
const runPromise = runnerCtx.run('choose'); const runPromise = runnerCtx.run("choose");
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'); promptEvent!.cancel("test cleanup");
await runPromise; await runPromise;
}); });
it('should resolve prompt with valid input', async () => { it("should resolve prompt with valid input", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
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>', (card) => card as string); const result = await this.prompt(
parseCommandSchema("select <card>"),
(card) => card as string,
);
this.context.log.push(`selected ${result}`); this.context.log.push(`selected ${result}`);
return result; return result;
}, },
@ -289,36 +311,36 @@ describe('prompt', () => {
let promptEvent: PromptEvent | null = null; let promptEvent: PromptEvent | null = null;
const runnerCtx = createCommandRunnerContext(registry, ctx); const runnerCtx = createCommandRunnerContext(registry, ctx);
runnerCtx.on('prompt', (e) => { runnerCtx.on("prompt", (e) => {
promptEvent = e; promptEvent = e;
}); });
const runPromise = runnerCtx.run('choose'); const runPromise = runnerCtx.run("choose");
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
expect(promptEvent).not.toBeNull(); expect(promptEvent).not.toBeNull();
const parsed = { name: 'select', params: ['Ace'], options: {}, flags: {} }; const parsed = { name: "select", params: ["Ace"], options: {}, flags: {} };
const error = promptEvent!.tryCommit(parsed); const error = promptEvent!.tryCommit(parsed);
expect(error).toBeNull(); expect(error).toBeNull();
const result = await runPromise; const result = await runPromise;
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toBe('Ace'); expect(result.result).toBe("Ace");
} }
expect(ctx.log).toEqual(['selected Ace']); expect(ctx.log).toEqual(["selected Ace"]);
}); });
it('should reject prompt with invalid input', async () => { it("should reject prompt with invalid input", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const chooseRunner: CommandRunner<TestContext, string> = { const chooseRunner: CommandRunner<TestContext, string> = {
schema: parseCommandSchema('choose'), schema: parseCommandSchema("choose"),
run: async function () { run: async function () {
try { try {
await this.prompt('select <card>', (card) => card as string); await this.prompt("select <card>", (card) => card as string);
return 'unexpected success'; return "unexpected success";
} catch (e) { } catch (e) {
return (e as Error).message; return (e as Error).message;
} }
@ -331,30 +353,30 @@ describe('prompt', () => {
let promptEvent: PromptEvent | null = null; let promptEvent: PromptEvent | null = null;
const runnerCtx = createCommandRunnerContext(registry, ctx); const runnerCtx = createCommandRunnerContext(registry, ctx);
runnerCtx.on('prompt', (e) => { runnerCtx.on("prompt", (e) => {
promptEvent = e; promptEvent = e;
}); });
const runPromise = runnerCtx.run('choose'); const runPromise = runnerCtx.run("choose");
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
expect(promptEvent).not.toBeNull(); expect(promptEvent).not.toBeNull();
promptEvent!.cancel('user cancelled'); promptEvent!.cancel("user cancelled");
const result = await runPromise; const result = await runPromise;
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toBe('user cancelled'); expect(result.result).toBe("user cancelled");
} }
}); });
it('should accept CommandSchema object in prompt', async () => { it("should accept CommandSchema object in prompt", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const schema = parseCommandSchema('pick <item>'); const schema = parseCommandSchema("pick <item>");
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, (item) => item as string); const result = await this.prompt(schema, (item) => item as string);
return result; return result;
@ -367,34 +389,39 @@ describe('prompt', () => {
let promptEvent: PromptEvent | null = null; let promptEvent: PromptEvent | null = null;
const runnerCtx = createCommandRunnerContext(registry, ctx); const runnerCtx = createCommandRunnerContext(registry, ctx);
runnerCtx.on('prompt', (e) => { runnerCtx.on("prompt", (e) => {
promptEvent = e; promptEvent = e;
}); });
const runPromise = runnerCtx.run('pick'); const runPromise = runnerCtx.run("pick");
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('pick'); expect(promptEvent!.schema.name).toBe("pick");
const error = promptEvent!.tryCommit({ name: 'pick', params: ['sword'], options: {}, flags: {} }); const error = promptEvent!.tryCommit({
name: "pick",
params: ["sword"],
options: {},
flags: {},
});
expect(error).toBeNull(); expect(error).toBeNull();
const result = await runPromise; const result = await runPromise;
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toBe('sword'); expect(result.result).toBe("sword");
} }
}); });
it('should allow multiple sequential prompts', async () => { it("should allow multiple sequential prompts", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
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>', (a) => a as string); const first = await this.prompt("first <a>", (a) => a as string);
const second = await this.prompt('second <b>', (b) => b as string); const second = await this.prompt("second <b>", (b) => b as string);
return [first, second]; return [first, second];
}, },
}; };
@ -405,49 +432,56 @@ describe('prompt', () => {
const promptEvents: PromptEvent[] = []; const promptEvents: PromptEvent[] = [];
const runnerCtx = createCommandRunnerContext(registry, ctx); const runnerCtx = createCommandRunnerContext(registry, ctx);
runnerCtx.on('prompt', (e) => { runnerCtx.on("prompt", (e) => {
promptEvents.push(e); promptEvents.push(e);
}); });
const runPromise = runnerCtx.run('multi'); const runPromise = runnerCtx.run("multi");
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
expect(promptEvents.length).toBe(1); expect(promptEvents.length).toBe(1);
expect(promptEvents[0].schema.name).toBe('first'); expect(promptEvents[0].schema.name).toBe("first");
const error1 = promptEvents[0].tryCommit({ name: 'first', params: ['one'], options: {}, flags: {} }); const error1 = promptEvents[0].tryCommit({
name: "first",
params: ["one"],
options: {},
flags: {},
});
expect(error1).toBeNull(); expect(error1).toBeNull();
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
expect(promptEvents.length).toBe(2); expect(promptEvents.length).toBe(2);
expect(promptEvents[1].schema.name).toBe('second'); expect(promptEvents[1].schema.name).toBe("second");
const error2 = promptEvents[1].tryCommit({ name: 'second', params: ['two'], options: {}, flags: {} }); const error2 = promptEvents[1].tryCommit({
name: "second",
params: ["two"],
options: {},
flags: {},
});
expect(error2).toBeNull(); expect(error2).toBeNull();
const result = await runPromise; const result = await runPromise;
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toEqual(['one', 'two']); expect(result.result).toEqual(["one", "two"]);
} }
}); });
it('should validate input with validator function', async () => { it("should validate input with validator function", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
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( const result = await this.prompt("select <card>", (card) => {
'select <card>',
(card) => {
const cardStr = card as string; const cardStr = card as string;
if (!['Ace', 'King', 'Queen'].includes(cardStr)) { if (!["Ace", "King", "Queen"].includes(cardStr)) {
throw `Invalid card: ${cardStr}. Must be Ace, King, or Queen.`; throw `Invalid card: ${cardStr}. Must be Ace, King, or Queen.`;
} }
return cardStr; return cardStr;
} });
);
return result; return result;
}, },
}; };
@ -458,39 +492,49 @@ describe('prompt', () => {
let promptEvent: PromptEvent | null = null; let promptEvent: PromptEvent | null = null;
const runnerCtx = createCommandRunnerContext(registry, ctx); const runnerCtx = createCommandRunnerContext(registry, ctx);
runnerCtx.on('prompt', (e) => { runnerCtx.on("prompt", (e) => {
promptEvent = e; promptEvent = e;
}); });
const runPromise = runnerCtx.run('choose'); const runPromise = runnerCtx.run("choose");
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
expect(promptEvent).not.toBeNull(); expect(promptEvent).not.toBeNull();
// Try invalid input // Try invalid input
const invalidError = promptEvent!.tryCommit({ name: 'select', params: ['Jack'], options: {}, flags: {} }); const invalidError = promptEvent!.tryCommit({
expect(invalidError).toContain('Invalid card: Jack'); name: "select",
params: ["Jack"],
options: {},
flags: {},
});
expect(invalidError).toContain("Invalid card: Jack");
// Try valid input // Try valid input
const validError = promptEvent!.tryCommit({ name: 'select', params: ['Ace'], options: {}, flags: {} }); const validError = promptEvent!.tryCommit({
name: "select",
params: ["Ace"],
options: {},
flags: {},
});
expect(validError).toBeNull(); expect(validError).toBeNull();
const result = await runPromise; const result = await runPromise;
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toBe('Ace'); expect(result.result).toBe("Ace");
} }
}); });
it('should allow cancel with custom reason', async () => { it("should allow cancel with custom reason", async () => {
const registry = createCommandRegistry<TestContext>(); const registry = createCommandRegistry<TestContext>();
const chooseRunner: CommandRunner<TestContext, string> = { const chooseRunner: CommandRunner<TestContext, string> = {
schema: parseCommandSchema('choose'), schema: parseCommandSchema("choose"),
run: async function () { run: async function () {
try { try {
await this.prompt('select <card>', (cmd) => cmd.params[0] as string); 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;
} }
@ -503,21 +547,21 @@ describe('prompt', () => {
let promptEvent: PromptEvent | null = null; let promptEvent: PromptEvent | null = null;
const runnerCtx = createCommandRunnerContext(registry, ctx); const runnerCtx = createCommandRunnerContext(registry, ctx);
runnerCtx.on('prompt', (e) => { runnerCtx.on("prompt", (e) => {
promptEvent = e; promptEvent = e;
}); });
const runPromise = runnerCtx.run('choose'); const runPromise = runnerCtx.run("choose");
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
expect(promptEvent).not.toBeNull(); expect(promptEvent).not.toBeNull();
promptEvent!.cancel('custom cancellation reason'); promptEvent!.cancel("custom cancellation reason");
const result = await runPromise; const result = await runPromise;
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.result).toBe('custom cancellation reason'); expect(result.result).toBe("custom cancellation reason");
} }
}); });
}); });