diff --git a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py index 18da635b8..656050bab 100644 --- a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py +++ b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py @@ -352,7 +352,7 @@ async def assert_reply( :param timeout: :return: """ - + # TODO: refactor method so expected can take a Callable[[Activity], None] def default_inspector(reply, description=None): if isinstance(expected, Activity): validate_activity(reply, expected) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py index 6acc1cbec..a9d17f16f 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from typing import List +from typing import List, Union from botbuilder.core import CardFactory, MessageFactory from botbuilder.schema import ActionTypes, Activity, CardAction, HeroCard, InputHints @@ -17,7 +17,7 @@ class ChoiceFactory: @staticmethod def for_channel( channel_id: str, - choices: List[Choice], + choices: List[Union[str, Choice]], text: str = None, speak: str = None, options: ChoiceFactoryOptions = None, @@ -36,8 +36,7 @@ def for_channel( if channel_id is None: channel_id = "" - if choices is None: - choices = [] + choices = ChoiceFactory._to_choices(choices) # Find maximum title length max_title_length = 0 @@ -74,7 +73,7 @@ def for_channel( @staticmethod def inline( - choices: List[Choice], + choices: List[Union[str, Choice]], text: str = None, speak: str = None, options: ChoiceFactoryOptions = None, @@ -89,8 +88,7 @@ def inline( speak: (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel. options: (Optional) The formatting options to use to tweak rendering of list. """ - if choices is None: - choices = [] + choices = ChoiceFactory._to_choices(choices) if options is None: options = ChoiceFactoryOptions() @@ -134,7 +132,7 @@ def inline( @staticmethod def list_style( - choices: List[Choice], + choices: List[Union[str, Choice]], text: str = None, speak: str = None, options: ChoiceFactoryOptions = None, @@ -153,8 +151,7 @@ def list_style( options: (Optional) The formatting options to use to tweak rendering of list. """ - if choices is None: - choices = [] + choices = ChoiceFactory._to_choices(choices) if options is None: options = ChoiceFactoryOptions() @@ -206,7 +203,7 @@ def suggested_action( @staticmethod def hero_card( - choices: List[Choice], text: str = None, speak: str = None + choices: List[Union[Choice, str]], text: str = None, speak: str = None ) -> Activity: """ Creates a message activity that includes a lsit of coices that have been added as `HeroCard`'s @@ -221,18 +218,22 @@ def hero_card( ) @staticmethod - def _to_choices(choices: List[str]) -> List[Choice]: + def _to_choices(choices: List[Union[str, Choice]]) -> List[Choice]: """ Takes a list of strings and returns them as [`Choice`]. """ if choices is None: return [] - return [Choice(value=choice.value) for choice in choices] + return [ + Choice(value=choice) if isinstance(choice, str) else choice + for choice in choices + ] @staticmethod - def _extract_actions(choices: List[Choice]) -> List[CardAction]: + def _extract_actions(choices: List[Union[str, Choice]]) -> List[CardAction]: if choices is None: choices = [] + choices = ChoiceFactory._to_choices(choices) card_actions: List[CardAction] = [] for choice in choices: if choice.action is not None: diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py index 1e907ae1d..0ab60ba17 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py @@ -217,8 +217,11 @@ def default() -> Activity: ): prompt.suggested_actions = msg.suggested_actions - if msg.attachments is not None and msg.attachments: - prompt.attachments = msg.attachments + if msg.attachments: + if prompt.attachments: + prompt.attachments.extend(msg.attachments) + else: + prompt.attachments = msg.attachments return prompt diff --git a/libraries/botbuilder-dialogs/tests/test_choice_prompt.py b/libraries/botbuilder-dialogs/tests/test_choice_prompt.py index 902e9adee..8b4499e1d 100644 --- a/libraries/botbuilder-dialogs/tests/test_choice_prompt.py +++ b/libraries/botbuilder-dialogs/tests/test_choice_prompt.py @@ -6,7 +6,7 @@ import aiounittest from recognizers_text import Culture -from botbuilder.core import ConversationState, MemoryStorage, TurnContext +from botbuilder.core import CardFactory, ConversationState, MemoryStorage, TurnContext from botbuilder.core.adapters import TestAdapter from botbuilder.dialogs import DialogSet, DialogTurnResult, DialogTurnStatus from botbuilder.dialogs.choices import Choice, ListStyle @@ -588,3 +588,108 @@ async def exec_test(turn_context: TurnContext): ) step3 = await step2.send("1") await step3.assert_reply("red") + + async def test_should_display_choices_on_hero_card(self): + size_choices = ["large", "medium", "small"] + + async def exec_test(turn_context: TurnContext): + dialog_context = await dialogs.create_context(turn_context) + + results: DialogTurnResult = await dialog_context.continue_dialog() + + if results.status == DialogTurnStatus.Empty: + options = PromptOptions( + prompt=Activity( + type=ActivityTypes.message, text="Please choose a size." + ), + choices=size_choices, + ) + await dialog_context.prompt("prompt", options) + elif results.status == DialogTurnStatus.Complete: + selected_choice = results.result + await turn_context.send_activity(selected_choice.value) + + await convo_state.save_changes(turn_context) + + def assert_expected_activity( + activity: Activity, description + ): # pylint: disable=unused-argument + assert len(activity.attachments) == 1 + assert ( + activity.attachments[0].content_type + == CardFactory.content_types.hero_card + ) + assert activity.attachments[0].content.text == "Please choose a size." + + adapter = TestAdapter(exec_test) + + convo_state = ConversationState(MemoryStorage()) + dialog_state = convo_state.create_property("dialogState") + dialogs = DialogSet(dialog_state) + + choice_prompt = ChoicePrompt("prompt") + + # Change the ListStyle of the prompt to ListStyle.none. + choice_prompt.style = ListStyle.hero_card + + dialogs.add(choice_prompt) + + step1 = await adapter.send("Hello") + step2 = await step1.assert_reply(assert_expected_activity) + step3 = await step2.send("1") + await step3.assert_reply(size_choices[0]) + + async def test_should_display_choices_on_hero_card_with_additional_attachment(self): + size_choices = ["large", "medium", "small"] + card = CardFactory.adaptive_card( + { + "type": "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.2", + "body": [], + } + ) + card_activity = Activity(attachments=[card]) + + async def exec_test(turn_context: TurnContext): + dialog_context = await dialogs.create_context(turn_context) + + results: DialogTurnResult = await dialog_context.continue_dialog() + + if results.status == DialogTurnStatus.Empty: + options = PromptOptions(prompt=card_activity, choices=size_choices) + await dialog_context.prompt("prompt", options) + elif results.status == DialogTurnStatus.Complete: + selected_choice = results.result + await turn_context.send_activity(selected_choice.value) + + await convo_state.save_changes(turn_context) + + def assert_expected_activity( + activity: Activity, description + ): # pylint: disable=unused-argument + assert len(activity.attachments) == 2 + assert ( + activity.attachments[0].content_type + == CardFactory.content_types.adaptive_card + ) + assert ( + activity.attachments[1].content_type + == CardFactory.content_types.hero_card + ) + + adapter = TestAdapter(exec_test) + + convo_state = ConversationState(MemoryStorage()) + dialog_state = convo_state.create_property("dialogState") + dialogs = DialogSet(dialog_state) + + choice_prompt = ChoicePrompt("prompt") + + # Change the ListStyle of the prompt to ListStyle.none. + choice_prompt.style = ListStyle.hero_card + + dialogs.add(choice_prompt) + + step1 = await adapter.send("Hello") + await step1.assert_reply(assert_expected_activity)