diff --git a/libraries/botbuilder-lg/package.json b/libraries/botbuilder-lg/package.json index a523a06b08..db0a7bd6e9 100644 --- a/libraries/botbuilder-lg/package.json +++ b/libraries/botbuilder-lg/package.json @@ -22,7 +22,8 @@ "antlr4ts": "0.5.0-alpha.1", "botframework-expressions": "4.1.6", "lodash": "^4.17.11", - "uuid": "^3.3.3" + "uuid": "^3.3.3", + "botbuilder-core": "4.1.6" }, "devDependencies": { "@types/mocha": "^5.2.5", diff --git a/libraries/botbuilder-lg/src/activityFactory.ts b/libraries/botbuilder-lg/src/activityFactory.ts new file mode 100644 index 0000000000..4e678f7528 --- /dev/null +++ b/libraries/botbuilder-lg/src/activityFactory.ts @@ -0,0 +1,357 @@ +/** + * @module botbuilder-lg + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { Activity, SuggestedActions, Attachment, ActivityTypes, ActionTypes, CardAction } from 'botframework-schema'; +import { MessageFactory, CardFactory } from 'botbuilder-core'; + +/** + * The ActivityFactory + * to generate text and then uses simple markdown semantics like chatdown to create Activity. + */ +export class ActivityFactory { + private static genericCardTypeMapping: Map = new Map + ([ + [ 'herocard', CardFactory.contentTypes.heroCard ], + [ 'thumbnailcard', CardFactory.contentTypes.thumbnailCard ], + [ 'audiocard', CardFactory.contentTypes.audioCard ], + [ 'videocard', CardFactory.contentTypes.videoCard ], + [ 'animationcard', CardFactory.contentTypes.animationCard ], + [ 'signincard', CardFactory.contentTypes.signinCard ], + [ 'oauthcard', CardFactory.contentTypes.oauthCard ], + ]); + + /** + * Generate the activity. + * @param lgResult string result from languageGenerator. + */ + public static CreateActivity(lgResult: any): Partial { + if (typeof lgResult === 'string') { + return this.buildActivityFromText(lgResult.trim()); + } + + return this.buildActivityFromLGStructuredResult(lgResult); + } + + /** + * Given a lg result, create a text activity. This method will create a MessageActivity from text. + * @param text lg text output. + */ + private static buildActivityFromText(text: string): Partial { + return MessageFactory.text(text, text); + } + + /** + * Given a structured lg result, create an activity. This method will create an MessageActivity from object + * @param lgValue lg output. + */ + private static buildActivityFromLGStructuredResult(lgValue: any): Partial { + let activity: Partial = {}; + + const type: string = this.getStructureType(lgValue); + if (ActivityFactory.genericCardTypeMapping.has(type)) { + const attachment: Attachment = this.getAttachment(lgValue); + if (attachment !== undefined) { + activity = MessageFactory.attachment(attachment); + } else { + throw new Error(`'${ lgValue }' is not an attachment format.`); + } + } else if (type === 'activity') { + activity = this.buildActivityFromObject(lgValue); + } else { + throw new Error(`type ${ type } is not support currently.`); + } + + return activity; + } + + private static buildActivityFromObject(activityValue: any): Partial { + let activity: Partial = {}; + + if ('type' in activityValue && activityValue.type === 'event') { + activity = this.buildEventActivity(activityValue); + } else { + activity = this.buildMessageActivity(activityValue); + } + + return activity; + } + + private static buildEventActivity(eventValue: any): Partial { + let activity: Partial = { type: ActivityTypes.Event }; + for (const item of Object.keys(eventValue)) { + const property: string = item.trim(); + const value: any = eventValue[item]; + switch (property.toLowerCase()) { + case 'name': + activity.name = value.toString(); + break; + case 'value': + activity.value = value.toString(); + break; + default: + activity[property] = value; + break; + } + } + + return activity; + } + + private static buildMessageActivity(messageValue: any): Partial { + let activity: Partial = { type: ActivityTypes.Message }; + for (const key of Object.keys(messageValue)) { + const property: string = key.trim(); + const value: any = messageValue[key]; + + switch (property.toLowerCase()) { + case 'text': + activity.text = value.toString(); + break; + case 'speak': + activity.speak = value.toString(); + break; + case 'inputhint': + activity.inputHint = value.toString(); + break; + case 'attachments': + activity.attachments = this.getAttachments(value); + break; + case 'suggestedactions': + activity.suggestedActions = this.getSuggestions(value); + break; + case 'attachmentlayout': + activity.attachmentLayout = value.toString(); + default: + activity[property] = value; + break; + } + } + + return activity; + } + + private static getSuggestions(suggestionsValue: any): SuggestedActions { + let actions: any[] = this.normalizedToList(suggestionsValue); + + let suggestedActions: SuggestedActions = { + actions : this.getCardActions(actions), + to: [] + }; + + return suggestedActions; + } + + private static getButtons(buttonsValue: any): CardAction[] { + let actions: any[] = this.normalizedToList(buttonsValue); + return this.getCardActions(actions); + } + + private static getCardActions(actions: any[]): CardAction[] { + let cardActions: (string|CardAction)[] = []; + for (const action of actions) { + if (typeof action === 'string') { + cardActions.push(action.trim()); + } else { + const cardAction: CardAction = this.getCardAction(action); + if (cardAction !== undefined) { + cardActions.push(cardAction); + } + } + } + + return CardFactory.actions(cardActions); + } + + private static getCardAction(cardActionValue: any): CardAction { + const type: string = this.getStructureType(cardActionValue); + let cardAction: CardAction = { + type: ActionTypes.ImBack, + title: '', + value: '' + }; + + if(type !== 'cardaction') { + return undefined; + } + + for (const key of Object.keys(cardActionValue)) { + const property: string = key.trim(); + const value: any = cardActionValue[key]; + + switch (property.toLowerCase()) { + case 'type': + cardAction.type = value.toString(); + break; + case 'title': + cardAction.title = value.toString(); + break; + case 'value': + cardAction.value = value.toString(); + break; + case 'displaytext': + cardAction.displayText = value.toString(); + break; + case 'text': + cardAction.text = value.toString(); + break; + case 'image': + cardAction.image = value.toString(); + break; + default: + cardAction[property] = value; + break; + } + } + + return cardAction; + } + + private static getStructureType(input: any): string { + let result = ''; + + if (input !== undefined) { + if ('$type' in input) { + result = input['$type'].toString(); + } else if ('type' in input) { + result = input['type'].toString(); + } + } + + return result.trim().toLowerCase(); + } + + private static getAttachments(input: any): Attachment[] { + let attachments: Attachment[] = []; + let attachmentsJsonList: any[] = this.normalizedToList(input); + + for (const attachmentsJson of attachmentsJsonList) { + if (typeof attachmentsJson === 'object') { + const attachment = this.getAttachment(attachmentsJson); + if (attachment !== undefined) { + attachments.push(attachment); + } else { + throw new Error(`'${ attachmentsJson }' is not an attachment format.`); + } + } else { + throw new Error(`'${ attachmentsJson }' is not an attachment format.`); + } + } + + return attachments; + } + + private static getAttachment(input: any): Attachment { + let attachment: Attachment = { + contentType: '' + }; + const type: string = this.getStructureType(input); + if (ActivityFactory.genericCardTypeMapping.has(type)) { + attachment = this.getCardAttachment(ActivityFactory.genericCardTypeMapping.get(type), input); + } else if(type === 'adaptivecard') { + attachment = CardFactory.adaptiveCard(input); + } else { + attachment = undefined; + } + + return attachment; + } + + private static getCardAttachment(type: string, input: any): Attachment { + let card: any = {}; + + for (const key in input) { + const property: string = key.trim().toLowerCase(); + const value: any = input[key]; + + switch (property) { + case 'title': + case 'subtitle': + case 'text': + case 'aspect': + case 'value': + card[property] = value; + break; + case 'connectionname': + card['connectionName'] = value; + break; + + case 'image': + case 'images': + if (type === CardFactory.contentTypes.heroCard || type === CardFactory.contentTypes.thumbnailCard) { + if (!('images' in card)) { + card['images'] = []; + } + + let imageList: string[] = this.normalizedToList(value).map((u): string => u.toString()); + imageList.forEach( (u): any => card['images'].push({url : u})); + } else { + card['image'] = {url: value.toString()}; + } + break; + case 'media': + if (!('media' in card)) { + card['media'] = []; + } + + let mediaList: string[] = this.normalizedToList(value).map((u): string => u.toString()); + mediaList.forEach( (u): any => card['media'].push({url : u})); + break; + case 'buttons': + if (!('buttons' in card)) { + card['buttons'] = []; + } + + let buttons: any[] = this.getButtons(value); + buttons.forEach( (u): any => card[property].push(u)); + break; + case 'autostart': + case 'shareable': + case 'autoloop': + const boolValue: boolean = this.getValidBooleanValue(value.toString()); + if (boolValue !== undefined) { + card[property] = boolValue; + } + break; + default: + card[property] = value; + break; + } + } + + const attachment: Attachment = { + contentType: type, + content: card + }; + + return attachment; + } + + private static getValidBooleanValue(boolValue: string): boolean{ + if (boolValue.toLowerCase() == 'true') + { + return true; + } + else if (boolValue.toLowerCase() == 'false') + { + return false; + } + + return undefined; + } + + private static normalizedToList(item: any): any[] { + if (item === undefined) { + return []; + } else if (Array.isArray(item)){ + return item; + } else { + return [item]; + } + } + +} \ No newline at end of file diff --git a/libraries/botbuilder-lg/src/index.ts b/libraries/botbuilder-lg/src/index.ts index 11b5b6fe22..0652e1f8a4 100644 --- a/libraries/botbuilder-lg/src/index.ts +++ b/libraries/botbuilder-lg/src/index.ts @@ -22,3 +22,4 @@ export * from './lgImport'; export * from './range'; export * from './position'; export * from './evaluationTarget'; +export * from './activityFactory'; diff --git a/libraries/botbuilder-lg/tests/ActivityFactoryTests.js b/libraries/botbuilder-lg/tests/ActivityFactoryTests.js new file mode 100644 index 0000000000..7979021a5b --- /dev/null +++ b/libraries/botbuilder-lg/tests/ActivityFactoryTests.js @@ -0,0 +1,423 @@ +const { TemplateEngine, ActivityFactory } = require('../'); +const assert = require('assert'); + +function getTemplateEngine(){ + const filePath = `${ __dirname }/testData/Examples/NormalStructuredLG.lg`; + return new TemplateEngine().addFile(filePath); +} + +function getActivity(templateName, data){ + const engine = getTemplateEngine(); + const lgResult = engine.evaluateTemplate(templateName, data); + return ActivityFactory.CreateActivity(lgResult); +} + + +describe('ActivityFactoryTest', function() { + it('HerocardWithCardAction', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('HerocardWithCardAction', data); + assertCardActionActivity(result); + }); + + it('adaptivecardActivity', function() { + let data = { + adaptiveCardTitle: 'test' + }; + let result = getActivity('adaptivecardActivity', data); + assertAdaptiveCardActivity(result); + }); + + it('eventActivity', function() { + let data = { + text: 'textContent', + adaptiveCardTitle: 'test' + }; + let result = getActivity('eventActivity', data); + assertEventActivity(result); + }); + + it('activityWithHeroCardAttachment', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithHeroCardAttachment', data); + assertActivityWithHeroCardAttachment(result); + }); + + it('activityWithMultiAttachments', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithMultiAttachments', data); + assertActivityWithMultiAttachments(result); + }); + + it('activityWithSuggestionActions', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithSuggestionActions', data); + assertActivityWithSuggestionActions(result); + }); + + it('messageActivityAll', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('messageActivityAll', data); + assertMessageActivityAll(result); + }); + + it('activityWithMultiStructuredSuggestionActions', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithMultiStructuredSuggestionActions', data); + assertActivityWithMultiStructuredSuggestionActions(result); + }); + + it('activityWithMultiStringSuggestionActions', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('activityWithMultiStringSuggestionActions', data); + assertActivityWithMultiStringSuggestionActions(result); + }); + + it('HeroCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + type: 'herocard' + }; + let result = getActivity('HeroCardTemplate', data); + assertHeroCardActivity(result); + }); + + it('ThumbnailCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + type: 'thumbnailcard' + }; + let result = getActivity('ThumbnailCardTemplate', data); + assertThumbnailCardActivity(result); + }); + + it('AudioCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + type: 'audiocard' + }; + let result = getActivity('AudioCardTemplate', data); + assertAudioCardActivity(result); + }); + + it('VideoCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + type: 'videocard' + }; + let result = getActivity('VideoCardTemplate', data); + assertVideoCardActivity(result); + }); + + it('SigninCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + signinlabel: 'Sign in', + url: 'https://login.microsoftonline.com/' + }; + let result = getActivity('SigninCardTemplate', data); + assertSigninCardActivity(result); + }); + + it('OAuthCardTemplate', function() { + let data = { + title: 'titleContent', + text: 'textContent', + connectionName: 'MyConnection', + signinlabel: 'Sign in', + url: 'https://login.microsoftonline.com/' + }; + let result = getActivity('OAuthCardTemplate', data); + assertOAuthCardActivity(result); + }); + + it('SuggestedActionsReference', function() { + let data = { + title: 'titleContent', + text: 'textContent' + }; + let result = getActivity('SuggestedActionsReference', data); + assertSuggestedActionsReferenceActivity(result); + }); +}); + +function assertSuggestedActionsReferenceActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 5); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'Add todo'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'View Todo'); + assert.strictEqual(activity.suggestedActions.actions[2].value, 'Remove Todo'); + assert.strictEqual(activity.suggestedActions.actions[3].value, 'Cancel'); + assert.strictEqual(activity.suggestedActions.actions[4].value, 'Help'); +} + +function assertOAuthCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.oauth'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.connectionName, 'MyConnection'); + assert.strictEqual(card.buttons.length, 1); + assert.strictEqual(card.buttons[0].title, 'Sign in'); + assert.strictEqual(card.buttons[0].type, 'signin'); + assert.strictEqual(card.buttons[0].value, 'https://login.microsoftonline.com/'); +} + +function assertSigninCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.signin'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.buttons.length, 1); + assert.strictEqual(card.buttons[0].title, 'Sign in'); + assert.strictEqual(card.buttons[0].type, 'signin'); + assert.strictEqual(card.buttons[0].value, 'https://login.microsoftonline.com/'); +} + +function assertVideoCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.video'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'videocard'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.image.url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.media[0].url, 'https://youtu.be/530FEFogfBQ'); + assert.strictEqual(card.shareable, false); + assert.strictEqual(card.autoloop, true); + assert.strictEqual(card.autostart, true); + assert.strictEqual(card.aspect, '16:9'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + +function assertAudioCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.audio'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'audiocard'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.image.url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.media[0].url, 'https://contoso.com/media/AllegrofromDuetinCMajor.mp3'); + assert.strictEqual(card.shareable, false); + assert.strictEqual(card.autoloop, true); + assert.strictEqual(card.autostart, true); + assert.strictEqual(card.aspect, '16:9'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + + +function assertThumbnailCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.thumbnail'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'thumbnailcard'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.images[0].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.images[1].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + +function assertHeroCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'herocard'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.images[0].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.images[1].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + +function assertActivityWithMultiStringSuggestionActions(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 3); + assert.strictEqual(activity.suggestedActions.actions[0].title, 'first suggestion'); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'first suggestion'); + assert.strictEqual(activity.suggestedActions.actions[1].title, 'second suggestion'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'second suggestion'); + assert.strictEqual(activity.suggestedActions.actions[2].title, 'third suggestion'); + assert.strictEqual(activity.suggestedActions.actions[2].value, 'third suggestion'); +} + +function assertActivityWithMultiStructuredSuggestionActions(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 3); + assert.strictEqual(activity.suggestedActions.actions[0].title, 'first suggestion'); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'first suggestion'); + assert.strictEqual(activity.suggestedActions.actions[1].title, 'second suggestion'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'second suggestion'); + assert.strictEqual(activity.suggestedActions.actions[2].title, 'third suggestion'); + assert.strictEqual(activity.suggestedActions.actions[2].value, 'third suggestion'); +} + +function assertMessageActivityAll(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.speak, 'textContent'); + assert.strictEqual(activity.inputHint, 'accepting'); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'titleContent'); + assert.strictEqual(card.text, 'textContent'); + assert.strictEqual(card.buttons.length, 1, 'should have one button'); + const button = card.buttons[0]; + assert.strictEqual(button.type, 'imBack'); + assert.strictEqual(button.title, 'titleContent'); + assert.strictEqual(button.value, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 2); + assert.strictEqual(activity.suggestedActions.actions[0].title, 'firstItem'); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'firstItem'); + assert.strictEqual(activity.suggestedActions.actions[1].title, 'titleContent'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'textContent'); +} + +function assertActivityWithSuggestionActions(activity) { + assert.strictEqual(activity.type, 'message'); + assert.strictEqual(activity.text, 'textContent'); + assert.strictEqual(activity.suggestedActions.actions.length, 2); + assert.strictEqual(activity.suggestedActions.actions[0].title, 'firstItem'); + assert.strictEqual(activity.suggestedActions.actions[0].value, 'firstItem'); + assert.strictEqual(activity.suggestedActions.actions[1].title, 'titleContent'); + assert.strictEqual(activity.suggestedActions.actions[1].value, 'textContent'); +} + +function assertActivityWithMultiAttachments(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 2); + assert.strictEqual(activity.attachments[1].contentType, 'application/vnd.microsoft.card.thumbnail'); + const card = activity.attachments[1].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'Cheese gromit!'); + assert.strictEqual(card.subtitle, 'type'); + assert.strictEqual(card.text, 'This is some text describing the card, it\'s cool because it\'s cool'); + assert.strictEqual(card.images[0].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.images[1].url, 'https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg'); + assert.strictEqual(card.buttons.length, 3); + + for (let i = 0; i < card.buttons.length; i++) { + assert.strictEqual(card.buttons[i].title, `Option ${ i + 1 }`); + } +} + +function assertActivityWithHeroCardAttachment(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'titleContent'); + assert.strictEqual(card.text, 'textContent'); + assert.strictEqual(card.buttons.length, 1, 'should have one button'); + const button = card.buttons[0]; + assert.strictEqual(button.type, 'imBack'); + assert.strictEqual(button.title, 'titleContent'); + assert.strictEqual(button.value, 'textContent'); +} + +function assertCardActionActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.hero'); + const card = activity.attachments[0].content; + assert(card !== undefined); + assert.strictEqual(card.title, 'titleContent'); + assert.strictEqual(card.text, 'textContent'); + assert.strictEqual(card.buttons.length, 1, 'should have one button'); + const button = card.buttons[0]; + assert.strictEqual(button.type, 'imBack'); + assert.strictEqual(button.title, 'titleContent'); + assert.strictEqual(button.value, 'textContent'); +} + +function assertAdaptiveCardActivity(activity) { + assert.strictEqual(activity.type, 'message'); + assert(activity.text === undefined); + assert(activity.speak === undefined); + assert.strictEqual(activity.attachments.length, 1, 'should have one attachment'); + assert.strictEqual(activity.attachments[0].contentType, 'application/vnd.microsoft.card.adaptive', 'attachment type should be adaptivecard'); + assert.strictEqual(activity.attachments[0].content.body[0].text, 'test', 'text of first body should have value'); +} + +function assertEventActivity(activity) { + assert.strictEqual(activity.type, 'event'); + assert.strictEqual(activity.name, 'textContent'); + assert.strictEqual(activity.value, 'textContent'); +} \ No newline at end of file diff --git a/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg b/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg new file mode 100644 index 0000000000..d0ec65aed1 --- /dev/null +++ b/libraries/botbuilder-lg/tests/testData/examples/NormalStructuredLG.lg @@ -0,0 +1,288 @@ +# HeroCardTemplate(type) +[HeroCard + title=Cheese gromit! + subtitle=@{type} + text=This is some text describing the card, it's cool because it's cool + image=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + images=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + buttons=Option 1| Option 2| Option 3 +] + +# ThumbnailCardTemplate(type) +[ThumbnailCard + title=Cheese gromit! + subtitle=@{type} + text=This is some text describing the card, it's cool because it's cool + image=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + images=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + buttons=Option 1| Option 2| Option 3 +] + +# AudioCardTemplate(type) +[Audiocard + title=Cheese gromit! + subtitle=@{type} + text=This is some text describing the card, it's cool because it's cool + image=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + buttons=Option 1| Option 2| Option 3 + Media=https://contoso.com/media/AllegrofromDuetinCMajor.mp3 + Shareable=false + Autoloop=true + Autostart=true + Aspect=16:9 +] + +# VideoCardTemplate(type) +[VideoCard + title=Cheese gromit! + subtitle=@{type} + text=This is some text describing the card, it's cool because it's cool + image=https://memegenerator.net/img/instances/500x/73055378/cheese-gromit.jpg + buttons=Option 1| Option 2| Option 3 + Media=https://youtu.be/530FEFogfBQ + Shareable=false + Autoloop=true + Autostart=true + Aspect=16:9 +] + +# SigninCardTemplate(signinlabel, url) +[SigninCard + text=This is some text describing the card, it's cool because it's cool + buttons=@{signinButton(signinlabel, url)} +] + +# signinButton(signinlabel, url) +[CardAction + Title = @{signinlabel} + Value = @{url} + Type = signin +] + +# OAuthCardTemplate(signinlabel, url, connectionName) +[OAuthCard + text=This is some text describing the card, it's cool because it's cool + buttons=@{cardActionTemplate('signin', signinlabel, url)} + ConnectionName=@{connectionName} +] + + +# AnimationCardTemplate +[AnimationCard + Title=Animation Card + Subtitle=look at it animate + autostart=true + autoloop=true + Image=https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png + Image=https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png + Image=https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png + Media=http://oi42.tinypic.com/1rchlx.jpg +] + +# HerocardWithCardAction(title, text) +[herocard + Title = @{title} + Text = @{text} + Buttons = @{cardActionTemplate(null, title, text)} +] + +# cardActionTemplate(type, title, value) +[CardAction + Type = @{if(type == null, 'imBack', type)} + Title = @{title} + Value = @{value} + Text = @{title} +] + +# eventActivity(text) +[Activity + Name = @{text} + Value = @{text} + Type = event +] + +# activityWithHeroCardAttachment(title, text) +[Activity + Attachments = @{HerocardWithCardAction(title, text)} +] + +# activityWithMultiAttachments(title, text) +[activity + Attachments = @{HerocardWithCardAction(title, text)} | @{ThumbnailCardTemplate('type')} +] + +# activityWithSuggestionActions(title, text) +[Activity + Text = @{text} + SuggestedActions = firstItem | @{cardActionTemplate(null, title, text)} +] + +# activityWithMultiStringSuggestionActions(title, text) +[Activity + Text = @{text} + SuggestedActions = @{getSuggestions()} +] + +# getSuggestions +- @{foreach(split(stringSuggestions(), '$'), x, trim(x))} + +# stringSuggestions +- first suggestion $ second suggestion $ third suggestion + +# activityWithMultiStructuredSuggestionActions(text) +[Activity + Text = @{text} + SuggestedActions = @{foreach(getSuggestions(), x, cardActionTemplate(null, x, x))} +] + +# adaptivecardActivity +[Activity + Attachments = @{json(adaptivecardjson())} +] + +# messageActivityAll(title, text) +[Activity + Text = @{text} + Speak = @{text} + InputHint = accepting + Attachments = @{HerocardWithCardAction(title, text)} + SuggestedActions = firstItem | @{cardActionTemplate(null, title, text)} + AttachmentLayout = list +] + +# notSupport +[Acti + key = value +] + +# SuggestedActionsReference(text) +[ Activity + Text = @{text} + @{WelcomeActions()} +] + +# WelcomeActions +[Activity + SuggestedActions = Add todo | View Todo | Remove Todo | Cancel | Help +] + +# adaptivecardjson +- ``` +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "TextBlock", + "text": "@{adaptiveCardTitle}", + "weight": "bolder", + "size": "medium" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "Image", + "url": "https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg", + "size": "small", + "style": "person" + } + ] + }, + { + "type": "Column", + "width": "stretch", + "items": [ + { + "type": "TextBlock", + "text": "Matt Hidinger", + "weight": "bolder", + "wrap": true + }, + { + "type": "TextBlock", + "spacing": "none", + "text": "Created aa", + "isSubtle": true, + "wrap": true + } + ] + } + ] + }, + { + "type": "TextBlock", + "text": "Now that we have defined the main rules and features of the format, we need to produce a schema and publish it to GitHub. The schema will be the starting point of our reference documentation.", + "wrap": true + }, + { + "type": "FactSet", + "facts": [ + { + "title": "Board:", + "value": "Adaptive Card" + }, + { + "title": "List:", + "value": "Backlog" + }, + { + "title": "Assigned to:", + "value": "Matt Hidinger" + }, + { + "title": "Due date:", + "value": "Not set" + } + ] + } + ], + "actions": [ + { + "type": "Action.ShowCard", + "title": "Set due date", + "card": { + "type": "AdaptiveCard", + "body": [ + { + "type": "Input.Date", + "id": "dueDate" + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "OK" + } + ] + } + }, + { + "type": "Action.ShowCard", + "title": "Comment", + "card": { + "type": "AdaptiveCard", + "body": [ + { + "type": "Input.Text", + "id": "comment", + "isMultiline": true, + "placeholder": "Enter your comment" + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "OK" + } + ] + } + } + ] +} +``` \ No newline at end of file