diff --git a/app/components/post_header/__snapshots__/post_header.test.js.snap b/app/components/post_header/__snapshots__/post_header.test.js.snap
index d12909a61..9a22034af 100644
--- a/app/components/post_header/__snapshots__/post_header.test.js.snap
+++ b/app/components/post_header/__snapshots__/post_header.test.js.snap
@@ -556,6 +556,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
"marginTop": 2,
}
}
+ testID="post_header.reply.count"
>
0
@@ -709,6 +710,7 @@ exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyB
"marginTop": 2,
}
}
+ testID="post_header.reply.count"
>
0
@@ -913,6 +915,7 @@ exports[`PostHeader should match snapshot when post should display reply button
"marginTop": 2,
}
}
+ testID="post_header.reply.count"
>
0
diff --git a/app/components/post_header/post_header.js b/app/components/post_header/post_header.js
index 404905ede..e09abeb62 100644
--- a/app/components/post_header/post_header.js
+++ b/app/components/post_header/post_header.js
@@ -261,7 +261,12 @@ export default class PostHeader extends PureComponent {
color={theme.linkColor}
/>
{!isSearchResult &&
- {commentCount}
+
+ {commentCount}
+
}
diff --git a/detox/e2e/support/ui/component/post.js b/detox/e2e/support/ui/component/post.js
index c5c2113fd..d6a9ea9c8 100644
--- a/detox/e2e/support/ui/component/post.js
+++ b/detox/e2e/support/ui/component/post.js
@@ -14,6 +14,7 @@ class Post {
postHeaderDisplayName: 'post_header.display_name',
postHeaderGuestTag: 'post_header.guest_tag',
postHeaderReply: 'post_header.reply',
+ postHeaderReplyCount: 'post_header.reply.count',
postPreHeaderText: 'post_pre_header.text',
showLessButton: 'show_more.button.minus',
showMoreButton: 'show_more.button.plus',
@@ -57,12 +58,14 @@ class Post {
const postItemHeaderDisplayNameMatcher = by.id(this.testID.postHeaderDisplayName).withAncestor(postItemMatcher);
const postItemHeaderGuestTagMatcher = by.id(this.testID.postHeaderGuestTag).withAncestor(postItemMatcher);
const postItemHeaderReplyMatcher = by.id(this.testID.postHeaderReply).withAncestor(postItemMatcher);
+ const postItemHeaderReplyCountMatcher = by.id(this.testID.postHeaderReplyCount).withAncestor(postItemMatcher);
return {
postItemHeaderDateTime: element(postItemHeaderDateTimeMatcher),
postItemHeaderDisplayName: element(postItemHeaderDisplayNameMatcher),
postItemHeaderGuestTag: element(postItemHeaderGuestTagMatcher),
postItemHeaderReply: element(postItemHeaderReplyMatcher),
+ postItemHeaderReplyCount: element(postItemHeaderReplyCountMatcher),
};
}
diff --git a/detox/e2e/support/ui/component/post_list.js b/detox/e2e/support/ui/component/post_list.js
index 20719407c..486c1b341 100644
--- a/detox/e2e/support/ui/component/post_list.js
+++ b/detox/e2e/support/ui/component/post_list.js
@@ -29,6 +29,7 @@ class PostList {
postItemHeaderDisplayName,
postItemHeaderGuestTag,
postItemHeaderReply,
+ postItemHeaderReplyCount,
postItemImage,
postItemMessage,
postItemPreHeaderText,
@@ -49,6 +50,7 @@ class PostList {
postListPostItemHeaderDisplayName: postItemHeaderDisplayName,
postListPostItemHeaderGuestTag: postItemHeaderGuestTag,
postListPostItemHeaderReply: postItemHeaderReply,
+ postListPostItemHeaderReplyCount: postItemHeaderReplyCount,
postListPostItemImage: postItemImage,
postListPostItemMessage: postItemMessage,
postListPostItemPreHeaderText: postItemPreHeaderText,
diff --git a/detox/e2e/test/messaging/message_reply.e2e.js b/detox/e2e/test/messaging/message_reply.e2e.js
new file mode 100644
index 000000000..b9e32c608
--- /dev/null
+++ b/detox/e2e/test/messaging/message_reply.e2e.js
@@ -0,0 +1,176 @@
+// 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 {PostOptions} from '@support/ui/component';
+import {
+ AddReactionScreen,
+ ChannelScreen,
+ ThreadScreen,
+} from '@support/ui/screen';
+import {
+ Channel,
+ Post,
+ Setup,
+} from '@support/server_api';
+
+describe('Message Reply', () => {
+ const {
+ getPostListPostItem,
+ openPostOptionsFor,
+ openReplyThreadFor,
+ postMessage,
+ } = ChannelScreen;
+ let townSquareChannel;
+
+ beforeAll(async () => {
+ const {team, user} = await Setup.apiInit();
+
+ ({channel: townSquareChannel} = await Channel.apiGetChannelByName(team.id, 'town-square'));
+
+ // # Open channel screen
+ await ChannelScreen.open(user);
+ });
+
+ afterAll(async () => {
+ await ChannelScreen.logout();
+ });
+
+ it('MM-T117 should be able to delete reply in reply thread', async () => {
+ // # Post message in main channel
+ const parentMessage = Date.now().toString();
+ await postMessage(parentMessage);
+
+ // # Post reply message
+ const replyMessage = Date.now().toString();
+ const {post: parentPost} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
+ await openReplyThreadFor(parentPost.id, parentMessage);
+ await ThreadScreen.postMessage(replyMessage);
+
+ // # Delete reply message
+ const {post: replyPost} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
+ await ThreadScreen.deletePost(replyPost.id, replyMessage, {isParentPost: false});
+
+ // * Verify reply message is deleted and parent post still exists
+ await ThreadScreen.toBeVisible();
+ await expect(element(by.text(replyMessage))).not.toExist();
+ await expect(element(by.text(parentMessage))).toExist();
+
+ // # Go back to channel
+ await ThreadScreen.back();
+ });
+
+ it('MM-T118 should have reply option available in long press menu', async () => {
+ const {replyAction} = PostOptions;
+
+ // # Post message as current user
+ const currentUserMessage = Date.now().toString();
+ await postMessage(currentUserMessage);
+
+ // * Verify long press menu contains reply option
+ const {post: currentUserPost} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
+ await openPostOptionsFor(currentUserPost.id, currentUserMessage);
+ await expect(replyAction).toBeVisible();
+
+ // # Post a new message to channel by sysadmin
+ await PostOptions.close();
+ const sysadminMessage = Date.now().toString();
+ await Post.apiCreatePost({
+ channelId: townSquareChannel.id,
+ message: sysadminMessage,
+ });
+
+ // * Verify long press menu contains reply option
+ const {post: sysadminPost} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
+ await openPostOptionsFor(sysadminPost.id, sysadminMessage);
+ await expect(replyAction).toBeVisible();
+
+ // # Go back to channel
+ await PostOptions.close();
+ });
+
+ it('MM-T148 should be able to add a reaction to a post in reply thread using long press and +:', async () => {
+ // # Post a message
+ const message = Date.now().toString();
+ await postMessage(message);
+
+ // # Add a reaction to the post in reply thread using long press
+ const {post} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
+ await openReplyThreadFor(post.id, message);
+ await ThreadScreen.openPostOptionsFor(post.id, message);
+ await AddReactionScreen.open();
+ await AddReactionScreen.searchInput.typeText(':fox_face:');
+ await element(by.text('🦊')).tap();
+
+ // # Add a reaction to the post in reply thread using +:
+ await ThreadScreen.postMessage('+:dog:');
+
+ // * Verify emojis are added to post
+ await expect(element(by.text('🦊').withAncestor(by.id(`thread.post_list.post.${post.id}`)))).toExist();
+ await expect(element(by.text('🐶').withAncestor(by.id(`thread.post_list.post.${post.id}`)))).toExist();
+
+ // # Go back to channel
+ await ThreadScreen.back();
+ });
+
+ it('MM-T2133 should have send button disabled in reply thread if no text in post input', async () => {
+ // # Post message in main channel
+ const message = Date.now().toString();
+ await postMessage(message);
+
+ // * Verify send button disabled in reply thread if no text in post input
+ const {post: parentPost} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
+ await openReplyThreadFor(parentPost.id, message);
+ await ThreadScreen.postInput.clearText();
+ await expect(ThreadScreen.sendButtonDisabled).toBeVisible();
+
+ // # Go back to channel
+ await ThreadScreen.back();
+ });
+
+ it('MM-T2134 should display reply count in parent post from main channel', async () => {
+ // # Post message in main channel
+ const parentMessage = Date.now().toString();
+ await postMessage(parentMessage);
+
+ // # Post 2 reply messages
+ const {post: parentPost} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
+ await openReplyThreadFor(parentPost.id, parentMessage);
+ await ThreadScreen.postMessage('reply 1');
+ await ThreadScreen.postMessage('reply 2');
+
+ // * Verify reply count in parent post from main channel
+ await ThreadScreen.back();
+ const {postListPostItemHeaderReplyCount} = await getPostListPostItem(parentPost.id, parentMessage);
+ await expect(postListPostItemHeaderReplyCount).toHaveText('2');
+ });
+
+ it('MM-T2135 should be able to reply thread using post header reply arrow', async () => {
+ // # Post message in main channel
+ const parentMessage = Date.now().toString();
+ await postMessage(parentMessage);
+
+ // # Post a reply message
+ const {post: parentPost} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
+ await openReplyThreadFor(parentPost.id, parentMessage);
+ await ThreadScreen.postMessage('reply 1');
+
+ // # Open reply thread using post header reply arrow
+ await ThreadScreen.back();
+ const {postListPostItemHeaderReply} = await getPostListPostItem(parentPost.id, parentMessage);
+ await postListPostItemHeaderReply.tap();
+
+ // * Verify user is able to post another reply
+ const secondReply = 'reply 2';
+ await ThreadScreen.postMessage(secondReply);
+ await expect(element(by.text(secondReply))).toBeVisible();
+
+ // # Go back to channel
+ await ThreadScreen.back();
+ });
+});