diff --git a/libraries/botbuilder-dialogs/src/prompts/confirmPrompt.ts b/libraries/botbuilder-dialogs/src/prompts/confirmPrompt.ts index 7b8b94efcd..7ca70a8e09 100644 --- a/libraries/botbuilder-dialogs/src/prompts/confirmPrompt.ts +++ b/libraries/botbuilder-dialogs/src/prompts/confirmPrompt.ts @@ -7,7 +7,7 @@ */ import * as Recognizers from '@microsoft/recognizers-text-choice'; import { Activity, TurnContext } from 'botbuilder-core'; -import { Choice, ChoiceFactoryOptions } from '../choices'; +import { Choice, ChoiceFactoryOptions, recognizeChoices } from '../choices'; import { ListStyle, Prompt, PromptOptions, PromptRecognizerResult, PromptValidator } from './prompt'; /** @@ -22,7 +22,7 @@ export class ConfirmPrompt extends Prompt { /** * Default confirm choices for a range of locales. */ - public static defaultConfirmChoices: { [locale: string]: (string|Choice)[] } = { + public static defaultConfirmChoices: { [locale: string]: (string | Choice)[] } = { 'es-es': ['Sí', 'No'], 'nl-nl': ['Ja', 'Nee'], 'en-us': ['Yes', 'No'], @@ -49,7 +49,7 @@ export class ConfirmPrompt extends Prompt { /** * The prompts default locale that should be recognized. */ - public defaultLocale: string|undefined; + public defaultLocale: string | undefined; /** * Style of the "yes" and "no" choices rendered to the user when prompting. @@ -63,12 +63,12 @@ export class ConfirmPrompt extends Prompt { * Additional options passed to the `ChoiceFactory` and used to tweak the style of choices * rendered to the user. */ - public choiceOptions: ChoiceFactoryOptions|undefined; + public choiceOptions: ChoiceFactoryOptions | undefined; /** * Custom list of choices to send for the prompt. */ - public confirmChoices: (string|Choice)[]|undefined; + public confirmChoices: (string | Choice)[] | undefined; /** * Creates a new ConfirmPrompt instance. @@ -113,6 +113,19 @@ export class ConfirmPrompt extends Prompt { if (results.length > 0 && results[0].resolution) { result.succeeded = true; result.value = results[0].resolution.value; + } else { + // If the prompt text was sent to the user with numbers, the prompt should recognize number choices. + const choiceOptions = this.choiceOptions || ConfirmPrompt.defaultChoiceOptions[locale]; + + if (typeof choiceOptions.includeNumbers !== 'boolean' || choiceOptions.includeNumbers) { + const confirmChoices = this.confirmChoices || ConfirmPrompt.defaultConfirmChoices[locale]; + const choices = [confirmChoices[0], confirmChoices[1]]; + const secondOrMoreAttemptResults = recognizeChoices(utterance, choices); + if (secondOrMoreAttemptResults.length > 0) { + result.succeeded = true; + result.value = secondOrMoreAttemptResults[0].resolution.index === 0; + } + } } return result; diff --git a/libraries/botbuilder-dialogs/src/prompts/prompt.ts b/libraries/botbuilder-dialogs/src/prompts/prompt.ts index 2c01ec8be7..f2f8c383c3 100644 --- a/libraries/botbuilder-dialogs/src/prompts/prompt.ts +++ b/libraries/botbuilder-dialogs/src/prompts/prompt.ts @@ -298,6 +298,8 @@ export abstract class Prompt extends Dialog { // Update prompt with text and actions if (typeof prompt === 'object') { + // Clone the prompt Activity as to not modify the original prompt. + prompt = JSON.parse(JSON.stringify(prompt)) as Activity; prompt.text = msg.text; if (msg.suggestedActions && Array.isArray(msg.suggestedActions.actions) && msg.suggestedActions.actions.length > 0) { prompt.suggestedActions = msg.suggestedActions; diff --git a/libraries/botbuilder-dialogs/tests/confirmPrompt.test.js b/libraries/botbuilder-dialogs/tests/confirmPrompt.test.js index 6e05b4b2b4..aa574d09ee 100644 --- a/libraries/botbuilder-dialogs/tests/confirmPrompt.test.js +++ b/libraries/botbuilder-dialogs/tests/confirmPrompt.test.js @@ -18,7 +18,7 @@ describe('ConfirmPrompt', function () { if (results.status === DialogTurnStatus.empty) { await dc.prompt('prompt', { prompt: 'Please confirm.' }); } else if (results.status === DialogTurnStatus.complete) { - await turnContext.sendActivity(`The result found is '${results.result}'.`); + await turnContext.sendActivity(`The result found is '${ results.result }'.`); } await convoState.saveChanges(turnContext); }); @@ -45,7 +45,7 @@ describe('ConfirmPrompt', function () { if (results.status === DialogTurnStatus.empty) { await dc.prompt('prompt', { prompt: 'Please confirm. Yes or No' }); } else if (results.status === DialogTurnStatus.complete) { - await turnContext.sendActivity(`The result found is '${results.result}'.`); + await turnContext.sendActivity(`The result found is '${ results.result }'.`); } await convoState.saveChanges(turnContext); }); @@ -78,7 +78,7 @@ describe('ConfirmPrompt', function () { retryPrompt: `Please reply with 'Yes' or 'No'.` }); } else if (results.status === DialogTurnStatus.complete) { - await turnContext.sendActivity(`The result found is '${results.result}'.`); + await turnContext.sendActivity(`The result found is '${ results.result }'.`); } await convoState.saveChanges(turnContext); }); @@ -111,7 +111,7 @@ describe('ConfirmPrompt', function () { retryPrompt: `Please reply with 'Yes' or 'No'.` }); } else if (results.status === DialogTurnStatus.complete) { - await turnContext.sendActivity(`The result found is '${results.result}'.`); + await turnContext.sendActivity(`The result found is '${ results.result }'.`); } await convoState.saveChanges(turnContext); }); @@ -146,7 +146,7 @@ describe('ConfirmPrompt', function () { retryPrompt: `Please reply with 'Yes' or 'No'.` }); } else if (results.status === DialogTurnStatus.complete) { - await turnContext.sendActivity(`The result found is '${results.result}'.`); + await turnContext.sendActivity(`The result found is '${ results.result }'.`); } await convoState.saveChanges(turnContext); }); @@ -182,7 +182,7 @@ describe('ConfirmPrompt', function () { if (results.status === DialogTurnStatus.empty) { await dc.beginDialog('prompt'); } else if (results.status === DialogTurnStatus.complete) { - await turnContext.sendActivity(`The result found is '${results.result}'.`); + await turnContext.sendActivity(`The result found is '${ results.result }'.`); } await convoState.saveChanges(turnContext); }); @@ -313,4 +313,161 @@ describe('ConfirmPrompt', function () { .send({ text: 'いいえ', type: ActivityTypes.Message, locale: 'ja-jp' }) .assertReply('false'); }); + + it('should recognize yes with no PromptOptions.', async function () { + const adapter = new TestAdapter(async (turnContext) => { + const dc = await dialogs.createContext(turnContext); + + const results = await dc.continueDialog(); + if (results.status === DialogTurnStatus.empty) { + await dc.prompt('prompt'); + } else if (results.status === DialogTurnStatus.complete) { + await turnContext.sendActivity(`The result found is '${ results.result }'.`); + } + await convoState.saveChanges(turnContext); + }); + + const convoState = new ConversationState(new MemoryStorage()); + + const dialogState = convoState.createProperty('dialogState'); + const dialogs = new DialogSet(dialogState); + const prompt = new ConfirmPrompt('prompt'); + prompt.choiceOptions = { includeNumbers: true }; + dialogs.add(prompt); + + await adapter.send('Hello') + .assertReply(' (1) Yes or (2) No') + .send('lala') + .assertReply(' (1) Yes or (2) No') + .send('yes') + .assertReply(`The result found is 'true'.`); + }); + + it('should recognize valid number when choiceOptions.includeNumbers is true.', async function () { + const adapter = new TestAdapter(async (turnContext) => { + const dc = await dialogs.createContext(turnContext); + + const results = await dc.continueDialog(); + if (results.status === DialogTurnStatus.empty) { + await dc.prompt('prompt', { + prompt: { text: 'Please confirm.', type: ActivityTypes.Message }, + retryPrompt: { text: 'Please confirm, say "yes" or "no" or something like that.', type: ActivityTypes.Message } + }); + } else if (results.status === DialogTurnStatus.complete) { + await turnContext.sendActivity(`The result found is '${ results.result }'.`); + } + await convoState.saveChanges(turnContext); + }); + + const convoState = new ConversationState(new MemoryStorage()); + + const dialogState = convoState.createProperty('dialogState'); + const dialogs = new DialogSet(dialogState); + const prompt = new ConfirmPrompt('prompt'); + prompt.choiceOptions = { includeNumbers: true }; + dialogs.add(prompt); + + await adapter.send('Hello') + .assertReply('Please confirm. (1) Yes or (2) No') + .send('lala') + .assertReply('Please confirm, say "yes" or "no" or something like that. (1) Yes or (2) No') + .send('1') + .assertReply(`The result found is 'true'.`); + }); + + it('should not recognize invalid number when choiceOptions.includeNumbers is true.', async function () { + const adapter = new TestAdapter(async (turnContext) => { + const dc = await dialogs.createContext(turnContext); + + const results = await dc.continueDialog(); + if (results.status === DialogTurnStatus.empty) { + await dc.prompt('prompt', { + prompt: { text: 'Please confirm.', type: ActivityTypes.Message }, + retryPrompt: { text: 'Please confirm, say "yes" or "no" or something like that.', type: ActivityTypes.Message } + }); + } else if (results.status === DialogTurnStatus.complete) { + await turnContext.sendActivity(`The result found is '${ results.result }'.`); + } + await convoState.saveChanges(turnContext); + }); + + const convoState = new ConversationState(new MemoryStorage()); + + const dialogState = convoState.createProperty('dialogState'); + const dialogs = new DialogSet(dialogState); + const prompt = new ConfirmPrompt('prompt'); + prompt.choiceOptions = { includeNumbers: true }; + dialogs.add(prompt); + + await adapter.send('Hello') + .assertReply('Please confirm. (1) Yes or (2) No') + .send('400') + .assertReply('Please confirm, say "yes" or "no" or something like that. (1) Yes or (2) No') + .send('1') + .assertReply(`The result found is 'true'.`); + }); + + it('should not recognize valid number choice when choiceOptions.includeNumbers is false.', async function () { + const adapter = new TestAdapter(async (turnContext) => { + const dc = await dialogs.createContext(turnContext); + + const results = await dc.continueDialog(); + if (results.status === DialogTurnStatus.empty) { + await dc.prompt('prompt', { + prompt: { text: 'Please confirm.', type: ActivityTypes.Message }, + retryPrompt: { text: 'Please confirm, say "yes" or "no" or something like that.', type: ActivityTypes.Message } + }); + } else if (results.status === DialogTurnStatus.complete) { + await turnContext.sendActivity(`The result found is '${ results.result }'.`); + } + await convoState.saveChanges(turnContext); + }); + + const convoState = new ConversationState(new MemoryStorage()); + + const dialogState = convoState.createProperty('dialogState'); + const dialogs = new DialogSet(dialogState); + const prompt = new ConfirmPrompt('prompt'); + prompt.choiceOptions = { includeNumbers: false, inlineSeparator: '~' }; + dialogs.add(prompt); + + await adapter.send('Hello') + .assertReply('Please confirm. Yes or No') + .send('1') + .assertReply('Please confirm, say "yes" or "no" or something like that. Yes or No') + .send('no') + .assertReply(`The result found is 'false'.`); + }); + + it('should recognize valid number when choiceOptions.includeNumbers is undefined.', async function () { + const adapter = new TestAdapter(async (turnContext) => { + const dc = await dialogs.createContext(turnContext); + + const results = await dc.continueDialog(); + if (results.status === DialogTurnStatus.empty) { + await dc.prompt('prompt', { + prompt: { text: 'Please confirm.', type: ActivityTypes.Message }, + retryPrompt: { text: 'Please confirm, say "yes" or "no" or something like that.', type: ActivityTypes.Message } + }); + } else if (results.status === DialogTurnStatus.complete) { + await turnContext.sendActivity(`The result found is '${ results.result }'.`); + } + await convoState.saveChanges(turnContext); + }); + + const convoState = new ConversationState(new MemoryStorage()); + + const dialogState = convoState.createProperty('dialogState'); + const dialogs = new DialogSet(dialogState); + const prompt = new ConfirmPrompt('prompt'); + prompt.choiceOptions = { includeNumbers: undefined }; + dialogs.add(prompt); + + await adapter.send('Hello') + .assertReply('Please confirm. Yes or No') + .send('lala') + .assertReply('Please confirm, say "yes" or "no" or something like that. Yes or No') + .send('1') + .assertReply(`The result found is 'true'.`); + }); });