Detox/E2E: Global threads e2e tests in Gekidou (#6300)

* Detox/E2E: Global threads e2e tests in Gekidou

* Revert update deps
This commit is contained in:
Joseph Baylon 2022-05-20 15:51:15 -07:00 committed by GitHub
parent b6e3c2c66d
commit 558d83719c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1497 additions and 62 deletions

View file

@ -35,7 +35,7 @@ const CopyPermalinkOption = ({teamName, post, sourceScreen}: Props) => {
defaultMessage='Copy Link'
onPress={handleCopyLink}
iconName='link-variant'
testID='post_options.copy.permalink.option'
testID='post_options.copy_permalink.option'
/>
);
};

View file

@ -52,11 +52,13 @@ const FollowThreadOption = ({thread, teamId}: FollowThreadOptionProps) => {
dismissBottomSheet(Screens.POST_OPTIONS);
};
const followThreadOptionTestId = thread.isFollowing ? 'post_options.following_thread.option' : 'post_options.follow_thread.option';
return (
<BaseOption
i18nId={id}
defaultMessage={defaultMessage}
testID='post_options.follow.thread.option'
testID={followThreadOptionTestId}
iconName={icon}
onPress={handleToggleFollow}
/>

View file

@ -31,7 +31,7 @@ const ReplyOption = ({post, location}: Props) => {
defaultMessage='Reply'
iconName='reply-outline'
onPress={handleReply}
testID='post_options.reply.post.option'
testID='post_options.reply_post.option'
/>
);
};

View file

@ -33,7 +33,7 @@ const SaveOption = ({isSaved, postId}: CopyTextProps) => {
defaultMessage={defaultMessage}
iconName='bookmark-outline'
onPress={onHandlePress}
testID={`post_options.${defaultMessage.toLocaleLowerCase()}.channel.option`}
testID={`post_options.${defaultMessage.toLocaleLowerCase()}_post.option`}
/>
);
};

View file

@ -20,7 +20,6 @@ import type UserModel from '@typings/database/models/servers/user';
type Props = {
participants: UserModel[];
teamId?: string;
testID: string;
thread: ThreadModel;
};
@ -77,7 +76,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
};
});
const Footer = ({participants, teamId, testID, thread}: Props) => {
const Footer = ({participants, teamId, thread}: Props) => {
const serverUrl = useServerUrl();
const theme = useTheme();
const styles = getStyleSheet(theme);
@ -102,7 +101,7 @@ const Footer = ({participants, teamId, testID, thread}: Props) => {
</View>
<FormattedText
style={styles.replies}
testID={`${testID}.reply_count`}
testID='post_footer.reply_count'
id='threads.replies'
defaultMessage='{count} {count, plural, one {reply} other {replies}}'
values={{
@ -117,7 +116,7 @@ const Footer = ({participants, teamId, testID, thread}: Props) => {
<TouchableOpacity
onPress={toggleFollow}
style={styles.followingButtonContainer}
testID={`${testID}.following`}
testID='post_footer.following_thread.button'
>
<FormattedText
id='threads.following'
@ -133,7 +132,7 @@ const Footer = ({participants, teamId, testID, thread}: Props) => {
<TouchableOpacity
onPress={toggleFollow}
style={styles.notFollowingButtonContainer}
testID={`${testID}.follow`}
testID='post_footer.follow_thread.button'
>
<FormattedText
id='threads.follow'

View file

@ -70,7 +70,7 @@ const HeaderReply = ({commentCount, location, post, theme}: HeaderReplyProps) =>
{location !== SEARCH && commentCount > 0 &&
<Text
style={style.replyText}
testID='post_header.reply.count'
testID='post_header.reply_count'
>
{commentCount}
</Text>

View file

@ -294,15 +294,12 @@ const Post = ({
if (isCRTEnabled && thread) {
if (thread.replyCount > 0 || thread.isFollowing) {
footer = (
<Footer
testID={`${itemTestID}.footer`}
thread={thread}
/>
<Footer thread={thread}/>
);
}
if (thread.unreadMentions || thread.unreadReplies) {
unreadDot = (
<UnreadDot testID={`${itemTestID}.badge`}/>
<UnreadDot/>
);
}
}

View file

@ -7,10 +7,6 @@ import {View} from 'react-native';
import {useTheme} from '@context/theme';
import {makeStyleSheetFromTheme} from '@utils/theme';
type Props = {
testID: string;
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
badgeContainer: {
@ -30,13 +26,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
};
});
const UnreadDot = ({testID}: Props) => {
const UnreadDot = () => {
const theme = useTheme();
const styles = getStyleSheet(theme);
return (
<View
style={styles.badgeContainer}
testID={testID}
testID='post_unread_dot.badge'
>
<View style={styles.unreadDot}/>
</View>

View file

@ -39,6 +39,7 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
"marginHorizontal": 4,
}
}
testID="thread-overview.no_replies"
>
No replies yet
</Text>
@ -60,7 +61,7 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
testID="thread-overview.save"
testID="thread-overview.unsave.button"
>
<View
accessible={true}
@ -89,7 +90,7 @@ exports[`ThreadOverview should match snapshot when post is not saved and 0 repli
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
testID="thread-overview.options"
testID="thread-overview.post_options.button"
>
<View
accessible={true}
@ -148,6 +149,7 @@ exports[`ThreadOverview should match snapshot when post is saved and has replies
"marginHorizontal": 4,
}
}
testID="thread-overview.replies_count"
>
2 replies
</Text>
@ -169,7 +171,7 @@ exports[`ThreadOverview should match snapshot when post is saved and has replies
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
testID="thread-overview.save"
testID="thread-overview.save.button"
>
<View
accessible={true}
@ -198,7 +200,7 @@ exports[`ThreadOverview should match snapshot when post is saved and has replies
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
testID="thread-overview.options"
testID="thread-overview.post_options.button"
>
<View
accessible={true}

View file

@ -96,6 +96,8 @@ const ThreadOverview = ({isSaved, repliesCount, rootPost, style, testID}: Props)
return container;
}, [repliesCount, style]);
const saveButtonTestId = isSaved ? `${testID}.unsave.button` : `${testID}.save.button`;
return (
<View
style={containerStyle}
@ -108,6 +110,7 @@ const ThreadOverview = ({isSaved, repliesCount, rootPost, style, testID}: Props)
style={styles.repliesCount}
id='thread.repliesCount'
defaultMessage='{repliesCount, number} {repliesCount, plural, one {reply} other {replies}}'
testID={`${testID}.replies_count`}
values={{repliesCount}}
/>
) : (
@ -115,6 +118,7 @@ const ThreadOverview = ({isSaved, repliesCount, rootPost, style, testID}: Props)
style={styles.repliesCount}
id='thread.noReplies'
defaultMessage='No replies yet'
testID={`${testID}.no_replies`}
/>
)
}
@ -123,7 +127,7 @@ const ThreadOverview = ({isSaved, repliesCount, rootPost, style, testID}: Props)
<TouchableOpacity
onPress={onHandleSavePress}
style={styles.optionContainer}
testID={`${testID}.save`}
testID={saveButtonTestId}
>
<CompassIcon
size={24}
@ -134,7 +138,7 @@ const ThreadOverview = ({isSaved, repliesCount, rootPost, style, testID}: Props)
<TouchableOpacity
onPress={showPostOptions}
style={styles.optionContainer}
testID={`${testID}.options`}
testID={`${testID}.post_options.button`}
>
<CompassIcon
size={24}

View file

@ -66,7 +66,10 @@ function EmptyState({isUnreads}: Props) {
});
}
return (
<View style={style.container}>
<View
style={style.container}
testID='global_threads.threads_list.empty_state'
>
<EmptyStateIllustration theme={theme}/>
<View style={style.textContainer}>
<Text style={style.title}>

View file

@ -172,7 +172,7 @@ const Header = ({setTab, tab, teamId, testID, unreadsCount}: Props) => {
{hasUnreads ? (
<View
style={styles.unreadsDot}
testID={`${testID}.unreads_dot`}
testID={`${testID}.unreads_dot.badge`}
/>
) : null}
</View>

View file

@ -143,7 +143,7 @@ const Thread = ({author, channel, post, teammateNameDisplay, testID, thread}: Pr
}, [isTablet, theme, thread]);
const threadStarterName = displayUsername(author, intl.locale, teammateNameDisplay);
const testIDPrefix = `${testID}.${thread.id}`;
const threadItemTestId = `${testID}.thread_item.${thread.id}`;
const needBadge = thread.unreadMentions || thread.unreadReplies;
let badgeComponent;
@ -152,7 +152,7 @@ const Thread = ({author, channel, post, teammateNameDisplay, testID, thread}: Pr
badgeComponent = (
<View
style={styles.mentionBadge}
testID={`${testIDPrefix}.unread_mentions`}
testID={`${threadItemTestId}.unread_mentions.badge`}
>
<Text style={styles.mentionBadgeText}>{thread.unreadMentions > 99 ? '99+' : thread.unreadMentions}</Text>
</View>
@ -161,7 +161,7 @@ const Thread = ({author, channel, post, teammateNameDisplay, testID, thread}: Pr
badgeComponent = (
<View
style={styles.unreadDot}
testID={`${testIDPrefix}.unread_dot`}
testID={`${threadItemTestId}.unread_dot.badge`}
/>
);
}
@ -176,6 +176,7 @@ const Thread = ({author, channel, post, teammateNameDisplay, testID, thread}: Pr
defaultMessage='Original Message Deleted'
style={[styles.threadStarter, styles.threadDeleted]}
numberOfLines={1}
testID={`${threadItemTestId}.thread_starter.user_display_name`}
/>
);
} else {
@ -183,6 +184,7 @@ const Thread = ({author, channel, post, teammateNameDisplay, testID, thread}: Pr
<Text
style={styles.threadStarter}
numberOfLines={1}
testID={`${threadItemTestId}.thread_starter.user_display_name`}
>
{threadStarterName}
</Text>
@ -207,7 +209,7 @@ const Thread = ({author, channel, post, teammateNameDisplay, testID, thread}: Pr
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
onLongPress={showThreadOptions}
onPress={showThread}
testID={`${testIDPrefix}.item`}
testID={threadItemTestId}
>
<View style={styles.container}>
<View style={styles.badgeContainer}>
@ -222,6 +224,7 @@ const Thread = ({author, channel, post, teammateNameDisplay, testID, thread}: Pr
<Text
style={styles.channelName}
numberOfLines={1}
testID={`${threadItemTestId}.thread_starter.channel_display_name`}
>
{channel?.displayName}
</Text>
@ -236,7 +239,7 @@ const Thread = ({author, channel, post, teammateNameDisplay, testID, thread}: Pr
{postBody}
<ThreadFooter
author={author}
testID={`${testIDPrefix}.footer`}
testID={`${threadItemTestId}.footer`}
thread={thread}
/>
</View>

View file

@ -161,7 +161,10 @@ function Permalink({channel, postId}: Props) {
}), []);
return (
<SafeAreaView style={containerStyle}>
<SafeAreaView
style={containerStyle}
testID='permalink.screen'
>
<Animated.View style={style.wrapper}>
<View style={style.header}>
<TouchableOpacity
@ -211,6 +214,7 @@ function Permalink({channel, postId}: Props) {
<TouchableOpacity
style={[style.footer, style.bottom]}
onPress={handlePress}
testID='permalink.jump_to_recent_messages.button'
>
<FormattedText
testID='permalink.search.jump'

View file

@ -28,7 +28,7 @@ const CopyTextOption = ({postMessage, sourceScreen}: Props) => {
defaultMessage='Copy Text'
iconName='content-copy'
onPress={handleCopyText}
testID='post_options.copy.text.option'
testID='post_options.copy_text.option'
/>
);
};

View file

@ -49,7 +49,7 @@ const DeletePostOption = ({combinedPost, post}: Props) => {
defaultMessage='Delete'
iconName='trash-can-outline'
onPress={onPress}
testID='post_options.delete.post.option'
testID='post_options.delete_post.option'
isDestructive={true}
/>
);

View file

@ -47,7 +47,7 @@ const EditOption = ({post, canDelete}: Props) => {
defaultMessage='Edit'
onPress={onPress}
iconName='pencil-outline'
testID='post_options.edit.post.option'
testID='post_options.edit_post.option'
/>
);
};

View file

@ -38,7 +38,7 @@ const MarkAsUnreadOption = ({sourceScreen, post, teamId}: Props) => {
defaultMessage='Mark as Unread'
iconName='mark-as-unread'
onPress={onPress}
testID='post_options.mark.unread.option'
testID='post_options.mark_as_unread.option'
/>
);
};

View file

@ -43,7 +43,7 @@ const PinChannelOption = ({isPostPinned, postId}: PinChannelProps) => {
defaultMessage={defaultMessage}
iconName='pin-outline'
onPress={onPress}
testID={`post_options.${key}.channel.option`}
testID={`post_options.${key}_post.option`}
/>
);
};

View file

@ -72,8 +72,13 @@ function ThreadFollow({isFollowing, teamId, threadId}: Props) {
};
}
const followThreadButtonTestId = isFollowing ? 'thread.following_thread.button' : 'thread.follow_thread.button';
return (
<TouchableOpacity onPress={onPress}>
<TouchableOpacity
onPress={onPress}
testID={followThreadButtonTestId}
>
<View style={containerStyle}>
<FormattedText
{...followTextProps}

View file

@ -30,6 +30,7 @@ const MarkAsUnreadOption = ({teamId, thread}: Props) => {
const id = thread.unreadReplies ? t('global_threads.options.mark_as_read') : t('mobile.post_info.mark_unread');
const defaultMessage = thread.unreadReplies ? 'Mark as Read' : 'Mark as Unread';
const markAsUnreadTestId = thread.unreadReplies ? 'thread_options.mark_as_read.option' : 'thread_options.mark_as_unread.option';
return (
<BaseOption
@ -37,7 +38,7 @@ const MarkAsUnreadOption = ({teamId, thread}: Props) => {
defaultMessage={defaultMessage}
iconName='mark-as-unread'
onPress={onHandlePress}
testID='thread.options.mark_as_read'
testID={markAsUnreadTestId}
/>
);
};

View file

@ -29,7 +29,7 @@ const OpenInChannelOption = ({threadId}: Props) => {
defaultMessage='Open in Channel'
iconName='globe'
onPress={onHandlePress}
testID='thread.options.open_in_channel'
testID='thread_options.open_in_channel.option'
/>
);
};

View file

@ -131,6 +131,7 @@ const ThreadOptions = ({
componentId={Screens.THREAD_OPTIONS}
initialSnapIndex={0}
snapPoints={[((options.length + 2) * ITEM_HEIGHT), 10]}
testID='thread_options'
/>
);
};

View file

@ -11,6 +11,7 @@ class Alert {
return isAndroid() ? element(by.text(title)) : element(by.label(title)).atIndex(0);
};
markAllAsReadTitle = isAndroid() ? element(by.text('Are you sure you want to mark all threads as read?')) : element(by.label('Are you sure you want to mark all threads as read?')).atIndex(0);
messageLengthTitle = isAndroid() ? element(by.text('Message Length')) : element(by.label('Message Length')).atIndex(0);
removeServerTitle = (serverDisplayName: string) => {
const title = `Are you sure you want to remove ${serverDisplayName}?`;
@ -22,6 +23,7 @@ class Alert {
cancelButton = isAndroid() ? element(by.text('CANCEL')) : element(by.label('Cancel')).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);
okButton = isAndroid() ? element(by.text('OK')) : element(by.label('OK')).atIndex(1);
removeButton = isAndroid() ? element(by.text('REMOVE')) : element(by.label('Remove')).atIndex(1);
}

View file

@ -11,14 +11,18 @@ class Post {
emoji: 'markdown_emoji',
image: 'markdown_image',
message: 'markdown_text',
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',
postHeaderReply: 'post_header.reply',
postHeaderReplyCount: 'post_header.reply.count',
postHeaderReplyCount: 'post_header.reply_count',
postPreHeaderText: 'post_pre_header.text',
postUnreadDotBadge: 'post_unread_dot.badge',
showLessButton: 'show_more.button.chevron-up',
showMoreButton: 'show_more.button.chevron-down',
table: 'markdown_table',
@ -39,6 +43,7 @@ class Post {
const postItemTableMatcher = by.id(this.testID.table).withAncestor(postItemMatcher);
const postItemTableExpandButtonMatcher = by.id(this.testID.tableExpandButton).withAncestor(postItemMatcher);
const postItemThematicBreakMatcher = by.id(this.testID.thematicBreak).withAncestor(postItemMatcher);
const postItemUnreadDotBadgeMatcher = by.id(this.testID.postUnreadDotBadge).withAncestor(postItemMatcher);
return {
postItem: element(postItemMatcher),
@ -53,11 +58,25 @@ class Post {
postItemTable: element(postItemTableMatcher),
postItemTableExpandButton: element(postItemTableExpandButtonMatcher),
postItemThematicBreak: element(postItemThematicBreakMatcher),
postItemUnreadDotBadge: element(postItemUnreadDotBadgeMatcher),
...this.getPostFooter(postItemMatcher),
...this.getPostHeader(postItemMatcher),
...this.getPostProfilePicture(postItemMatcher, postProfileOptions),
};
};
getPostFooter = (postItemMatcher: Detox.NativeMatcher) => {
const postItemFooterFollowButtonMatcher = by.id(this.testID.postFooterFollowButton).withAncestor(postItemMatcher);
const postItemFooterFollowingButtonMatcher = by.id(this.testID.postFooterFollowingButton).withAncestor(postItemMatcher);
const postItemFooterReplyCountMatcher = by.id(this.testID.postFooterReplyCount).withAncestor(postItemMatcher);
return {
postItemFooterFollowButton: element(postItemFooterFollowButtonMatcher),
postItemFooterFollowingButton: element(postItemFooterFollowingButtonMatcher),
postItemFooterReplyCount: element(postItemFooterReplyCountMatcher),
};
};
getPostHeader = (postItemMatcher: Detox.NativeMatcher) => {
const postItemHeaderCommentedOnMatcher = by.id(this.testID.postHeaderCommentedOn).withAncestor(postItemMatcher);
const postItemHeaderDateTimeMatcher = by.id(this.testID.postHeaderDateTime).withAncestor(postItemMatcher);

View file

@ -11,6 +11,12 @@ class PostList {
flatList: `${screenPrefix}post_list.flat_list`,
moreMessagesButton: `${screenPrefix}post_list.more_messages_button`,
newMessagesDivider: `${screenPrefix}post_list.new_messages_line`,
threadOverview: `${screenPrefix}post_list.thread_overview`,
threadOverviewRepliesCount: `${screenPrefix}post_list.replies_count`,
threadOverviewNoReplies: `${screenPrefix}post_list.no_replies`,
threadOverviewSaveButton: `${screenPrefix}post_list.thread_overview.save.button`,
threadOverviewUnsaveButton: `${screenPrefix}post_list.thread_overview.unsave.button`,
threadOverviewPostOptionsButton: `${screenPrefix}post_list.thread_overview.post_options.button`,
postListPostItem: `${screenPrefix}post_list.post`,
};
}
@ -27,12 +33,39 @@ class PostList {
return element(by.id(this.testID.newMessagesDivider));
};
getThreadOverview = () => {
return element(by.id(this.testID.threadOverview));
};
getThreadOverviewRepliesCount = () => {
return element(by.id(this.testID.threadOverviewRepliesCount));
};
getThreadOverviewNoReplies = () => {
return element(by.id(this.testID.threadOverviewNoReplies));
};
getThreadOverviewSaveButton = () => {
return element(by.id(this.testID.threadOverviewSaveButton));
};
getThreadOverviewUnsaveButton = () => {
return element(by.id(this.testID.threadOverviewUnsaveButton));
};
getThreadOverviewPostOptionsButton = () => {
return element(by.id(this.testID.threadOverviewPostOptionsButton));
};
getPost = (postId: string, postMessage: string, postProfileOptions = {}) => {
const {
postItem,
postItemBlockQuote,
postItemEditedIndicator,
postItemEmoji,
postItemFooterFollowButton,
postItemFooterFollowingButton,
postItemFooterReplyCount,
postItemHeaderCommentedOn,
postItemHeaderDateTime,
postItemHeaderDisplayName,
@ -50,6 +83,7 @@ class PostList {
postItemTable,
postItemTableExpandButton,
postItemThematicBreak,
postItemUnreadDotBadge,
} = Post.getPost(this.testID.postListPostItem, postId, postMessage, postProfileOptions);
return {
@ -57,6 +91,9 @@ class PostList {
postListPostItemBlockQuote: postItemBlockQuote,
postListPostItemEditedIndicator: postItemEditedIndicator,
postListPostItemEmoji: postItemEmoji,
postListPostItemFooterFollowButton: postItemFooterFollowButton,
postListPostItemFooterFollowingButton: postItemFooterFollowingButton,
postListPostItemFooterReplyCount: postItemFooterReplyCount,
postListPostItemHeaderCommentedOn: postItemHeaderCommentedOn,
postListPostItemHeaderDateTime: postItemHeaderDateTime,
postListPostItemHeaderDisplayName: postItemHeaderDisplayName,
@ -74,6 +111,7 @@ class PostList {
postListPostItemTable: postItemTable,
postListPostItemTableExpandButton: postItemTableExpandButton,
postListPostItemThematicBreak: postItemThematicBreak,
postListPostItemUnreadDotBadge: postItemUnreadDotBadge,
};
};

View file

@ -2,29 +2,90 @@
// See LICENSE.txt for license information.
import {NavigationHeader} from '@support/ui/component';
import {timeouts} from '@support/utils';
import {
ChannelListScreen,
ThreadOptionsScreen,
} from '@support/ui/screen';
import {timeouts, wait} from '@support/utils';
import {expect} from 'detox';
class GlobalThreadsScreen {
testID = {
globalThreadsScreen: 'global_threads.screen',
headerAllThreadsButton: 'global_threads.threads_list.header.all_threads.button',
headerUnreadThreadsButton: 'global_threads.threads_list.header.unread_threads.button',
headerUnreadDotBadge: 'global_threads.threads_list.header.unread_dot.badge',
headerMarkAllAsReadButton: 'global_threads.threads_list.header.mark_all_as_read.button',
emptyThreadsList: 'global_threads.threads_list.empty_state',
flatThreadsList: 'global_threads.threads_list.flat_list',
};
globalThreadsScreen = element(by.id(this.testID.globalThreadsScreen));
headerAllThreadsButton = element(by.id(this.testID.headerAllThreadsButton));
headerUnreadThreadsButton = element(by.id(this.testID.headerUnreadThreadsButton));
headerUnreadDotBadge = element(by.id(this.testID.headerUnreadDotBadge));
headerMarkAllAsReadButton = element(by.id(this.testID.headerMarkAllAsReadButton));
emptyThreadsList = element(by.id(this.testID.emptyThreadsList));
flatThreadsList = element(by.id(this.testID.flatThreadsList));
// convenience props
backButton = NavigationHeader.backButton;
getThreadItem = (threadId: string) => {
return element(by.id(`global_threads.threads_list.thread_item.${threadId}`));
};
getThreadItemUnreadMentionsBadge = (threadId: string) => {
return element(by.id(`global_threads.threads_list.thread_item.${threadId}.unread_mentions.badge`));
};
getThreadItemUnreadDotBadge = (threadId: string) => {
return element(by.id(`global_threads.threads_list.thread_item.${threadId}.unread_dot.badge`));
};
getThreadItemThreadStarterUserDisplayName = (threadId: string) => {
return element(by.id(`global_threads.threads_list.thread_item.${threadId}.thread_starter.user_display_name`));
};
getThreadItemThreadStarterChannelDisplayName = (threadId: string) => {
return element(by.id(`global_threads.threads_list.thread_item.${threadId}.thread_starter.channel_display_name`));
};
getThreadItemFooterUnreadReplies = (threadId: string) => {
return element(by.id(`global_threads.threads_list.thread_item.${threadId}.footer.unread_replies`));
};
getThreadItemFooterReplyCount = (threadId: string) => {
return element(by.id(`global_threads.threads_list.thread_item.${threadId}.footer.reply_count`));
};
toBeVisible = async () => {
await waitFor(this.globalThreadsScreen).toExist().withTimeout(timeouts.TEN_SEC);
return this.globalThreadsScreen;
};
open = async () => {
// # Open global threads screen
await ChannelListScreen.threadsButton.tap();
return this.toBeVisible();
};
back = async () => {
await this.backButton.tap();
await expect(this.globalThreadsScreen).not.toBeVisible();
};
openThreadOptionsFor = async (postId: string) => {
const threadItem = this.getThreadItem(postId);
await expect(threadItem).toBeVisible();
// # Open thread options
await threadItem.longPress();
await ThreadOptionsScreen.toBeVisible();
await wait(timeouts.TWO_SEC);
};
}
const globalThreadsScreen = new GlobalThreadsScreen();

View file

@ -13,10 +13,12 @@ import EditServerScreen from './edit_server';
import GlobalThreadsScreen from './global_threads';
import HomeScreen from './home';
import LoginScreen from './login';
import PermalinkScreen from './permalink';
import PostOptionsScreen from './post_options';
import ServerScreen from './server';
import ServerListScreen from './server_list';
import ThreadScreen from './thread';
import ThreadOptionsScreen from './thread_options';
export {
AccountScreen,
@ -31,8 +33,10 @@ export {
GlobalThreadsScreen,
HomeScreen,
LoginScreen,
PermalinkScreen,
PostOptionsScreen,
ServerScreen,
ServerListScreen,
ThreadScreen,
ThreadOptionsScreen,
};

View file

@ -0,0 +1,54 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {PostList} from '@support/ui/component';
import {expect} from 'detox';
class PermalinkScreen {
testID = {
permalinkScreenPrefix: 'permalink.',
permalinkScreen: 'permalink.screen',
permalinkPostList: 'permalink.post_list',
jumpToRecentMessagesButton: 'permalink.jump_to_recent_messages.button',
};
permalinkScreen = element(by.id(this.testID.permalinkScreen));
permalinkPostList = element(by.id(this.testID.permalinkPostList));
jumpToRecentMessagesButton = element(by.id(this.testID.jumpToRecentMessagesButton));
postList = new PostList(this.testID.permalinkScreenPrefix);
getPostListPostItem = (postId: string, text: string, postProfileOptions = {}) => {
return this.postList.getPost(postId, text, postProfileOptions);
};
getPostMessageAtIndex = (index: number) => {
return this.postList.getPostMessageAtIndex(index);
};
toBeVisible = async () => {
await expect(this.permalinkScreen).toBeVisible();
return this.permalinkScreen;
};
jumpToRecentMessages = async () => {
// # Jump to recent messages
await this.jumpToRecentMessagesButton.tap();
await expect(this.permalinkScreen).not.toBeVisible();
};
hasPostMessage = async (postId: string, postMessage: string) => {
const {postListPostItem} = this.getPostListPostItem(postId, postMessage);
await expect(postListPostItem).toBeVisible();
};
hasPostMessageAtIndex = async (index: number, postMessage: string) => {
await expect(
this.getPostMessageAtIndex(index),
).toHaveText(postMessage);
};
}
const permalinkScreen = new PermalinkScreen();
export default permalinkScreen;

View file

@ -11,33 +11,35 @@ class PostOptionsScreen {
postOptionsScreen: 'post_options.screen',
postOptionsBackdrop: 'post_options.backdrop',
pickReaction: 'post_options.reaction_bar.pick_reaction',
replyPostOption: 'post_options.reply.post.option',
copyLinkOption: 'post_options.copy.permalink.option',
saveChannelOption: 'post_options.save.channel.option',
unsaveChannelOption: 'post_options.unsave.channel.option',
copyTextOption: 'post_options.copy.text.option',
pinChannelOption: 'post_options.pin.channel.option',
unpinChannelOption: 'post_options.unpin.channel.option',
editPostOption: 'post_options.edit.post.option',
deletePostOption: 'post_options.delete.post.option',
followThreadOption: 'post_options.follow.thread.option',
markUnreadOption: 'post_options.mark.unread.option',
replyPostOption: 'post_options.reply_post.option',
followThreadOption: 'post_options.follow_thread.option',
followingThreadOption: 'post_options.following_thread.option',
markAsUnreadOption: 'post_options.mark_as_unread.option',
copyLinkOption: 'post_options.copy_permalink.option',
savePostOption: 'post_options.save_post.option',
unsavePostOption: 'post_options.unsave_post.option',
copyTextOption: 'post_options.copy_text.option',
pinPostOption: 'post_options.pin_post.option',
unpinPostOption: 'post_options.unpin_post.option',
editPostOption: 'post_options.edit_post.option',
deletePostOption: 'post_options.delete_post.option',
};
postOptionsScreen = element(by.id(this.testID.postOptionsScreen));
postOptionsBackdrop = element(by.id(this.testID.postOptionsBackdrop));
pickReaction = element(by.id(this.testID.pickReaction));
replyPostOption = element(by.id(this.testID.replyPostOption));
followThreadOption = element(by.id(this.testID.followThreadOption));
followingThreadOption = element(by.id(this.testID.followingThreadOption));
markAsUnreadOption = element(by.id(this.testID.markAsUnreadOption));
copyLinkOption = element(by.id(this.testID.copyLinkOption));
saveChannelOption = element(by.id(this.testID.saveChannelOption));
unsaveChannelOption = element(by.id(this.testID.unsaveChannelOption));
savePostOption = element(by.id(this.testID.savePostOption));
unsavePostOption = element(by.id(this.testID.unsavePostOption));
copyTextOption = element(by.id(this.testID.copyTextOption));
pinChannelOption = element(by.id(this.testID.pinChannelOption));
unpinChannelOption = element(by.id(this.testID.unpinChannelOption));
pinPostOption = element(by.id(this.testID.pinPostOption));
unpinPostOption = element(by.id(this.testID.unpinPostOption));
editPostOption = element(by.id(this.testID.editPostOption));
deletePostOption = element(by.id(this.testID.deletePostOption));
followThreadOption = element(by.id(this.testID.followThreadOption));
markUnreadOption = element(by.id(this.testID.markUnreadOption));
getReactionEmoji = (emojiName: string) => {
return element(by.id(`${this.testID.reactionEmojiPrefix}${emojiName}`));

View file

@ -19,11 +19,15 @@ class ThreadScreen {
threadScreenPrefix: 'thread.',
threadScreen: 'thread.screen',
backButton: 'screen.back.button',
followButton: 'thread.follow_thread.button',
followingButton: 'thread.following_thread.button',
flatPostList: 'thread.post_list.flat_list',
};
threadScreen = element(by.id(this.testID.threadScreen));
backButton = element(by.id(this.testID.backButton));
followButton = element(by.id(this.testID.followButton));
followingButton = element(by.id(this.testID.followingButton));
flatPostList = element(by.id(this.testID.flatPostList));
// convenience props
@ -46,6 +50,30 @@ class ThreadScreen {
postList = new PostList(this.testID.threadScreenPrefix);
getThreadOverview = () => {
return this.postList.getThreadOverview();
};
getThreadOverviewRepliesCount = () => {
return this.postList.getThreadOverviewRepliesCount();
};
getThreadOverviewNoReplies = () => {
return this.postList.getThreadOverviewNoReplies();
};
getThreadOverviewSaveButton = () => {
return this.postList.getThreadOverviewSaveButton();
};
getThreadOverviewUnsaveButton = () => {
return this.postList.getThreadOverviewUnsaveButton();
};
getThreadOverviewPostOptionsButton = () => {
return this.postList.getThreadOverviewPostOptionsButton();
};
getPostListPostItem = (postId: string, text: string, postProfileOptions: any = {}) => {
return this.postList.getPost(postId, text, postProfileOptions);
};

View file

@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {timeouts} from '@support/utils';
import {expect} from 'detox';
class ThreadOptionsScreen {
testID = {
threadOptionsScreen: 'thread_options.screen',
threadOptionsBackdrop: 'thread_options.backdrop',
replyThreadOption: 'post_options.reply_post.option',
followThreadOption: 'post_options.follow_thread.option',
followingThreadOption: 'post_options.following_thread.option',
openInChannelOption: 'thread_options.open_in_channel.option',
markAsReadOption: 'thread_options.mark_as_unread.option',
markAsUnreadOption: 'thread_options.mark_as_unread.option',
saveThreadOption: 'post_options.save_post.option',
unsaveThreadOption: 'post_options.unsave_post.option',
copyLinkOption: 'post_options.copy_permalink.option',
};
threadOptionsScreen = element(by.id(this.testID.threadOptionsScreen));
threadOptionsBackdrop = element(by.id(this.testID.threadOptionsBackdrop));
replyThreadOption = element(by.id(this.testID.replyThreadOption));
followThreadOption = element(by.id(this.testID.followThreadOption));
followingThreadOption = element(by.id(this.testID.followingThreadOption));
openInChannelOption = element(by.id(this.testID.openInChannelOption));
markAsReadOption = element(by.id(this.testID.markAsUnreadOption));
markAsUnreadOption = element(by.id(this.testID.markAsUnreadOption));
saveThreadOption = element(by.id(this.testID.saveThreadOption));
unsaveThreadOption = element(by.id(this.testID.unsaveThreadOption));
copyLinkOption = element(by.id(this.testID.copyLinkOption));
toBeVisible = async () => {
await waitFor(this.threadOptionsScreen).toExist().withTimeout(timeouts.TEN_SEC);
return threadOptionsScreen;
};
close = async () => {
await this.threadOptionsBackdrop.tap({x: 5, y: 10});
await expect(this.threadOptionsScreen).not.toBeVisible();
};
}
const threadOptionsScreen = new ThreadOptionsScreen();
export default threadOptionsScreen;

View file

@ -0,0 +1,161 @@
// 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 {
ChannelScreen,
ChannelListScreen,
GlobalThreadsScreen,
HomeScreen,
LoginScreen,
PermalinkScreen,
ServerScreen,
ThreadOptionsScreen,
ThreadScreen,
} from '@support/ui/screen';
import {getRandomId} from '@support/utils';
import {expect} from 'detox';
describe('Smoke Test - Threads', () => {
const serverOneDisplayName = 'Server 1';
const channelsCategory = 'channels';
const savedText = 'Saved';
let testChannel: any;
beforeAll(async () => {
System.apiUpdateConfig(siteOneUrl, {
ServiceSettings: {
CollapsedThreads: 'default_on',
},
});
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-T4811_1 - should be able to create a thread, follow/unfollow a thread, mark a thread as read/unread, and reply to thread', async () => {
// # Create a thread and unfollow thread via thread navigation
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
await ThreadScreen.postMessage(`${parentMessage} reply`);
await ThreadScreen.followingButton.tap();
// * Verify thread is not followed by user via thread navigation
await expect(ThreadScreen.followButton).toBeVisible();
// # Follow thread via thread navigation
await ThreadScreen.followButton.tap();
// * Verify thread is followed by user via thread navigation
await expect(ThreadScreen.followingButton).toBeVisible();
// # Go back to channel list screen, then go to global threads screen, tap on all your threads button, open thread options for thread, tap on mark as unread option, and tap on unread threads button
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.headerAllThreadsButton.tap();
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.markAsUnreadOption.tap();
await GlobalThreadsScreen.headerUnreadThreadsButton.tap();
// * Verify thread is displayed in unread threads section
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
// # Open thread options for thread and tap on mark as read option
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.markAsReadOption.tap();
// * Verify thread is not displayed anymore in unread threads section
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).not.toBeVisible();
// # Tap on all your threads button, tap on the thread, and add new reply to thread
await GlobalThreadsScreen.headerAllThreadsButton.tap();
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
const newReplyMessage = `${parentMessage} new reply`;
await ThreadScreen.postMessage(newReplyMessage);
// * Verify new reply is posted
const {post: newReplyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem} = ThreadScreen.getPostListPostItem(newReplyPost.id, newReplyMessage);
await expect(postListPostItem).toBeVisible();
// # Go back to channel list screen
await ThreadScreen.back();
await GlobalThreadsScreen.back();
});
it('MM-T4811_2 - should be able to save/unsave a thread and open a thread in channel', async () => {
// # Create a thread, go back to channel list screen, then go to global threads screen, open thread options for thread, tap on save option, and tap on thread
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.saveThreadOption.tap();
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
// * Verify saved text is displayed on the post pre-header
const {postListPostItemPreHeaderText} = ThreadScreen.getPostListPostItem(parentPost.id, parentMessage);
await expect(postListPostItemPreHeaderText).toHaveText(savedText);
// # Go back to global threads screen, open thread options for thread, tap on save option, and tap on thread
await ThreadScreen.back();
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.unsaveThreadOption.tap();
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
// * Verify saved text is not displayed on the post pre-header
await expect(postListPostItemPreHeaderText).not.toBeVisible();
// # Go back to global threads screen, open thread options for thread, tap on open in channel option, and jump to recent messages
await ThreadScreen.back();
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.openInChannelOption.tap();
await PermalinkScreen.jumpToRecentMessages();
// * Verify on channel screen and thread is displayed
await ChannelScreen.toBeVisible();
const {postListPostItem} = ThreadScreen.getPostListPostItem(parentPost.id, parentMessage);
await expect(postListPostItem).toBeVisible();
// # Go back to channel list screen
await ChannelScreen.back();
});
});

View file

@ -0,0 +1,201 @@
// 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,
GlobalThreadsScreen,
HomeScreen,
LoginScreen,
PostOptionsScreen,
ServerScreen,
ThreadOptionsScreen,
ThreadScreen,
} from '@support/ui/screen';
import {getRandomId} from '@support/utils';
import {expect} from 'detox';
describe('Threads - Follow and Unfollow Thread', () => {
const serverOneDisplayName = 'Server 1';
const channelsCategory = 'channels';
let testChannel: any;
beforeAll(async () => {
System.apiUpdateConfig(siteOneUrl, {
ServiceSettings: {
CollapsedThreads: 'default_on',
},
});
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-T4806_1 - should be able to follow/unfollow a thread via thread navigation', async () => {
// # Create a thread
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
await ThreadScreen.postMessage(`${parentMessage} reply`);
// * Verify thread is followed by user by default via thread navigation
await expect(ThreadScreen.followingButton).toBeVisible();
// # Unfollow thread via thread navigation
await ThreadScreen.followingButton.tap();
// * Verify thread is not followed by user via thread navigation
await expect(ThreadScreen.followButton).toBeVisible();
// # Follow thread via thread navigation
await ThreadScreen.followButton.tap();
// * Verify thread is followed by user via thread navigation
await expect(ThreadScreen.followingButton).toBeVisible();
// # Go back to channel list screen
await ThreadScreen.back();
await ChannelScreen.back();
});
it('MM-T4806_2 - should be able to follow/unfollow a thread via post footer', async () => {
// # Create a thread and go back to channel screen
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
await ThreadScreen.postMessage(`${parentMessage} reply`);
await ThreadScreen.back();
// * Verify thread is followed by user by default via post footer
const {postListPostItemFooterFollowButton, postListPostItemFooterFollowingButton} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
await expect(postListPostItemFooterFollowingButton).toBeVisible();
// # Unfollow thread via post footer
await postListPostItemFooterFollowingButton.tap();
// * Verify thread is not followed by user via post footer
await expect(postListPostItemFooterFollowButton).toBeVisible();
// # Follow thread via post footer
await postListPostItemFooterFollowButton.tap();
// * Verify thread is followed by user via post footer
await expect(postListPostItemFooterFollowingButton).toBeVisible();
// # Go back to channel list screen
await ThreadScreen.back();
await ChannelScreen.back();
});
it('MM-T4806_3 - should be able to follow/unfollow a thread via post options', async () => {
// # Create a thread, go back to channel screen, and open post options for thread
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
await ThreadScreen.postMessage(`${parentMessage} reply`);
await ThreadScreen.back();
await ChannelScreen.openPostOptionsFor(parentPost.id, parentMessage);
// * Verify thread is followed by user by default via post options
await expect(PostOptionsScreen.followingThreadOption).toBeVisible();
// # Unfollow thread via post options
await PostOptionsScreen.followingThreadOption.tap();
// * Verify thread is not followed by user via post footer
const {postListPostItemFooterFollowButton, postListPostItemFooterFollowingButton} = ChannelScreen.getPostListPostItem(parentPost.id, parentMessage);
await expect(postListPostItemFooterFollowButton).toBeVisible();
// # Open post options for thread
await ChannelScreen.openPostOptionsFor(parentPost.id, parentMessage);
// * Verify thread is not followed by user via post options
await expect(PostOptionsScreen.followThreadOption).toBeVisible();
// # Open post options for thread and follow thread via post options
await ChannelScreen.openPostOptionsFor(parentPost.id, parentMessage);
await PostOptionsScreen.followThreadOption.tap();
// * Verify thread is followed by user via post footer
await expect(postListPostItemFooterFollowingButton).toBeVisible();
// # Open post options for thread
await ChannelScreen.openPostOptionsFor(parentPost.id, parentMessage);
// * Verify thread is followed by user via post options
await expect(PostOptionsScreen.followingThreadOption).toBeVisible();
// # Go back to channel list screen
await PostOptionsScreen.close();
await ChannelScreen.back();
});
it('MM-T4806_4 - should be able to unfollow a thread via thread options', async () => {
// # Create a thread, go back to channel list screen, then go to global threads screen, and tap on all your threads button
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.headerAllThreadsButton.tap();
// * Verify thread is displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
// # Open thread options for thread
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
// * Verify thread is followed by user via thread options
await expect(ThreadOptionsScreen.followingThreadOption).toBeVisible();
// # Unfollow thread via thread options
await ThreadOptionsScreen.followingThreadOption.tap();
// * Verify thread is not displayed anymore in all your threads section
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
// # Go back to channel list screen
await GlobalThreadsScreen.back();
await ChannelScreen.back();
});
});

View file

@ -0,0 +1,211 @@
// 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,
GlobalThreadsScreen,
HomeScreen,
LoginScreen,
ServerScreen,
ThreadScreen,
} from '@support/ui/screen';
import {getRandomId} from '@support/utils';
import {expect} from 'detox';
describe('Threads - Global Threads', () => {
const serverOneDisplayName = 'Server 1';
const channelsCategory = 'channels';
let testChannel: any;
let testUser: any;
beforeAll(async () => {
System.apiUpdateConfig(siteOneUrl, {
ServiceSettings: {
CollapsedThreads: 'default_on',
},
});
const {channel, user} = await Setup.apiInit(siteOneUrl);
testChannel = channel;
testUser = user;
// # 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-T4805_1 - should match elements on global threads screen', async () => {
// # Open global threads screen
await ChannelListScreen.open();
// * Verify basic elements on global threads screen
await expect(GlobalThreadsScreen.headerAllThreadsButton).toBeVisible();
await expect(GlobalThreadsScreen.headerUnreadThreadsButton).toBeVisible();
await expect(GlobalThreadsScreen.headerMarkAllAsReadButton).toBeVisible();
await expect(GlobalThreadsScreen.emptyThreadsList).toBeVisible();
// # Go back to channel list screen
await GlobalThreadsScreen.back();
});
it('MM-T4805_2 - should be able to go to a thread a user started and followed', async () => {
// # Create a thread started by the current user
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage);
// * Verify thread is followed by the current user by default
await expect(ThreadScreen.followingButton).toBeVisible();
// # Go back to channel list screen, then go to global threads screen, and tap on all your threads button
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.headerAllThreadsButton.tap();
// * Verify the thread started by the current user is displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemThreadStarterUserDisplayName(parentPost.id)).toHaveText(testUser.username);
await expect(GlobalThreadsScreen.getThreadItemThreadStarterChannelDisplayName(parentPost.id)).toHaveText(testChannel.display_name.toUpperCase());
await expect(GlobalThreadsScreen.getThreadItemFooterUnreadReplies(parentPost.id)).toHaveText('1 new reply');
// # Tap on the thread
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
// * Verify on thread screen
await ThreadScreen.toBeVisible();
await expect(replyPostListPostItem).toBeVisible();
// # Go back to channel list screen
await ThreadScreen.back();
await GlobalThreadsScreen.back();
});
it('MM-T4805_3 - should not display a thread a user started but not followed', async () => {
// # Create a thread started by the current user and current user unfollows the thread
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
await ThreadScreen.postMessage(`${parentMessage} reply`);
await ThreadScreen.followingButton.tap();
// * Verify thread is not followed by the current user
await expect(ThreadScreen.followButton).toBeVisible();
// # Go back to channel list screen, then go to global threads screen, and tap on all your threads button
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.headerAllThreadsButton.tap();
// * Verify the thread started by the current user is not displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).not.toBeVisible();
// # Go back to channel list screen
await GlobalThreadsScreen.back();
});
it('MM-T4805_4 - should be able to go to a thread a user replied to and followed', async () => {
// # Create a thread started by another user which the current user replied to
const parentMessage = `Message ${getRandomId()}`;
const {post: parentPost} = await Post.apiCreatePost(siteOneUrl, {
channelId: testChannel.id,
message: parentMessage,
});
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage);
// * Verify thread is followed by the current user by default after replying to post
await expect(ThreadScreen.followingButton).toBeVisible();
// # Go back to channel list screen, then go to global threads screen, and tap on all your threads button
await ThreadScreen.back();
await ChannelScreen.back();
await device.reloadReactNative();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.headerAllThreadsButton.tap();
// * Verify the thread replied to by the current user is displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemThreadStarterUserDisplayName(parentPost.id)).toHaveText('sysadmin');
await expect(GlobalThreadsScreen.getThreadItemThreadStarterChannelDisplayName(parentPost.id)).toHaveText(testChannel.display_name.toUpperCase());
await expect(GlobalThreadsScreen.getThreadItemFooterUnreadReplies(parentPost.id)).toHaveText('1 new reply');
// # Tap on the thread
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
// * Verify on thread screen
await ThreadScreen.toBeVisible();
await expect(replyPostListPostItem).toBeVisible();
// # Go back to channel list screen
await ThreadScreen.back();
await GlobalThreadsScreen.back();
});
it('MM-T4805_5 - should not display a thread a user replied to but not followed', async () => {
// # Create a thread started by another user which the current user replied to and current user unfollows the thread
const parentMessage = `Message ${getRandomId()}`;
const {post: parentPost} = await Post.apiCreatePost(siteOneUrl, {
channelId: testChannel.id,
message: parentMessage,
});
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
await ThreadScreen.postMessage(`${parentMessage} reply`);
await ThreadScreen.followingButton.tap();
// * Verify thread is not followed by the current user
await expect(ThreadScreen.followButton).toBeVisible();
// # Go back to channel list screen, then go to global threads screen, and tap on all your threads button
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.headerAllThreadsButton.tap();
// * Verify the thread replied to by the current user is not displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).not.toBeVisible();
// # Go back to channel list screen
await GlobalThreadsScreen.back();
});
});

View file

@ -0,0 +1,183 @@
// 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 {Alert} from '@support/ui/component';
import {
ChannelListScreen,
ChannelScreen,
GlobalThreadsScreen,
HomeScreen,
LoginScreen,
ServerScreen,
ThreadOptionsScreen,
ThreadScreen,
} from '@support/ui/screen';
import {getRandomId} from '@support/utils';
import {expect} from 'detox';
describe('Threads - Mark Thread as Read and Unread', () => {
const serverOneDisplayName = 'Server 1';
const channelsCategory = 'channels';
let testChannel: any;
beforeAll(async () => {
System.apiUpdateConfig(siteOneUrl, {
ServiceSettings: {
CollapsedThreads: 'default_on',
},
});
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-T4807_1 - should be able to mark a thread as read by opening thread', async () => {
// # Create a thread, go back to channel list screen, then go to global threads screen, and tap on unread threads button
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.headerUnreadThreadsButton.tap();
// * Verify thread is displayed as unread in unread threads section with unread dot badge and footer unread replies
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemUnreadDotBadge(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemFooterUnreadReplies(parentPost.id)).toHaveText('1 new reply');
// # Tap on the thread and go back to global threads screen
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
await ThreadScreen.back();
// * Verify thread is not displayed anymore in unread threads section
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).not.toBeVisible();
// # Tap on all your threads button
await GlobalThreadsScreen.headerAllThreadsButton.tap();
// * Verify thread is displayed as read in all your threads section without unread dot badge and with footer reply count
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemUnreadDotBadge(parentPost.id)).not.toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemFooterReplyCount(parentPost.id)).toHaveText('1 reply');
// # Go back to channel list screen
await GlobalThreadsScreen.back();
});
it('MM-T4807_2 - should be able to mark a thread as read/unread via thread options', async () => {
// # Create a thread, go back to channel list screen, then go to global threads screen, and tap on unread threads button
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.headerUnreadThreadsButton.tap();
// * Verify thread is displayed as unread in unread threads section with unread dot badge and footer unread replies
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemUnreadDotBadge(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemFooterUnreadReplies(parentPost.id)).toHaveText('1 new reply');
// # Open thread options for thread and tap on mark as read option
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.markAsReadOption.tap();
// * Verify thread is not displayed anymore in unread threads section
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).not.toBeVisible();
// # Tap on all your threads button
await GlobalThreadsScreen.headerAllThreadsButton.tap();
// * Verify thread is displayed as read in all your threads section without unread dot badge and with footer reply count
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemUnreadDotBadge(parentPost.id)).not.toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemFooterReplyCount(parentPost.id)).toHaveText('1 reply');
// # Open thread options for thread and tap on mark as unread option
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.markAsUnreadOption.tap();
// * Verify thread is displayed as unread in all your threads section with unread dot badge and footer unread replies
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemUnreadDotBadge(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemFooterUnreadReplies(parentPost.id)).toHaveText('1 new reply');
// # Go back to channel list screen
await GlobalThreadsScreen.back();
});
it('MM-T4807_3 - should be able to mark all threads as read', async () => {
// # Create a thread, go back to channel list screen, then go to global threads screen, and tap on unread threads button
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
await GlobalThreadsScreen.headerUnreadThreadsButton.tap();
// * Verify thread is displayed as unread in unread threads section with unread dot badge and footer unread replies
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemUnreadDotBadge(parentPost.id)).toBeVisible();
await expect(GlobalThreadsScreen.getThreadItemFooterUnreadReplies(parentPost.id)).toHaveText('1 new reply');
// # Tap on mark all as read button
await GlobalThreadsScreen.headerMarkAllAsReadButton.tap();
// * Verify mark all as read alert is displayed
await expect(Alert.markAllAsReadTitle).toBeVisible();
// # Tap on mark read button
await Alert.markReadButton.tap();
// * Verify thread is not displayed anymore and unread threads section is empty
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).not.toBeVisible();
await expect(GlobalThreadsScreen.emptyThreadsList).toBeVisible();
// # Go back to channel list screen
await GlobalThreadsScreen.back();
});
});

View file

@ -0,0 +1,137 @@
// 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,
GlobalThreadsScreen,
HomeScreen,
LoginScreen,
PermalinkScreen,
ServerScreen,
ThreadOptionsScreen,
ThreadScreen,
} from '@support/ui/screen';
import {getRandomId, timeouts, wait} from '@support/utils';
import {expect} from 'detox';
describe('Threads - Open Thread in Channel', () => {
const serverOneDisplayName = 'Server 1';
const channelsCategory = 'channels';
let testChannel: any;
beforeAll(async () => {
System.apiUpdateConfig(siteOneUrl, {
ServiceSettings: {
CollapsedThreads: 'default_on',
},
});
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-T4810_1 - should be able to open a thread in channel via thread options', async () => {
// # Create a thread, go back to channel list screen, and then go to global threads screen
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
// * Verify thread is displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
// # Open thread options for thread and tap open in channel option
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.openInChannelOption.tap();
// * Verify on permalink screen
await PermalinkScreen.toBeVisible();
// # Jump to recent messages
await PermalinkScreen.jumpToRecentMessages();
// * Verify on channel screen and thread is displayed
await ChannelScreen.toBeVisible();
const {postListPostItem} = ThreadScreen.getPostListPostItem(parentPost.id, parentMessage);
await expect(postListPostItem).toBeVisible();
// # Go back to channel list screen
await ChannelScreen.back();
});
it('MM-T4810_1 - should be able to open a thread in channel by permalink', async () => {
// # Create a thread, go back to channel list screen, and then go to global threads screen
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
// * Verify thread is displayed
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 permalinkMessage = `[${permalinkLabel}](/_redirect/pl/${parentPost.id})`;
await GlobalThreadsScreen.back();
await ChannelScreen.open(channelsCategory, 'off-topic');
await ChannelScreen.postMessage(permalinkMessage);
await element(by.text(permalinkLabel)).tap({x: 5, y: 10});
await wait(timeouts.ONE_SEC);
// * Verify on permalink screen
await PermalinkScreen.toBeVisible();
// # Jump to recent messages
await PermalinkScreen.jumpToRecentMessages();
// * Verify on channel screen and thread is displayed
await ChannelScreen.toBeVisible();
const {postListPostItem} = ThreadScreen.getPostListPostItem(parentPost.id, parentMessage);
await expect(postListPostItem).toBeVisible();
// # Go back to channel list screen
await ChannelScreen.back();
});
});

View file

@ -0,0 +1,134 @@
// 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,
GlobalThreadsScreen,
HomeScreen,
LoginScreen,
ServerScreen,
ThreadOptionsScreen,
ThreadScreen,
} from '@support/ui/screen';
import {getRandomId} from '@support/utils';
import {expect} from 'detox';
describe('Threads - Reply to Thread', () => {
const serverOneDisplayName = 'Server 1';
const channelsCategory = 'channels';
let testChannel: any;
beforeAll(async () => {
System.apiUpdateConfig(siteOneUrl, {
ServiceSettings: {
CollapsedThreads: 'default_on',
},
});
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-T4809_1 - should be able to reply to a thread via thread options', async () => {
// # Create a thread, go back to channel list screen, and then go to global threads screen
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
// * Verify thread is displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
// # Open thread options for thread and tap on reply option
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.replyThreadOption.tap();
// * Verify on thread screen
await ThreadScreen.toBeVisible();
// # Add new reply to thread
const newReplyMessage = `${parentMessage} new reply`;
await ThreadScreen.postMessage(newReplyMessage);
// * Verify new reply is posted
const {post: newReplyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem} = ThreadScreen.getPostListPostItem(newReplyPost.id, newReplyMessage);
await expect(postListPostItem).toBeVisible();
// # Go back to channel list screen
await ThreadScreen.back();
await GlobalThreadsScreen.back();
});
it('MM-T4809_2 - should be able to reply to a thread by opening thread', async () => {
// # Create a thread, go back to channel list screen, and then go to global threads screen
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
// * Verify thread is displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
// # Tap on the thread
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
// * Verify on thread screen
await ThreadScreen.toBeVisible();
// # Add new reply to thread
const newReplyMessage = `${parentMessage} new reply`;
await ThreadScreen.postMessage(newReplyMessage);
// * Verify new reply is posted
const {post: newReplyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem} = ThreadScreen.getPostListPostItem(newReplyPost.id, newReplyMessage);
await expect(postListPostItem).toBeVisible();
// # Go back to channel list screen
await ThreadScreen.back();
await GlobalThreadsScreen.back();
});
});

View file

@ -0,0 +1,136 @@
// 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,
GlobalThreadsScreen,
HomeScreen,
LoginScreen,
ServerScreen,
ThreadOptionsScreen,
ThreadScreen,
} from '@support/ui/screen';
import {getRandomId} from '@support/utils';
import {expect} from 'detox';
describe('Threads - Save and Unsave Thread', () => {
const serverOneDisplayName = 'Server 1';
const channelsCategory = 'channels';
const savedText = 'Saved';
let testChannel: any;
beforeAll(async () => {
System.apiUpdateConfig(siteOneUrl, {
ServiceSettings: {
CollapsedThreads: 'default_on',
},
});
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-T4808_1 - should be able to save/unsave a thread via thread options', async () => {
// # Create a thread, go back to channel list screen, and then go to global threads screen
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
// * Verify thread is displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
// # Open thread options for thread, tap on save option, and tap on thread
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.saveThreadOption.tap();
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
// * Verify saved text is displayed on the post pre-header
const {postListPostItemPreHeaderText} = ThreadScreen.getPostListPostItem(parentPost.id, parentMessage);
await expect(postListPostItemPreHeaderText).toHaveText(savedText);
// # Go back to global threads screen, open thread options for thread, tap on save option, and tap on thread
await ThreadScreen.back();
await GlobalThreadsScreen.openThreadOptionsFor(parentPost.id);
await ThreadOptionsScreen.unsaveThreadOption.tap();
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
// * Verify saved text is not displayed on the post pre-header
await expect(postListPostItemPreHeaderText).not.toBeVisible();
// # Go back to channel list screen
await ThreadScreen.back();
await ChannelScreen.back();
});
it('MM-T4808_2 - should be able to save/unsave a thread via thread overview', async () => {
// # Create a thread, go back to channel list screen, and then go to global threads screen
const parentMessage = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(parentMessage);
const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await ChannelScreen.openReplyThreadFor(parentPost.id, parentMessage);
const replyMessage = `${parentMessage} reply`;
await ThreadScreen.postMessage(replyMessage);
await ThreadScreen.back();
await ChannelScreen.back();
await GlobalThreadsScreen.open();
// * Verify thread is displayed
await expect(GlobalThreadsScreen.getThreadItem(parentPost.id)).toBeVisible();
// # Tap on thread and tap on thread overview save button
await GlobalThreadsScreen.getThreadItem(parentPost.id).tap();
await ThreadScreen.getThreadOverviewSaveButton().tap();
// * Verify saved text is displayed on the post pre-header
const {postListPostItemPreHeaderText} = ThreadScreen.getPostListPostItem(parentPost.id, parentMessage);
await expect(postListPostItemPreHeaderText).toHaveText(savedText);
// # Tap on thread overview unsave button
await ThreadScreen.getThreadOverviewUnsaveButton().tap();
// * Verify saved text is not displayed on the post pre-header
await expect(postListPostItemPreHeaderText).not.toBeVisible();
// # Go back to channel list screen
await ThreadScreen.back();
await ChannelScreen.back();
});
});