From ec9e46fb624821630e1e6bcb795a519f4a90ec2c Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Tue, 26 Mar 2019 16:59:45 -0700 Subject: [PATCH] MM-13618 Adding bot tags. (#2669) * Adding bot tags. * Snapshot update. * Moving bot tag to own component. * Snapshots. * Adding missing bot tags, fixing bot profile, allowing tap of bot username to open profile. * Snapshots and tests. --- .../at_mention_item/at_mention_item.js | 7 + .../autocomplete/at_mention_item/index.js | 1 + .../channel_mention_item.js | 7 + .../channel_mention_item/index.js | 13 ++ app/components/bot_tag.js | 50 ++++++ app/components/channel_icon.js | 14 +- app/components/channel_intro/channel_intro.js | 32 ++-- .../__snapshots__/user_list_test.test.js.snap | 157 +++++++++++++++--- .../user_list_row/user_list_row.js | 24 ++- app/components/post_header/index.js | 1 + app/components/post_header/post_header.js | 33 ++-- .../profile_picture/profile_picture.js | 4 +- .../__snapshots__/channel_item.test.js.snap | 7 + .../channel_item/channel_item.js | 3 + .../channel_item/channel_item.test.js | 1 + .../main/channels_list/channel_item/index.js | 14 +- .../filtered_list/filtered_list.js | 2 + app/screens/channel_info/channel_info.js | 3 + .../channel_info/channel_info_header.js | 3 + app/screens/channel_info/index.js | 6 + .../__snapshots__/user_profile.test.js.snap | 78 ++++++++- app/screens/user_profile/index.js | 4 + app/screens/user_profile/user_profile.js | 62 +++++-- app/screens/user_profile/user_profile.test.js | 48 +++++- package-lock.json | 51 ++++-- package.json | 3 +- 26 files changed, 537 insertions(+), 91 deletions(-) create mode 100644 app/components/bot_tag.js diff --git a/app/components/autocomplete/at_mention_item/at_mention_item.js b/app/components/autocomplete/at_mention_item/at_mention_item.js index d0d630425..63c1f40dd 100644 --- a/app/components/autocomplete/at_mention_item/at_mention_item.js +++ b/app/components/autocomplete/at_mention_item/at_mention_item.js @@ -10,6 +10,7 @@ import { } from 'react-native'; import ProfilePicture from 'app/components/profile_picture'; +import BotTag from 'app/components/bot_tag'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; export default class AtMentionItem extends PureComponent { @@ -19,6 +20,7 @@ export default class AtMentionItem extends PureComponent { onPress: PropTypes.func.isRequired, userId: PropTypes.string.isRequired, username: PropTypes.string, + isBot: PropTypes.bool, theme: PropTypes.object.isRequired, }; @@ -34,6 +36,7 @@ export default class AtMentionItem extends PureComponent { userId, username, theme, + isBot, } = this.props; const style = getStyleFromTheme(theme); @@ -54,6 +57,10 @@ export default class AtMentionItem extends PureComponent { /> {`@${username}`} + {hasFullName && {' - '}} {hasFullName && {`${firstName} ${lastName}`}} diff --git a/app/components/autocomplete/at_mention_item/index.js b/app/components/autocomplete/at_mention_item/index.js index 1d7c70c78..355d7dce2 100644 --- a/app/components/autocomplete/at_mention_item/index.js +++ b/app/components/autocomplete/at_mention_item/index.js @@ -16,6 +16,7 @@ function mapStateToProps(state, ownProps) { firstName: user.first_name, lastName: user.last_name, username: user.username, + isBot: Boolean(user.is_bot), theme: getTheme(state), }; } diff --git a/app/components/autocomplete/channel_mention_item/channel_mention_item.js b/app/components/autocomplete/channel_mention_item/channel_mention_item.js index 6bdfcc7f7..867b14d5f 100644 --- a/app/components/autocomplete/channel_mention_item/channel_mention_item.js +++ b/app/components/autocomplete/channel_mention_item/channel_mention_item.js @@ -9,6 +9,7 @@ import { } from 'react-native'; import {General} from 'mattermost-redux/constants'; +import BotTag from 'app/components/bot_tag'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -18,6 +19,7 @@ export default class ChannelMentionItem extends PureComponent { displayName: PropTypes.string, name: PropTypes.string, type: PropTypes.string, + isBot: PropTypes.bool.isRequired, onPress: PropTypes.func.isRequired, theme: PropTypes.object.isRequired, }; @@ -38,6 +40,7 @@ export default class ChannelMentionItem extends PureComponent { name, theme, type, + isBot, } = this.props; const style = getStyleFromTheme(theme); @@ -53,6 +56,10 @@ export default class ChannelMentionItem extends PureComponent { style={style.row} > {'@' + displayName} + ); } diff --git a/app/components/autocomplete/channel_mention_item/index.js b/app/components/autocomplete/channel_mention_item/index.js index 30b590167..a1b57f86a 100644 --- a/app/components/autocomplete/channel_mention_item/index.js +++ b/app/components/autocomplete/channel_mention_item/index.js @@ -3,6 +3,8 @@ import {connect} from 'react-redux'; +import {General} from 'mattermost-redux/constants'; + import {getChannel} from 'mattermost-redux/selectors/entities/channels'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; @@ -11,14 +13,25 @@ import {getChannelNameForSearchAutocomplete} from 'app/selectors/channel'; import ChannelMentionItem from './channel_mention_item'; +import {getUser} from 'mattermost-redux/selectors/entities/users'; + function mapStateToProps(state, ownProps) { const channel = getChannel(state, ownProps.channelId); const displayName = getChannelNameForSearchAutocomplete(state, ownProps.channelId); + let isBot = false; + if (channel.type === General.DM_CHANNEL) { + const teammate = getUser(state, channel.teammate_id); + if (teammate && teammate.is_bot) { + isBot = true; + } + } + return { displayName, name: channel.name, type: channel.type, + isBot, theme: getTheme(state), }; } diff --git a/app/components/bot_tag.js b/app/components/bot_tag.js new file mode 100644 index 000000000..84ad5062a --- /dev/null +++ b/app/components/bot_tag.js @@ -0,0 +1,50 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {PureComponent} from 'react'; +import PropTypes from 'prop-types'; +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; +import FormattedText from 'app/components/formatted_text'; + +export default class BotTag extends PureComponent { + static defaultProps = { + show: true, + }; + + static propTypes = { + show: PropTypes.bool, + theme: PropTypes.object.isRequired, + }; + + render() { + if (!this.props.show) { + return null; + } + const style = createStyleSheet(this.props.theme); + + return ( + + ); + } +} + +const createStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + bot: { + alignSelf: 'center', + backgroundColor: changeOpacity(theme.centerChannelColor, 0.15), + borderRadius: 2, + color: theme.centerChannelColor, + fontSize: 10, + fontWeight: '600', + marginRight: 5, + marginLeft: 5, + paddingVertical: 2, + paddingHorizontal: 4, + }, + }; +}); diff --git a/app/components/channel_icon.js b/app/components/channel_icon.js index f448f557b..9a2422ae2 100644 --- a/app/components/channel_icon.js +++ b/app/components/channel_icon.js @@ -8,7 +8,7 @@ import { Text, View, } from 'react-native'; -import Icon from 'react-native-vector-icons/FontAwesome'; +import Icon from 'react-native-vector-icons/FontAwesome5'; import {General} from 'mattermost-redux/constants'; @@ -26,6 +26,7 @@ export default class ChannelIcon extends React.PureComponent { theme: PropTypes.object.isRequired, type: PropTypes.string.isRequired, isArchived: PropTypes.bool.isRequired, + isBot: PropTypes.bool.isRequired, }; static defaultProps = { @@ -47,6 +48,7 @@ export default class ChannelIcon extends React.PureComponent { theme, type, isArchived, + isBot, } = this.props; const style = getStyleSheet(theme); @@ -85,6 +87,13 @@ export default class ChannelIcon extends React.PureComponent { style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]} /> ); + } else if (isBot) { + icon = ( + + ); } else if (hasDraft) { icon = ( { iconInfo: { color: theme.centerChannelColor, }, + iconBot: { + marginLeft: -5, + }, groupBox: { alignSelf: 'flex-start', alignItems: 'center', diff --git a/app/components/channel_intro/channel_intro.js b/app/components/channel_intro/channel_intro.js index 80ad62ed3..b852ccbe0 100644 --- a/app/components/channel_intro/channel_intro.js +++ b/app/components/channel_intro/channel_intro.js @@ -14,6 +14,7 @@ import {General} from 'mattermost-redux/constants'; import {injectIntl, intlShape} from 'react-intl'; import ProfilePicture from 'app/components/profile_picture'; +import BotTag from 'app/components/bot_tag'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; @@ -91,16 +92,24 @@ class ChannelIntro extends PureComponent { const {currentChannelMembers, theme} = this.props; const style = getStyleSheet(theme); - return currentChannelMembers.map((member, index) => ( - this.goToUserProfile(member.id))} - > - - {index === currentChannelMembers.length - 1 ? this.getDisplayName(member) : `${this.getDisplayName(member)}, `} - - - )); + return currentChannelMembers.map((member, index) => { + return ( + this.goToUserProfile(member.id))} + > + + + {index === currentChannelMembers.length - 1 ? this.getDisplayName(member) : `${this.getDisplayName(member)}, `} + + + + + ); + }); }; buildDMContent = () => { @@ -382,6 +391,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flexWrap: 'wrap', justifyContent: 'flex-start', }, + indicatorContainer: { + flexDirection: 'row', + }, }; }); diff --git a/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap b/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap index 583e4a898..da2d83785 100644 --- a/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap +++ b/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap @@ -41,18 +41,57 @@ exports[`UserListRow should match snapshot 1`] = ` } > - - @user - + + @user + + + @@ -100,16 +139,55 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated } > - + > + + + @@ -157,18 +235,57 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = ` } > - - @user - + + @user + + + - - {usernameDisplay} - + + + {usernameDisplay} + + + {showTeammateDisplay && @@ -140,6 +147,9 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { fontSize: 15, color: theme.centerChannelColor, }, + indicatorContainer: { + flexDirection: 'row', + }, deactivated: { marginTop: 2, fontSize: 12, diff --git a/app/components/post_header/index.js b/app/components/post_header/index.js index 3f852b2a5..fcc84fc9f 100644 --- a/app/components/post_header/index.js +++ b/app/components/post_header/index.js @@ -44,6 +44,7 @@ function makeMapStateToProps() { overrideUsername: post?.props?.override_username, // eslint-disable-line camelcase theme: getTheme(state), username: user.username, + isBot: user.is_bot || false, userTimezone, }; }; diff --git a/app/components/post_header/post_header.js b/app/components/post_header/post_header.js index a4a90ff11..f5bde134e 100644 --- a/app/components/post_header/post_header.js +++ b/app/components/post_header/post_header.js @@ -14,6 +14,7 @@ import FormattedText from 'app/components/formatted_text'; import FormattedTime from 'app/components/formatted_time'; import FormattedDate from 'app/components/formatted_date'; import ReplyIcon from 'app/components/reply_icon'; +import BotTag from 'app/components/bot_tag'; import {emptyFunction} from 'app/utils/general'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -38,6 +39,7 @@ export default class PostHeader extends PureComponent { showFullDate: PropTypes.bool, theme: PropTypes.object.isRequired, username: PropTypes.string, + isBot: PropTypes.bool, userTimezone: PropTypes.string, enableTimezone: PropTypes.bool, }; @@ -61,6 +63,7 @@ export default class PostHeader extends PureComponent { isSystemMessage, fromAutoResponder, overrideUsername, + isBot, } = this.props; if (fromWebHook) { @@ -74,13 +77,24 @@ export default class PostHeader extends PureComponent { {name} - ); + } else if (isBot) { + return ( + + + + {this.props.displayName} + + + + + ); } else if (fromAutoResponder) { let name = this.props.displayName; if (overrideUsername && enablePostUsernameOverride) { @@ -291,17 +305,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { indicatorContainer: { flexDirection: 'row', }, - bot: { - alignSelf: 'center', - backgroundColor: changeOpacity(theme.centerChannelColor, 0.15), - borderRadius: 2, - color: theme.centerChannelColor, - fontSize: 10, - fontWeight: '600', - marginRight: 5, - paddingVertical: 2, - paddingHorizontal: 4, - }, displayName: { color: theme.centerChannelColor, fontSize: 15, diff --git a/app/components/profile_picture/profile_picture.js b/app/components/profile_picture/profile_picture.js index a079c89da..61a20bcbc 100644 --- a/app/components/profile_picture/profile_picture.js +++ b/app/components/profile_picture/profile_picture.js @@ -111,7 +111,7 @@ export default class ProfilePicture extends PureComponent { } render() { - const {edit, showStatus, theme} = this.props; + const {edit, showStatus, theme, user} = this.props; const {pictureUrl} = this.state; const style = getStyleSheet(theme); @@ -172,7 +172,7 @@ export default class ProfilePicture extends PureComponent { return ( {image} - {(showStatus || edit) && + {(showStatus || edit) && (user && !user.is_bot) && {statusIcon} diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap index 874631f9e..c413377ea 100644 --- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap +++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap @@ -39,6 +39,7 @@ exports[`ChannelItem should match snapshot 1`] = ` hasDraft={false} isActive={false} isArchived={false} + isBot={false} isInfo={false} isUnread={true} membersCount={1} @@ -153,6 +154,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you hasDraft={false} isActive={true} isArchived={false} + isBot={false} isInfo={false} isUnread={true} membersCount={1} @@ -267,6 +269,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you hasDraft={false} isActive={true} isArchived={false} + isBot={false} isInfo={false} isUnread={true} membersCount={1} @@ -381,6 +384,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh hasDraft={false} isActive={true} isArchived={true} + isBot={false} isInfo={false} isUnread={true} membersCount={1} @@ -484,6 +488,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes hasDraft={false} isActive={false} isArchived={true} + isBot={false} isInfo={false} isUnread={true} membersCount={1} @@ -593,6 +598,7 @@ exports[`ChannelItem should match snapshot with draft 1`] = ` hasDraft={true} isActive={false} isArchived={false} + isBot={false} isInfo={false} isUnread={true} membersCount={1} @@ -698,6 +704,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = ` hasDraft={false} isActive={false} isArchived={false} + isBot={false} isInfo={false} isUnread={true} membersCount={1} diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js index ff7c9c71c..b82c91845 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js @@ -39,6 +39,7 @@ export default class ChannelItem extends PureComponent { theme: PropTypes.object.isRequired, unreadMsgs: PropTypes.number.isRequired, isSearchResult: PropTypes.bool, + isBot: PropTypes.bool.isRequired, }; static defaultProps = { @@ -99,6 +100,7 @@ export default class ChannelItem extends PureComponent { theme, isSearchResult, channel, + isBot, } = this.props; const isArchived = channel.delete_at > 0; @@ -183,6 +185,7 @@ export default class ChannelItem extends PureComponent { theme={theme} type={channel.type} isArchived={isArchived} + isBot={isBot} /> ); diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js index a073e88b4..8774c4614 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js @@ -38,6 +38,7 @@ describe('ChannelItem', () => { theme: Preferences.THEMES.default, unreadMsgs: 1, isSearchResult: false, + isBot: false, }; test('should match snapshot', () => { diff --git a/app/components/sidebars/main/channels_list/channel_item/index.js b/app/components/sidebars/main/channels_list/channel_item/index.js index 3c381669d..d2f748c98 100644 --- a/app/components/sidebars/main/channels_list/channel_item/index.js +++ b/app/components/sidebars/main/channels_list/channel_item/index.js @@ -12,7 +12,7 @@ import { } from 'mattermost-redux/selectors/entities/channels'; import {getTheme, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users'; -import {isChannelMuted} from 'mattermost-redux/utils/channel_utils'; +import {getUserIdFromChannelName, isChannelMuted} from 'mattermost-redux/utils/channel_utils'; import {displayUsername} from 'mattermost-redux/utils/user_utils'; import {getDraftForChannel} from 'app/selectors/views'; @@ -29,12 +29,19 @@ function makeMapStateToProps() { const channelDraft = getDraftForChannel(state, channel.id); let displayName = channel.display_name; + let isBot = false; if (channel.type === General.DM_CHANNEL) { - if (!ownProps.isSearchResult) { - const teammate = getUser(state, channel.teammate_id); + if (ownProps.isSearchResult) { + isBot = channel.isBot; + } else { + const teammateId = getUserIdFromChannelName(currentUserId, channel.name); + const teammate = getUser(state, teammateId); const teammateNameDisplay = getTeammateNameDisplaySetting(state); displayName = displayUsername(teammate, teammateNameDisplay, false); + if (teammate && teammate.is_bot) { + isBot = true; + } } } @@ -73,6 +80,7 @@ function makeMapStateToProps() { showUnreadForMsgs, theme: getTheme(state), unreadMsgs, + isBot, }; }; } diff --git a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js index e2966e46e..9006deee2 100644 --- a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js +++ b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js @@ -218,6 +218,7 @@ class FilteredList extends Component { nickname: u.nickname, fullname: `${u.first_name} ${u.last_name}`, delete_at: u.delete_at, + isBot: u.is_bot, // need name key for DM's as we use it for sortChannelsByDisplayName with same display_name name: displayName, @@ -263,6 +264,7 @@ class FilteredList extends Component { nickname: u.nickname, fullname: `${u.first_name} ${u.last_name}`, delete_at: u.delete_at, + isBot: u.is_bot, }; }); diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js index a6ed97a98..ed90a3226 100644 --- a/app/screens/channel_info/channel_info.js +++ b/app/screens/channel_info/channel_info.js @@ -58,6 +58,7 @@ export default class ChannelInfo extends PureComponent { canManageUsers: PropTypes.bool.isRequired, canEditChannel: PropTypes.bool.isRequired, ignoreChannelMentions: PropTypes.bool.isRequired, + isBot: PropTypes.bool.isRequired, }; static contextTypes = { @@ -510,6 +511,7 @@ export default class ChannelInfo extends PureComponent { navigator, status, theme, + isBot, } = this.props; const style = getStyleSheet(theme); @@ -548,6 +550,7 @@ export default class ChannelInfo extends PureComponent { theme={theme} type={currentChannel.type} isArchived={currentChannel.delete_at !== 0} + isBot={isBot} /> } diff --git a/app/screens/channel_info/channel_info_header.js b/app/screens/channel_info/channel_info_header.js index 2cc7f9472..237680e04 100644 --- a/app/screens/channel_info/channel_info_header.js +++ b/app/screens/channel_info/channel_info_header.js @@ -29,6 +29,7 @@ export default class ChannelInfoHeader extends React.PureComponent { theme: PropTypes.object.isRequired, type: PropTypes.string.isRequired, isArchived: PropTypes.bool.isRequired, + isBot: PropTypes.bool.isRequired, }; render() { @@ -45,6 +46,7 @@ export default class ChannelInfoHeader extends React.PureComponent { theme, type, isArchived, + isBot, } = this.props; const style = getStyleSheet(theme); @@ -62,6 +64,7 @@ export default class ChannelInfoHeader extends React.PureComponent { theme={theme} type={type} isArchived={isArchived} + isBot={isBot} /> + + + fred + + + - @undefined + @fred + + + USERNAME + + + fred + + { const {navigator, theme} = this.props; @@ -78,7 +87,17 @@ export default class UserProfile extends PureComponent { const displayName = displayUsername(user, teammateNameDisplay); if (displayName) { - return {displayName}; + return ( + + + {displayName} + + + + ); } return null; @@ -198,8 +217,34 @@ export default class UserProfile extends PureComponent { return additionalOptions; }; + renderDetailsBlock = (style) => { + if (this.props.user.is_bot) { + if (!this.props.bot) { + return null; + } + return ( + + + {'DESCRIPTION'} + {this.props.bot.description || ''} + + + ); + } + + return ( + + {this.props.enableTimezone && this.buildTimezoneBlock()} + {this.buildDisplayBlock('username')} + {this.props.config.ShowEmailAddress === 'true' && this.buildDisplayBlock('email')} + {this.buildDisplayBlock('nickname')} + {this.buildDisplayBlock('position')} + + ); + } + render() { - const {config, theme, user, enableTimezone} = this.props; + const {theme, user} = this.props; const style = createStyleSheet(theme); if (!user) { @@ -222,13 +267,7 @@ export default class UserProfile extends PureComponent { {this.getDisplayName()} {`@${user.username}`} - - {enableTimezone && this.buildTimezoneBlock()} - {this.buildDisplayBlock('username')} - {config.ShowEmailAddress === 'true' && this.buildDisplayBlock('email')} - {this.buildDisplayBlock('nickname')} - {this.buildDisplayBlock('position')} - + {this.renderDetailsBlock(style)} { marginHorizontal: 15, }, displayName: { - marginTop: 15, color: theme.centerChannelColor, fontSize: 17, fontWeight: '600', @@ -284,6 +322,10 @@ const createStyleSheet = makeStyleSheetFromTheme((theme) => { color: theme.centerChannelColor, fontSize: 15, }, + indicatorContainer: { + marginTop: 15, + flexDirection: 'row', + }, }; }); diff --git a/app/screens/user_profile/user_profile.test.js b/app/screens/user_profile/user_profile.test.js index c1b5a551a..6b61fe051 100644 --- a/app/screens/user_profile/user_profile.test.js +++ b/app/screens/user_profile/user_profile.test.js @@ -6,6 +6,7 @@ import {shallow} from 'enzyme'; import Preferences from 'mattermost-redux/constants/preferences'; import UserProfile from './user_profile.js'; +import BotTag from 'app/components/bot_tag'; jest.mock('react-intl'); jest.mock('app/utils/theme', () => { @@ -20,6 +21,7 @@ describe('user_profile', () => { const actions = { setChannelDisplayName: jest.fn(), makeDirectChannel: jest.fn(), + loadBot: jest.fn(), }; const baseProps = { actions, @@ -33,21 +35,53 @@ describe('user_profile', () => { teams: [], theme: Preferences.THEMES.default, enableTimezone: false, - user: { + militaryTime: false, + }; + + const user = { + email: 'test@test.com', + first_name: 'test', + id: '4hzdnk6mg7gepe7yze6m3domnc', + last_name: 'fake', + nickname: 'nick', + username: 'fred', + is_bot: false, + }; + + test('should match snapshot', async () => { + const wrapper = shallow( + , + {context: {intl: {formatMessage: jest.fn()}}}, + ); + expect(wrapper.getElement()).toMatchSnapshot(); + }); + + test('should contain bot tag', async () => { + const botUser = { email: 'test@test.com', first_name: 'test', id: '4hzdnk6mg7gepe7yze6m3domnc', last_name: 'fake', nickname: 'nick', - }, - militaryTime: false, - }; + username: 'fred', + is_bot: true, + }; - test('should match snapshot', async () => { const wrapper = shallow( - , + , {context: {intl: {formatMessage: jest.fn()}}}, ); - expect(wrapper.getElement()).toMatchSnapshot(); + expect(wrapper.containsMatchingElement( + + )).toEqual(true); }); }); diff --git a/package-lock.json b/package-lock.json index 7fb98652a..d5411d304 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2688,6 +2688,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -2732,7 +2733,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "4.0.0", @@ -4359,7 +4361,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4377,11 +4380,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4394,15 +4399,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4505,7 +4513,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4515,6 +4524,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4527,17 +4537,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4554,6 +4567,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4626,7 +4640,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4636,6 +4651,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -4711,7 +4727,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -4741,6 +4758,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4758,6 +4776,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4796,11 +4815,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -12338,7 +12359,8 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "2.3.2", @@ -12624,7 +12646,8 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true }, "micromatch": { "version": "3.1.10", diff --git a/package.json b/package.json index 672edd94a..58339b76d 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,8 @@ "postinstall": "make post-install", "test": "jest --forceExit --detectOpenHandles", "test:watch": "jest --watch", - "test:coverage": "jest --coverage" + "test:coverage": "jest --coverage", + "updatesnapshot": "jest --updateSnapshot" }, "rnpm": { "assets": [