Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix ConfirmPrompt and Prompt.appendChoices() bugs, add tests #537

Merged
merged 2 commits into from
Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions libraries/botbuilder-dialogs/src/prompts/confirmPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -22,7 +22,7 @@ export class ConfirmPrompt extends Prompt<boolean> {
/**
* 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'],
Expand All @@ -49,7 +49,7 @@ export class ConfirmPrompt extends Prompt<boolean> {
/**
* 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.
Expand All @@ -63,12 +63,12 @@ export class ConfirmPrompt extends Prompt<boolean> {
* 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.
Expand Down Expand Up @@ -113,6 +113,19 @@ export class ConfirmPrompt extends Prompt<boolean> {
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;
Expand Down
2 changes: 2 additions & 0 deletions libraries/botbuilder-dialogs/src/prompts/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ export abstract class Prompt<T> 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;
Expand Down
169 changes: 163 additions & 6 deletions libraries/botbuilder-dialogs/tests/confirmPrompt.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand All @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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'.`);
});
});