diff --git a/app/components/autocomplete/at_mention/at_mention.tsx b/app/components/autocomplete/at_mention/at_mention.tsx index cbde145ab..b19044355 100644 --- a/app/components/autocomplete/at_mention/at_mention.tsx +++ b/app/components/autocomplete/at_mention/at_mention.tsx @@ -308,6 +308,7 @@ const AtMention = ({ defaultMessage={item.defaultMessage} id={item.id} onPress={completeMention} + testID='autocomplete.special_mention_item' /> ); }, [completeMention]); @@ -319,6 +320,7 @@ const AtMention = ({ name={item.name} displayName={item.displayName} onPress={completeMention} + testID='autocomplete.group_mention_item' /> ); }, [completeMention]); @@ -326,9 +328,9 @@ const AtMention = ({ const renderAtMentions = useCallback((item: UserProfile | UserModel) => { return ( ); }, [completeMention]); diff --git a/app/components/autocomplete/at_mention_group/at_mention_group.tsx b/app/components/autocomplete/at_mention_group/at_mention_group.tsx index fe74f4a96..d820bbef7 100644 --- a/app/components/autocomplete/at_mention_group/at_mention_group.tsx +++ b/app/components/autocomplete/at_mention_group/at_mention_group.tsx @@ -60,12 +60,14 @@ type Props = { name: string; displayName: string; onPress: (handle: string) => void; + testID?: string; } const GroupMentionItem = ({ onPress, name, displayName, + testID, }: Props) => { const insets = useSafeAreaInsets(); const theme = useTheme(); @@ -79,11 +81,14 @@ const GroupMentionItem = ({ onPress(name); }, [onPress, name]); + const groupMentionItemTestId = `${testID}.${name}`; + return ( - {`${displayName} `} - {`@${name}`} + + {`${displayName} `} + + + {`@${name}`} + ); diff --git a/app/components/autocomplete/at_mention_item/index.tsx b/app/components/autocomplete/at_mention_item/index.tsx index a1a7cdf98..f4b9684a0 100644 --- a/app/components/autocomplete/at_mention_item/index.tsx +++ b/app/components/autocomplete/at_mention_item/index.tsx @@ -31,7 +31,6 @@ const AtMentionItem = ({ return ( ); diff --git a/app/components/autocomplete/channel_mention/channel_mention.tsx b/app/components/autocomplete/channel_mention/channel_mention.tsx index d0d910212..48aaf80d1 100644 --- a/app/components/autocomplete/channel_mention/channel_mention.tsx +++ b/app/components/autocomplete/channel_mention/channel_mention.tsx @@ -285,7 +285,7 @@ const ChannelMention = ({ ); }, [completeMention]); diff --git a/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx b/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx index 1684d8ea5..8158fedb1 100644 --- a/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx +++ b/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx @@ -77,6 +77,7 @@ const ChannelMentionItem = ({ let component; const isArchived = ('delete_at' in channel ? channel.delete_at : channel.deleteAt) > 0; + const channelMentionItemTestId = `${testID}.${channel.name}`; if (isDMorGM(channel)) { if (!displayName) { @@ -88,15 +89,22 @@ const ChannelMentionItem = ({ key={channel.id} onPress={completeMention} style={rowStyle} - testID={testID} + testID={channelMentionItemTestId} type={'opacity'} > - {'@' + displayName} + + {'@' + displayName} + ); @@ -107,7 +115,7 @@ const ChannelMentionItem = ({ onPress={completeMention} style={margins} underlayColor={changeOpacity(theme.buttonBg, 0.08)} - testID={testID} + testID={channelMentionItemTestId} type={'native'} > @@ -120,8 +128,18 @@ const ChannelMentionItem = ({ size={18} style={style.icon} /> - {displayName} - {` ~${channel.name}`} + + {displayName} + + + {` ~${channel.name}`} + ); diff --git a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.tsx b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.tsx index 953461ba8..117b14e4a 100644 --- a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.tsx +++ b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.tsx @@ -169,6 +169,7 @@ const EmojiSuggestion = ({ const renderItem = useCallback(({item}: {item: string}) => { const completeItemSuggestion = () => completeSuggestion(item); + const emojiSuggestionItemTestId = `autocomplete.emoji_suggestion_item.${item}`; return ( - {`:${item}:`} + + {`:${item}:`} + ); @@ -211,7 +218,6 @@ const EmojiSuggestion = ({ return ( ); }; diff --git a/app/components/autocomplete/slash_suggestion/app_slash_suggestion/app_slash_suggestion.tsx b/app/components/autocomplete/slash_suggestion/app_slash_suggestion/app_slash_suggestion.tsx index ab1b45fc9..7a79f7c2d 100644 --- a/app/components/autocomplete/slash_suggestion/app_slash_suggestion/app_slash_suggestion.tsx +++ b/app/components/autocomplete/slash_suggestion/app_slash_suggestion/app_slash_suggestion.tsx @@ -120,7 +120,7 @@ const AppSlashSuggestion = ({ ); case COMMAND_SUGGESTION_CHANNEL: @@ -131,7 +131,7 @@ const AppSlashSuggestion = ({ ); default: diff --git a/app/components/autocomplete/slash_suggestion/slash_suggestion.tsx b/app/components/autocomplete/slash_suggestion/slash_suggestion.tsx index 2d86e1f43..33e898ec6 100644 --- a/app/components/autocomplete/slash_suggestion/slash_suggestion.tsx +++ b/app/components/autocomplete/slash_suggestion/slash_suggestion.tsx @@ -232,7 +232,6 @@ const SlashSuggestion = ({ return ( ); }; diff --git a/app/components/autocomplete/slash_suggestion/slash_suggestion_item.tsx b/app/components/autocomplete/slash_suggestion/slash_suggestion_item.tsx index a049b7ced..b6253fb51 100644 --- a/app/components/autocomplete/slash_suggestion/slash_suggestion_item.tsx +++ b/app/components/autocomplete/slash_suggestion/slash_suggestion_item.tsx @@ -153,11 +153,14 @@ const SlashSuggestionItem = ({ } } + const slashSuggestionItemTestId = `autocomplete.slash_suggestion_item.${suggestion}`; + return ( @@ -165,12 +168,18 @@ const SlashSuggestionItem = ({ {image} - {`${suggestionText}`} + + {`${suggestionText}`} + {Boolean(description) && {description} diff --git a/app/components/autocomplete/special_mention_item.tsx b/app/components/autocomplete/special_mention_item.tsx index bd5ed7728..479dee291 100644 --- a/app/components/autocomplete/special_mention_item.tsx +++ b/app/components/autocomplete/special_mention_item.tsx @@ -54,12 +54,14 @@ type Props = { defaultMessage: string; id: string; onPress: (handle: string) => void; + testID?: string; } const SpecialMentionItem = ({ completeHandle, defaultMessage, id, onPress, + testID, }: Props) => { const theme = useTheme(); const style = getStyleFromTheme(theme); @@ -67,10 +69,13 @@ const SpecialMentionItem = ({ onPress(completeHandle); }, [completeHandle, onPress]); + const specialMentionItemTestId = `${testID}.${id}`; + return ( @@ -84,11 +89,17 @@ const SpecialMentionItem = ({ style={style.textWrapper} numberOfLines={1} > - {`@${completeHandle} - `} + + {`@${completeHandle} - `} + diff --git a/app/components/channel_actions/channel_actions.tsx b/app/components/channel_actions/channel_actions.tsx index 20f169275..3f1d3963a 100644 --- a/app/components/channel_actions/channel_actions.tsx +++ b/app/components/channel_actions/channel_actions.tsx @@ -16,6 +16,7 @@ type Props = { channelId: string; channelType?: string; inModal?: boolean; + testID?: string; } const OPTIONS_HEIGHT = 62; @@ -31,7 +32,7 @@ const styles = StyleSheet.create({ }, }); -const ChannelActions = ({channelId, channelType, inModal = false}: Props) => { +const ChannelActions = ({channelId, channelType, inModal = false, testID}: Props) => { const onCopyLinkAnimationEnd = useCallback(() => { if (!inModal) { requestAnimationFrame(async () => { @@ -45,17 +46,20 @@ const ChannelActions = ({channelId, channelType, inModal = false}: Props) => { {channelType && DIRECT_CHANNELS.includes(channelType) && } {channelType && !DIRECT_CHANNELS.includes(channelType) && @@ -63,11 +67,13 @@ const ChannelActions = ({channelId, channelType, inModal = false}: Props) => { } diff --git a/app/components/channel_item/channel_item.tsx b/app/components/channel_item/channel_item.tsx index 0bc712f99..31ff43dae 100644 --- a/app/components/channel_item/channel_item.tsx +++ b/app/components/channel_item/channel_item.tsx @@ -208,8 +208,8 @@ const ChannelListItem = ({ {teamDisplayName} @@ -225,8 +225,8 @@ const ChannelListItem = ({ {teamDisplayName} diff --git a/app/components/markdown/markdown.tsx b/app/components/markdown/markdown.tsx index c95348aac..b13f2c2ad 100644 --- a/app/components/markdown/markdown.tsx +++ b/app/components/markdown/markdown.tsx @@ -177,7 +177,7 @@ const Markdown = ({ }; const renderBreak = () => { - return {'\n'}; + return {'\n'}; }; const renderChannelLink = ({context, channelName}: MarkdownChannelMentionRenderer) => { @@ -197,7 +197,7 @@ const Markdown = ({ const renderCheckbox = ({isChecked}: {isChecked: boolean}) => { return ( - + { const {code} = textStyles; - return {literal}; + return ( + + {literal} + + ); }; const renderEditedIndicator = ({context}: {context: string[]}) => { @@ -288,7 +295,10 @@ const Markdown = ({ const renderHeading = ({children, level}: {children: ReactElement; level: string}) => { if (disableHeading) { return ( - + {children} ); @@ -301,7 +311,10 @@ const Markdown = ({ const textStyle = textStyles[`heading${level}Text`]; return ( - + {children} @@ -314,7 +327,10 @@ const Markdown = ({ if (props.isBlock) { rendered = ( - + {rendered} ); @@ -419,7 +435,10 @@ const Markdown = ({ } return ( - + {children} diff --git a/app/components/markdown/markdown_code_block/index.tsx b/app/components/markdown/markdown_code_block/index.tsx index 5226ad610..91dc3d003 100644 --- a/app/components/markdown/markdown_code_block/index.tsx +++ b/app/components/markdown/markdown_code_block/index.tsx @@ -201,6 +201,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc diff --git a/app/components/markdown/markdown_image/index.tsx b/app/components/markdown/markdown_image/index.tsx index bbfefdc1f..c647e8642 100644 --- a/app/components/markdown/markdown_image/index.tsx +++ b/app/components/markdown/markdown_image/index.tsx @@ -235,10 +235,7 @@ const MarkdownImage = ({ onLongPress={handleLinkLongPress} onPress={onGestureEvent} > - + { onPress={handlePress} onLongPress={handleLongPress} type={'opacity'} + testID='markdown_latex_code_block' > { {renderChildren} diff --git a/app/components/markdown/markdown_list_item/index.tsx b/app/components/markdown/markdown_list_item/index.tsx index ef427b043..8ebef76d0 100644 --- a/app/components/markdown/markdown_list_item/index.tsx +++ b/app/components/markdown/markdown_list_item/index.tsx @@ -41,9 +41,15 @@ const MarkdownListItem = ({index, level, bulletWidth, bulletStyle, children, con } return ( - + - + {bullet} diff --git a/app/components/markdown/markdown_table_cell/index.tsx b/app/components/markdown/markdown_table_cell/index.tsx index bc05528ab..634d9addc 100644 --- a/app/components/markdown/markdown_table_cell/index.tsx +++ b/app/components/markdown/markdown_table_cell/index.tsx @@ -53,7 +53,10 @@ const MarkdownTableCell = ({isLastCell, align, children}: MarkdownTableCellProps } return ( - + {children} ); diff --git a/app/components/markdown/markdown_table_image/index.tsx b/app/components/markdown/markdown_table_image/index.tsx index db7edfb1c..b802e9a38 100644 --- a/app/components/markdown/markdown_table_image/index.tsx +++ b/app/components/markdown/markdown_table_image/index.tsx @@ -113,10 +113,7 @@ const MarkTableImage = ({disabled, imagesMetadata, location, postId, serverURL, disabled={disabled} onPress={onGestureEvent} > - + + {image} ); diff --git a/app/components/markdown/markdown_table_row/index.tsx b/app/components/markdown/markdown_table_row/index.tsx index 3f78c39f8..e1880b299 100644 --- a/app/components/markdown/markdown_table_row/index.tsx +++ b/app/components/markdown/markdown_table_row/index.tsx @@ -49,7 +49,14 @@ const MarkdownTableRow = ({isFirstRow, isLastRow, children}: MarkdownTableRowPro isLastCell: true, }); - return {renderChildren}; + return ( + + {renderChildren} + + ); }; export default MarkdownTableRow; diff --git a/app/components/post_list/post/avatar/avatar.tsx b/app/components/post_list/post/avatar/avatar.tsx index d043a1d08..5da7add59 100644 --- a/app/components/post_list/post/avatar/avatar.tsx +++ b/app/components/post_list/post/avatar/avatar.tsx @@ -113,7 +113,7 @@ const Avatar = ({author, enablePostIconOverride, isAutoReponse, location, post}: size={ViewConstant.PROFILE_PICTURE_SIZE} iconSize={24} showStatus={!isAutoReponse || author.isBot} - testID={`post_profile_picture.${author.id}.profile_picture`} + testID={`post_avatar.${author.id}.profile_picture`} /> ); diff --git a/app/components/post_list/post/header/tag/index.tsx b/app/components/post_list/post/header/tag/index.tsx index e9b8f4c42..bb3cbf648 100644 --- a/app/components/post_list/post/header/tag/index.tsx +++ b/app/components/post_list/post/header/tag/index.tsx @@ -28,14 +28,14 @@ const HeaderTag = ({ return ( ); } else if (isGuest) { return ( ); } else if (isAutoResponder) { @@ -44,6 +44,7 @@ const HeaderTag = ({ id={t('post_info.auto_responder')} defaultMessage={'Automatic Reply'} style={style.tag} + testID='post_header.auto_responder.tag' /> ); } diff --git a/app/components/post_list/post/system_message/__snapshots__/system_message_helpers.test.js.snap b/app/components/post_list/post/system_message/__snapshots__/system_message_helpers.test.js.snap index a8d93ce4f..14d092c8c 100644 --- a/app/components/post_list/post/system_message/__snapshots__/system_message_helpers.test.js.snap +++ b/app/components/post_list/post/system_message/__snapshots__/system_message_helpers.test.js.snap @@ -18,6 +18,7 @@ exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] = }, ] } + testID="markdown_paragraph" > - {bot && } - {guest && } + {bot && } + {guest && } {Boolean(name.length) && } {Boolean(user) && diff --git a/app/components/user_list_row/index.tsx b/app/components/user_list_row/index.tsx index a36559773..6a8a0328b 100644 --- a/app/components/user_list_row/index.tsx +++ b/app/components/user_list_row/index.tsx @@ -198,9 +198,7 @@ export default function UserListRow({ const teammateDisplay = displayUsername(user, intl.locale, teammateNameDisplay); const showTeammateDisplay = teammateDisplay !== username; - const itemTestID = `${testID}.${id}`; - const displayNameTestID = `${itemTestID}.display_name`; - const profilePictureTestID = `${itemTestID}.profile_picture`; + const userItemTestID = `${testID}.${id}`; return ( <> @@ -212,14 +210,14 @@ export default function UserListRow({ @@ -228,15 +226,17 @@ export default function UserListRow({ style={style.username} ellipsizeMode='tail' numberOfLines={1} - testID={displayNameTestID} + testID={`${userItemTestID}.display_name`} > {usernameDisplay} {showTeammateDisplay && @@ -245,6 +245,7 @@ export default function UserListRow({ style={style.displayName} ellipsizeMode='tail' numberOfLines={1} + testID={`${userItemTestID}.team_display_name`} > {teammateDisplay} @@ -254,6 +255,7 @@ export default function UserListRow({ {formatMessage({id: 'mobile.user_list.deactivated', defaultMessage: 'Deactivated'})} diff --git a/app/screens/channel/channel_post_list/intro/options/index.tsx b/app/screens/channel/channel_post_list/intro/options/index.tsx index 5faaae3f5..9cfe52705 100644 --- a/app/screens/channel/channel_post_list/intro/options/index.tsx +++ b/app/screens/channel/channel_post_list/intro/options/index.tsx @@ -46,27 +46,27 @@ const IntroOptions = ({channelId, header, favorite, people}: Props) => { } {header && } {favorite && } ); diff --git a/app/screens/channel/header/quick_actions/index.tsx b/app/screens/channel/header/quick_actions/index.tsx index a245d25ca..dd58a8080 100644 --- a/app/screens/channel/header/quick_actions/index.tsx +++ b/app/screens/channel/header/quick_actions/index.tsx @@ -39,14 +39,20 @@ const ChannelQuickAction = ({channelId}: Props) => { return ( - + - + ); }; diff --git a/app/screens/channel_info/channel_info.tsx b/app/screens/channel_info/channel_info.tsx index 4a4806e09..dd7af36a8 100644 --- a/app/screens/channel_info/channel_info.tsx +++ b/app/screens/channel_info/channel_info.tsx @@ -54,11 +54,13 @@ const ChannelInfo = ({channelId, closeButtonId, componentId, type}: Props) => { { <ChannelActions channelId={channelId} inModal={true} + testID='channel_info.channel_actions' /> <Extra channelId={channelId}/> <View style={styles.separator}/> diff --git a/app/screens/channel_info/destructive_options/archive/archive.tsx b/app/screens/channel_info/destructive_options/archive/archive.tsx index 067447d8e..560d5afd6 100644 --- a/app/screens/channel_info/destructive_options/archive/archive.tsx +++ b/app/screens/channel_info/destructive_options/archive/archive.tsx @@ -130,6 +130,7 @@ const Archive = ({ icon='archive-outline' destructive={true} type='default' + testID='channel_info.options.archive_channel.option' /> ); }; diff --git a/app/screens/channel_info/destructive_options/convert_private/convert_private.tsx b/app/screens/channel_info/destructive_options/convert_private/convert_private.tsx index 63a08dc2b..87b4d737c 100644 --- a/app/screens/channel_info/destructive_options/convert_private/convert_private.tsx +++ b/app/screens/channel_info/destructive_options/convert_private/convert_private.tsx @@ -78,6 +78,7 @@ const ConvertPrivate = ({canConvert, channelId, displayName}: Props) => { label={intl.formatMessage({id: 'channel_info.convert_private', defaultMessage: 'Convert to private channel'})} icon='lock-outline' type='default' + testID='channel_info.options.convert_private.option' /> ); }; diff --git a/app/screens/channel_info/destructive_options/index.tsx b/app/screens/channel_info/destructive_options/index.tsx index dddad4d75..ce56e43be 100644 --- a/app/screens/channel_info/destructive_options/index.tsx +++ b/app/screens/channel_info/destructive_options/index.tsx @@ -24,6 +24,7 @@ const DestructiveOptions = ({channelId, componentId, type}: Props) => { <LeaveChannelLabel channelId={channelId} isOptionItem={true} + testID='channel_info.options.leave_channel.option' /> {type !== General.DM_CHANNEL && type !== General.GM_CHANNEL && <Archive diff --git a/app/screens/channel_info/options/edit_channel/index.tsx b/app/screens/channel_info/options/edit_channel/index.tsx index eb149170b..e10b325b5 100644 --- a/app/screens/channel_info/options/edit_channel/index.tsx +++ b/app/screens/channel_info/options/edit_channel/index.tsx @@ -28,6 +28,7 @@ const EditChannel = ({channelId}: Props) => { label={title} icon='pencil-outline' type={Platform.select({ios: 'arrow', default: 'default'})} + testID='channel_info.options.edit_channel.option' /> ); }; diff --git a/app/screens/channel_info/options/ignore_mentions/ignore_mentions.tsx b/app/screens/channel_info/options/ignore_mentions/ignore_mentions.tsx index 6c79aaba5..2fef6479b 100644 --- a/app/screens/channel_info/options/ignore_mentions/ignore_mentions.tsx +++ b/app/screens/channel_info/options/ignore_mentions/ignore_mentions.tsx @@ -34,6 +34,7 @@ const IgnoreMentions = ({channelId, ignoring}: Props) => { icon='at' type='toggle' selected={ignored} + testID={`channel_info.options.ignore_mentions.option.toggled.${ignored}`} /> ); }; diff --git a/app/screens/channel_info/options/members/members.tsx b/app/screens/channel_info/options/members/members.tsx index 9b179f137..778abbb1b 100644 --- a/app/screens/channel_info/options/members/members.tsx +++ b/app/screens/channel_info/options/members/members.tsx @@ -30,6 +30,7 @@ const Members = ({channelId, count}: Props) => { icon='account-multiple-outline' type={Platform.select({ios: 'arrow', default: 'default'})} info={count.toString()} + testID='channel_info.options.members.option' /> ); }; diff --git a/app/screens/channel_info/options/notification_preference/notification_preference.tsx b/app/screens/channel_info/options/notification_preference/notification_preference.tsx index 41b2bca36..f8b3991a8 100644 --- a/app/screens/channel_info/options/notification_preference/notification_preference.tsx +++ b/app/screens/channel_info/options/notification_preference/notification_preference.tsx @@ -59,6 +59,7 @@ const NotificationPreference = ({channelId, notifyLevel}: Props) => { icon='cellphone' type={Platform.select({ios: 'arrow', default: 'default'})} info={notificationLevelToText()} + testID='channel_info.options.notification_preference.option' /> ); }; diff --git a/app/screens/channel_info/options/pinned_messages/pinned_messages.tsx b/app/screens/channel_info/options/pinned_messages/pinned_messages.tsx index 08b0b6a4e..5922e4a98 100644 --- a/app/screens/channel_info/options/pinned_messages/pinned_messages.tsx +++ b/app/screens/channel_info/options/pinned_messages/pinned_messages.tsx @@ -45,6 +45,7 @@ const PinnedMessages = ({channelId, count, displayName}: Props) => { icon='pin-outline' type={Platform.select({ios: 'arrow', default: 'default'})} info={count.toString()} + testID='channel_info.options.pinned_messages.option' /> ); }; diff --git a/app/screens/channel_info/title/direct_message/direct_message.tsx b/app/screens/channel_info/title/direct_message/direct_message.tsx index 82ea597cf..c6d5ba7a0 100644 --- a/app/screens/channel_info/title/direct_message/direct_message.tsx +++ b/app/screens/channel_info/title/direct_message/direct_message.tsx @@ -50,21 +50,27 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const DirectMessage = ({displayName, user}: Props) => { const theme = useTheme(); const styles = getStyleSheet(theme); + const directMessageUserTestId = `channel_info.title.direct_message.${user?.id}`; return ( - <View style={styles.container}> + <View + style={styles.container} + testID={directMessageUserTestId} + > <ProfilePicture author={user} size={64} iconSize={64} showStatus={true} statusSize={24} + testID={`${directMessageUserTestId}.profile_picture`} /> <View style={styles.titleContainer}> <View style={styles.displayName}> <Text numberOfLines={1} style={styles.title} + testID={`${directMessageUserTestId}.display_name`} > {displayName} </Text> @@ -72,22 +78,30 @@ const DirectMessage = ({displayName, user}: Props) => { <GuestTag textStyle={styles.tag} style={styles.tagContainer} + testID={`${directMessageUserTestId}.guest.tag`} /> } {user?.isBot && <BotTag textStyle={styles.tag} style={styles.tagContainer} + testID={`${directMessageUserTestId}.bot.tag`} /> } </View> {Boolean(user?.position) && - <Text style={styles.position}> + <Text + style={styles.position} + testID={`${directMessageUserTestId}.position`} + > {user?.position} </Text> } {Boolean(user?.isBot && user.props?.bot_description) && - <Text style={styles.position}> + <Text + style={styles.position} + testID={`${directMessageUserTestId}.bot_description`} + > {user?.props?.bot_description} </Text> } diff --git a/app/screens/channel_info/title/group_message/avatars/avatars.tsx b/app/screens/channel_info/title/group_message/avatars/avatars.tsx index ed9f4786c..cbf48fd98 100644 --- a/app/screens/channel_info/title/group_message/avatars/avatars.tsx +++ b/app/screens/channel_info/title/group_message/avatars/avatars.tsx @@ -57,7 +57,10 @@ const GroupAvatars = ({users}: Props) => { }); return ( - <View style={[styles.container]}> + <View + style={[styles.container]} + testID='channel_info.title.group_message.group_avatars' + > {group} </View> ); diff --git a/app/screens/channel_info/title/group_message/group_message.tsx b/app/screens/channel_info/title/group_message/group_message.tsx index 6d5b35cca..f50d87127 100644 --- a/app/screens/channel_info/title/group_message/group_message.tsx +++ b/app/screens/channel_info/title/group_message/group_message.tsx @@ -40,7 +40,10 @@ const GroupMessage = ({currentUserId, displayName, members}: Props) => { <GroupAvatars userIds={userIds} /> - <Text style={styles.title}> + <Text + style={styles.title} + testID='channel_info.title.group_message.display_name' + > {displayName} </Text> </> diff --git a/app/screens/channel_info/title/public_private/public_private.tsx b/app/screens/channel_info/title/public_private/public_private.tsx index 4e5d55f9d..96fd2ee25 100644 --- a/app/screens/channel_info/title/public_private/public_private.tsx +++ b/app/screens/channel_info/title/public_private/public_private.tsx @@ -28,14 +28,21 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const PublicPrivate = ({displayName, purpose}: Props) => { const theme = useTheme(); const styles = getStyleSheet(theme); + const publicPrivateTestId = 'channel_info.title.public_private'; return ( <> - <Text style={styles.title}> + <Text + style={styles.title} + testID={`${publicPrivateTestId}.display_name`} + > {displayName} </Text> {Boolean(purpose) && - <Text style={styles.purpose}> + <Text + style={styles.purpose} + testID={`${publicPrivateTestId}.purpose`} + > {purpose} </Text> } diff --git a/app/screens/find_channels/filtered_list/filtered_list.tsx b/app/screens/find_channels/filtered_list/filtered_list.tsx index 3e86a40b0..5d405eb81 100644 --- a/app/screens/find_channels/filtered_list/filtered_list.tsx +++ b/app/screens/find_channels/filtered_list/filtered_list.tsx @@ -220,6 +220,7 @@ const FilteredList = ({ <UserItem onPress={onOpenDirectMessage} user={item} + testID='find_channels.filtered_list.user_item' /> ); } @@ -229,6 +230,7 @@ const FilteredList = ({ channel={item} onPress={onJoinChannel} showTeamName={showTeamName} + testID='find_channels.filtered_list.remote_channel_item' /> ); }, [onJoinChannel, onOpenDirectMessage, onSwitchToChannel, showTeamName, teammateDisplayNameSetting]); diff --git a/app/screens/find_channels/filtered_list/remote_channel_item/remote_channel_item.tsx b/app/screens/find_channels/filtered_list/remote_channel_item/remote_channel_item.tsx index 901df2017..e90ad7020 100644 --- a/app/screens/find_channels/filtered_list/remote_channel_item/remote_channel_item.tsx +++ b/app/screens/find_channels/filtered_list/remote_channel_item/remote_channel_item.tsx @@ -101,7 +101,7 @@ const RemoteChannelItem = ({onPress, channel, teamDisplayName, testID}: Props) = <Text ellipsizeMode='tail' numberOfLines={1} - testID={`${testID}.${teamDisplayName}.display_name`} + testID={`${testID}.${channel.name}.team_display_name`} style={styles.teamName} > {teamDisplayName} @@ -112,7 +112,7 @@ const RemoteChannelItem = ({onPress, channel, teamDisplayName, testID}: Props) = <Text ellipsizeMode='tail' numberOfLines={1} - testID={`${testID}.${teamDisplayName}.display_name`} + testID={`${testID}.${channel.name}.team_display_name`} style={[styles.teamName, styles.teamNameTablet]} > {teamDisplayName} diff --git a/app/screens/find_channels/filtered_list/user_item/user_item.tsx b/app/screens/find_channels/filtered_list/user_item/user_item.tsx index 5899ebc9b..4efcec50d 100644 --- a/app/screens/find_channels/filtered_list/user_item/user_item.tsx +++ b/app/screens/find_channels/filtered_list/user_item/user_item.tsx @@ -54,6 +54,7 @@ const UserItem = ({currentUserId, onPress, teammateDisplayNameSetting, testID, u const styles = getStyleSheet(theme); const isOwnDirectMessage = currentUserId === user.id; const displayName = displayUsername(user, locale, teammateDisplayNameSetting); + const userItemTestId = `${testID}.${user.id}`; const handleOnPress = useCallback(() => { onPress(user.id, displayName); @@ -64,7 +65,7 @@ const UserItem = ({currentUserId, onPress, teammateDisplayNameSetting, testID, u <> <View style={styles.container} - testID={`${testID}.${user.id}`} + testID={userItemTestId} > <View style={styles.wrapper}> <View style={styles.avatar}> @@ -74,6 +75,7 @@ const UserItem = ({currentUserId, onPress, teammateDisplayNameSetting, testID, u showStatus={true} statusSize={12} statusStyle={styles.status} + testID={`${userItemTestId}.profile_picture`} /> </View> <View> @@ -81,7 +83,7 @@ const UserItem = ({currentUserId, onPress, teammateDisplayNameSetting, testID, u ellipsizeMode='tail' numberOfLines={1} style={styles.text} - testID={`${testID}.${user.id}.display_name`} + testID={`${userItemTestId}.display_name`} > {isOwnDirectMessage ? formatMessage({id: 'channel_header.directchannel.you', defaultMessage: '{displayName} (you)'}, {displayName}) : displayName} </Text> diff --git a/app/screens/home/account/components/user_info/index.tsx b/app/screens/home/account/components/user_info/index.tsx index b25a421af..9e75084b1 100644 --- a/app/screens/home/account/components/user_info/index.tsx +++ b/app/screens/home/account/components/user_info/index.tsx @@ -52,6 +52,7 @@ const AccountUserInfo = ({user, showFullName, theme}: Props) => { const nickName = user.nickname ? ` (${user.nickname})` : ''; const title = `${user.firstName} ${user.lastName}${nickName}`; const userName = `@${user.username}`; + const accountUserInfoTestId = `account_user_info.${user.id}`; return ( <View style={styles.container}> @@ -62,10 +63,15 @@ const AccountUserInfo = ({user, showFullName, theme}: Props) => { author={user} statusStyle={styles.statusStyle} statusSize={24} - testID={`account.${user.id}.profile_picture`} + testID={`${accountUserInfoTestId}.profile_picture`} /> {showFullName && <Text style={styles.textFullName}>{title}</Text>} - <Text style={showFullName ? styles.textUserName : styles.textFullName}>{`${userName}`}</Text> + <Text + style={showFullName ? styles.textUserName : styles.textFullName} + testID={`${accountUserInfoTestId}.username`} + > + {`${userName}`} + </Text> </View> ); }; diff --git a/app/screens/reactions/reactors_list/index.tsx b/app/screens/reactions/reactors_list/index.tsx index 7c21fe950..5e6a6e05e 100644 --- a/app/screens/reactions/reactors_list/index.tsx +++ b/app/screens/reactions/reactors_list/index.tsx @@ -66,7 +66,7 @@ const ReactorsList = ({location, reactions}: Props) => { scrollEnabled={enabled} scrollEventThrottle={60} {...panResponder.panHandlers} - testID='reactors_list.flat_list' + testID='reactions.reactors_list.flat_list' /> ); }; diff --git a/app/screens/reactions/reactors_list/reactor/reactor.tsx b/app/screens/reactions/reactors_list/reactor/reactor.tsx index 882fc95a0..89be4702c 100644 --- a/app/screens/reactions/reactors_list/reactor/reactor.tsx +++ b/app/screens/reactions/reactors_list/reactor/reactor.tsx @@ -45,7 +45,7 @@ const Reactor = ({channelId, location, user}: Props) => { <UserItem containerStyle={style.container} user={user} - testID='reactors_list.user_item' + testID='reactions.reactor_item' /> </TouchableOpacity> ); diff --git a/app/screens/user_profile/label.tsx b/app/screens/user_profile/label.tsx index bea3f3de4..ea3198c0a 100644 --- a/app/screens/user_profile/label.tsx +++ b/app/screens/user_profile/label.tsx @@ -11,6 +11,7 @@ import {typography} from '@utils/typography'; type Props = { title: string; description: string; + testID?: string; }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -28,14 +29,24 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, })); -const UserProfileLabel = ({title, description}: Props) => { +const UserProfileLabel = ({title, description, testID}: Props) => { const theme = useTheme(); const styles = getStyleSheet(theme); return ( <View style={styles.container}> - <Text style={styles.title}>{title}</Text> - <Text style={styles.description}>{description}</Text> + <Text + style={styles.title} + testID={`${testID}.title`} + > + {title} + </Text> + <Text + style={styles.description} + testID={`${testID}.description`} + > + {description} + </Text> </View> ); }; diff --git a/app/screens/user_profile/options.tsx b/app/screens/user_profile/options.tsx index c410f3a45..14a924fb0 100644 --- a/app/screens/user_profile/options.tsx +++ b/app/screens/user_profile/options.tsx @@ -77,12 +77,14 @@ const UserProfileOptions = ({location, type, userId, username}: Props) => { <OptionBox iconName='send' onPress={openChannel} + testID='user_profile_options.send_message.option' text={intl.formatMessage({id: 'channel_info.send_mesasge', defaultMessage: 'Send message'})} /> <View style={styles.divider}/> <OptionBox iconName='at' onPress={mentionUser} + testID='user_profile_options.mention.option' text={intl.formatMessage({id: 'channel_info.mention', defaultMessage: 'Mention'})} /> </View> @@ -94,6 +96,7 @@ const UserProfileOptions = ({location, type, userId, username}: Props) => { <TouchableOpacity style={[buttonStyle, styles.singleButton]} onPress={openChannel} + testID='user_profile_options.send_message.option' > <CompassIcon color={theme.buttonBg} diff --git a/app/screens/user_profile/title/avatar.tsx b/app/screens/user_profile/title/avatar.tsx index 0b240c1c8..c58913ed7 100644 --- a/app/screens/user_profile/title/avatar.tsx +++ b/app/screens/user_profile/title/avatar.tsx @@ -43,6 +43,7 @@ const UserProfileAvatar = ({enablePostIconOverride, user, userIconOverride}: Pro showStatus={true} size={96} statusSize={24} + testID={`user_profile_avatar.${user.id}.profile_picture`} /> ); }; diff --git a/app/screens/user_profile/title/index.tsx b/app/screens/user_profile/title/index.tsx index 77f603a23..deee3c7db 100644 --- a/app/screens/user_profile/title/index.tsx +++ b/app/screens/user_profile/title/index.tsx @@ -89,6 +89,7 @@ const UserProfileTitle = ({ <Text numberOfLines={1} style={styles.displayName} + testID='user_profile.display_name' > {`${prefix}${displayName}`} </Text> @@ -96,6 +97,7 @@ const UserProfileTitle = ({ <Text numberOfLines={1} style={styles.username} + testID='user_profile.username' > {`@${user.username}`} </Text> diff --git a/app/screens/user_profile/title/tag.tsx b/app/screens/user_profile/title/tag.tsx index 4a5edf682..ec85013ab 100644 --- a/app/screens/user_profile/title/tag.tsx +++ b/app/screens/user_profile/title/tag.tsx @@ -24,11 +24,19 @@ const styles = StyleSheet.create({ const UserProfileTag = ({isBot, isChannelAdmin, isGuest, isSystemAdmin, isTeamAdmin}: Props) => { if (isBot) { - return (<BotTag style={styles.tag}/>); + return ( + <BotTag + style={styles.tag} + testID='user_profile.bot.tag' + />); } if (isGuest) { - return (<GuestTag style={styles.tag}/>); + return ( + <GuestTag + style={styles.tag} + testID='user_profile.guest.tag' + />); } if (isSystemAdmin) { @@ -37,6 +45,7 @@ const UserProfileTag = ({isBot, isChannelAdmin, isGuest, isSystemAdmin, isTeamAd id='user_profile.system_admin' defaultMessage='System Admin' style={styles.tag} + testID='user_profile.system_admin.tag' /> ); } @@ -47,6 +56,7 @@ const UserProfileTag = ({isBot, isChannelAdmin, isGuest, isSystemAdmin, isTeamAd id='user_profile.team_admin' defaultMessage='Team Admin' style={styles.tag} + testID='user_profile.team_admin.tag' /> ); } @@ -57,6 +67,7 @@ const UserProfileTag = ({isBot, isChannelAdmin, isGuest, isSystemAdmin, isTeamAd id='user_profile.channel_admin' defaultMessage='Channel Admin' style={styles.tag} + testID='user_profile.channel_admin.tag' /> ); } diff --git a/app/screens/user_profile/user_profile.tsx b/app/screens/user_profile/user_profile.tsx index 69f1b5df5..b565bcd81 100644 --- a/app/screens/user_profile/user_profile.tsx +++ b/app/screens/user_profile/user_profile.tsx @@ -127,18 +127,21 @@ const UserProfile = ({ {Boolean(user.nickname) && !override && !user.isBot && <UserProfileLabel description={user.nickname} + testID='user_profile.nickname' title={formatMessage({id: 'channel_info.nickname', defaultMessage: 'Nickname'})} /> } {Boolean(user.position) && !override && !user.isBot && <UserProfileLabel description={user.position} + testID='user_profile.position' title={formatMessage({id: 'channel_info.position', defaultMessage: 'Position'})} /> } {Boolean(localTime) && !override && !user.isBot && <UserProfileLabel description={localTime!} + testID='user_profile.local_time' title={formatMessage({id: 'channel_info.local_time', defaultMessage: 'Local Time'})} /> } @@ -153,7 +156,7 @@ const UserProfile = ({ componentId={Screens.USER_PROFILE} initialSnapIndex={0} snapPoints={snapPoints} - testID='post_options' + testID='user_profile' /> ); }; diff --git a/detox/e2e/support/server_api/channel.ts b/detox/e2e/support/server_api/channel.ts index 96f416923..14c9ec6a5 100644 --- a/detox/e2e/support/server_api/channel.ts +++ b/detox/e2e/support/server_api/channel.ts @@ -102,6 +102,23 @@ export const apiCreateGroupChannel = async (baseUrl: string, userIds: string[] = } }; +/** + * Delete a channel. + * See https://api.mattermost.com/#operation/DeleteChannel + * @param {string} baseUrl - the base server URL + * @param {string} channelId - The channel ID to be deleted + * @return {Object} returns {deleted} on success or {error, status} on error + */ +export const apiDeleteChannel = async (baseUrl: string, channelId: string): Promise<any> => { + try { + const response = await client.delete(`${baseUrl}/api/v4/channels/${channelId}`); + + return {deleted: response.data}; + } catch (err) { + return getResponseFromError(err); + } +}; + /** * Get a channel by name. * See https://api.mattermost.com/#operation/GetChannelByName @@ -172,6 +189,23 @@ export const apiGetUnreadMessages = async (baseUrl: string, userId: string, chan } }; +/** + * Restore a channel. + * See https://api.mattermost.com/#operation/RestoreChannel + * @param {string} baseUrl - the base server URL + * @param {string} channelId - The channel ID to be restored + * @return {Object} returns {restored} on success or {error, status} on error + */ +export const apiRestoreChannel = async (baseUrl: string, channelId: string): Promise<any> => { + try { + const response = await client.post(`${baseUrl}/api/v4/channels/${channelId}/restore`); + + return {restored: response.data}; + } catch (err) { + return getResponseFromError(err); + } +}; + /** * Remove user from channel. * See https://api.mattermost.com/#operation/RemoveUserFromChannel @@ -231,9 +265,11 @@ export const Channel = { apiCreateChannel, apiCreateDirectChannel, apiCreateGroupChannel, + apiDeleteChannel, apiGetChannelByName, apiGetChannelsForUser, apiGetUnreadMessages, + apiRestoreChannel, apiRemoveUserFromChannel, apiViewChannel, generateRandomChannel, diff --git a/detox/e2e/support/ui/component/alert.ts b/detox/e2e/support/ui/component/alert.ts index 1f467097a..650cea660 100644 --- a/detox/e2e/support/ui/component/alert.ts +++ b/detox/e2e/support/ui/component/alert.ts @@ -5,6 +5,7 @@ import {isAndroid} from '@support/utils'; class Alert { // alert titles + confirmSendingNotificationsTitle = isAndroid() ? element(by.text('Confirm sending notifications to entire channel')) : element(by.label('Confirm sending notifications to entire channel')).atIndex(0); deletePostTitle = isAndroid() ? element(by.text('Delete Post')) : element(by.label('Delete Post')).atIndex(0); logoutTitle = (serverDisplayName: string) => { const title = `Are you sure you want to log out of ${serverDisplayName}?`; @@ -21,6 +22,7 @@ class Alert { // alert buttons cancelButton = isAndroid() ? element(by.text('CANCEL')) : element(by.label('Cancel')).atIndex(1); + confirmButton = isAndroid() ? element(by.text('CONFIRM')) : element(by.label('Confirm')).atIndex(1); deleteButton = isAndroid() ? element(by.text('DELETE')) : element(by.label('Delete')).atIndex(0); logoutButton = isAndroid() ? element(by.text('LOG OUT')) : element(by.label('Log out')).atIndex(1); markReadButton = isAndroid() ? element(by.text('MARK READ')) : element(by.label('Mark read')).atIndex(1); diff --git a/detox/e2e/support/ui/component/autocomplete.ts b/detox/e2e/support/ui/component/autocomplete.ts new file mode 100644 index 000000000..e52594200 --- /dev/null +++ b/detox/e2e/support/ui/component/autocomplete.ts @@ -0,0 +1,125 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {expect} from 'detox'; + +import ProfilePicture from './profile_picture'; + +class Autocomplete { + testID = { + atMentionItemPrefix: 'autocomplete.at_mention_item.', + groupMentionItemPrefix: 'autocomplete.group_mention_item.', + specialMentionItemPrefix: 'autocomplete.special_mention_item.', + channelMentionItemPrefix: 'autocomplete.channel_mention_item.', + emojiSuggestionItemPrefix: 'autocomplete.emoji_suggestion_item.', + slashSuggestionItemPrefix: 'autocomplete.slash_suggestion_item.', + autocomplete: 'autocomplete', + sectionAtMentionList: 'autocomplete.at_mention.section_list', + sectionChannelMentionList: 'autocomplete.channel_mention.section_list', + flatEmojiSuggestionList: 'autocomplete.emoji_suggestion.flat_list', + flatSlashSuggestionList: 'autocomplete.slash_suggestion.flat_list', + }; + + autocomplete = element(by.id(this.testID.autocomplete)); + sectionAtMentionList = element(by.id(this.testID.sectionAtMentionList)); + sectionChannelMentionList = element(by.id(this.testID.sectionChannelMentionList)); + flatEmojiSuggestionList = element(by.id(this.testID.flatEmojiSuggestionList)); + flatSlashSuggestionList = element(by.id(this.testID.flatSlashSuggestionList)); + + getAtMentionItem = (userId: string) => { + const atMentionItemTestId = `${this.testID.atMentionItemPrefix}${userId}`; + const atMentionItemMatcher = by.id(atMentionItemTestId); + const atMentionItemProfilePictureMatcher = ProfilePicture.getProfilePictureItemMatcher(this.testID.atMentionItemPrefix, userId); + const atMentionItemBotTagMatcher = by.id(`${atMentionItemTestId}.bot.tag`); + const atMentionItemGuestTagMatcher = by.id(`${atMentionItemTestId}.guest.tag`); + const atMentionItemUserDisplayNameMatcher = by.id(`${atMentionItemTestId}.display_name`); + const atMentionItemCurrentUserIndicatorMatcher = by.id(`${atMentionItemTestId}.current_user_indicator`); + const atMentionItemUsernameMatcher = by.id(`${atMentionItemTestId}.username`); + + return { + atMentionItem: element(atMentionItemMatcher), + atMentionItemProfilePicture: element(atMentionItemProfilePictureMatcher), + atMentionItemBotTag: element(atMentionItemBotTagMatcher), + atMentionItemGuestTag: element(atMentionItemGuestTagMatcher), + atMentionItemUserDisplayName: element(atMentionItemUserDisplayNameMatcher), + atMentionItemCurrentUserIndicator: element(atMentionItemCurrentUserIndicatorMatcher), + atMentionItemUsername: element(atMentionItemUsernameMatcher), + }; + }; + + getGrouptMentionItem = (groupName: string) => { + const groupMentionItemTestId = `${this.testID.groupMentionItemPrefix}${groupName}`; + const groupMentionItemMatcher = by.id(groupMentionItemTestId); + const groupMentionItemGroupDisplayNameMatcher = by.id(`${groupMentionItemTestId}.display_name`); + const groupMentionItemGroupNameMatcher = by.id(`${groupMentionItemTestId}.name`); + + return { + groupMentionItem: element(groupMentionItemMatcher), + groupMentionItemGroupDisplayName: element(groupMentionItemGroupDisplayNameMatcher), + groupMentionItemGroupName: element(groupMentionItemGroupNameMatcher), + }; + }; + + getSpecialMentionItem = (handleName: string) => { + const specialMentionItemTestId = `${this.testID.specialMentionItemPrefix}${handleName}`; + const specialMentionItemMatcher = by.id(specialMentionItemTestId); + const specialMentionItemGroupDisplayNameMatcher = by.id(`${specialMentionItemTestId}.display_name`); + const specialMentionItemGroupNameMatcher = by.id(`${specialMentionItemTestId}.name`); + + return { + specialMentionItem: element(specialMentionItemMatcher), + specialMentionItemGroupDisplayName: element(specialMentionItemGroupDisplayNameMatcher), + specialMentionItemGroupName: element(specialMentionItemGroupNameMatcher), + }; + }; + + getChannelMentionItem = (channelName: string) => { + const channelMentionItemTestId = `${this.testID.channelMentionItemPrefix}${channelName}`; + const channelMentionItemMatcher = by.id(channelMentionItemTestId); + const channelMentionItemChannelDisplayNameMatcher = by.id(`${channelMentionItemTestId}.display_name`); + const channelMentionItemChannelNameMatcher = by.id(`${channelMentionItemTestId}.name`); + + return { + channelMentionItem: element(channelMentionItemMatcher), + channelMentionItemChannelDisplayName: element(channelMentionItemChannelDisplayNameMatcher), + channelMentionItemChannelName: element(channelMentionItemChannelNameMatcher), + }; + }; + + getEmojiSuggestionItem = (emojiName: string) => { + const emojiSuggestionItemTestId = `${this.testID.emojiSuggestionItemPrefix}${emojiName}`; + const emojiSuggestionItemMatcher = by.id(emojiSuggestionItemTestId); + const emojiSuggestionItemEmojiNameMatcher = by.id(`${emojiSuggestionItemTestId}.name`); + + return { + emojiSuggestionItem: element(emojiSuggestionItemMatcher), + emojiSuggestionItemEmojiName: element(emojiSuggestionItemEmojiNameMatcher), + }; + }; + + getSlashSuggestionItem = (slashCommand: string) => { + const slashSuggestionItemTestId = `${this.testID.slashSuggestionItemPrefix}/${slashCommand}`; + const slashSuggestionItemMatcher = by.id(slashSuggestionItemTestId); + const slashSuggestionItemSlashCommandNameMatcher = by.id(`${slashSuggestionItemTestId}.name`); + const slashSuggestionItemSlashCommandDescriptionMatcher = by.id(`${slashSuggestionItemTestId}.description`); + + return { + slashSuggestionItem: element(slashSuggestionItemMatcher), + slashSuggestionItemSlashCommandName: element(slashSuggestionItemSlashCommandNameMatcher), + slashSuggestionItemSlashCommandDescription: element(slashSuggestionItemSlashCommandDescriptionMatcher), + }; + }; + + toBeVisible = async (isVisible = true) => { + if (isVisible) { + await expect(this.autocomplete.atIndex(0)).toBeVisible(); + return this.autocomplete; + } + + await expect(this.autocomplete).not.toBeVisible(); + return null; + }; +} + +const autocomplete = new Autocomplete(); +export default autocomplete; diff --git a/detox/e2e/support/ui/component/index.ts b/detox/e2e/support/ui/component/index.ts index e1ba26801..4f2109da5 100644 --- a/detox/e2e/support/ui/component/index.ts +++ b/detox/e2e/support/ui/component/index.ts @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import Alert from './alert'; +import Autocomplete from './autocomplete'; import CameraQuickAction from './camera_quick_action'; import FileQuickAction from './file_quick_action'; import ImageQuickAction from './image_quick_action'; @@ -18,6 +19,7 @@ import TeamSidebar from './team_sidebar'; export { Alert, + Autocomplete, CameraQuickAction, FileQuickAction, ImageQuickAction, diff --git a/detox/e2e/support/ui/component/post.ts b/detox/e2e/support/ui/component/post.ts index de48c6ddb..be71ce5cd 100644 --- a/detox/e2e/support/ui/component/post.ts +++ b/detox/e2e/support/ui/component/post.ts @@ -5,20 +5,33 @@ import ProfilePicture from './profile_picture'; class Post { testID = { - postProfilePicturePrefix: 'post_profile_picture.', + postAvatarPrefix: 'post_avatar.', blockQuote: 'markdown_block_quote', + break: 'markdown_break', + checkbox: 'markdown_checkbox', + codeBlock: 'markdown_code_block', + codeSpan: 'markdown_code_span', editedIndicator: 'edited_indicator', emoji: 'markdown_emoji', + heading: 'markdown_heading', + html: 'markdown_html', image: 'markdown_image', message: 'markdown_text', + latexCodeBlock: 'markdown_latex_code_block', + latexInLine: 'markdown_latex_inline', + link: 'markdown_link', + listItem: 'markdown_list_item', + listItemBullet: 'markdown_list_item.bullet', + paragraph: 'markdown_paragraph', postFooterFollowButton: 'post_footer.follow_thread.button', postFooterFollowingButton: 'post_footer.following_thread.button', postFooterReplyCount: 'post_footer.reply_count', postHeaderCommentedOn: 'post_header.commented_on', postHeaderDateTime: 'post_header.date_time', postHeaderDisplayName: 'post_header.display_name', - postHeaderBotTag: 'post_header.bot_tag', - postHeaderGuestTag: 'post_header.guest_tag', + postHeaderBotTag: 'post_header.bot.tag', + postHeaderGuestTag: 'post_header.guest.tag', + postHeaderAutoResponderTag: 'post_header.auto_responder.tag', postHeaderReply: 'post_header.reply', postHeaderReplyCount: 'post_header.reply_count', postPreHeaderText: 'post_pre_header.text', @@ -26,37 +39,70 @@ class Post { showLessButton: 'show_more.button.chevron-up', showMoreButton: 'show_more.button.chevron-down', table: 'markdown_table', + tableCell: 'markdown_table_cell', tableExpandButton: 'markdown_table.expand.button', + tableImage: 'markdown_table_image', + tableRow: 'markdown_table_row', thematicBreak: 'markdown_thematic_break', }; getPost = (postItemSourceTestID: string, postId: string, postMessage: string, postProfileOptions: any = {}) => { const postItemMatcher = this.getPostItemMatcher(postItemSourceTestID, postId, postMessage); const postItemBlockQuoteMatcher = by.id(this.testID.blockQuote).withAncestor(postItemMatcher); + const postItemBreakMatcher = by.id(this.testID.break).withAncestor(postItemMatcher); + const postItemCheckboxMatcher = by.id(this.testID.checkbox).withAncestor(postItemMatcher); + const postItemCodeBlockMatcher = by.id(this.testID.codeBlock).withAncestor(postItemMatcher); + const postItemCodeSpanMatcher = by.id(this.testID.codeSpan).withAncestor(postItemMatcher); const postItemEditedIndicator = by.id(this.testID.editedIndicator).withAncestor(postItemMatcher); const postItemEmojiMatcher = by.id(this.testID.emoji).withAncestor(postItemMatcher); + const postItemHeadingMatcher = by.id(this.testID.heading).withAncestor(postItemMatcher); + const postItemHtmlMatcher = by.id(this.testID.html).withAncestor(postItemMatcher); const postItemImageMatcher = by.id(this.testID.image).withAncestor(postItemMatcher); const postItemMessageMatcher = by.id(this.testID.message).withAncestor(postItemMatcher); + const postItemLatexCodeBlockMatcher = by.id(this.testID.latexCodeBlock).withAncestor(postItemMatcher); + const postItemInlineLatexMatcher = by.id(this.testID.latexInLine).withAncestor(postItemMatcher); + const postItemLinkMatcher = by.id(this.testID.link).withAncestor(postItemMatcher); + const postItemListItemMatcher = by.id(this.testID.listItem).withAncestor(postItemMatcher); + const postItemListItemBulletMatcher = by.id(this.testID.listItemBullet).withAncestor(postItemMatcher); + const postItemParagraphMatcher = by.id(this.testID.paragraph).withAncestor(postItemMatcher); const postItemPreHeaderTextMatch = by.id(this.testID.postPreHeaderText).withAncestor(postItemMatcher); const postItemShowLessButtonMatcher = by.id(this.testID.showLessButton).withAncestor(postItemMatcher); const postItemShowMoreButtonMatcher = by.id(this.testID.showMoreButton).withAncestor(postItemMatcher); const postItemTableMatcher = by.id(this.testID.table).withAncestor(postItemMatcher); + const postItemTableCellMatcher = by.id(this.testID.tableCell).withAncestor(postItemMatcher); const postItemTableExpandButtonMatcher = by.id(this.testID.tableExpandButton).withAncestor(postItemMatcher); + const postItemTableImageMatcher = by.id(this.testID.tableImage).withAncestor(postItemMatcher); + const postItemTableRowMatcher = by.id(this.testID.tableRow).withAncestor(postItemMatcher); const postItemThematicBreakMatcher = by.id(this.testID.thematicBreak).withAncestor(postItemMatcher); const postItemUnreadDotBadgeMatcher = by.id(this.testID.postUnreadDotBadge).withAncestor(postItemMatcher); return { postItem: element(postItemMatcher), postItemBlockQuote: element(postItemBlockQuoteMatcher), + postItemBreak: element(postItemBreakMatcher), + postItemCheckbox: element(postItemCheckboxMatcher), + postItemCodeBlock: element(postItemCodeBlockMatcher), + postItemCodeSpan: element(postItemCodeSpanMatcher), postItemEditedIndicator: element(postItemEditedIndicator), postItemEmoji: element(postItemEmojiMatcher), + postItemHeading: element(postItemHeadingMatcher), + postItemHtml: element(postItemHtmlMatcher), postItemImage: element(postItemImageMatcher), postItemMessage: element(postItemMessageMatcher), + postItemLatexCodeBlock: element(postItemLatexCodeBlockMatcher), + postItemInlineLatex: element(postItemInlineLatexMatcher), + postItemLink: element(postItemLinkMatcher), + postItemListItem: element(postItemListItemMatcher), + postItemListItemBullet: element(postItemListItemBulletMatcher), + postItemParagraph: element(postItemParagraphMatcher), postItemPreHeaderText: element(postItemPreHeaderTextMatch), postItemShowLessButton: element(postItemShowLessButtonMatcher), postItemShowMoreButton: element(postItemShowMoreButtonMatcher), postItemTable: element(postItemTableMatcher), + postItemTableCell: element(postItemTableCellMatcher), postItemTableExpandButton: element(postItemTableExpandButtonMatcher), + postItemTableImage: element(postItemTableImageMatcher), + postItemTableRow: element(postItemTableRowMatcher), postItemThematicBreak: element(postItemThematicBreakMatcher), postItemUnreadDotBadge: element(postItemUnreadDotBadgeMatcher), ...this.getPostFooter(postItemMatcher), @@ -83,6 +129,7 @@ class Post { const postItemHeaderDisplayNameMatcher = by.id(this.testID.postHeaderDisplayName).withAncestor(postItemMatcher); const postItemHeaderBotTagMatcher = by.id(this.testID.postHeaderBotTag).withAncestor(postItemMatcher); const postItemHeaderGuestTagMatcher = by.id(this.testID.postHeaderGuestTag).withAncestor(postItemMatcher); + const postItemHeaderAutoResponderTagMatcher = by.id(this.testID.postHeaderAutoResponderTag).withAncestor(postItemMatcher); const postItemHeaderReplyMatcher = by.id(this.testID.postHeaderReply).withAncestor(postItemMatcher); const postItemHeaderReplyCountMatcher = by.id(this.testID.postHeaderReplyCount).withAncestor(postItemMatcher); @@ -92,6 +139,7 @@ class Post { postItemHeaderDisplayName: element(postItemHeaderDisplayNameMatcher), postItemHeaderBotTag: element(postItemHeaderBotTagMatcher), postItemHeaderGuestTag: element(postItemHeaderGuestTagMatcher), + postItemHeaderAutoResponderTag: element(postItemHeaderAutoResponderTagMatcher), postItemHeaderReply: element(postItemHeaderReplyMatcher), postItemHeaderReplyCount: element(postItemHeaderReplyCountMatcher), }; @@ -115,7 +163,7 @@ class Post { }; } - const profilePictureItemMatcher = ProfilePicture.getProfilePictureItemMatcher(this.testID.postProfilePicturePrefix, postProfileOptions.userId); + const profilePictureItemMatcher = ProfilePicture.getProfilePictureItemMatcher(this.testID.postAvatarPrefix, postProfileOptions.userId); const profilePictureItemUserStatusMatcher = ProfilePicture.getProfilePictureItemUserStatusMatcher(profilePictureItemMatcher, postProfileOptions.userStatus); const postItemProfilePictureMatcher = profilePictureItemMatcher.withAncestor(postItemMatcher); const postItemProfilePictureUserStatusMatcher = profilePictureItemUserStatusMatcher.withAncestor(postItemMatcher); diff --git a/detox/e2e/support/ui/component/post_list.ts b/detox/e2e/support/ui/component/post_list.ts index 978e0d4ec..4b47f73a8 100644 --- a/detox/e2e/support/ui/component/post_list.ts +++ b/detox/e2e/support/ui/component/post_list.ts @@ -61,6 +61,10 @@ class PostList { const { postItem, postItemBlockQuote, + postItemBreak, + postItemCheckbox, + postItemCodeBlock, + postItemCodeSpan, postItemEditedIndicator, postItemEmoji, postItemFooterFollowButton, @@ -71,17 +75,29 @@ class PostList { postItemHeaderDisplayName, postItemHeaderBotTag, postItemHeaderGuestTag, + postItemHeaderAutoResponderTag, postItemHeaderReply, postItemHeaderReplyCount, + postItemHeading, + postItemHtml, postItemImage, postItemMessage, + postItemLatexCodeBlock, + postItemInlineLatex, + postItemLink, + postItemListItem, + postItemListItemBullet, + postItemParagraph, postItemPreHeaderText, postItemProfilePicture, postItemProfilePictureUserStatus, postItemShowLessButton, postItemShowMoreButton, postItemTable, + postItemTableCell, postItemTableExpandButton, + postItemTableImage, + postItemTableRow, postItemThematicBreak, postItemUnreadDotBadge, } = Post.getPost(this.testID.postListPostItem, postId, postMessage, postProfileOptions); @@ -89,6 +105,10 @@ class PostList { return { postListPostItem: postItem, postListPostItemBlockQuote: postItemBlockQuote, + postListPostItemBreak: postItemBreak, + postListPostItemCheckbox: postItemCheckbox, + postListPostItemCodeBlock: postItemCodeBlock, + postListPostItemCodeSpan: postItemCodeSpan, postListPostItemEditedIndicator: postItemEditedIndicator, postListPostItemEmoji: postItemEmoji, postListPostItemFooterFollowButton: postItemFooterFollowButton, @@ -99,17 +119,29 @@ class PostList { postListPostItemHeaderDisplayName: postItemHeaderDisplayName, postListPostItemHeaderBotTag: postItemHeaderBotTag, postListPostItemHeaderGuestTag: postItemHeaderGuestTag, + postListPostItemHeaderAutoResponderTag: postItemHeaderAutoResponderTag, postListPostItemHeaderReply: postItemHeaderReply, postListPostItemHeaderReplyCount: postItemHeaderReplyCount, + postListPostItemHeading: postItemHeading, + postListPostItemHtml: postItemHtml, postListPostItemImage: postItemImage, postListPostItemMessage: postItemMessage, + postListPostItemLatexCodeBlock: postItemLatexCodeBlock, + postListPostItemInlineLatex: postItemInlineLatex, + postListPostItemLink: postItemLink, + postListPostItemListItem: postItemListItem, + postListPostItemListItemBullet: postItemListItemBullet, + postListPostItemParagraph: postItemParagraph, postListPostItemPreHeaderText: postItemPreHeaderText, postListPostItemProfilePicture: postItemProfilePicture, postListPostItemProfilePictureUserStatus: postItemProfilePictureUserStatus, postListPostItemShowLessButton: postItemShowLessButton, postListPostItemShowMoreButton: postItemShowMoreButton, postListPostItemTable: postItemTable, + postListPostItemTableCell: postItemTableCell, postListPostItemTableExpandButton: postItemTableExpandButton, + postListPostItemTableImage: postItemTableImage, + postListPostItemTableRow: postItemTableRow, postListPostItemThematicBreak: postItemThematicBreak, postListPostItemUnreadDotBadge: postItemUnreadDotBadge, }; diff --git a/detox/e2e/support/ui/screen/browse_channels.ts b/detox/e2e/support/ui/screen/browse_channels.ts index 774bd93b7..85ee05160 100644 --- a/detox/e2e/support/ui/screen/browse_channels.ts +++ b/detox/e2e/support/ui/screen/browse_channels.ts @@ -9,6 +9,7 @@ class BrowseChannelsScreen { testID = { browseChannelsScreen: 'browse_channels.screen', closeButton: 'close.browse_channels.button', + createButton: 'browse_channels.create.button', searchInput: 'browse_channels.search_bar.search.input', searchClearButton: 'browse_channels.search_bar.search.clear.button', searchCancelButton: 'browse_channels.search_bar.search.cancel.button', @@ -21,6 +22,7 @@ class BrowseChannelsScreen { browseChannelsScreen = element(by.id(this.testID.browseChannelsScreen)); closeButton = element(by.id(this.testID.closeButton)); + createButton = element(by.id(this.testID.createButton)); searchInput = element(by.id(this.testID.searchInput)); searchClearButton = element(by.id(this.testID.searchClearButton)); searchCancelButton = element(by.id(this.testID.searchCancelButton)); diff --git a/detox/e2e/support/ui/screen/channel.ts b/detox/e2e/support/ui/screen/channel.ts index b526cdf17..a191ce265 100644 --- a/detox/e2e/support/ui/screen/channel.ts +++ b/detox/e2e/support/ui/screen/channel.ts @@ -24,17 +24,19 @@ class ChannelScreen { channelScreenPrefix: 'channel.', channelScreen: 'channel.screen', introDisplayName: 'channel_post_list.intro.display_name', - introOptionAddPeopleItem: 'channel_post_list.intro.option_item.add_people', - introOptionSetHeaderItem: 'channel_post_list.intro.option_item.set_header', - introOptionChannelDetailsItem: 'channel_post_list.intro.option_item.channel_details', + introAddPeopleOption: 'channel_post_list.intro_options.add_people.option', + introSetHeaderOption: 'channel_post_list.intro_options.set_header.option', + introFavoriteOption: 'channel_post_list.intro_options.set_header.option', + introChannelDetailsOption: 'channel_post_list.intro_options.channel_details.option', flatPostList: 'channel.post_list.flat_list', }; channelScreen = element(by.id(this.testID.channelScreen)); introDisplayName = element(by.id(this.testID.introDisplayName)); - introOptionAddPeopleItem = element(by.id(this.testID.introOptionAddPeopleItem)); - introOptionSetHeaderItem = element(by.id(this.testID.introOptionSetHeaderItem)); - introOptionChannelDetailsItem = element(by.id(this.testID.introOptionChannelDetailsItem)); + introAddPeopleOption = element(by.id(this.testID.introAddPeopleOption)); + introSetHeaderOption = element(by.id(this.testID.introSetHeaderOption)); + introFavoriteOption = element(by.id(this.testID.introFavoriteOption)); + introChannelDetailsOption = element(by.id(this.testID.introChannelDetailsOption)); flatPostList = element(by.id(this.testID.flatPostList)); // convenience props @@ -71,7 +73,7 @@ class ChannelScreen { return this.postList.getNewMessagesDivider(); }; - getPostListPostItem = (postId: string, text: string, postProfileOptions: any = {}) => { + getPostListPostItem = (postId: string, text = '', postProfileOptions: any = {}) => { return this.postList.getPost(postId, text, postProfileOptions); }; diff --git a/detox/e2e/support/ui/screen/channel_info.ts b/detox/e2e/support/ui/screen/channel_info.ts new file mode 100644 index 000000000..2d5931383 --- /dev/null +++ b/detox/e2e/support/ui/screen/channel_info.ts @@ -0,0 +1,77 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {ChannelScreen} from '@support/ui/screen'; +import {timeouts} from '@support/utils'; +import {expect} from 'detox'; + +class ChannelInfoScreen { + testID = { + channelInfoScreen: 'channel_info.screen', + closeButton: 'close.channel_info.button', + scrollView: 'channel_info.scrollview', + favoriteAction: 'channel_info.channel_actions.favorite.action', + muteAction: 'channel_info.channel_actions.mute.action', + setHeaderAction: 'channel_info.channel_actions.set_header.action', + addPeopleAction: 'channel_info.channel_actions.add_people.action', + copyChannelLinkAction: 'channel_info.channel_actions.copy_channel_link.action', + ignoreMentionsOptionToggledOff: 'channel_info.options.ignore_mentions.option.toggled.false', + ignoreMentionsOptionToggledOn: 'channel_info.options.ignore_mentions.option.toggled.true', + notificationPreferenceOption: 'channel_info.options.notification_preference.option', + pinnedMessagesOption: 'channel_info.options.pinned_messages.option', + membersOption: 'channel_info.options.members.option', + editChannelOption: 'channel_info.options.edit_channel.option', + convertPrivateOption: 'channel_info.options.convert_private.option', + leaveChannelOption: 'channel_info.options.leave_channel.option', + archiveChannelOption: 'channel_info.options.archive_channel.option', + }; + + channelInfoScreen = element(by.id(this.testID.channelInfoScreen)); + closeButton = element(by.id(this.testID.closeButton)); + scrollView = element(by.id(this.testID.scrollView)); + favoriteAction = element(by.id(this.testID.favoriteAction)); + muteAction = element(by.id(this.testID.muteAction)); + setHeaderAction = element(by.id(this.testID.setHeaderAction)); + addPeopleAction = element(by.id(this.testID.addPeopleAction)); + copyChannelLinkAction = element(by.id(this.testID.copyChannelLinkAction)); + ignoreMentionsOptionToggledOff = element(by.id(this.testID.ignoreMentionsOptionToggledOff)); + ignoreMentionsOptionToggledOn = element(by.id(this.testID.ignoreMentionsOptionToggledOn)); + notificationPreferenceOption = element(by.id(this.testID.notificationPreferenceOption)); + pinnedMessagesOption = element(by.id(this.testID.pinnedMessagesOption)); + membersOption = element(by.id(this.testID.membersOption)); + editChannelOption = element(by.id(this.testID.editChannelOption)); + convertPrivateOption = element(by.id(this.testID.convertPrivateOption)); + leaveChannelOption = element(by.id(this.testID.leaveChannelOption)); + archiveChannelOption = element(by.id(this.testID.archiveChannelOption)); + + toBeVisible = async () => { + await waitFor(this.channelInfoScreen).toExist().withTimeout(timeouts.TEN_SEC); + + return this.channelInfoScreen; + }; + + open = async () => { + // # Open channel info screen + await ChannelScreen.headerTitle.tap(); + + return this.toBeVisible(); + }; + + close = async () => { + await this.closeButton.tap(); + await expect(this.channelInfoScreen).not.toBeVisible(); + }; + + toggleIgnoreMentionsOptionOn = async () => { + await this.ignoreMentionsOptionToggledOff.tap(); + await expect(this.ignoreMentionsOptionToggledOn).toBeVisible(); + }; + + toggleIgnoreMentionsOff = async () => { + await this.ignoreMentionsOptionToggledOn.tap(); + await expect(this.ignoreMentionsOptionToggledOff).toBeVisible(); + }; +} + +const channelInfoScreen = new ChannelInfoScreen(); +export default channelInfoScreen; diff --git a/detox/e2e/support/ui/screen/create_direct_message.ts b/detox/e2e/support/ui/screen/create_direct_message.ts index 0dd5cfe76..6cf52fa95 100644 --- a/detox/e2e/support/ui/screen/create_direct_message.ts +++ b/detox/e2e/support/ui/screen/create_direct_message.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {ProfilePicture} from '@support/ui/component'; import {ChannelListScreen} from '@support/ui/screen'; import {timeouts} from '@support/utils'; import {expect} from 'detox'; @@ -45,7 +46,7 @@ class CreateDirectMessageScreen { }; getUserItemProfilePicture = (userId: string) => { - return element(by.id(`create_direct_message.user_list.user_item.${userId}.profile_picture`)); + return element(ProfilePicture.getProfilePictureItemMatcher('create_direct_message.user_list.user_item.', userId)); }; getUserItemDisplayName = (userId: string) => { diff --git a/detox/e2e/support/ui/screen/create_or_edit_channel.ts b/detox/e2e/support/ui/screen/create_or_edit_channel.ts index e238f1511..1a04b3ac9 100644 --- a/detox/e2e/support/ui/screen/create_or_edit_channel.ts +++ b/detox/e2e/support/ui/screen/create_or_edit_channel.ts @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import { + ChannelInfoScreen, ChannelScreen, ChannelListScreen, } from '@support/ui/screen'; @@ -12,6 +13,7 @@ class CreateOrEditChannelScreen { testID = { createOrEditChannelScreen: 'create_or_edit_channel.screen', closeButton: 'close.create_or_edit_channel.button', + backButton: 'screen.back.button', createButton: 'create_or_edit_channel.create.button', saveButton: 'create_or_edit_channel.save.button', scrollView: 'create_or_edit_channel.scrollview', @@ -27,6 +29,7 @@ class CreateOrEditChannelScreen { createOrEditChannelScreen = element(by.id(this.testID.createOrEditChannelScreen)); closeButton = element(by.id(this.testID.closeButton)); + backButton = element(by.id(this.testID.backButton)); createButton = element(by.id(this.testID.createButton)); saveButton = element(by.id(this.testID.saveButton)); scrollView = element(by.id(this.testID.scrollView)); @@ -55,11 +58,23 @@ class CreateOrEditChannelScreen { openEditChannel = async () => { // # Open edit channel screen - await ChannelScreen.introOptionSetHeaderItem.tap(); + await ChannelInfoScreen.editChannelOption.tap(); return this.toBeVisible(); }; + openEditChannelHeader = async () => { + // # Open edit channel header screen + await ChannelScreen.introSetHeaderOption.tap(); + + return this.toBeVisible(); + }; + + back = async () => { + await this.backButton.tap(); + await expect(this.createOrEditChannelScreen).not.toBeVisible(); + }; + close = async () => { await this.closeButton.tap(); await expect(this.createOrEditChannelScreen).not.toBeVisible(); diff --git a/detox/e2e/support/ui/screen/edit_post.ts b/detox/e2e/support/ui/screen/edit_post.ts index cbb2d5543..e4e2d381d 100644 --- a/detox/e2e/support/ui/screen/edit_post.ts +++ b/detox/e2e/support/ui/screen/edit_post.ts @@ -1,7 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {PostOptionsScreen} from '@support/ui/screen'; import {timeouts} from '@support/utils'; +import {expect} from 'detox'; class EditPostScreen { testID = { @@ -26,6 +28,17 @@ class EditPostScreen { return this.editPostScreen; }; + + open = async () => { + await PostOptionsScreen.editPostOption.tap(); + + return this.toBeVisible(); + }; + + close = async () => { + await this.closeButton.tap(); + await expect(this.editPostScreen).not.toBeVisible(); + }; } const editPostScreen = new EditPostScreen(); diff --git a/detox/e2e/support/ui/screen/index.ts b/detox/e2e/support/ui/screen/index.ts index 3f28c9d16..00fda22d0 100644 --- a/detox/e2e/support/ui/screen/index.ts +++ b/detox/e2e/support/ui/screen/index.ts @@ -5,6 +5,7 @@ import AccountScreen from './account'; import BrowseChannelsScreen from './browse_channels'; import ChannelScreen from './channel'; import ChannelDropdownMenuScreen from './channel_dropdown_menu'; +import ChannelInfoScreen from './channel_info'; import ChannelListScreen from './channel_list'; import CreateDirectMessageScreen from './create_direct_message'; import CreateOrEditChannelScreen from './create_or_edit_channel'; @@ -20,14 +21,17 @@ import PostOptionsScreen from './post_options'; import ReactionsScreen from './reactions'; import ServerScreen from './server'; import ServerListScreen from './server_list'; +import TableScreen from './table'; import ThreadScreen from './thread'; import ThreadOptionsScreen from './thread_options'; +import UserProfileScreen from './user_profile'; export { AccountScreen, BrowseChannelsScreen, ChannelScreen, ChannelDropdownMenuScreen, + ChannelInfoScreen, ChannelListScreen, CreateDirectMessageScreen, CreateOrEditChannelScreen, @@ -43,6 +47,8 @@ export { ReactionsScreen, ServerScreen, ServerListScreen, + TableScreen, ThreadScreen, ThreadOptionsScreen, + UserProfileScreen, }; diff --git a/detox/e2e/support/ui/screen/permalink.ts b/detox/e2e/support/ui/screen/permalink.ts index 3b54d9c0b..a6337161d 100644 --- a/detox/e2e/support/ui/screen/permalink.ts +++ b/detox/e2e/support/ui/screen/permalink.ts @@ -18,7 +18,7 @@ class PermalinkScreen { postList = new PostList(this.testID.permalinkScreenPrefix); - getPostListPostItem = (postId: string, text: string, postProfileOptions = {}) => { + getPostListPostItem = (postId: string, text = '', postProfileOptions = {}) => { return this.postList.getPost(postId, text, postProfileOptions); }; diff --git a/detox/e2e/support/ui/screen/reactions.ts b/detox/e2e/support/ui/screen/reactions.ts index 4a450092d..ffee206e8 100644 --- a/detox/e2e/support/ui/screen/reactions.ts +++ b/detox/e2e/support/ui/screen/reactions.ts @@ -1,14 +1,15 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {ProfilePicture} from '@support/ui/component'; import {expect} from 'detox'; class ReactionsScreen { testID = { - reactorItemPrefix: 'reactors_list.user_item.', + reactorItemPrefix: 'reactions.reactor_item.', reactionsScreen: 'reactions.screen', reactionsBackdrop: 'reactions.backdrop', - flatReactorsList: 'reactors_list.flat_list', + flatReactorsList: 'reactions.reactors_list.flat_list', }; reactionsScreen = element(by.id(this.testID.reactionsScreen)); @@ -30,16 +31,16 @@ class ReactionsScreen { const reactorItemEmojiAliasesMatcher = by.id(`emoji_aliases.${emojiName}`); const reactorItemUserTestId = `${this.testID.reactorItemPrefix}${userId}`; const reactorItemUserMatcher = by.id(reactorItemUserTestId); - const reactorItemUserProfilePictureMatcher = by.id(`${reactorItemUserTestId}.profile_picture`); + const reactorItemUserProfilePictureMatcher = ProfilePicture.getProfilePictureItemMatcher(this.testID.reactorItemPrefix, userId); const reactorItemUserDisplayNameMatcher = by.id(`${reactorItemUserTestId}.display_name`); - const reactorItemUserUsernameMatcher = by.id(`${reactorItemUserTestId}.username`); + const reactorItemUsernameMatcher = by.id(`${reactorItemUserTestId}.username`); return { reactorItemEmojiAliases: element(reactorItemEmojiAliasesMatcher), reactorItemUser: element(reactorItemUserMatcher), reactorItemUserProfilePicture: element(reactorItemUserProfilePictureMatcher), reactorItemUserDisplayName: element(reactorItemUserDisplayNameMatcher), - reactorItemUserUsername: element(reactorItemUserUsernameMatcher), + reactorItemUsername: element(reactorItemUsernameMatcher), }; }; } diff --git a/detox/e2e/support/ui/screen/table.ts b/detox/e2e/support/ui/screen/table.ts new file mode 100644 index 000000000..7ee755491 --- /dev/null +++ b/detox/e2e/support/ui/screen/table.ts @@ -0,0 +1,30 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {expect} from 'detox'; + +class TableScreen { + testID = { + tableScreen: 'table.screen', + tableScrollView: 'table.scroll_view', + backButton: 'screen.back.button', + }; + + tableScreen = element(by.id(this.testID.tableScreen)); + tableScrollView = element(by.id(this.testID.tableScrollView)); + backButton = element(by.id(this.testID.backButton)); + + toBeVisible = async () => { + await expect(this.tableScreen).toBeVisible(); + + return this.tableScreen; + }; + + back = async () => { + await this.backButton.tap(); + await expect(this.tableScreen).not.toBeVisible(); + }; +} + +const tableScreen = new TableScreen(); +export default tableScreen; diff --git a/detox/e2e/support/ui/screen/thread.ts b/detox/e2e/support/ui/screen/thread.ts index b1034b548..28d4a3fd9 100644 --- a/detox/e2e/support/ui/screen/thread.ts +++ b/detox/e2e/support/ui/screen/thread.ts @@ -74,7 +74,7 @@ class ThreadScreen { return this.postList.getThreadOverviewPostOptionsButton(); }; - getPostListPostItem = (postId: string, text: string, postProfileOptions: any = {}) => { + getPostListPostItem = (postId: string, text = '', postProfileOptions: any = {}) => { return this.postList.getPost(postId, text, postProfileOptions); }; diff --git a/detox/e2e/support/ui/screen/user_profile.ts b/detox/e2e/support/ui/screen/user_profile.ts new file mode 100644 index 000000000..42d7c1e01 --- /dev/null +++ b/detox/e2e/support/ui/screen/user_profile.ts @@ -0,0 +1,58 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {ProfilePicture} from '@support/ui/component'; +import {timeouts} from '@support/utils'; +import {expect} from 'detox'; + +class UserProfileScreen { + testID = { + userProfileScreen: 'user_profile.screen', + userProfileAvatarPrefix: 'user_profile_avatar.', + userProfileBackdrop: 'user_profile.backdrop', + systemAdminTag: 'user_profile.system_admin.tag', + teamAdminTag: 'user_profile.team_admin.tag', + channelAdminTag: 'user_profile.channel_admin.tag', + userDisplayName: 'user_profile.display_name', + username: 'user_profile.username', + sendMessageProfileOption: 'user_profile_options.send_message.option', + mentionProfileOption: 'user_profile_options.mention.option', + userNicknameTitle: 'user_profile.nickname.title', + userNicknameDescription: 'user_profile.nickname.description', + userPositionTitle: 'user_profile.position.title', + userPositionDescription: 'user_profile.position.description', + userLocalTimeTitle: 'user_profile.local_time.title', + userLocalTimeDescription: 'user_profile.local_time.description', + }; + + userProfileScreen = element(by.id(this.testID.userProfileScreen)); + userProfileBackdrop = element(by.id(this.testID.userProfileBackdrop)); + userDisplayName = element(by.id(this.testID.userDisplayName)); + username = element(by.id(this.testID.username)); + sendMessageProfileOption = element(by.id(this.testID.sendMessageProfileOption)); + mentionProfileOption = element(by.id(this.testID.mentionProfileOption)); + userNicknameTitle = element(by.id(this.testID.userNicknameTitle)); + userNicknameDescription = element(by.id(this.testID.userNicknameDescription)); + userPositionTitle = element(by.id(this.testID.userPositionTitle)); + userPositionDescription = element(by.id(this.testID.userPositionDescription)); + userLocalTimeTitle = element(by.id(this.testID.userLocalTimeTitle)); + userLocalTimeDescription = element(by.id(this.testID.userLocalTimeDescription)); + + getUserProfilePicture = (userId: string) => { + return element(ProfilePicture.getProfilePictureItemMatcher(this.testID.userProfileAvatarPrefix, userId)); + }; + + toBeVisible = async () => { + await waitFor(this.userProfileScreen).toExist().withTimeout(timeouts.TEN_SEC); + + return this.userProfileScreen; + }; + + close = async () => { + await this.userProfileBackdrop.tap({x: 5, y: 10}); + await expect(this.userProfileScreen).not.toBeVisible(); + }; +} + +const userProfileScreen = new UserProfileScreen(); +export default userProfileScreen; diff --git a/detox/e2e/test/autocomplete/at_mention.e2e.ts b/detox/e2e/test/autocomplete/at_mention.e2e.ts new file mode 100644 index 000000000..3665eae46 --- /dev/null +++ b/detox/e2e/test/autocomplete/at_mention.e2e.ts @@ -0,0 +1,324 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Channel, + Setup, + Team, + User, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Autocomplete} from '@support/ui/component'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId, timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +describe('Autocomplete - At-Mention', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + let testTeam: any; + let testUser: any; + let testOtherUser: any; + let userAtMentionAutocomplete: any; + let otherUserAtMentionAutocomplete: any; + + beforeAll(async () => { + const {channel, team, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + testTeam = team; + testUser = user; + ({atMentionItem: userAtMentionAutocomplete} = Autocomplete.getAtMentionItem(testUser.id)); + + ({user: testOtherUser} = await User.apiCreateUser(siteOneUrl)); + await Team.apiAddUserToTeam(siteOneUrl, testOtherUser.id, testTeam.id); + await Channel.apiAddUserToChannel(siteOneUrl, testOtherUser.id, testChannel.id); + ({atMentionItem: otherUserAtMentionAutocomplete} = Autocomplete.getAtMentionItem(testOtherUser.id)); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(testUser); + + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + + // # Open a channel screen + await ChannelScreen.open(channelsCategory, testChannel.name); + }); + + beforeEach(async () => { + // # Clear post input + await ChannelScreen.postInput.clearText(); + + // * Verify autocomplete is not displayed + await Autocomplete.toBeVisible(false); + }); + + afterAll(async () => { + // # Log out + await ChannelScreen.back(); + await HomeScreen.logout(); + }); + + it('MM-T4878_1 - should suggest user based on username', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in username + await ChannelScreen.postInput.typeText(testUser.username); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(userAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_2 - should suggest user based on nickname', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in nickname + await ChannelScreen.postInput.typeText(testUser.nickname); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(userAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_3 - should suggest user based on first name', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in first name + await ChannelScreen.postInput.typeText(testUser.first_name); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(userAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_4 - should suggest user based on last name', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in last name + await ChannelScreen.postInput.typeText(testUser.last_name); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(userAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_5 - should suggest user based on lowercase first name', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in lowercase first name + await ChannelScreen.postInput.typeText(testUser.first_name.toLowerCase()); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(userAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_6 - should suggest user based on lowercase last name', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in lowercase last name + await ChannelScreen.postInput.typeText(testUser.last_name.toLowerCase()); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(userAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_7 - should suggest user based on full name with space', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in full name with space + await ChannelScreen.postInput.typeText(`${testUser.first_name} ${testUser.last_name}`); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(userAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_8 - should suggest user based on partial full name with space', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in partial full name with space + await ChannelScreen.postInput.typeText(`${testUser.first_name} ${testUser.last_name.substring(0, testUser.last_name.length - 6)}`); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(userAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_9 - should stop suggesting user after full name with trailing space -- KNOWN ISSUE: MM-45279', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in full name + await ChannelScreen.postInput.typeText(`${testUser.first_name} ${testUser.last_name}`); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(userAtMentionAutocomplete).toBeVisible(); + + // # Type in trailing space + await ChannelScreen.postInput.typeText(' '); + await wait(timeouts.ONE_SEC); + + // * Verify at-mention autocomplete does not contain associated user suggestion + await expect(userAtMentionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4878_10 - should stop suggesting user when keyword is not associated with any user', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in keyword not associated with any user + await ChannelScreen.postInput.typeText(getRandomId()); + + // * Verify at-mention autocomplete does not contain associated user suggestion + await expect(userAtMentionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4878_11 - should be able to select at-mention multiple times', async () => { + // # Type in "@" to activate at-mention autocomplete + await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + await ChannelScreen.postInput.typeText('@'); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in username and tap on user at-mention autocomplete + await ChannelScreen.postInput.typeText(testUser.username); + await userAtMentionAutocomplete.tap(); + + // * Verify at-mention list disappears + await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + + // # Type in "@" again to re-activate at-mention list + await ChannelScreen.postInput.typeText('@'); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + }); + + it('MM-T4878_12 - should not be able to autocomplete deactivated user', async () => { + // # Deactivate another channel member user and type in "@" to activate at-mention autocomplete + await User.apiDeactivateUser(siteOneUrl, testOtherUser.id); + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in username of deactivated user + await ChannelScreen.postInput.typeText(testOtherUser.username); + + // * Verify at-mention autocomplete does not contain associated user suggestion + await expect(otherUserAtMentionAutocomplete).not.toBeVisible(); + + // # Reactivate user, clear post input, and type in "@" to activate at-mention list + await User.apiUpdateUserActiveStatus(siteOneUrl, testOtherUser.id, true); + await ChannelScreen.postInput.clearText(); + await Autocomplete.toBeVisible(false); + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in username of reactivated user + await ChannelScreen.postInput.typeText(testOtherUser.username); + + // * Verify at-mention autocomplete contains associated user suggestion + await expect(otherUserAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_13 - should be able to autocomplete out of channel user', async () => { + // # Type in "@" to activate at-mention autocomplete + const {user: outOfChannelUser} = await User.apiCreateUser(siteOneUrl); + await Team.apiAddUserToTeam(siteOneUrl, outOfChannelUser.id, testTeam.id); + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in username of out of channel user + await ChannelScreen.postInput.typeText(outOfChannelUser.username); + + // * Verify at-mention autocomplete contains associated user suggestion + const {atMentionItem: outOfChannelUserAtMentionAutocomplete} = Autocomplete.getAtMentionItem(outOfChannelUser.id); + await expect(outOfChannelUserAtMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4878_14 - should include current user in autocomplete', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in username of current user + await ChannelScreen.postInput.typeText(testUser.username); + + // * Verify at-mention autocomplete contains current user + const {atMentionItemUserDisplayName, atMentionItemCurrentUserIndicator, atMentionItemUsername} = Autocomplete.getAtMentionItem(testUser.id); + await expect(atMentionItemUserDisplayName).toHaveText(`${testUser.first_name} ${testUser.last_name}`); + await expect(atMentionItemCurrentUserIndicator).toHaveText(' (you)'); + await expect(atMentionItemUsername).toHaveText(` @${testUser.username}`); + }); +}); diff --git a/detox/e2e/test/autocomplete/channel_mention.e2e.ts b/detox/e2e/test/autocomplete/channel_mention.e2e.ts new file mode 100644 index 000000000..6a1f4b6f5 --- /dev/null +++ b/detox/e2e/test/autocomplete/channel_mention.e2e.ts @@ -0,0 +1,266 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Channel, + Setup, + System, + Team, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Autocomplete} from '@support/ui/component'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId, timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +describe('Autocomplete - Channel Mention', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + let testOtherChannel: any; + let testTeam: any; + let channelMentionAutocomplete: any; + let otherChannelMentionAutocomplete: any; + + beforeAll(async () => { + System.apiUpdateConfig(siteOneUrl, { + ServiceSettings: { + EnableAPIChannelDeletion: true, + }, + }); + + const {channel, team, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + testTeam = team; + ({channelMentionItem: channelMentionAutocomplete} = Autocomplete.getChannelMentionItem(testChannel.name)); + + ({channel: testOtherChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id})); + await Channel.apiAddUserToChannel(siteOneUrl, user.id, testOtherChannel.id); + ({channelMentionItem: otherChannelMentionAutocomplete} = Autocomplete.getChannelMentionItem(testOtherChannel.name)); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + + // # Open a channel screen + await ChannelScreen.open(channelsCategory, testChannel.name); + }); + + beforeEach(async () => { + // # Clear post input + await ChannelScreen.postInput.clearText(); + + // * Verify autocomplete is not displayed + await Autocomplete.toBeVisible(false); + }); + + afterAll(async () => { + // # Log out + await ChannelScreen.back(); + await HomeScreen.logout(); + }); + + it('MM-T4879_1 - should suggest channel based on channel name', async () => { + // # Type in "~" to activate channel mention autocomplete + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in channel name + await ChannelScreen.postInput.typeText(testChannel.name); + + // * Verify channel mention autocomplete contains associated channel suggestion + await expect(channelMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4879_2 - should suggest channel based on channel display name', async () => { + // # Type in "~" to activate channel mention autocomplete + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in channel display name + await ChannelScreen.postInput.typeText(testChannel.display_name); + + // * Verify channel mention autocomplete contains associated channel suggestion + await expect(channelMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4879_3 - should suggest channel based on lowercase channel display name', async () => { + // # Type in "~" to activate channel mention autocomplete + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in lowercase channel display name + await ChannelScreen.postInput.typeText(testChannel.display_name.toLowerCase()); + + // * Verify channel mention autocomplete contains associated channel suggestion + await expect(channelMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4879_4 - should suggest channel based on partial channel display name', async () => { + // # Type in "~" to activate channel mention autocomplete + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in partial channel display name + await ChannelScreen.postInput.typeText(`${testChannel.display_name.substring(0, testChannel.display_name.length - 4)}`); + + // * Verify channel mention autocomplete contains associated channel suggestion + await expect(channelMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4879_5 - should stop suggesting channel after channel display name with trailing space -- KNOWN ISSUE: MM-45279', async () => { + // # Type in "~" to activate channel mention autocomplete + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in channel display name + await ChannelScreen.postInput.typeText(testChannel.display_name); + + // * Verify channel mention autocomplete contains associated channel suggestion + await expect(channelMentionAutocomplete).toBeVisible(); + + // # Type in trailing space + await ChannelScreen.postInput.typeText(' '); + await wait(timeouts.ONE_SEC); + + // * Verify channel mention autocomplete does not contain associated channel suggestion + await expect(channelMentionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4879_6 - should stop suggesting channel when keyword is not associated with any channel', async () => { + // # Type in "~" to activate channel mention autocomplete + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in keyword not associated with any channel + await ChannelScreen.postInput.typeText(getRandomId()); + + // * Verify channel mention autocomplete does not contain associated channel suggestion + await expect(channelMentionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4879_7 - should be able to select channel mention multiple times', async () => { + // # Type in "~" to activate channel mention autocomplete + await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + await ChannelScreen.postInput.typeText('~'); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in channel name and tap on channel mention autocomplete + await ChannelScreen.postInput.typeText(testChannel.name); + await channelMentionAutocomplete.tap(); + + // * Verify channel mention list disappears + await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + + // # Type in "~" again to re-activate channel mention list + await ChannelScreen.postInput.typeText('~'); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + }); + + it('MM-T4879_8 - should not be able to autocomplete archived channel', async () => { + // # Archive another team channel and type in "~" to activate channel mention autocomplete + await Channel.apiDeleteChannel(siteOneUrl, testOtherChannel.id); + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in channel name of archived channel + await ChannelScreen.postInput.typeText(testOtherChannel.name); + + // * Verify channel mention autocomplete does not contain associated channel suggestion + await expect(otherChannelMentionAutocomplete).not.toBeVisible(); + + // # Unarchive channel, clear post input, and type in "~" to activate channel mention list + await Channel.apiRestoreChannel(siteOneUrl, testOtherChannel.id); + await ChannelScreen.postInput.clearText(); + await Autocomplete.toBeVisible(false); + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in channel name of unarchived channel + await ChannelScreen.postInput.typeText(testOtherChannel.name); + + // * Verify channel mention autocomplete contains associated channel suggestion + await expect(otherChannelMentionAutocomplete).toBeVisible(); + }); + + it('MM-T4879_9 - should not be able to autocomplete out of team channel', async () => { + // # Type in "~" to activate channel mention autocomplete + const {team: otherTeam} = await Team.apiCreateTeam(siteOneUrl); + const {channel: outOfTeamChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: otherTeam.id}); + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in channel name of out of team channel + await ChannelScreen.postInput.typeText(outOfTeamChannel.name); + + // * Verify channel mention autocomplete does not contain associated channel suggestion + const {channelMentionItem: outOfTeamChannelChannelMentionAutocomplete} = Autocomplete.getChannelMentionItem(outOfTeamChannel.name); + await expect(outOfTeamChannelChannelMentionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4879_10 - should include current channel in autocomplete', async () => { + // # Type in "~" to activate channel mention autocomplete + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in channel name of current channel + await ChannelScreen.postInput.typeText(testChannel.name); + + // * Verify channel mention autocomplete contains current channel + const {channelMentionItemChannelDisplayName, channelMentionItemChannelName} = Autocomplete.getChannelMentionItem(testChannel.name); + await expect(channelMentionItemChannelDisplayName).toHaveText(`${testChannel.display_name}`); + await expect(channelMentionItemChannelName).toHaveText(` ~${testChannel.name}`); + }); +}); diff --git a/detox/e2e/test/autocomplete/edit_channel.e2e.ts b/detox/e2e/test/autocomplete/edit_channel.e2e.ts new file mode 100644 index 000000000..fe1f5c5ec --- /dev/null +++ b/detox/e2e/test/autocomplete/edit_channel.e2e.ts @@ -0,0 +1,103 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import {Setup} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Autocomplete} from '@support/ui/component'; +import { + ChannelInfoScreen, + ChannelListScreen, + ChannelScreen, + CreateOrEditChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Autocomplete - Edit Channel', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + + // # Open a channel screen, open channel info screen, and open edit channel screen + await ChannelScreen.open(channelsCategory, channel.name); + await ChannelInfoScreen.open(); + await CreateOrEditChannelScreen.openEditChannel(); + }); + + beforeEach(async () => { + // # Clear header input + await CreateOrEditChannelScreen.headerInput.clearText(); + }); + + afterAll(async () => { + // # Log out + await CreateOrEditChannelScreen.back(); + await ChannelInfoScreen.close(); + await ChannelScreen.back(); + await HomeScreen.logout(); + }); + + it('MM-T4885_1 - should render at-mention autocomplete in header input', async () => { + // * Verify at-mention list is not displayed + await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + + // # Type in "@" to activate at-mention autocomplete + await CreateOrEditChannelScreen.headerInput.typeText('@'); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + }); + + it('MM-T4885_2 - should render channel mention autocomplete in header input', async () => { + // * Verify channel mention list is not displayed + await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + + // # Type in "~" to activate channel mention autocomplete + await CreateOrEditChannelScreen.headerInput.typeText('~'); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + }); + + it('MM-T4885_3 - should render emoji suggestion autocomplete in header input', async () => { + // * Verify emoji suggestion list is not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + + // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete + await CreateOrEditChannelScreen.headerInput.typeText(':sm'); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + }); + + it('MM-T4885_4 - should not render slash suggestion autocomplete in header input', async () => { + // * Verify slash suggestion list is not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + + // # Type in "/" to activate slash suggestion autocomplete + await CreateOrEditChannelScreen.headerInput.typeText('/'); + + // * Verify slash suggestion list is still not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + }); +}); diff --git a/detox/e2e/test/autocomplete/edit_channel_header.e2e.ts b/detox/e2e/test/autocomplete/edit_channel_header.e2e.ts new file mode 100644 index 000000000..b8bb6cf00 --- /dev/null +++ b/detox/e2e/test/autocomplete/edit_channel_header.e2e.ts @@ -0,0 +1,100 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import {Setup} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Autocomplete} from '@support/ui/component'; +import { + ChannelListScreen, + ChannelScreen, + CreateOrEditChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Autocomplete - Edit Channel Header', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + + // # Open a channel screen and open edit channel header screen + await ChannelScreen.open(channelsCategory, channel.name); + await CreateOrEditChannelScreen.openEditChannelHeader(); + }); + + beforeEach(async () => { + // # Clear header input + await CreateOrEditChannelScreen.headerInput.clearText(); + }); + + afterAll(async () => { + // # Log out + await CreateOrEditChannelScreen.close(); + await ChannelScreen.back(); + await HomeScreen.logout(); + }); + + it('MM-T4884_1 - should render at-mention autocomplete in header input', async () => { + // * Verify at-mention list is not displayed + await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + + // # Type in "@" to activate at-mention autocomplete + await CreateOrEditChannelScreen.headerInput.typeText('@'); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + }); + + it('MM-T4884_2 - should render channel mention autocomplete in header input', async () => { + // * Verify channel mention list is not displayed + await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + + // # Type in "~" to activate channel mention autocomplete + await CreateOrEditChannelScreen.headerInput.typeText('~'); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + }); + + it('MM-T4884_3 - should render emoji suggestion autocomplete in header input', async () => { + // * Verify emoji suggestion list is not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + + // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete + await CreateOrEditChannelScreen.headerInput.typeText(':sm'); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + }); + + it('MM-T4884_4 - should not render slash suggestion autocomplete in header input', async () => { + // * Verify slash suggestion list is not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + + // # Type in "/" to activate slash suggestion autocomplete + await CreateOrEditChannelScreen.headerInput.typeText('/'); + + // * Verify slash suggestion list is still not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + }); +}); diff --git a/detox/e2e/test/autocomplete/edit_post.e2e.ts b/detox/e2e/test/autocomplete/edit_post.e2e.ts new file mode 100644 index 000000000..fa81bd8f3 --- /dev/null +++ b/detox/e2e/test/autocomplete/edit_post.e2e.ts @@ -0,0 +1,108 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Autocomplete} from '@support/ui/component'; +import { + ChannelListScreen, + ChannelScreen, + EditPostScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Autocomplete - Edit Post', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + + // # Open a channel screen, post a message, open post options for message, and open edit post screen + const message = `Messsage ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, channel.name); + await ChannelScreen.postMessage(message); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, channel.id); + await ChannelScreen.openPostOptionsFor(post.id, message); + await EditPostScreen.open(); + }); + + beforeEach(async () => { + // # Clear message input + await EditPostScreen.messageInput.clearText(); + }); + + afterAll(async () => { + // # Log out + await EditPostScreen.close(); + await ChannelScreen.back(); + await HomeScreen.logout(); + }); + + it('MM-T4883_1 - should render at-mention autocomplete in message input', async () => { + // * Verify at-mention list is not displayed + await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + + // # Type in "@" to activate at-mention autocomplete + await EditPostScreen.messageInput.typeText('@'); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + }); + + it('MM-T4883_2 - should render channel mention autocomplete in message input', async () => { + // * Verify channel mention list is not displayed + await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + + // # Type in "~" to activate channel mention autocomplete + await EditPostScreen.messageInput.typeText('~'); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + }); + + it('MM-T4883_3 - should render emoji suggestion autocomplete in message input', async () => { + // * Verify emoji suggestion list is not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + + // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete + await EditPostScreen.messageInput.typeText(':sm'); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + }); + + it('MM-T4883_4 - should not render slash suggestion autocomplete in message input', async () => { + // * Verify slash suggestion list is not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + + // # Type in "/" to activate slash suggestion autocomplete + await EditPostScreen.messageInput.typeText('/'); + + // * Verify slash suggestion list is still not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + }); +}); diff --git a/detox/e2e/test/autocomplete/emoji_suggestion.e2e.ts b/detox/e2e/test/autocomplete/emoji_suggestion.e2e.ts new file mode 100644 index 000000000..691871790 --- /dev/null +++ b/detox/e2e/test/autocomplete/emoji_suggestion.e2e.ts @@ -0,0 +1,167 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import {Setup} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Autocomplete} from '@support/ui/component'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId, timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +describe('Autocomplete - Emoji Suggestion', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + const emojiName = 'fox_face'; + const emojiNameFirst2Chars = emojiName.substring(0, 2); + const emojiName3rdToLastChars = emojiName.substring(2); + let emojiSuggestionAutocomplete: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + + ({emojiSuggestionItem: emojiSuggestionAutocomplete} = Autocomplete.getEmojiSuggestionItem(emojiName)); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + + // # Open a channel screen + await ChannelScreen.open(channelsCategory, channel.name); + }); + + beforeEach(async () => { + // # Clear post input + await ChannelScreen.postInput.clearText(); + + // * Verify autocomplete is not displayed + await Autocomplete.toBeVisible(false); + }); + + afterAll(async () => { + // # Log out + await ChannelScreen.back(); + await HomeScreen.logout(); + }); + + it('MM-T4880_1 - should suggest emoji based on emoji name', async () => { + // # Type in ":" then first 2 characters of emoji name to activate emoji suggestion autocomplete + await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); + await Autocomplete.toBeVisible(); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + + // # Type in 3rd to last characters of emoji name + await ChannelScreen.postInput.typeText(emojiName3rdToLastChars); + + // * Verify emoji suggestion autocomplete contains associated emoji suggestion + await expect(emojiSuggestionAutocomplete).toBeVisible(); + }); + + it('MM-T4880_2 - should suggest emoji based on uppercase emoji name', async () => { + // # Type in ":" then uppercase first 2 characters of emoji name to activate emoji suggestion autocomplete + await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars.toUpperCase()}`); + await Autocomplete.toBeVisible(); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + + // # Type in uppercase 3rd to last characters of emoji name + await ChannelScreen.postInput.typeText(emojiName3rdToLastChars.toUpperCase()); + + // * Verify emoji suggestion autocomplete contains associated emoji suggestion + await expect(emojiSuggestionAutocomplete).toBeVisible(); + }); + + it('MM-T4880_3 - should suggest emoji based on partial emoji name', async () => { + // # Type in ":" then first 2 characters of emoji name to activate emoji suggestion autocomplete + await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); + await Autocomplete.toBeVisible(); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + + // # Type in partial emoji name + await ChannelScreen.postInput.typeText(`${emojiName.substring(2, 4)}`); + + // * Verify emoji suggestion autocomplete contains associated emoji suggestion + await expect(emojiSuggestionAutocomplete).toBeVisible(); + }); + + it('MM-T4880_4 - should stop suggesting emoji after emoji name with trailing space', async () => { + // # Type in ":" then first 2 characters of emoji name to activate emoji suggestion autocomplete + await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); + await Autocomplete.toBeVisible(); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + + // # Type in 3rd to last characters of emoji name + await ChannelScreen.postInput.typeText(emojiName3rdToLastChars); + + // * Verify emoji suggestion autocomplete contains associated emoji suggestion + await expect(emojiSuggestionAutocomplete).toBeVisible(); + + // # Type in trailing space + await ChannelScreen.postInput.typeText(' '); + await wait(timeouts.ONE_SEC); + + // * Verify emoji suggestion autocomplete does not contain associated emoji suggestion + await expect(emojiSuggestionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4880_5 - should stop suggesting emoji when keyword is not associated with any emoji', async () => { + // # Type in ":" then first 2 characters of emoji name to activate emoji suggestion autocomplete + await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); + await Autocomplete.toBeVisible(); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + + // # Type in keyword not associated with any emoji + await ChannelScreen.postInput.typeText(getRandomId()); + + // * Verify emoji suggestion autocomplete does not contain associated emoji suggestion + await expect(emojiSuggestionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4880_6 - should be able to select emoji suggestion multiple times', async () => { + // # Type in ":" then first 2 characters of emoji name to activate emoji suggestion autocomplete + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + + // # Type in 3rd to last characters of emoji name and tap on emoji suggestion autocomplete + await ChannelScreen.postInput.typeText(emojiName3rdToLastChars); + await emojiSuggestionAutocomplete.tap(); + + // * Verify emoji suggestion list disappears + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + + // # Type in ":" then first 2 characters of emoji name again to re-activate emoji suggestion list + await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + }); +}); diff --git a/detox/e2e/test/autocomplete/post_draft.e2e.ts b/detox/e2e/test/autocomplete/post_draft.e2e.ts new file mode 100644 index 000000000..f441aa1cd --- /dev/null +++ b/detox/e2e/test/autocomplete/post_draft.e2e.ts @@ -0,0 +1,100 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import {Setup} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Autocomplete} from '@support/ui/component'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Autocomplete - Post Draft', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + + // # Open a channel screen + await ChannelScreen.open(channelsCategory, channel.name); + }); + + beforeEach(async () => { + // # Clear post input + await ChannelScreen.postInput.clearText(); + + // * Verify autocomplete is not displayed + await Autocomplete.toBeVisible(false); + }); + + afterAll(async () => { + // # Log out + await ChannelScreen.back(); + await HomeScreen.logout(); + }); + + it('MM-T4882_1 - should render at-mention autocomplete in post input', async () => { + // * Verify at-mention list is not displayed + await expect(Autocomplete.sectionAtMentionList).not.toBeVisible(); + + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + }); + + it('MM-T4882_2 - should render channel mention autocomplete in post input', async () => { + // * Verify channel mention list is not displayed + await expect(Autocomplete.sectionChannelMentionList).not.toBeVisible(); + + // # Type in "~" to activate channel mention autocomplete + await ChannelScreen.postInput.typeText('~'); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + }); + + it('MM-T4882_3 - should render emoji suggestion autocomplete in post input', async () => { + // * Verify emoji suggestion list is not displayed + await expect(Autocomplete.flatEmojiSuggestionList).not.toBeVisible(); + + // # Type in ":" followed by 2 characters to activate emoji suggestion autocomplete + await ChannelScreen.postInput.typeText(':sm'); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + }); + + it('MM-T4882_4 - should render slash suggestion autocomplete in post input', async () => { + // * Verify slash suggestion list is not displayed + await expect(Autocomplete.flatSlashSuggestionList).not.toBeVisible(); + + // # Type in "/" to activate slash suggestion autocomplete + await ChannelScreen.postInput.typeText('/'); + + // * Verify slash suggestion list is displayed + await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + }); +}); diff --git a/detox/e2e/test/autocomplete/slash_suggestion.e2e.ts b/detox/e2e/test/autocomplete/slash_suggestion.e2e.ts new file mode 100644 index 000000000..b550650f5 --- /dev/null +++ b/detox/e2e/test/autocomplete/slash_suggestion.e2e.ts @@ -0,0 +1,165 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import {Setup} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Autocomplete} from '@support/ui/component'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId, timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +describe('Autocomplete - Slash Suggestion', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + const slashCommand = 'away'; + let slashSuggestionAutocomplete: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + + ({slashSuggestionItem: slashSuggestionAutocomplete} = Autocomplete.getSlashSuggestionItem(slashCommand)); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + + // # Open a channel screen + await ChannelScreen.open(channelsCategory, channel.name); + }); + + beforeEach(async () => { + // # Clear post input + await ChannelScreen.postInput.clearText(); + + // * Verify autocomplete is not displayed + await Autocomplete.toBeVisible(false); + }); + + afterAll(async () => { + // # Log out + await ChannelScreen.back(); + await HomeScreen.logout(); + }); + + it('MM-T4881_1 - should suggest slash command based on slash command name', async () => { + // # Type in "/" to activate slash suggestion autocomplete + await ChannelScreen.postInput.typeText('/'); + await Autocomplete.toBeVisible(); + + // * Verify slash suggestion list is displayed + await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + + // # Type in slash command name + await ChannelScreen.postInput.typeText(slashCommand); + + // * Verify slash suggestion autocomplete contains associated slash command suggestion + await expect(slashSuggestionAutocomplete).toBeVisible(); + }); + + it('MM-T4881_2 - should suggest slash command based on partial slash command name', async () => { + // # Type in "/" to activate slash suggestion autocomplete + await ChannelScreen.postInput.typeText('/'); + await Autocomplete.toBeVisible(); + + // * Verify slash suggestion list is displayed + await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + + // # Type in partial slash command name + await ChannelScreen.postInput.typeText(`${slashCommand.substring(0, slashCommand.length - 2)}`); + + // * Verify slash suggestion autocomplete contains associated slash command suggestion + await expect(slashSuggestionAutocomplete).toBeVisible(); + }); + + it('MM-T4881_3 - should stop suggesting slash command after uppercase slash command name', async () => { + // # Type in "/" to activate slash suggestion autocomplete + await ChannelScreen.postInput.typeText('/'); + await Autocomplete.toBeVisible(); + + // * Verify slash suggestion list is displayed + await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + + // # Type in uppercase slash command name + await ChannelScreen.postInput.typeText(slashCommand.toUpperCase()); + + // * Verify slash suggestion autocomplete does not contain associated slash command suggestion + await expect(slashSuggestionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4881_4 - should stop suggesting slash command after slash command name with trailing space', async () => { + // # Type in "/" to activate slash suggestion autocomplete + await ChannelScreen.postInput.typeText('/'); + await Autocomplete.toBeVisible(); + + // * Verify slash suggestion list is displayed + await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + + // # Type in slash command name + await ChannelScreen.postInput.typeText(slashCommand); + + // * Verify slash suggestion autocomplete contains associated slash command suggestion + await expect(slashSuggestionAutocomplete).toBeVisible(); + + // # Type in trailing space + await ChannelScreen.postInput.typeText(' '); + await wait(timeouts.ONE_SEC); + + // * Verify slash suggestion autocomplete does not contain associated slash command suggestion + await expect(slashSuggestionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4881_5 - should stop suggesting slash command when keyword is not associated with any slash command', async () => { + // # Type in "/" to activate slash suggestion autocomplete + await ChannelScreen.postInput.typeText('/'); + await Autocomplete.toBeVisible(); + + // * Verify slash suggestion list is displayed + await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + + // # Type in keyword not associated with any slash command + await ChannelScreen.postInput.typeText(getRandomId()); + + // * Verify slash suggestion autocomplete does not contain associated slash command suggestion + await expect(slashSuggestionAutocomplete).not.toBeVisible(); + }); + + it('MM-T4881_6 - should not be able to select slash suggestion multiple times', async () => { + // # Type in "/" to activate slash suggestion autocomplete + await expect(Autocomplete.flatSlashSuggestionList).not.toBeVisible(); + await ChannelScreen.postInput.typeText('/'); + + // * Verify slash suggestion list is displayed + await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + + // # Type in slash command name and tap on slash suggestion autocomplete + await ChannelScreen.postInput.typeText(slashCommand); + await slashSuggestionAutocomplete.tap(); + + // * Verify slash suggestion list disappears + await expect(Autocomplete.flatSlashSuggestionList).not.toBeVisible(); + + // # Type in "/" again to re-activate slash suggestion list + await ChannelScreen.postInput.typeText('/'); + + // * Verify slash suggestion list is not displayed + await expect(Autocomplete.flatSlashSuggestionList).not.toBeVisible(); + }); +}); diff --git a/detox/e2e/test/channels/channel_post_list.e2e.ts b/detox/e2e/test/channels/channel_post_list.e2e.ts index 77831bf7d..fe5294177 100644 --- a/detox/e2e/test/channels/channel_post_list.e2e.ts +++ b/detox/e2e/test/channels/channel_post_list.e2e.ts @@ -57,9 +57,9 @@ describe('Channels - Channel Post List', () => { await expect(ChannelScreen.backButton).toBeVisible(); await expect(ChannelScreen.headerTitle).toHaveText(testChannel.display_name); await expect(ChannelScreen.introDisplayName).toHaveText(testChannel.display_name); - await expect(ChannelScreen.introOptionAddPeopleItem).toBeVisible(); - await expect(ChannelScreen.introOptionSetHeaderItem).toBeVisible(); - await expect(ChannelScreen.introOptionChannelDetailsItem).toBeVisible(); + await expect(ChannelScreen.introAddPeopleOption).toBeVisible(); + await expect(ChannelScreen.introSetHeaderOption).toBeVisible(); + await expect(ChannelScreen.introChannelDetailsOption).toBeVisible(); await expect(ChannelScreen.postList.getFlatList()).toBeVisible(); await expect(ChannelScreen.postDraft).toBeVisible(); await expect(ChannelScreen.postInput).toBeVisible(); diff --git a/detox/e2e/test/channels/create_and_edit_header.e2e.ts b/detox/e2e/test/channels/create_and_edit_header.e2e.ts index 030ecc7eb..18e424f3b 100644 --- a/detox/e2e/test/channels/create_and_edit_header.e2e.ts +++ b/detox/e2e/test/channels/create_and_edit_header.e2e.ts @@ -82,7 +82,7 @@ describe('Channels - Create Channel and Edit Channel Header', () => { await expect(ChannelScreen.introDisplayName).toHaveText(displayName); // # Tap on set header option to edit the channel header - await ChannelScreen.introOptionSetHeaderItem.tap(); + await ChannelScreen.introSetHeaderOption.tap(); // * Verify channel header is correct await expect(CreateOrEditChannelScreen.headerInput).toHaveValue(header); @@ -90,7 +90,7 @@ describe('Channels - Create Channel and Edit Channel Header', () => { // # Edit the channel header, save, and re-open edit channel header screen await CreateOrEditChannelScreen.headerInput.replaceText(`${header} edit`); await CreateOrEditChannelScreen.saveButton.tap(); - await CreateOrEditChannelScreen.openEditChannel(); + await CreateOrEditChannelScreen.openEditChannelHeader(); // * Verify channel header has new value await expect(CreateOrEditChannelScreen.headerInput).toHaveValue(`${header} edit`); @@ -119,7 +119,7 @@ describe('Channels - Create Channel and Edit Channel Header', () => { await expect(ChannelScreen.introDisplayName).toHaveText(displayName); // # Tap on set header option to edit the channel header - await ChannelScreen.introOptionSetHeaderItem.tap(); + await ChannelScreen.introSetHeaderOption.tap(); // * Verify channel header is correct await expect(CreateOrEditChannelScreen.headerInput).toHaveValue(header); @@ -127,7 +127,7 @@ describe('Channels - Create Channel and Edit Channel Header', () => { // # Edit the channel header, save, and re-open edit channel header screen await CreateOrEditChannelScreen.headerInput.replaceText(`${header} edit`); await CreateOrEditChannelScreen.saveButton.tap(); - await CreateOrEditChannelScreen.openEditChannel(); + await CreateOrEditChannelScreen.openEditChannelHeader(); // * Verify channel header has new value await expect(CreateOrEditChannelScreen.headerInput).toHaveValue(`${header} edit`); diff --git a/detox/e2e/test/channels/create_direct_message.e2e.ts b/detox/e2e/test/channels/create_direct_message.e2e.ts index eadc5b1b3..b71d2a4f2 100644 --- a/detox/e2e/test/channels/create_direct_message.e2e.ts +++ b/detox/e2e/test/channels/create_direct_message.e2e.ts @@ -40,7 +40,7 @@ describe('Channels - Create Direct Message', () => { // # Log in to server await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); - await LoginScreen.login(user); + await LoginScreen.login(testUser); }); beforeEach(async () => { diff --git a/detox/e2e/test/messaging/at_mention.e2e.ts b/detox/e2e/test/messaging/at_mention.e2e.ts new file mode 100644 index 000000000..dba1d2a57 --- /dev/null +++ b/detox/e2e/test/messaging/at_mention.e2e.ts @@ -0,0 +1,160 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Channel, + Post, + Setup, + Team, + User, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Alert} from '@support/ui/component'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, + UserProfileScreen, +} from '@support/ui/screen'; +import {timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - At-Mention', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + let testTeam: any; + let testUser: any; + let testOtherUser: any; + + beforeAll(async () => { + const {channel, team, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + testTeam = team; + testUser = user; + + ({user: testOtherUser} = await User.apiCreateUser(siteOneUrl)); + await Team.apiAddUserToTeam(siteOneUrl, testOtherUser.id, testTeam.id); + await Channel.apiAddUserToChannel(siteOneUrl, testOtherUser.id, testChannel.id); + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(testUser); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4874_1 - should post at-mention as lowercase', async () => { + // # Open a channel screen and post a message with lowercase at-mention + const camelCaseUsernameMessage = `Message @${testUser.username.substring(0, 1).toUpperCase()}${testUser.username.substring(1)}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(camelCaseUsernameMessage); + + // * Verify at-mention is posted as lowercase + const {post: lowerCasePost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(lowerCasePost.id, `Message @${testUser.username.toLowerCase()}`); + + // # Post a message with uppercase at-mention + const upperCaseUsernameMessage = `Message @${testOtherUser.username.toUpperCase()}`; + await ChannelScreen.postMessage(upperCaseUsernameMessage); + + // * Verify at-mention is posted as lowercase + const {post: upperCasePost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(upperCasePost.id, `Message @${testOtherUser.username.toLowerCase()}`); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4874_2 - should display confirmation dialog when posting @all, @channel, and @here', async () => { + // # Add more users to the channel, open a channel screen, and post @all + [...Array(3).keys()].forEach(async (key) => { + const {user} = await User.apiCreateUser(siteOneUrl, {prefix: `a-${key}-`}); + await Team.apiAddUserToTeam(siteOneUrl, user.id, testTeam.id); + await Channel.apiAddUserToChannel(siteOneUrl, user.id, testChannel.id); + }); + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postInput.replaceText('@all'); + await ChannelScreen.sendButton.tap(); + + // * Verify confirmation dialog is displayed + await expect(Alert.confirmSendingNotificationsTitle).toBeVisible(); + + // # Tap on confirm button + await Alert.confirmButton.tap(); + + // * Verify @all is posted + const {post: atAllPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(atAllPost.id, '@all'); + + // # Post @channel + await ChannelScreen.postInput.clearText(); + await ChannelScreen.postInput.replaceText('@channel'); + await ChannelScreen.sendButton.tap(); + + // * Verify confirmation dialog is displayed + await expect(Alert.confirmSendingNotificationsTitle).toBeVisible(); + + // # Tap on confirm button + await Alert.confirmButton.tap(); + + // * Verify @channel is posted + const {post: atChannelPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(atChannelPost.id, '@channel'); + + // # Post @here + await ChannelScreen.postInput.clearText(); + await ChannelScreen.postInput.replaceText('@here'); + await ChannelScreen.sendButton.tap(); + + // * Verify confirmation dialog is displayed + await expect(Alert.confirmSendingNotificationsTitle).toBeVisible(); + + // # Tap on confirm button + await Alert.confirmButton.tap(); + + // * Verify @here is posted + const {post: atHerePost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(atHerePost.id, '@here'); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4874_3 - should be able to open user profile by tapping on at-mention', async () => { + // # Open a channel screen, post a message with at-mention, and tap on at-mention + const message = `@${testUser.username}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + await element(by.text(message)).tap({x: 5, y: 10}); + await wait(timeouts.ONE_SEC); + + // * Verify on user profile screen + await UserProfileScreen.toBeVisible(); + await expect(UserProfileScreen.getUserProfilePicture(testUser.id)).toBeVisible(); + await expect(UserProfileScreen.userDisplayName).toHaveText(`@${testUser.username}`); + + // # Go back to channel list screen + await UserProfileScreen.close(); + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/channel_link.e2e.ts b/detox/e2e/test/messaging/channel_link.e2e.ts new file mode 100644 index 000000000..f718f73ff --- /dev/null +++ b/detox/e2e/test/messaging/channel_link.e2e.ts @@ -0,0 +1,95 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Channel, + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - Channel Link', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + let testTeam: any; + let testUser: any; + + beforeAll(async () => { + const {channel, team, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + testTeam = team; + testUser = user; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(testUser); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4877_1 - should be able to open joined channel by tapping on channel link from main channel', async () => { + // # Open a channel screen and post a channel link to target channel + await ChannelScreen.open(channelsCategory, testChannel.name); + const {channel: targetChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); + await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); + const channelLink = `${serverOneUrl}/${testTeam.name}/channels/${targetChannel.name}`; + await ChannelScreen.postMessage(channelLink); + + // # Tap on channel link + await element(by.text(channelLink)).tap({x: 5, y: 10}); + await wait(timeouts.ONE_SEC); + + // * Verify redirected to target channel + await expect(ChannelScreen.headerTitle).toHaveText(targetChannel.display_name); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4877_2 - should be able to open joined channel by tapping on channel link from reply thread', async () => { + // # Open a channel screen, post a channel link to target channel, tap on post to open reply thread, and tap on channel link + await ChannelScreen.open(channelsCategory, testChannel.name); + const {channel: targetChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); + await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); + const channelLink = `${serverOneUrl}/${testTeam.name}/channels/${targetChannel.name}`; + await ChannelScreen.postMessage(channelLink); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id); + await postListPostItem.tap({x: 1, y: 1}); + await element(by.text(channelLink)).tap({x: 5, y: 10}); + await wait(timeouts.ONE_SEC); + + // * Verify redirected to target channel + await expect(ChannelScreen.headerTitle).toHaveText(targetChannel.display_name); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/channel_mention.e2e.ts b/detox/e2e/test/messaging/channel_mention.e2e.ts new file mode 100644 index 000000000..f9505c088 --- /dev/null +++ b/detox/e2e/test/messaging/channel_mention.e2e.ts @@ -0,0 +1,91 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Channel, + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - Channel Mention', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + let testTeam: any; + let testUser: any; + + beforeAll(async () => { + const {channel, team, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + testTeam = team; + testUser = user; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(testUser); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4875_1 - should post channel mention as channel display name', async () => { + // # Open a channel screen and post a channel name mention + await ChannelScreen.open(channelsCategory, testChannel.name); + const {channel: targetChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); + await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); + const channelNameMention = `~${targetChannel.name}`; + await ChannelScreen.postMessage(channelNameMention); + + // * Verify post shows channel display name mention + const channelDisplayNameMention = `~${targetChannel.display_name}`; + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(post.id, channelDisplayNameMention); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4875_2 - should be able to open joined channel by tapping on channel mention', async () => { + // # Open a channel screen, post a channel name mention, and tap on channel display name mention + await ChannelScreen.open(channelsCategory, testChannel.name); + const {channel: targetChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); + await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); + const channelNameMention = `~${targetChannel.name}`; + const channelDisplayNameMention = `~${targetChannel.display_name}`; + await ChannelScreen.postMessage(channelNameMention); + await element(by.text(channelDisplayNameMention)).tap({x: 5, y: 10}); + await wait(timeouts.ONE_SEC); + + // * Verify redirected to target channel + await expect(ChannelScreen.headerTitle).toHaveText(targetChannel.display_name); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/emojis_and_reactions.e2e.ts b/detox/e2e/test/messaging/emojis_and_reactions.e2e.ts index b3a958909..c0f9f4f89 100644 --- a/detox/e2e/test/messaging/emojis_and_reactions.e2e.ts +++ b/detox/e2e/test/messaging/emojis_and_reactions.e2e.ts @@ -41,7 +41,7 @@ describe('Messaging - Emojis and Reactions', () => { // # Log in to server await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); - await LoginScreen.login(user); + await LoginScreen.login(testUser); }); beforeEach(async () => { @@ -72,7 +72,7 @@ describe('Messaging - Emojis and Reactions', () => { // # Open emoji picker screen and add a new reaction await EmojiPickerScreen.open(); - await EmojiPickerScreen.searchInput.typeText('clown_face'); + await EmojiPickerScreen.searchInput.replaceText('clown_face'); await element(by.text('🤡')).tap(); // * Verify new reaction is added to the message @@ -103,7 +103,7 @@ describe('Messaging - Emojis and Reactions', () => { const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); await ChannelScreen.openPostOptionsFor(post.id, message); await EmojiPickerScreen.open(); - await EmojiPickerScreen.searchInput.typeText('fire'); + await EmojiPickerScreen.searchInput.replaceText('fire'); await element(by.text('🔥')).tap(); // * Verify reaction is added to the message @@ -115,11 +115,11 @@ describe('Messaging - Emojis and Reactions', () => { // * Verify user who reacted with the emoji await ReactionsScreen.toBeVisible(); - const {reactorItemEmojiAliases, reactorItemUserProfilePicture, reactorItemUserDisplayName, reactorItemUserUsername} = ReactionsScreen.getReactorItem(testUser.id, 'fire'); + const {reactorItemEmojiAliases, reactorItemUserProfilePicture, reactorItemUserDisplayName, reactorItemUsername} = ReactionsScreen.getReactorItem(testUser.id, 'fire'); await expect(reactorItemEmojiAliases).toHaveText(':fire:'); await expect(reactorItemUserProfilePicture).toBeVisible(); await expect(reactorItemUserDisplayName).toHaveText(`${testUser.first_name} ${testUser.last_name}`); - await expect(reactorItemUserUsername).toHaveText(` @${testUser.username}`); + await expect(reactorItemUsername).toHaveText(` @${testUser.username}`); // # Go back to channel list screen await ReactionsScreen.close(); @@ -168,7 +168,7 @@ describe('Messaging - Emojis and Reactions', () => { const searchTerm = 'blahblahblahblah'; await ChannelScreen.openPostOptionsFor(post.id, message); await EmojiPickerScreen.open(); - await EmojiPickerScreen.searchInput.typeText(searchTerm); + await EmojiPickerScreen.searchInput.replaceText(searchTerm); // * Verify empty search state for emoji picker await expect(element(by.text(`No results for “${searchTerm}”`))).toBeVisible(); diff --git a/detox/e2e/test/messaging/markdown_block_quote.e2e.ts b/detox/e2e/test/messaging/markdown_block_quote.e2e.ts new file mode 100644 index 000000000..6d6b0a19f --- /dev/null +++ b/detox/e2e/test/messaging/markdown_block_quote.e2e.ts @@ -0,0 +1,67 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Messaging - Markdown Block Quote', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4898_1 - should be able to display markdown block quote', async () => { + // # Open a channel screen and post a markdown block quote + const message = 'this is a quote that i am making long so it wraps on mobile this is a quote that i am making long so it wraps on mobile this is a quote that i am making long so it wraps on mobile this is a quote that i am making long so it wraps on mobile'; + const markdownBlockQuote = `>${message}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownBlockQuote); + + // * Verify markdown block quote is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemBlockQuote} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItemBlockQuote).toBeVisible(); + await expect(element(by.text(message))).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/markdown_code.e2e.ts b/detox/e2e/test/messaging/markdown_code.e2e.ts new file mode 100644 index 000000000..bdd9d92b8 --- /dev/null +++ b/detox/e2e/test/messaging/markdown_code.e2e.ts @@ -0,0 +1,88 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Messaging - Markdown Code', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4895_1 - should be able to display markdown code block', async () => { + // # Open a channel screen and post a markdown code block + const line1 = 'let x = 10;'; + const line2 = 'let y = 20;'; + // eslint-disable-next-line no-template-curly-in-string + const line3 = 'console.log(`sum: ${x + y}`);'; + const message = `${line1}\n${line2}\n${line3}`; + const markdownCodeBlock = `\`\`\`\n${message}\n\`\`\``; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownCodeBlock); + + // * Verify markdown code block is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemCodeBlock} = ChannelScreen.getPostListPostItem(post.id); + await ChannelScreen.flatPostList.scrollTo('bottom'); + await expect(postListPostItemCodeBlock).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4895_2- should be able to display markdown html', async () => { + // # Open a channel screen and post a html + const message = '<html>\n<body>\n<span>This is html block</span>\n</body>\n</html>'; + const markdownHtml = `\`\`\`html\n${message}\n\`\`\``; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownHtml); + + // * Verify markdown html is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemCodeBlock} = ChannelScreen.getPostListPostItem(post.id); + await ChannelScreen.flatPostList.scrollTo('bottom'); + await expect(postListPostItemCodeBlock).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/markdown_heading.e2e.ts b/detox/e2e/test/messaging/markdown_heading.e2e.ts new file mode 100644 index 000000000..fc7bb6263 --- /dev/null +++ b/detox/e2e/test/messaging/markdown_heading.e2e.ts @@ -0,0 +1,67 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Messaging - Markdown Heading', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4893_1 - should be able to display markdown heading', async () => { + // # Open a channel screen and post a markdown heading + const message = 'Heading'; + const markdownHeading = `## ${message}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownHeading); + + // * Verify markdown heading is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemHeading} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItemHeading).toBeVisible(); + await expect(element(by.text(message))).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/markdown_image.e2e.ts b/detox/e2e/test/messaging/markdown_image.e2e.ts new file mode 100644 index 000000000..c9917a86a --- /dev/null +++ b/detox/e2e/test/messaging/markdown_image.e2e.ts @@ -0,0 +1,80 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Messaging - Markdown Image', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4896_1 - should be able to display markdown image', async () => { + // # Open a channel screen and post a markdown image + const markdownImage = '![Mattermost](https://docs.mattermost.com/_images/icon-76x76.png)'; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownImage); + + // * Verify markdown image is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemImage} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemImage).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4896_2 - should be able to display markdown image with link', async () => { + // # Open a channel screen and post a markdown image with link + const markdownImage = '[![Mattermost](https://docs.mattermost.com/_images/icon-76x76.png)](https://github.com/mattermost/mattermost-server)'; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownImage); + + // * Verify markdown image with link is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemImage} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemImage).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/markdown_latex.e2e.ts b/detox/e2e/test/messaging/markdown_latex.e2e.ts new file mode 100644 index 000000000..b8ff749ae --- /dev/null +++ b/detox/e2e/test/messaging/markdown_latex.e2e.ts @@ -0,0 +1,93 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, + System, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Messaging - Markdown Latex', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + System.apiUpdateConfig(siteOneUrl, { + ServiceSettings: { + EnableLatex: true, + EnableInlineLatex: true, + }, + }); + + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4900_1 - should be able to display markdown latex code block', async () => { + // # Open a channel screen and post a markdown latex code block + // eslint-disable-next-line no-useless-escape + const message = 'X_k = \sum_{n=0}^{2N-1} x_n \cos \left[\frac{\pi}{N} \left(n+\frac{1}{2}+\frac{N}{2}\right) \left(k+\frac{1}{2}\right) \right]'; + const markdownLatexCodeBlock = `\`\`\`latex\n${message}\n\`\`\``; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownLatexCodeBlock); + + // * Verify markdown latex code block is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemLatexCodeBlock} = ChannelScreen.getPostListPostItem(post.id); + await ChannelScreen.flatPostList.scrollTo('bottom'); + await expect(postListPostItemLatexCodeBlock).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4900_2 - should be able to display markdown inline latex -- KNOWN ISSUE: MM-45466', async () => { + // # Open a channel screen and post a markdown inline latex + // eslint-disable-next-line no-useless-escape + const message = 'X_k = \sum_{n=0}^{2N-1} x_n \cos \left[\frac{\pi}{N} \left(n+\frac{1}{2}+\frac{N}{2}\right) \left(k+\frac{1}{2}\right) \right]'; + const markdownInlineLatex = `$${message}$`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownInlineLatex); + + // * Verify markdown inline latex is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemInlineLatex} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemInlineLatex).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/markdown_list.e2e.ts b/detox/e2e/test/messaging/markdown_list.e2e.ts new file mode 100644 index 000000000..a3d268f97 --- /dev/null +++ b/detox/e2e/test/messaging/markdown_list.e2e.ts @@ -0,0 +1,102 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Messaging - Markdown List', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4894_1 - should be able to display markdown bullet list', async () => { + // # Open a channel screen and post a markdown bullet list + const item1 = 'item one'; + const item2 = 'item two'; + const item2SubPoint = 'item two sub-point'; + const markdownBulletList = `* ${item1}\n- ${item2}\n + ${item2SubPoint}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownBulletList); + + // * Verify markdown bullet list is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemListItem, postListPostItemListItemBullet} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemListItem.atIndex(0)).toBeVisible(); + await expect(postListPostItemListItemBullet.atIndex(0)).toHaveText('•'); + await expect(element(by.text(item1))).toBeVisible(); + await expect(postListPostItemListItem.atIndex(1)).toBeVisible(); + await expect(postListPostItemListItemBullet.atIndex(1)).toHaveText('•'); + await expect(element(by.text(item2))).toBeVisible(); + await expect(postListPostItemListItem.atIndex(2)).toBeVisible(); + await expect(postListPostItemListItemBullet.atIndex(2)).toHaveText('◦'); + await expect(element(by.text(item2SubPoint))).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4894_2 - should be able to display markdown ordered list', async () => { + // # Open a channel screen and post a markdown ordered list + const item1 = 'Item one'; + const item2 = 'Item two'; + const item3 = 'Item three'; + const markdownOrderedList = `1. ${item1}\n1. ${item2}\n1. ${item3}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdownOrderedList); + + // * Verify markdown ordered list is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemListItem, postListPostItemListItemBullet} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemListItem.atIndex(0)).toBeVisible(); + await expect(postListPostItemListItemBullet.atIndex(0)).toHaveText('1.'); + await expect(element(by.text(item1))).toBeVisible(); + await expect(postListPostItemListItem.atIndex(1)).toBeVisible(); + await expect(postListPostItemListItemBullet.atIndex(1)).toHaveText('2.'); + await expect(element(by.text(item2))).toBeVisible(); + await expect(postListPostItemListItem.atIndex(2)).toBeVisible(); + await expect(postListPostItemListItemBullet.atIndex(2)).toHaveText('3.'); + await expect(element(by.text(item3))).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/markdown_separator.e2e.ts b/detox/e2e/test/messaging/markdown_separator.e2e.ts new file mode 100644 index 000000000..1232d6a78 --- /dev/null +++ b/detox/e2e/test/messaging/markdown_separator.e2e.ts @@ -0,0 +1,68 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Messaging - Markdown Separator', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4897_1 - should be able to display markdown separator', async () => { + // # Open a channel screen and post a markdown separator + const markdownSeparator = '---'; + await ChannelScreen.open(channelsCategory, testChannel.name); + await Post.apiCreatePost(siteOneUrl, { + channelId: testChannel.id, + message: markdownSeparator, + }); + + // * Verify markdown separator is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemThematicBreak} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemThematicBreak).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/markdown_table.e2e.ts b/detox/e2e/test/messaging/markdown_table.e2e.ts new file mode 100644 index 000000000..6995c28ca --- /dev/null +++ b/detox/e2e/test/messaging/markdown_table.e2e.ts @@ -0,0 +1,228 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, + TableScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Messaging - Markdown Table', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4899_1 - should be able to display markdown table', async () => { + // # Open a channel screen and post a markdown table + const markdownTable = + '| A | B | C |\n' + + '|:---|:---|:---|\n' + + '| 1 | Name | Toast |\n' + + '| 2 | Name | Server |\n'; + await ChannelScreen.open(channelsCategory, testChannel.name); + await Post.apiCreatePost(siteOneUrl, { + channelId: testChannel.id, + message: markdownTable, + }); + + // * Verify markdown table is displayed + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemTable} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemTable).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4899_2 - should be able to display markdown table with long text wrapped properly', async () => { + // # Open a channel screen and post a markdown table with long text + const markdownTable = + '| Left header that wraps | Center header that wraps | Right header that wraps |\n' + + '| :-- | :-: | --: |\n' + + '| Left text that wraps row | Center text that wraps row | Right text that wraps row |\n'; + await ChannelScreen.open(channelsCategory, testChannel.name); + await Post.apiCreatePost(siteOneUrl, { + channelId: testChannel.id, + message: markdownTable, + }); + + // * Verify table is displayed with long text wrapped properly + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemTable, postListPostItemTableExpandButton} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemTable).toBeVisible(); + await expect(element(by.text('Left header that wraps'))).toBeVisible(); + await expect(element(by.text('Center header that wraps'))).toBeVisible(); + await expect(element(by.text('Right header that wraps'))).toBeVisible(); + await expect(element(by.text('Left text that wraps row'))).toBeVisible(); + await expect(element(by.text('Center text that wraps row'))).toBeVisible(); + await expect(element(by.text('Right text that wraps row'))).toBeVisible(); + + // # Expand to full view + await ChannelScreen.flatPostList.scrollTo('bottom'); + await postListPostItemTableExpandButton.tap(); + + // * Verify on table screen with the markdown table + await TableScreen.toBeVisible(); + await expect(element(by.text('Left header that wraps'))).toBeVisible(); + await expect(element(by.text('Center header that wraps'))).toBeVisible(); + await expect(element(by.text('Right header that wraps'))).toBeVisible(); + await expect(element(by.text('Left text that wraps row'))).toBeVisible(); + await expect(element(by.text('Center text that wraps row'))).toBeVisible(); + await expect(element(by.text('Right text that wraps row'))).toBeVisible(); + + // # Go back to channel list screen + await TableScreen.back(); + await ChannelScreen.back(); + }); + + it('MM-T4899_3 - should be able to open markdown table in full view and allow horizontal scroll', async () => { + // # Open a channel screen and post a markdown table with more columns past horizontal view + const markdownTable = + '| Header | Header | Header | Header | Header | Header | Header | Header HS last |\n' + + '| :-- | :-: | --: | --: | :-- | :-: | --: | --: |\n' + + '| Left | Center | Right | Right | Left | Center | Right | Right |\n'.repeat(7) + + '| Left | Center | Right | Right | Left | Center | Right | Right HS last |\n'; + await ChannelScreen.open(channelsCategory, testChannel.name); + await Post.apiCreatePost(siteOneUrl, { + channelId: testChannel.id, + message: markdownTable, + }); + + // * Verify table is displayed with some right columns not visible + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemTable, postListPostItemTableExpandButton} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemTable).toBeVisible(); + await expect(element(by.text('Header HS last'))).not.toBeVisible(); + await expect(element(by.text('Right HS last'))).not.toBeVisible(); + + // # Expand to full view + await ChannelScreen.flatPostList.scrollTo('bottom'); + await postListPostItemTableExpandButton.tap(); + await TableScreen.toBeVisible(); + await expect(element(by.text('Header HS last'))).not.toBeVisible(); + await expect(element(by.text('Right HS last'))).not.toBeVisible(); + + // * Verify table screen is scrollable to the right + await TableScreen.tableScrollView.scrollTo('right'); + await expect(element(by.text('Header HS last'))).toBeVisible(); + await expect(element(by.text('Right HS last'))).toBeVisible(); + + // # Go back to channel list screen + await TableScreen.back(); + await ChannelScreen.back(); + }); + + it('MM-T4899_4 - should be able to open markdown table in full view and allow vertical scroll', async () => { + // # Open a channel screen and post a markdown table with more rows past vertical view + const markdownTable = + '| Header | Header | Header VS last |\n' + + '| :-- | :-: | --: |\n' + + '| Left | Center | Right |\n'.repeat(30) + + '| Left | Center | Right VS last |\n'; + await ChannelScreen.open(channelsCategory, testChannel.name); + await Post.apiCreatePost(siteOneUrl, { + channelId: testChannel.id, + message: markdownTable, + }); + + // * Verify table is displayed with some bottom rows not visible + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemTable, postListPostItemTableExpandButton} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemTable).toBeVisible(); + await expect(element(by.text('Header VS last'))).toBeVisible(); + await expect(element(by.text('Right VS last'))).not.toBeVisible(); + + // # Expand to full view + await ChannelScreen.flatPostList.scrollTo('bottom'); + await postListPostItemTableExpandButton.tap(); + await TableScreen.toBeVisible(); + await expect(element(by.text('Header VS last'))).toBeVisible(); + await expect(element(by.text('Right VS last'))).not.toBeVisible(); + + // * Verify table screen is scrollable to the bottom + await TableScreen.tableScrollView.scrollTo('bottom'); + await expect(element(by.text('Header VS last'))).not.toBeVisible(); + await expect(element(by.text('Right VS last'))).toBeVisible(); + + // # Go back to channel list screen + await TableScreen.back(); + await ChannelScreen.back(); + }); + + it('MM-T4899_5 - should be able to open markdown table in full view and allow both horizontal and vertical scrolls', async () => { + // # Open a channel screen and post a markdown table with more columns and rows past horizontal and vertical views + const markdownTable = + '| Header | Header | Header | Header | Header | Header | Header | Header last |\n' + + '| :-- | :-: | --: | --: | :-- | :-: | --: | --: |\n' + + '| Left | Center | Right | Right | Left | Center | Right | Right |\n'.repeat(30) + + '| Left | Center | Right | Right | Left | Center | Right | Right last |\n'; + await ChannelScreen.open(channelsCategory, testChannel.name); + await Post.apiCreatePost(siteOneUrl, { + channelId: testChannel.id, + message: markdownTable, + }); + + // * Verify table is displayed with some right columns and bottom rows not visible + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemTable, postListPostItemTableExpandButton} = ChannelScreen.getPostListPostItem(post.id); + await expect(postListPostItemTable).toBeVisible(); + await expect(element(by.text('Header last'))).not.toBeVisible(); + await expect(element(by.text('Right last'))).not.toBeVisible(); + + // # Expand to full view + await ChannelScreen.flatPostList.scrollTo('bottom'); + await postListPostItemTableExpandButton.tap(); + await TableScreen.toBeVisible(); + await expect(element(by.text('Header last'))).not.toBeVisible(); + await expect(element(by.text('Right last'))).not.toBeVisible(); + + // * Verify table screen is scrollable to the right and scrollable to the bottom + await TableScreen.tableScrollView.scrollTo('right'); + await expect(element(by.text('Header last'))).toBeVisible(); + await expect(element(by.text('Right last'))).not.toBeVisible(); + await TableScreen.tableScrollView.scrollTo('bottom'); + await expect(element(by.text('Header last'))).not.toBeVisible(); + await expect(element(by.text('Right last'))).toBeVisible(); + + // # Go back to channel list screen + await TableScreen.back(); + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/permalink.e2e.ts b/detox/e2e/test/messaging/permalink.e2e.ts new file mode 100644 index 000000000..cc1a356e3 --- /dev/null +++ b/detox/e2e/test/messaging/permalink.e2e.ts @@ -0,0 +1,145 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Channel, + Post, + Setup, + User, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + PermalinkScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId, timeouts, wait} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - Permalink', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + let testTeam: any; + let testUser: any; + + beforeAll(async () => { + const {channel, team, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + testTeam = team; + testUser = user; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(testUser); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + const expectPermalinkTargetMessage = async (teamName: any, permalinkTargetPost: any, permalinkTargetChannelDiplayName: string) => { + // # Open a channel screen, post a permalink to target post, and tap on permalink + await ChannelScreen.open(channelsCategory, testChannel.name); + const permalinkLabel = `permalink-${getRandomId()}`; + const permalinkMessage = `[${permalinkLabel}](/${teamName}/pl/${permalinkTargetPost.id})`; + await ChannelScreen.postMessage(permalinkMessage); + await element(by.text(permalinkLabel)).tap({x: 5, y: 10}); + await wait(timeouts.ONE_SEC); + + // * Verify on permalink screen and target post is displayed + await PermalinkScreen.toBeVisible(); + const {postListPostItem: permalinkPostListPostItem} = PermalinkScreen.getPostListPostItem(permalinkTargetPost.id, permalinkTargetPost.message); + await expect(permalinkPostListPostItem).toBeVisible(); + + // # Jump to recent messages + await PermalinkScreen.jumpToRecentMessages(); + + // * Verify on channel screen and target post is displayed + await expect(ChannelScreen.headerTitle).toHaveText(permalinkTargetChannelDiplayName); + const {postListPostItem: channelPostListPostItem} = ChannelScreen.getPostListPostItem(permalinkTargetPost.id, permalinkTargetPost.message); + await expect(channelPostListPostItem).toBeVisible(); + }; + + it('MM-T4876_1 - should be able to jump to target public channel post by tapping on permalink with team name', async () => { + // # Post a target message in a target public channel + const permalinkTargetMessage = `Message ${getRandomId()}`; + const {channel: targetChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); + await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); + const permalinkTargetPost = await Post.apiCreatePost(siteOneUrl, { + channelId: targetChannel.id, + message: permalinkTargetMessage, + }); + + await expectPermalinkTargetMessage(testTeam.name, permalinkTargetPost.post, targetChannel.display_name); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4876_2 - should be able to jump to target public channel post by tapping on permalink with _redirect', async () => { + // # Post a target message in a target public channel + const permalinkTargetMessage = `Message ${getRandomId()}`; + const {channel: targetChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); + await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); + const permalinkTargetPost = await Post.apiCreatePost(siteOneUrl, { + channelId: targetChannel.id, + message: permalinkTargetMessage, + }); + + await expectPermalinkTargetMessage('_redirect', permalinkTargetPost.post, targetChannel.display_name); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4876_3 - should be able to jump to target DM post by tapping on permalink with team name', async () => { + // # Post a target message in a target DM channel + const {user: dmOtherUser} = await User.apiCreateUser(siteOneUrl, {prefix: 'testchannel-1'}); + const {channel: targetChannel} = await Channel.apiCreateDirectChannel(siteOneUrl, [testUser.id, dmOtherUser.id]); + const permalinkTargetMessage = `Message ${getRandomId()}`; + const permalinkTargetPost = await Post.apiCreatePost(siteOneUrl, { + channelId: targetChannel.id, + message: permalinkTargetMessage, + }); + + await expectPermalinkTargetMessage(testTeam.name, permalinkTargetPost.post, dmOtherUser.username); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4876_4 - should be able to jump to target DM post by tapping on permalink with _redirect', async () => { + // # Post a target message in a target DM channel + const {user: dmOtherUser} = await User.apiCreateUser(siteOneUrl, {prefix: 'testchannel-1'}); + const {channel: targetChannel} = await Channel.apiCreateDirectChannel(siteOneUrl, [testUser.id, dmOtherUser.id]); + const permalinkTargetMessage = `Message ${getRandomId()}`; + const permalinkTargetPost = await Post.apiCreatePost(siteOneUrl, { + channelId: targetChannel.id, + message: permalinkTargetMessage, + }); + + await expectPermalinkTargetMessage('_redirect', permalinkTargetPost.post, dmOtherUser.username); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/smoke_test/autocomplete.e2e.ts b/detox/e2e/test/smoke_test/autocomplete.e2e.ts new file mode 100644 index 000000000..50afd3ef2 --- /dev/null +++ b/detox/e2e/test/smoke_test/autocomplete.e2e.ts @@ -0,0 +1,162 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Autocomplete} from '@support/ui/component'; +import { + ChannelListScreen, + ChannelScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {expect} from 'detox'; + +describe('Autocomplete - At-Mention', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + let testUser: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + testUser = user; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(testUser); + + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + + // # Open a channel screen + await ChannelScreen.open(channelsCategory, testChannel.name); + }); + + beforeEach(async () => { + // # Clear post input + await ChannelScreen.postInput.clearText(); + + // * Verify autocomplete is not displayed + await Autocomplete.toBeVisible(false); + }); + + afterAll(async () => { + // # Log out + await ChannelScreen.back(); + await HomeScreen.logout(); + }); + + it('MM-T4886_1 - should be able to select and post at-mention suggestion', async () => { + // # Type in "@" to activate at-mention autocomplete + await ChannelScreen.postInput.typeText('@'); + await Autocomplete.toBeVisible(); + + // * Verify at-mention list is displayed + await expect(Autocomplete.sectionAtMentionList).toBeVisible(); + + // # Type in username + await ChannelScreen.postInput.typeText(testUser.username); + + // * Verify at-mention autocomplete contains associated user suggestion + const {atMentionItem} = Autocomplete.getAtMentionItem(testUser.id); + await expect(atMentionItem).toBeVisible(); + + // # Select and post at-mention suggestion + await atMentionItem.tap(); + await ChannelScreen.sendButton.tap(); + + // * Verify at-mention suggestion is posted + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(post.id, `@${testUser.username}`); + }); + + it('MM-T4886_2 - should be able to select and post channel mention suggestion', async () => { + // # Type in "~" to activate channel mention autocomplete + await ChannelScreen.postInput.typeText('~'); + await Autocomplete.toBeVisible(); + + // * Verify channel mention list is displayed + await expect(Autocomplete.sectionChannelMentionList).toBeVisible(); + + // # Type in channel name + await ChannelScreen.postInput.typeText(testChannel.name); + + // * Verify channel mention autocomplete contains associated channel suggestion + const {channelMentionItem} = Autocomplete.getChannelMentionItem(testChannel.name); + await expect(channelMentionItem).toBeVisible(); + + // # Select and post channel mention suggestion + await channelMentionItem.tap(); + await ChannelScreen.sendButton.tap(); + + // * Verify channel mention suggestion is posted + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(post.id, `~${testChannel.display_name}`); + }); + + it('MM-T4886_3 - should be able to select and post emoji suggestion', async () => { + // # Type in ":" then first 2 characters of emoji name to activate emoji suggestion autocomplete + const emojiName = 'fox_face'; + const emojiNameFirst2Chars = emojiName.substring(0, 2); + const emojiName3rdToLastChars = emojiName.substring(2); + await ChannelScreen.postInput.typeText(`:${emojiNameFirst2Chars}`); + await Autocomplete.toBeVisible(); + + // * Verify emoji suggestion list is displayed + await expect(Autocomplete.flatEmojiSuggestionList).toBeVisible(); + + // # Type in 3rd to last characters of emoji name + await ChannelScreen.postInput.typeText(emojiName3rdToLastChars); + + // * Verify emoji suggestion autocomplete contains associated emoji suggestion + const {emojiSuggestionItem} = Autocomplete.getEmojiSuggestionItem(emojiName); + await expect(emojiSuggestionItem).toBeVisible(); + + // # Select and post emoji suggestion + await emojiSuggestionItem.tap(); + await ChannelScreen.sendButton.tap(); + + // * Verify emoji suggestion is posted + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(post.id, '🦊'); + }); + + it('MM-T4886_4 - should be able to select and post slash suggestion', async () => { + // # Type in "/" to activate slash suggestion autocomplete + await ChannelScreen.postInput.typeText('/'); + await Autocomplete.toBeVisible(); + + // * Verify slash suggestion list is displayed + await expect(Autocomplete.flatSlashSuggestionList).toBeVisible(); + + // # Type in slash command name + const slashCommand = 'away'; + await ChannelScreen.postInput.typeText(slashCommand); + + // * Verify slash suggestion autocomplete contains associated slash command suggestion + const {slashSuggestionItem} = Autocomplete.getSlashSuggestionItem(slashCommand); + await expect(slashSuggestionItem).toBeVisible(); + + // # Select and post slash suggestion + await slashSuggestionItem.tap(); + await ChannelScreen.sendButton.tap(); + + // * Verify slash suggestion is posted + await expect(element(by.text('You are now away'))).toBeVisible(); + }); +}); diff --git a/detox/e2e/test/smoke_test/messaging.e2e.ts b/detox/e2e/test/smoke_test/messaging.e2e.ts index 23365a6d7..7b7091869 100644 --- a/detox/e2e/test/smoke_test/messaging.e2e.ts +++ b/detox/e2e/test/smoke_test/messaging.e2e.ts @@ -8,6 +8,7 @@ // ******************************************************************* import { + Channel, Post, Setup, } from '@support/server_api'; @@ -35,14 +36,18 @@ describe('Smoke Test - Messaging', () => { const savedText = 'Saved'; const pinnedText = 'Pinned'; let testChannel: any; + let testTeam: any; + let testUser: any; beforeAll(async () => { - const {channel, user} = await Setup.apiInit(siteOneUrl); + const {channel, team, user} = await Setup.apiInit(siteOneUrl); testChannel = channel; + testTeam = team; + testUser = user; // # Log in to server await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); - await LoginScreen.login(user); + await LoginScreen.login(testUser); }); beforeEach(async () => { @@ -135,7 +140,7 @@ describe('Smoke Test - Messaging', () => { // # Open post options for message, open emoji picker screen, and add a reaction await ChannelScreen.openPostOptionsFor(post.id, resolvedMessage); await EmojiPickerScreen.open(); - await EmojiPickerScreen.searchInput.typeText('clown_face'); + await EmojiPickerScreen.searchInput.replaceText('clown_face'); await element(by.text('🤡')).tap(); // * Verify reaction is added to the message @@ -198,4 +203,64 @@ describe('Smoke Test - Messaging', () => { // # Go back to channel list screen await ChannelScreen.back(); }); + + it('MM-T4786_5 - should be able to post a message with at-mention and channel mention', async () => { + // # Open a channel screen and post a message with at-mention and channel mention + const {channel: targetChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); + await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); + const message = `Message @${testUser.username} ~${targetChannel.name}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify at-mention is posted as lowercase and channel mention is posted as display name + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(post.id, `Message @${testUser.username.toLowerCase()} ~${targetChannel.display_name}`); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4786_6 - should be able to post labeled permalink and labeled channel link', async () => { + // # Post a target message in a target channel + const permalinkTargetMessage = `Message ${getRandomId()}`; + const {channel: targetChannel} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeam.id}); + await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, targetChannel.id); + const permalinkTargetPost = await Post.apiCreatePost(siteOneUrl, { + channelId: targetChannel.id, + message: permalinkTargetMessage, + }); + + // # Open a channel screen and post a message with labeled permalink to the target message and labeled channel link to the target channel + await ChannelScreen.open(channelsCategory, testChannel.name); + const permalinkLabel = `permalink-${getRandomId()}`; + const permalinkMessage = `[${permalinkLabel}](/${testTeam.name}/pl/${permalinkTargetPost.id})`; + const channelLinkLabel = `channel-link-${getRandomId()}`; + const channelLinkMessage = `[${channelLinkLabel}](${serverOneUrl}/${testTeam.name}/channels/${targetChannel.name})`; + const message = `Message ${permalinkMessage} ${channelLinkMessage}`; + await ChannelScreen.postMessage(message); + + // * Verify permalink and channel link are posted as labeled links + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.hasPostMessage(post.id, `Message ${permalinkLabel} ${channelLinkLabel}`); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4786_7 - should be able to post a message with markdown', async () => { + // # Open a channel screen and post a message with markdown + const message = `Message ${getRandomId()}`; + const markdown = `#### ${message}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(markdown); + + // * Verify message with markdown is posted + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItemHeading} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItemHeading).toBeVisible(); + await expect(element(by.text(message))).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); }); diff --git a/detox/e2e/test/threads/global_threads.e2e.ts b/detox/e2e/test/threads/global_threads.e2e.ts index a537d6a65..42d013457 100644 --- a/detox/e2e/test/threads/global_threads.e2e.ts +++ b/detox/e2e/test/threads/global_threads.e2e.ts @@ -47,7 +47,7 @@ describe('Threads - Global Threads', () => { // # Log in to server await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); - await LoginScreen.login(user); + await LoginScreen.login(testUser); }); beforeEach(async () => { diff --git a/detox/e2e/test/threads/open_thread_in_channel.e2e.ts b/detox/e2e/test/threads/open_thread_in_channel.e2e.ts index 9a5e06ed6..22af7da78 100644 --- a/detox/e2e/test/threads/open_thread_in_channel.e2e.ts +++ b/detox/e2e/test/threads/open_thread_in_channel.e2e.ts @@ -113,7 +113,7 @@ describe('Threads - Open Thread in Channel', () => { await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible(); // # Open thread options for thread, tap on copy link option, go back to channel list screen, go to another channel, post the permalink, and tap on permalink - const permalinkLabel = `permalink-${Date.now().toString()}`; + const permalinkLabel = `permalink-${getRandomId()}`; const permalinkMessage = `[${permalinkLabel}](/_redirect/pl/${parentPost.id})`; await GlobalThreadsScreen.back(); await ChannelScreen.open(channelsCategory, 'off-topic');