From 19196170dc78751b553030a1a4be99acaa6d3663 Mon Sep 17 00:00:00 2001 From: Joseph Baylon Date: Tue, 1 Jun 2021 07:37:34 -0700 Subject: [PATCH] MM-35674 Detox/E2E: Add e2e for autocomplete and at-mention (#5404) * MM-35674 Detox/E2E: Add e2e for autocomplete and at-mention * Uncomment previously commented out line * Fix verification comment --- .../at_mention_item/at_mention_item.tsx | 6 +- detox/e2e/support/server_api/user.js | 15 +- detox/e2e/support/ui/component/alert.js | 2 + .../e2e/support/ui/component/autocomplete.js | 15 +- detox/e2e/support/ui/screen/channel.js | 5 + detox/e2e/support/ui/screen/permalink.js | 5 + detox/e2e/support/ui/screen/thread.js | 5 + detox/e2e/test/autocomplete/at_mention.e2e.js | 2 +- .../e2e/test/autocomplete/autocomplete.e2e.js | 186 ++++++++++++++++++ detox/e2e/test/messaging/at_mention.e2e.js | 97 +++++++++ detox/e2e/test/smoke_test/search.e2e.js | 2 +- 11 files changed, 328 insertions(+), 12 deletions(-) create mode 100644 detox/e2e/test/autocomplete/autocomplete.e2e.js create mode 100644 detox/e2e/test/messaging/at_mention.e2e.js diff --git a/app/components/autocomplete/at_mention_item/at_mention_item.tsx b/app/components/autocomplete/at_mention_item/at_mention_item.tsx index ae771e93d..56aee2b39 100644 --- a/app/components/autocomplete/at_mention_item/at_mention_item.tsx +++ b/app/components/autocomplete/at_mention_item/at_mention_item.tsx @@ -123,7 +123,7 @@ const AtMentionItem = (props: AtMentionItemProps) => { size={24} status={null} showStatus={false} - testID={`${testID}.profile_picture`} + testID='at_mention_item.profile_picture' /> @@ -139,13 +139,15 @@ const AtMentionItem = (props: AtMentionItemProps) => { - {name} {name} + {name} } {isCurrentUser && { } }; -function generateRandomUser(prefix) { - const randomId = getRandomId(); +export const generateRandomUser = ({prefix = 'user', randomIdLength = 6} = {}) => { + const randomId = getRandomId(randomIdLength); return { email: `${prefix}${randomId}@sample.mattermost.com`, username: `${prefix}${randomId}`, password: 'passwd', - first_name: `First${randomId}`, - last_name: `Last${randomId}`, - nickname: `Nickname${randomId}`, - position: `Position${randomId}`, + first_name: `F${randomId}`, + last_name: `L${randomId}`, + nickname: `N${randomId}`, + position: `P${randomId}`, }; -} +}; export const User = { apiAdminLogin, @@ -220,6 +220,7 @@ export const User = { apiLogout, apiPatchMe, apiPatchUser, + generateRandomUser, }; export default User; diff --git a/detox/e2e/support/ui/component/alert.js b/detox/e2e/support/ui/component/alert.js index 47053332c..86b6ddc27 100644 --- a/detox/e2e/support/ui/component/alert.js +++ b/detox/e2e/support/ui/component/alert.js @@ -7,6 +7,7 @@ class Alert { // alert titles archivePrivateChannelTitle = isAndroid() ? element(by.text('Archive Private Channel')) : element(by.label('Archive Private Channel')).atIndex(0); archivePublicChannelTitle = isAndroid() ? element(by.text('Archive Public Channel')) : element(by.label('Archive Public Channel')).atIndex(0); + confirmSendingNotificationsTitle = isAndroid() ? element(by.text('Confirm sending notifications to entire channel')) : element(by.label('Confirm sending notifications to entire channel')).atIndex(0); deleteDocumentsAndDataTitle = isAndroid() ? element(by.text('Delete Documents & Data')) : element(by.label('Delete Documents & Data')).atIndex(0); deletePostTitle = isAndroid() ? element(by.text('Delete Post')) : element(by.label('Delete Post')).atIndex(0); joinPrivateChannelTitle = isAndroid() ? element(by.text('Join private channel')) : element(by.label('Join private channel')).atIndex(0); @@ -16,6 +17,7 @@ class Alert { // alert buttons cancelButton = isAndroid() ? element(by.text('CANCEL')) : element(by.label('Cancel')).atIndex(1); + confirmButton = isAndroid() ? element(by.text('CONFIRM')) : element(by.label('Confirm')).atIndex(1); deleteButton = isAndroid() ? element(by.text('DELETE')) : element(by.label('Delete')).atIndex(0); joinButton = isAndroid() ? element(by.text('JOIN')) : element(by.label('Join')).atIndex(0); okButton = isAndroid() ? element(by.text('OK')) : element(by.label('OK')).atIndex(1); diff --git a/detox/e2e/support/ui/component/autocomplete.js b/detox/e2e/support/ui/component/autocomplete.js index 3cafdf5da..aa4b4edbb 100644 --- a/detox/e2e/support/ui/component/autocomplete.js +++ b/detox/e2e/support/ui/component/autocomplete.js @@ -6,6 +6,9 @@ class Autocomplete { atMentionItemPrefix: 'autocomplete.at_mention.item.', channelMentionItemPrefix: 'autocomplete.channel_mention.item.', autocomplete: 'autocomplete', + atMentionItemName: 'at_mention_item.name', + atMentionItemProfilePicture: 'at_mention_item.profile_picture', + atMentionItemUsername: 'at_mention_item.username', atMentionSuggestionList: 'at_mention_suggestion.list', channelMentionSuggestionList: 'channel_mention_suggestion.list', dateSuggestion: 'autocomplete.date_suggestion', @@ -21,7 +24,17 @@ class Autocomplete { slashSuggestionList = element(by.id(this.testID.slashSuggestionList)); getAtMentionItem = (userId) => { - return element(by.id(`${this.testID.atMentionItemPrefix}${userId}`)); + const atMentionItemMatcher = by.id(`${this.testID.atMentionItemPrefix}${userId}`); + const atMentionItemNameMatcher = by.id(this.testID.atMentionItemName).withAncestor(atMentionItemMatcher); + const atMentionItemProfilePictureMatcher = by.id(this.testID.atMentionItemProfilePicture).withAncestor(atMentionItemMatcher); + const atMentionItemUsernameMatcher = by.id(this.testID.atMentionItemUsername).withAncestor(atMentionItemMatcher); + + return { + atMentionItem: element(atMentionItemMatcher), + atMentionItemName: element(atMentionItemNameMatcher), + atMentionItemProfilePicture: element(atMentionItemProfilePictureMatcher), + atMentionItemUsername: element(atMentionItemUsernameMatcher), + }; } getChannelMentionItem = (channelId) => { diff --git a/detox/e2e/support/ui/screen/channel.js b/detox/e2e/support/ui/screen/channel.js index fcb548ea3..aeeac432b 100644 --- a/detox/e2e/support/ui/screen/channel.js +++ b/detox/e2e/support/ui/screen/channel.js @@ -224,6 +224,11 @@ class ChannelScreen { ).toHaveText(postMessage); } + hasPostMessage = async (postId, postMessage) => { + const {postListPostItem} = this.getPostListPostItem(postId, postMessage); + await expect(postListPostItem).toBeVisible(); + } + hasPostMessageAtIndex = async (index, postMessage) => { await expect( this.getPostMessageAtIndex(index), diff --git a/detox/e2e/support/ui/screen/permalink.js b/detox/e2e/support/ui/screen/permalink.js index a45986e40..22acf5edc 100644 --- a/detox/e2e/support/ui/screen/permalink.js +++ b/detox/e2e/support/ui/screen/permalink.js @@ -52,6 +52,11 @@ class PermalinkScreen { ).toHaveText(postMessage); } + hasPostMessage = async (postId, postMessage) => { + const {postListPostItem} = this.getPostListPostItem(postId, postMessage); + await expect(postListPostItem).toBeVisible(); + } + hasPostMessageAtIndex = async (index, postMessage) => { await expect( this.getPostMessageAtIndex(index), diff --git a/detox/e2e/support/ui/screen/thread.js b/detox/e2e/support/ui/screen/thread.js index e5091a517..b121a37aa 100644 --- a/detox/e2e/support/ui/screen/thread.js +++ b/detox/e2e/support/ui/screen/thread.js @@ -119,6 +119,11 @@ class ThreadScreen { ).toHaveText(postMessage); } + hasPostMessage = async (postId, postMessage) => { + const {postListPostItem} = this.getPostListPostItem(postId, postMessage); + await expect(postListPostItem).toBeVisible(); + } + hasPostMessageAtIndex = async (index, postMessage) => { await expect( this.getPostMessageAtIndex(index), diff --git a/detox/e2e/test/autocomplete/at_mention.e2e.js b/detox/e2e/test/autocomplete/at_mention.e2e.js index 49142fdac..432ec2241 100644 --- a/detox/e2e/test/autocomplete/at_mention.e2e.js +++ b/detox/e2e/test/autocomplete/at_mention.e2e.js @@ -19,7 +19,7 @@ describe('Autocomplete', () => { beforeAll(async () => { ({user} = await Setup.apiInit()); - userAtMentionAutocomplete = Autocomplete.getAtMentionItem(user.id); + ({atMentionItem: userAtMentionAutocomplete} = Autocomplete.getAtMentionItem(user.id)); // # Open channel screen await ChannelScreen.open(user); diff --git a/detox/e2e/test/autocomplete/autocomplete.e2e.js b/detox/e2e/test/autocomplete/autocomplete.e2e.js new file mode 100644 index 000000000..70b2f04fd --- /dev/null +++ b/detox/e2e/test/autocomplete/autocomplete.e2e.js @@ -0,0 +1,186 @@ +// 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 {Autocomplete} from '@support/ui/component'; +import { + ChannelScreen, + SearchScreen, + ThreadScreen, +} from '@support/ui/screen'; +import { + Channel, + Post, + Setup, + Team, + User, +} from '@support/server_api'; + +describe('Autocomplete', () => { + const { + goToChannel, + openReplyThreadFor, + postInput, + postMessage, + } = ChannelScreen; + const { + atMentionSuggestionList, + emojiSuggestionList, + slashSuggestionList, + } = Autocomplete; + let testUser; + let testOtherUser; + let testChannel; + let townSquareChannel; + + beforeAll(async () => { + const currentUser = User.generateRandomUser(); + delete currentUser.nickname; + const {channel, team, user} = await Setup.apiInit({userOptions: {user: currentUser}}); + testChannel = channel; + testUser = user; + + ({user: testOtherUser} = await User.apiCreateUser()); + await Team.apiAddUserToTeam(testOtherUser.id, team.id); + + ({channel: townSquareChannel} = await Channel.apiGetChannelByName(team.id, 'town-square')); + + // # Open channel screen + await ChannelScreen.open(testUser); + }); + + beforeEach(async () => { + // # Clear text and verify that Autocomplete disappeared + await postInput.clearText(); + await Autocomplete.toBeVisible(false); + }); + + afterAll(async () => { + await ChannelScreen.logout(); + }); + + it('MM-T168 should have autocomplete items available from input boxes', async () => { + // # Type ":ta" to activate emoji autocomplete + await postInput.typeText(':ta'); + + // * Verify emoji autocomplete is displayed + await Autocomplete.toBeVisible(); + await expect(emojiSuggestionList).toExist(); + + // # Type space to close emoji autocomplete + await postInput.typeText(' '); + + // * Verify emoji autocomplete is not displayed + await expect(emojiSuggestionList).not.toExist(); + + // # Type "@" to activate at mention autocomplete + await postInput.typeText('@'); + + // * Verify at mention autocomplete is displayed + await Autocomplete.toBeVisible(); + await expect(atMentionSuggestionList).toExist(); + + // # Post message, open reply thread, and type "/" to activate slash autocomplete + const message = Date.now().toString(); + await postMessage(message, {quickReplace: true}); + const {post} = await Post.apiGetLastPostInChannel(townSquareChannel.id); + await openReplyThreadFor(post.id, message); + await ThreadScreen.postInput.typeText('/'); + + // * Verify slash autocomplete is displayed + await Autocomplete.toBeVisible(); + await expect(slashSuggestionList).toExist(); + + // # Go back to channel + await ThreadScreen.postInput.clearText(); + await ThreadScreen.back(); + }); + + it('MM-T171 should have at-mention autocomplete available out of channel members', async () => { + // # Go to test channel + await goToChannel(testChannel.display_name); + + // # Activate at mention autocomplete for other user + await postInput.typeText(`@${testOtherUser.username}`); + + // * Verify at mention autocomplete with other user is displayed + await Autocomplete.toBeVisible(); + await expect(atMentionSuggestionList).toExist(); + const {atMentionItem: testOtherUserAutocomplete} = Autocomplete.getAtMentionItem(testOtherUser.id); + await expect(testOtherUserAutocomplete).toExist(); + }); + + it('MM-T1798 should include self in user autocomplete', async () => { + // # Activate at mention autocomplete for current user + await postInput.typeText(`@${testUser.username}`); + + // * Verify at mention autocomplete with current user is displayed + await Autocomplete.toBeVisible(); + await expect(atMentionSuggestionList).toExist(); + const { + atMentionItem, + atMentionItemName, + atMentionItemUsername, + } = Autocomplete.getAtMentionItem(testUser.id); + await expect(atMentionItem).toExist(); + await expect(atMentionItemName).toHaveText(`${testUser.first_name} ${testUser.last_name}`); + await expect(atMentionItemUsername).toHaveText(`(you) @${testUser.username}`); + }); + + it('MM-T2349 should have autocomplete using nickname', async () => { + // # Activate at mention autocomplete for other user using nickname + await postInput.typeText(`@${testOtherUser.nickname}`); + + // * Verify at mention autocomplete with other user is displayed + await Autocomplete.toBeVisible(); + await expect(atMentionSuggestionList).toExist(); + const { + atMentionItem, + atMentionItemName, + atMentionItemUsername, + } = Autocomplete.getAtMentionItem(testOtherUser.id); + await expect(atMentionItem).toExist(); + await expect(atMentionItemName).toHaveText(`${testOtherUser.first_name} ${testOtherUser.last_name} (${testOtherUser.nickname})`); + await expect(atMentionItemUsername).toHaveText(` @${testOtherUser.username}`); + }); + + it('MM-T170 should be able to search usernames as case insensitive', async () => { + const { + searchInput, + searchFromSection, + } = SearchScreen; + + // # Search user using lowercase username + await SearchScreen.open(); + await searchInput.clearText(); + await searchFromSection.tap(); + const lowerCaseSearchTerm = testUser.username; + await searchInput.typeText(lowerCaseSearchTerm); + + // * Verify user is displayed using lowercase search term + await Autocomplete.toBeVisible(); + await expect(atMentionSuggestionList).toExist(); + const {atMentionItem: lowerCaseAtMention} = await Autocomplete.getAtMentionItem(testUser.id); + await expect(lowerCaseAtMention).toBeVisible(); + + // # Search user using uppercase username + await searchInput.clearText(); + await searchFromSection.tap(); + const upperCaseSearchTerm = testUser.username.toUpperCase(); + await searchInput.typeText(upperCaseSearchTerm); + + // * Verify user is displayed using uppercase search term + await Autocomplete.toBeVisible(); + await expect(atMentionSuggestionList).toExist(); + const {atMentionItem: upperCaseAtMention} = await Autocomplete.getAtMentionItem(testUser.id); + await expect(upperCaseAtMention).toBeVisible(); + + // # Go back to channel + await SearchScreen.cancel(); + }); +}); diff --git a/detox/e2e/test/messaging/at_mention.e2e.js b/detox/e2e/test/messaging/at_mention.e2e.js new file mode 100644 index 000000000..18158909c --- /dev/null +++ b/detox/e2e/test/messaging/at_mention.e2e.js @@ -0,0 +1,97 @@ +// 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 {Alert} from '@support/ui/component'; +import {ChannelScreen} from '@support/ui/screen'; +import { + Channel, + Post, + Setup, + Team, + User, +} from '@support/server_api'; + +describe('At Mention', () => { + const { + hasPostMessage, + postInput, + postMessage, + sendButton, + } = ChannelScreen; + let testUser; + let testOtherUser; + let townSquareChannel; + let testTeam; + + beforeAll(async () => { + const {team, user} = await Setup.apiInit(); + testTeam = team; + testUser = user; + + ({user: testOtherUser} = await User.apiCreateUser()); + await Team.apiAddUserToTeam(testOtherUser.id, testTeam.id); + + ({channel: townSquareChannel} = await Channel.apiGetChannelByName(testTeam.id, 'town-square')); + + // # Open channel screen + await ChannelScreen.open(testUser); + }); + + afterAll(async () => { + await ChannelScreen.logout(); + }); + + it('MM-T169 should post at-mentions as lowercase', async () => { + // # Post lowercase at-mention + const lowerCaseMessage = `@${testUser.username}`; + await postMessage(lowerCaseMessage); + + // * Verify at-mention is posted as lowercase + const {post: lowerCasePost} = await Post.apiGetLastPostInChannel(townSquareChannel.id); + await hasPostMessage(lowerCasePost.id, lowerCaseMessage); + + // # Post uppercase at-mention + const upperCaseMessage = `@${testOtherUser.username.toUpperCase()}`; + await postMessage(upperCaseMessage); + + // * Verify at-mention is posted as lowercase + const {post: upperCasePost} = await Post.apiGetLastPostInChannel(townSquareChannel.id); + await hasPostMessage(upperCasePost.id, upperCaseMessage.toLowerCase()); + }); + + it('MM-T172 should display confirmation dialog when posting @all and @channel', async () => { + const { + cancelButton, + confirmSendingNotificationsTitle, + } = Alert; + + // # Create 20 more users + [...Array(20).keys()].forEach(async (key) => { + const {user} = await User.apiCreateUser({prefix: `a-${key}-`}); + await Team.apiAddUserToTeam(user.id, testTeam.id); + }); + + // # Post @all + await postInput.typeText('@all'); + await sendButton.tap(); + + // * Verify confirmation dialog is displayed + await expect(confirmSendingNotificationsTitle).toBeVisible(); + await cancelButton.tap(); + + // # Post @channel + await postInput.clearText(); + await postInput.typeText('@channel'); + await sendButton.tap(); + + // * Verify confirmation dialog is displayed + await expect(confirmSendingNotificationsTitle).toBeVisible(); + await cancelButton.tap(); + }); +}); diff --git a/detox/e2e/test/smoke_test/search.e2e.js b/detox/e2e/test/smoke_test/search.e2e.js index a6cf57d84..ff33b27d3 100644 --- a/detox/e2e/test/smoke_test/search.e2e.js +++ b/detox/e2e/test/smoke_test/search.e2e.js @@ -226,7 +226,7 @@ async function postMessageAndSearchFrom(testMessage, testUser, atMentionSuggesti // # Select user from autocomplete await expect(atMentionSuggestionList).toExist(); - const userAtMentionAutocomplete = await Autocomplete.getAtMentionItem(testUser.id); + const {atMentionItem: userAtMentionAutocomplete} = await Autocomplete.getAtMentionItem(testUser.id); await userAtMentionAutocomplete.tap(); // # Type end of search term