diff --git a/CHANGELOG.md b/CHANGELOG.md index 72660b268e..f3a7e3263a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - PR [#2542](https://github.com/microsoft/BotFramework-WebChat/pull/2542): `useLanguage`, `useLocalize`, `useLocalizeDate` - PR [#2543](https://github.com/microsoft/BotFramework-WebChat/pull/2543): `useAdaptiveCardsHostConfig`, `useAdaptiveCardsPackage`, `useRenderMarkdownAsHTML` - Bring your own Adaptive Cards package by specifying `adaptiveCardsPackage` prop, by [@compulim](https://github.com/compulim) in PR [#2543](https://github.com/microsoft/BotFramework-WebChat/pull/2543) + - PR [#2544](https://github.com/microsoft/BotFramework-WebChat/pull/2544): `useAvatarForBot`, `useAvatarForUser` ### Fixed diff --git a/__tests__/hooks/useAvatarForBot.js b/__tests__/hooks/useAvatarForBot.js new file mode 100644 index 0000000000..af8f3edd2f --- /dev/null +++ b/__tests__/hooks/useAvatarForBot.js @@ -0,0 +1,32 @@ +import { timeouts } from '../constants.json'; + +// selenium-webdriver API doc: +// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html + +jest.setTimeout(timeouts.test); + +test('getter should return image and initial of avatar for bot', async () => { + const { pageObjects } = await setupWebDriver({ + props: { + styleOptions: { + botAvatarImage: 'about:blank#bot-icon', + botAvatarInitials: 'WC' + } + } + }); + + const [{ image, initials }] = await pageObjects.runHook('useAvatarForBot'); + + expect({ image, initials }).toMatchInlineSnapshot(` + Object { + "image": "about:blank#bot-icon", + "initials": "WC", + } + `); +}); + +test('setter should throw exception', async () => { + const { pageObjects } = await setupWebDriver(); + + await expect(pageObjects.runHook('useAvatarForBot', [], result => result[1]())).rejects.toThrow(); +}); diff --git a/__tests__/hooks/useAvatarForUser.js b/__tests__/hooks/useAvatarForUser.js new file mode 100644 index 0000000000..23054a746c --- /dev/null +++ b/__tests__/hooks/useAvatarForUser.js @@ -0,0 +1,32 @@ +import { timeouts } from '../constants.json'; + +// selenium-webdriver API doc: +// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html + +jest.setTimeout(timeouts.test); + +test('getter should return image and initial of avatar for user', async () => { + const { pageObjects } = await setupWebDriver({ + props: { + styleOptions: { + userAvatarImage: 'about:blank#user-icon', + userAvatarInitials: 'WW' + } + } + }); + + const [{ image, initials }] = await pageObjects.runHook('useAvatarForUser'); + + expect({ image, initials }).toMatchInlineSnapshot(` + Object { + "image": "about:blank#user-icon", + "initials": "WW", + } + `); +}); + +test('setter should throw exception', async () => { + const { pageObjects } = await setupWebDriver(); + + await expect(pageObjects.runHook('useAvatarForUser', [], result => result[1]())).rejects.toThrow(); +}); diff --git a/packages/component/src/Activity/Avatar.js b/packages/component/src/Activity/Avatar.js index 1fedf42b3b..0b7f7dc23e 100644 --- a/packages/component/src/Activity/Avatar.js +++ b/packages/component/src/Activity/Avatar.js @@ -4,6 +4,8 @@ import React from 'react'; import connectToWebChat from '../connectToWebChat'; import CroppedImage from '../Utils/CroppedImage'; +import useAvatarForBot from '../hooks/useAvatarForBot'; +import useAvatarForUser from '../hooks/useAvatarForUser'; import useStyleSet from '../hooks/useStyleSet'; const connectAvatar = (...selectors) => @@ -25,17 +27,21 @@ const connectAvatar = (...selectors) => // TODO: [P2] Consider memoizing "style={ backgroundImage }" in our upstreamers // We have 2 different upstreamers and -const Avatar = ({ 'aria-hidden': ariaHidden, avatarImage, avatarInitials, className, fromUser }) => { +const Avatar = ({ 'aria-hidden': ariaHidden, className, fromUser }) => { + const [botAvatar] = useAvatarForBot(); + const [userAvatar] = useAvatarForUser(); const [{ avatar: avatarStyleSet }] = useStyleSet(); + const { image, initials } = fromUser ? userAvatar : botAvatar; + return ( - !!(avatarImage || avatarInitials) && ( + !!(image || initials) && (
- {avatarInitials} - {!!avatarImage && } + {initials} + {!!image && }
) ); @@ -43,20 +49,16 @@ const Avatar = ({ 'aria-hidden': ariaHidden, avatarImage, avatarInitials, classN Avatar.defaultProps = { 'aria-hidden': false, - avatarImage: '', - avatarInitials: '', className: '', fromUser: false }; Avatar.propTypes = { 'aria-hidden': PropTypes.bool, - avatarImage: PropTypes.string, - avatarInitials: PropTypes.string, className: PropTypes.string, fromUser: PropTypes.bool }; -export default connectAvatar()(Avatar); +export default Avatar; export { connectAvatar }; diff --git a/packages/component/src/Activity/CarouselFilmStrip.js b/packages/component/src/Activity/CarouselFilmStrip.js index afcb634a02..59298560a3 100644 --- a/packages/component/src/Activity/CarouselFilmStrip.js +++ b/packages/component/src/Activity/CarouselFilmStrip.js @@ -15,6 +15,8 @@ import ScreenReaderText from '../ScreenReaderText'; import SendStatus from './SendStatus'; import textFormatToContentType from '../Utils/textFormatToContentType'; import Timestamp from './Timestamp'; +import useAvatarForBot from '../hooks/useAvatarForBot'; +import useAvatarForUser from '../hooks/useAvatarForUser'; import useLocalize from '../hooks/useLocalize'; import useStyleOptions from '../hooks/useStyleOptions'; import useStyleSet from '../hooks/useStyleSet'; @@ -88,13 +90,14 @@ const connectCarouselFilmStrip = (...selectors) => const WebChatCarouselFilmStrip = ({ activity, - avatarInitials, children, className, itemContainerRef, scrollableRef, timestampClassName }) => { + const [{ initials: botInitials }] = useAvatarForBot(); + const [{ initials: userInitials }] = useAvatarForUser(); const [{ bubbleNubSize, bubbleFromUserNubSize }] = useStyleOptions(); const [{ carouselFilmStrip: carouselFilmStripStyleSet }] = useStyleSet(); @@ -112,13 +115,13 @@ const WebChatCarouselFilmStrip = ({ const fromUser = role === 'user'; const activityDisplayText = messageBackDisplayText || text; const indented = fromUser ? bubbleFromUserNubSize : bubbleNubSize; - + const initials = fromUser ? userInitials : botInitials; const roleLabel = fromUser ? userRoleLabel : botRoleLabel; return (
@@ -162,7 +165,6 @@ const WebChatCarouselFilmStrip = ({ }; WebChatCarouselFilmStrip.defaultProps = { - avatarInitials: '', children: undefined, className: '', timestampClassName: '' @@ -184,7 +186,6 @@ WebChatCarouselFilmStrip.propTypes = { textFormat: PropTypes.string, timestamp: PropTypes.string }).isRequired, - avatarInitials: PropTypes.string, children: PropTypes.any, className: PropTypes.string, itemContainerRef: PropTypes.any.isRequired, @@ -192,14 +193,10 @@ WebChatCarouselFilmStrip.propTypes = { timestampClassName: PropTypes.string }; -const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(({ avatarInitials }) => ({ - avatarInitials -}))(WebChatCarouselFilmStrip); - const CarouselFilmStrip = props => ( {({ itemContainerRef, scrollableRef }) => ( - + )} ); diff --git a/packages/component/src/Activity/StackedLayout.js b/packages/component/src/Activity/StackedLayout.js index 3227ae0ea8..a569b8b623 100644 --- a/packages/component/src/Activity/StackedLayout.js +++ b/packages/component/src/Activity/StackedLayout.js @@ -16,6 +16,8 @@ import ScreenReaderText from '../ScreenReaderText'; import SendStatus from './SendStatus'; import textFormatToContentType from '../Utils/textFormatToContentType'; import Timestamp from './Timestamp'; +import useAvatarForBot from '../hooks/useAvatarForBot'; +import useAvatarForUser from '../hooks/useAvatarForUser'; import useLocalize from '../hooks/useLocalize'; import useStyleOptions from '../hooks/useStyleOptions'; import useStyleSet from '../hooks/useStyleSet'; @@ -84,7 +86,9 @@ const connectStackedLayout = (...selectors) => ...selectors ); -const StackedLayout = ({ activity, avatarInitials, children, timestampClassName }) => { +const StackedLayout = ({ activity, children, timestampClassName }) => { + const [{ initials: botInitials }] = useAvatarForBot(); + const [{ initials: userInitials }] = useAvatarForUser(); const [{ botAvatarInitials, bubbleNubSize, bubbleFromUserNubSize, userAvatarInitials }] = useStyleOptions(); const [{ stackedLayout: stackedLayoutStyleSet }] = useStyleSet(); @@ -98,6 +102,7 @@ const StackedLayout = ({ activity, avatarInitials, children, timestampClassName const activityDisplayText = messageBackDisplayText || text; const fromUser = role === 'user'; + const initials = fromUser ? userInitials : botInitials; const showSendStatus = state === SENDING || state === SEND_FAILED; const plainText = remark() .use(stripMarkdown) @@ -109,8 +114,8 @@ const StackedLayout = ({ activity, avatarInitials, children, timestampClassName const roleLabel = fromUser ? botRoleLabel : userRoleLabel; - const botAriaLabel = useLocalize('Bot said something', avatarInitials, plainText); - const userAriaLabel = useLocalize('User said something', avatarInitials, plainText); + const botAriaLabel = useLocalize('Bot said something', initials, plainText); + const userAriaLabel = useLocalize('User said something', initials, plainText); const ariaLabel = fromUser ? userAriaLabel : botAriaLabel; @@ -120,10 +125,10 @@ const StackedLayout = ({ activity, avatarInitials, children, timestampClassName 'from-user': fromUser, webchat__stacked_extra_left_indent: fromUser && !botAvatarInitials && bubbleNubSize, webchat__stacked_extra_right_indent: !fromUser && !userAvatarInitials && bubbleFromUserNubSize, - webchat__stacked_indented_content: avatarInitials && !indented + webchat__stacked_indented_content: initials && !indented })} > - {!avatarInitials && !!(fromUser ? bubbleFromUserNubSize : bubbleNubSize) &&
} + {!initials && !!(fromUser ? bubbleFromUserNubSize : bubbleNubSize) &&
}
{!!activityDisplayText && ( @@ -189,13 +194,10 @@ StackedLayout.propTypes = { timestamp: PropTypes.string, type: PropTypes.string.isRequired }).isRequired, - avatarInitials: PropTypes.string.isRequired, children: PropTypes.any, timestampClassName: PropTypes.string }; -export default connectStackedLayout(({ avatarInitials }) => ({ - avatarInitials -}))(StackedLayout); +export default StackedLayout; export { connectStackedLayout }; diff --git a/packages/component/src/hooks/index.js b/packages/component/src/hooks/index.js index 4249a422af..226d78edaa 100644 --- a/packages/component/src/hooks/index.js +++ b/packages/component/src/hooks/index.js @@ -1,4 +1,6 @@ import useActivities from './useActivities'; +import useAvatarForBot from './useAvatarForBot'; +import useAvatarForUser from './useAvatarForUser'; import useLanguage from './useLanguage'; import useLocalize from './useLocalize'; import useLocalizeDate from './useLocalizeDate'; @@ -11,6 +13,8 @@ import { useSendBoxDictationStarted } from '../BasicSendBox'; export { useActivities, + useAvatarForBot, + useAvatarForUser, useLanguage, useLocalize, useLocalizeDate, diff --git a/packages/component/src/hooks/useAvatarForBot.js b/packages/component/src/hooks/useAvatarForBot.js new file mode 100644 index 0000000000..8f93e8b96d --- /dev/null +++ b/packages/component/src/hooks/useAvatarForBot.js @@ -0,0 +1,12 @@ +import useStyleOptions from './useStyleOptions'; + +export default function useAvatarForBot() { + const [{ botAvatarImage: image, botAvatarInitials: initials }] = useStyleOptions(); + + return [ + { + image, + initials + } + ]; +} diff --git a/packages/component/src/hooks/useAvatarForUser.js b/packages/component/src/hooks/useAvatarForUser.js new file mode 100644 index 0000000000..74555c1d30 --- /dev/null +++ b/packages/component/src/hooks/useAvatarForUser.js @@ -0,0 +1,12 @@ +import useStyleOptions from './useStyleOptions'; + +export default function useAvatarForUser() { + const [{ userAvatarImage: image, userAvatarInitials: initials }] = useStyleOptions(); + + return [ + { + image, + initials + } + ]; +}