test: reformat and expand test coverage
This commit is contained in:
parent
b83ff28f60
commit
974c1a828c
|
|
@ -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,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue