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) => {
{
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) => {
{type !== General.DM_CHANNEL && type !== General.GM_CHANNEL &&
{
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 (
-
+
{displayName}
@@ -72,22 +78,30 @@ const DirectMessage = ({displayName, user}: Props) => {
}
{user?.isBot &&
}
{Boolean(user?.position) &&
-
+
{user?.position}
}
{Boolean(user?.isBot && user.props?.bot_description) &&
-
+
{user?.props?.bot_description}
}
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 (
-
+
{group}
);
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) => {
-
+
{displayName}
>
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 (
<>
-
+
{displayName}
{Boolean(purpose) &&
-
+
{purpose}
}
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 = ({
);
}
@@ -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) =
{teamDisplayName}
@@ -112,7 +112,7 @@ const RemoteChannelItem = ({onPress, channel, teamDisplayName, testID}: Props) =
{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
<>
@@ -74,6 +75,7 @@ const UserItem = ({currentUserId, onPress, teammateDisplayNameSetting, testID, u
showStatus={true}
statusSize={12}
statusStyle={styles.status}
+ testID={`${userItemTestId}.profile_picture`}
/>
@@ -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}
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 (
@@ -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 && {title}}
- {`${userName}`}
+
+ {`${userName}`}
+
);
};
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) => {
);
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 (
- {title}
- {description}
+
+ {title}
+
+
+ {description}
+
);
};
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) => {
@@ -94,6 +96,7 @@ const UserProfileOptions = ({location, type, userId, username}: Props) => {
);
};
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 = ({
{`${prefix}${displayName}`}
@@ -96,6 +97,7 @@ const UserProfileTitle = ({
{`@${user.username}`}
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 ();
+ return (
+ );
}
if (isGuest) {
- return ();
+ return (
+ );
}
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 &&
}
{Boolean(user.position) && !override && !user.isBot &&
}
{Boolean(localTime) && !override && !user.isBot &&
}
@@ -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 => {
+ 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 => {
+ 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 = '\n\nThis is html block\n\n';
+ 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 = '';
+ 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 = '[](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');