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(); + }); +});