refactor: change PromptEvent reject/resolve to cancel/tryCommit

This commit is contained in:
hyper
2026-04-02 19:08:14 +08:00
parent a766050773
commit c886e904a8
5 changed files with 155 additions and 33 deletions
+2 -1
View File
@@ -64,7 +64,8 @@ describe('createGameContext', () => {
expect(promptEvent).not.toBeNull();
expect(promptEvent.schema.name).toBe('prompt');
promptEvent.resolve({ name: 'prompt', params: ['yes'], options: {}, flags: {} });
const error = promptEvent.tryCommit({ name: 'prompt', params: ['yes'], options: {}, flags: {} });
expect(error).toBeNull();
const result = await runPromise;
expect(result.success).toBe(true);
+14 -8
View File
@@ -464,7 +464,7 @@ describe('Boop - game flow', () => {
expect(promptEvent).not.toBeNull();
expect(promptEvent.schema.name).toBe('play');
promptEvent.reject(new Error('test end'));
promptEvent.cancel('test end');
const result = await runPromise;
expect(result.success).toBe(false);
@@ -480,7 +480,8 @@ describe('Boop - game flow', () => {
expect(promptEvent).not.toBeNull();
expect(promptEvent.schema.name).toBe('play');
promptEvent.resolve({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
const error = promptEvent.tryCommit({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
expect(error).toBeNull();
const result = await runPromise;
expect(result.success).toBe(true);
@@ -497,12 +498,14 @@ describe('Boop - game flow', () => {
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn white');
const promptEvent1 = await promptPromise;
promptEvent1.resolve({ name: 'play', params: ['black', 2, 2], options: {}, flags: {} });
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['black', 2, 2], options: {}, flags: {} });
expect(error1).not.toBeNull();
const promptEvent2 = await waitForPrompt(ctx);
expect(promptEvent2).not.toBeNull();
promptEvent2.resolve({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
const error2 = promptEvent2.tryCommit({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
expect(error2).toBeNull();
const result = await runPromise;
expect(result.success).toBe(true);
@@ -519,12 +522,14 @@ describe('Boop - game flow', () => {
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn white');
const promptEvent1 = await promptPromise;
promptEvent1.resolve({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['white', 2, 2], options: {}, flags: {} });
expect(error1).not.toBeNull();
const promptEvent2 = await waitForPrompt(ctx);
expect(promptEvent2).not.toBeNull();
promptEvent2.resolve({ name: 'play', params: ['white', 0, 0], options: {}, flags: {} });
const error2 = promptEvent2.tryCommit({ name: 'play', params: ['white', 0, 0], options: {}, flags: {} });
expect(error2).toBeNull();
const result = await runPromise;
expect(result.success).toBe(true);
@@ -543,12 +548,13 @@ describe('Boop - game flow', () => {
const runPromise = ctx.commands.run<{winner: WinnerType}>('turn white');
const promptEvent1 = await promptPromise;
promptEvent1.resolve({ name: 'play', params: ['white', 0, 0], options: {}, flags: {} });
const error1 = promptEvent1.tryCommit({ name: 'play', params: ['white', 0, 0], options: {}, flags: {} });
expect(error1).not.toBeNull();
const promptEvent2 = await waitForPrompt(ctx);
expect(promptEvent2).not.toBeNull();
promptEvent2.reject(new Error('test end'));
promptEvent2.cancel('test end');
const result = await runPromise;
expect(result.success).toBe(false);
+98 -5
View File
@@ -296,7 +296,8 @@ describe('prompt', () => {
expect(promptEvent).not.toBeNull();
const parsed = { name: 'select', params: ['Ace'], options: {}, flags: {} };
promptEvent!.resolve(parsed);
const error = promptEvent!.tryCommit(parsed);
expect(error).toBeNull();
const result = await runPromise;
expect(result.success).toBe(true);
@@ -336,7 +337,7 @@ describe('prompt', () => {
await new Promise((r) => setTimeout(r, 0));
expect(promptEvent).not.toBeNull();
promptEvent!.reject(new Error('user cancelled'));
promptEvent!.cancel('user cancelled');
const result = await runPromise;
expect(result.success).toBe(true);
@@ -373,7 +374,8 @@ describe('prompt', () => {
expect(promptEvent).not.toBeNull();
expect(promptEvent!.schema.name).toBe('pick');
promptEvent!.resolve({ name: 'pick', params: ['sword'], options: {}, flags: {} });
const error = promptEvent!.tryCommit({ name: 'pick', params: ['sword'], options: {}, flags: {} });
expect(error).toBeNull();
const result = await runPromise;
expect(result.success).toBe(true);
@@ -410,13 +412,15 @@ describe('prompt', () => {
expect(promptEvents.length).toBe(1);
expect(promptEvents[0].schema.name).toBe('first');
promptEvents[0].resolve({ name: 'first', params: ['one'], options: {}, flags: {} });
const error1 = promptEvents[0].tryCommit({ name: 'first', params: ['one'], options: {}, flags: {} });
expect(error1).toBeNull();
await new Promise((r) => setTimeout(r, 0));
expect(promptEvents.length).toBe(2);
expect(promptEvents[1].schema.name).toBe('second');
promptEvents[1].resolve({ name: 'second', params: ['two'], options: {}, flags: {} });
const error2 = promptEvents[1].tryCommit({ name: 'second', params: ['two'], options: {}, flags: {} });
expect(error2).toBeNull();
const result = await runPromise;
expect(result.success).toBe(true);
@@ -424,4 +428,93 @@ describe('prompt', () => {
expect(result.result).toEqual(['one', 'two']);
}
});
it('should validate input with validator function', async () => {
const registry = createCommandRegistry<TestContext>();
const chooseRunner: CommandRunner<TestContext, string> = {
schema: parseCommandSchema('choose'),
run: async function () {
const result = await this.prompt(
'select <card>',
(cmd) => {
const card = cmd.params[0] as string;
if (!['Ace', 'King', 'Queen'].includes(card)) {
return `Invalid card: ${card}. Must be Ace, King, or Queen.`;
}
return null;
}
);
return result.params[0] as string;
},
};
registerCommand(registry, chooseRunner);
const ctx = { counter: 0, log: [] };
let promptEvent: PromptEvent | null = null;
const runnerCtx = createCommandRunnerContext(registry, ctx);
runnerCtx.on('prompt', (e) => {
promptEvent = e;
});
const runPromise = runnerCtx.run('choose');
await new Promise((r) => setTimeout(r, 0));
expect(promptEvent).not.toBeNull();
// Try invalid input
const invalidError = promptEvent!.tryCommit({ name: 'select', params: ['Jack'], options: {}, flags: {} });
expect(invalidError).toContain('Invalid card: Jack');
// Try valid input
const validError = promptEvent!.tryCommit({ name: 'select', params: ['Ace'], options: {}, flags: {} });
expect(validError).toBeNull();
const result = await runPromise;
expect(result.success).toBe(true);
if (result.success) {
expect(result.result).toBe('Ace');
}
});
it('should allow cancel with custom reason', async () => {
const registry = createCommandRegistry<TestContext>();
const chooseRunner: CommandRunner<TestContext, string> = {
schema: parseCommandSchema('choose'),
run: async function () {
try {
await this.prompt('select <card>');
return 'unexpected success';
} catch (e) {
return (e as Error).message;
}
},
};
registerCommand(registry, chooseRunner);
const ctx = { counter: 0, log: [] };
let promptEvent: PromptEvent | null = null;
const runnerCtx = createCommandRunnerContext(registry, ctx);
runnerCtx.on('prompt', (e) => {
promptEvent = e;
});
const runPromise = runnerCtx.run('choose');
await new Promise((r) => setTimeout(r, 0));
expect(promptEvent).not.toBeNull();
promptEvent!.cancel('custom cancellation reason');
const result = await runPromise;
expect(result.success).toBe(true);
if (result.success) {
expect(result.result).toBe('custom cancellation reason');
}
});
});