diff --git a/packages/botkit/src/conversation.ts b/packages/botkit/src/conversation.ts index 6db2ff0ae..68ac4e00f 100644 --- a/packages/botkit/src/conversation.ts +++ b/packages/botkit/src/conversation.ts @@ -562,8 +562,12 @@ export class BotkitConversation extends Dialog { */ public async resumeDialog(dc, reason, result): Promise { // Increment step index and run step - const state = dc.activeDialog.state; - return await this.runStep(dc, state.stepIndex + 1, state.thread || 'default', reason, result); + if (dc.activeDialog) { + const state = dc.activeDialog.state; + return await this.runStep(dc, state.stepIndex + 1, state.thread || 'default', reason, result); + } else { + return Dialog.EndOfTurn; + } } /** @@ -647,6 +651,11 @@ export class BotkitConversation extends Dialog { } } + // was the dialog canceled during the last action? + if (!dc.activeDialog) { + return await this.end(dc); + } + // Handle the current step if (step.index < thread.length) { let line = thread[step.index]; @@ -745,12 +754,17 @@ export class BotkitConversation extends Dialog { public async end(dc: DialogContext): Promise { // TODO: may have to move these around // shallow copy todo: may need deep copy - const result = { - ...dc.activeDialog.state.values - }; + // protect against canceled dialog. + if (dc.activeDialog && dc.activeDialog.state) { + const result = { + ...dc.activeDialog.state.values + }; + await dc.endDialog(result); + await this.runAfter(dc, result); + } else { + await dc.endDialog(); + } - await dc.endDialog(result); - await this.runAfter(dc, result); return DialogTurnStatus.complete; } @@ -942,6 +956,9 @@ export class BotkitConversation extends Dialog { await path.handler.call(this, step.result, convo, bot); + if (!dc.activeDialog) { + return false; + } // did we just change threads? if so, restart this turn if (index !== step.index || thread_name !== step.thread) { return await this.runStep(dc, step.index, step.thread, DialogReason.nextCalled, null); diff --git a/packages/botkit/tests/Dialog.tests.js b/packages/botkit/tests/Dialog.tests.js index ca5ca6895..48ad4e8c8 100644 --- a/packages/botkit/tests/Dialog.tests.js +++ b/packages/botkit/tests/Dialog.tests.js @@ -443,6 +443,75 @@ describe('Botkit dialog', function() { assert(correct,'results did not match expected format'); }); + it('should allow cancel', async function() { + + let after_fired = false; + const botConvo = new BotkitConversation('testConvo', bot); + botConvo.say('errr'); + botConvo.say('boo'); + botConvo.ask('huh?', async(response, convo, bot) => { + await bot.cancelAllDialogs(); + },'wha'); + botConvo.say('foo'); + botConvo.after(async(results, bot) => { + after_fired = true; + }); + bot.addDialog(botConvo); + + // set up a test client + const client = new BotkitTestClient('test', bot, 'testConvo'); + let msg = await client.sendActivity('..'); + assert(msg.text == 'errr','no errr'); + msg = await client.getNextReply(); + assert(msg.text == 'boo','no boo'); + msg = await client.getNextReply(); + assert(msg.text == 'huh?', 'wrong prompt'); + msg = await client.sendActivity('..'); + assert(msg == null,'did not cancel'); + msg = await client.getNextReply(); + assert(msg == null,'did not cancel 2'); + assert(after_fired === false, 'after fired after cancel'); + + }); + + + it('should allow cancel inside child dialog', async function() { + + let after_fired = false; + let after_fired2 = false; + + const botConvo = new BotkitConversation('testConvo', bot); + const botConvo2 = new BotkitConversation('testConvo2', bot); + botConvo.say('hi'); + botConvo.addChildDialog('testConvo2'); + botConvo.say('foo'); + botConvo2.ask('huh?', async(r, convo, bot) => { + await bot.cancelAllDialogs(); + }); + botConvo2.say('blarg'); + + botConvo.after(async(results, bot) => { + after_fired = true; + }); + botConvo2.after(async(results, bot) => { + after_fired2 = true; + }); + + bot.addDialog(botConvo); + bot.addDialog(botConvo2); + + // set up a test client + const client = new BotkitTestClient('test', bot, ['testConvo', 'testConvo2']); + let msg = await client.sendActivity('..'); + assert(msg.text === 'hi', 'wrong msg 1'); + msg = await client.getNextReply(); + assert(msg.text == 'huh?', 'wrong msg 2'); + msg = await client.sendActivity('..'); + assert(msg == null, 'did not cancel'); + assert(after_fired === false,'after dialog fired'); + assert(after_fired2 === false,'after dialog of child fired'); + + }); afterEach(async () => { await bot.shutdown(); });