From 78c05270bacfceaccaf2fa0dbfbd04440d2023d2 Mon Sep 17 00:00:00 2001 From: Joseph Baylon Date: Mon, 22 Mar 2021 11:16:40 -0700 Subject: [PATCH] MM-30427 Detox/E2E: Add e2e for MM-T3197, MM-T3225, MM-T3239, MM-T3274 (#5230) --- app/components/emoji/emoji.js | 16 ++- app/components/markdown/markdown.js | 1 + .../markdown/markdown_emoji/markdown_emoji.js | 1 + .../channel_add_members.js | 2 + .../search/__snapshots__/search.test.js.snap | 1 + app/screens/search/search.js | 1 + detox/e2e/support/ui/component/post.js | 3 + detox/e2e/support/ui/component/post_list.js | 2 + .../support/ui/screen/channel_add_members.js | 104 ++++++++++++++++++ detox/e2e/support/ui/screen/index.js | 2 + detox/e2e/support/ui/screen/long_post.js | 2 + detox/e2e/support/ui/screen/search.js | 2 + .../support/ui/screen/search_result_post.js | 2 + .../smoke_test/channel_add_members.e2e.js | 63 +++++++++++ ...s.e2e.js => channel_manage_members.e2e.js} | 2 +- .../test/smoke_test/message_posting.e2e.js | 11 ++ detox/e2e/test/smoke_test/search.e2e.js | 31 ++++++ detox/e2e/test/smoke_test/select_team.e2e.js | 56 ++++++++++ 18 files changed, 299 insertions(+), 3 deletions(-) create mode 100644 detox/e2e/support/ui/screen/channel_add_members.js create mode 100644 detox/e2e/test/smoke_test/channel_add_members.e2e.js rename detox/e2e/test/smoke_test/{manage_members.e2e.js => channel_manage_members.e2e.js} (98%) create mode 100644 detox/e2e/test/smoke_test/select_team.e2e.js diff --git a/app/components/emoji/emoji.js b/app/components/emoji/emoji.js index a21c39eac..80f62c39b 100644 --- a/app/components/emoji/emoji.js +++ b/app/components/emoji/emoji.js @@ -39,6 +39,7 @@ export default class Emoji extends React.PureComponent { textStyle: CustomPropTypes.Style, unicode: PropTypes.string, customEmojiStyle: CustomPropTypes.Style, + testID: PropTypes.string, }; static defaultProps = { @@ -55,6 +56,7 @@ export default class Emoji extends React.PureComponent { imageUrl, literal, unicode, + testID, textStyle, } = this.props; @@ -67,7 +69,13 @@ export default class Emoji extends React.PureComponent { } if (displayTextOnly) { - return {literal}; + return ( + + {literal} + ); } const width = size; @@ -84,7 +92,10 @@ export default class Emoji extends React.PureComponent { }, ''); return ( - + {code} ); @@ -101,6 +112,7 @@ export default class Emoji extends React.PureComponent { source={{uri: imageUrl}} onError={this.onError} resizeMode={FastImage.resizeMode.contain} + testID={testID} /> ); } diff --git a/app/components/markdown/markdown.js b/app/components/markdown/markdown.js index 6b4785a5f..a7863ada0 100644 --- a/app/components/markdown/markdown.js +++ b/app/components/markdown/markdown.js @@ -259,6 +259,7 @@ export default class Markdown extends PureComponent { ); diff --git a/app/components/markdown/markdown_emoji/markdown_emoji.js b/app/components/markdown/markdown_emoji/markdown_emoji.js index 22dd9cbe4..3161f942a 100644 --- a/app/components/markdown/markdown_emoji/markdown_emoji.js +++ b/app/components/markdown/markdown_emoji/markdown_emoji.js @@ -59,6 +59,7 @@ export default class MarkdownEmoji extends PureComponent { ); diff --git a/app/screens/channel_add_members/channel_add_members.js b/app/screens/channel_add_members/channel_add_members.js index b99dcfe2e..c0bb24675 100644 --- a/app/screens/channel_add_members/channel_add_members.js +++ b/app/screens/channel_add_members/channel_add_members.js @@ -73,6 +73,7 @@ export default class ChannelAddMembers extends PureComponent { text: context.intl.formatMessage({id: 'integrations.add', defaultMessage: 'Add'}), color: props.theme.sidebarHeaderTextColor, showAsAction: 'always', + testID: 'channel_add_members.add.button', }; setButtons(props.componentId, { @@ -369,6 +370,7 @@ export default class ChannelAddMembers extends PureComponent { onLoadMore={this.getProfiles} onRowPress={this.handleSelectProfile} renderItem={this.renderItem} + testID='channel_add_members.custom_list' theme={theme} /> diff --git a/app/screens/search/__snapshots__/search.test.js.snap b/app/screens/search/__snapshots__/search.test.js.snap index 4b959440c..448c6b91f 100644 --- a/app/screens/search/__snapshots__/search.test.js.snap +++ b/app/screens/search/__snapshots__/search.test.js.snap @@ -156,6 +156,7 @@ exports[`Search should match snapshot 1`] = ` "flex": 1, } } + testID="search.results_list" updateCellsBatchingPeriod={50} windowSize={21} /> diff --git a/app/screens/search/search.js b/app/screens/search/search.js index b1843cd55..a15a048d1 100644 --- a/app/screens/search/search.js +++ b/app/screens/search/search.js @@ -757,6 +757,7 @@ export default class Search extends PureComponent { scrollEventThrottle={60} ListFooterComponent={this.renderFooter} onViewableItemsChanged={this.onViewableItemsChanged} + testID='search.results_list' /> { const postItemMatcher = this.getPostItemMatcher(postItemSourceTestID, postId, postMessage); + const postItemEmojiMatcher = by.id(this.testID.emoji).withAncestor(postItemMatcher); const postItemImageMatcher = by.id(this.testID.image).withAncestor(postItemMatcher); const postItemMessageMatcher = by.id(this.testID.message).withAncestor(postItemMatcher); const postItemPreHeaderTextMatch = by.id(this.testID.postPreHeaderText).withAncestor(postItemMatcher); @@ -27,6 +29,7 @@ class Post { return { postItem: element(postItemMatcher), + postItemEmoji: element(postItemEmojiMatcher), postItemImage: element(postItemImageMatcher), postItemMessage: element(postItemMessageMatcher), postItemPreHeaderText: element(postItemPreHeaderTextMatch), diff --git a/detox/e2e/support/ui/component/post_list.js b/detox/e2e/support/ui/component/post_list.js index 7e05f25b9..d2695a65e 100644 --- a/detox/e2e/support/ui/component/post_list.js +++ b/detox/e2e/support/ui/component/post_list.js @@ -13,6 +13,7 @@ class PostList { getPost = (postId, postMessage, postProfileOptions = {}) => { const { postItem, + postItemEmoji, postItemHeaderDateTime, postItemHeaderDisplayName, postItemHeaderGuestTag, @@ -28,6 +29,7 @@ class PostList { return { postListPostItem: postItem, + postListPostItemEmoji: postItemEmoji, postListPostItemHeaderDateTime: postItemHeaderDateTime, postListPostItemHeaderDisplayName: postItemHeaderDisplayName, postListPostItemHeaderGuestTag: postItemHeaderGuestTag, diff --git a/detox/e2e/support/ui/screen/channel_add_members.js b/detox/e2e/support/ui/screen/channel_add_members.js new file mode 100644 index 000000000..1e1a7f527 --- /dev/null +++ b/detox/e2e/support/ui/screen/channel_add_members.js @@ -0,0 +1,104 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import { + Alert, + MainSidebar, + SearchBar, +} from '@support/ui/component'; +import {timeouts, wait} from '@support/utils'; + +class ChannelAddMembersScreen { + testID = { + channelAddMembersScreenPrefix: 'channel_add_members.', + channelAddMembersScreen: 'channel_add_members.screen', + addButton: 'channel_add_members.add.button', + backButton: 'screen.back.button', + usersList: 'channel_add_members.custom_list', + userItem: 'channel_add_members.custom_list.user_item', + userItemDisplayUsername: 'channel_add_members.custom_list.user_item.display_username', + } + + channelAddMembersScreen = element(by.id(this.testID.channelAddMembersScreen)); + addButton = element(by.id(this.testID.addButton)); + backButton = element(by.id(this.testID.backButton)); + usersList = element(by.id(this.testID.usersList)); + + // convenience props + searchBar = SearchBar.getSearchBar(this.testID.channelAddMembersScreenPrefix); + searchInput = SearchBar.getSearchInput(this.testID.channelAddMembersScreenPrefix); + cancelButton = SearchBar.getCancelButton(this.testID.channelAddMembersScreenPrefix); + clearButton = SearchBar.getClearButton(this.testID.channelAddMembersScreenPrefix); + + getUser = (userId, diplayUsername) => { + const userItemTestID = `${this.testID.userItem}.${userId}`; + const baseMatcher = by.id(userItemTestID); + const userItemMatcher = diplayUsername ? baseMatcher.withDescendant(by.text(diplayUsername)) : baseMatcher; + const userItemUsernameDisplayMatcher = by.id(this.testID.userItemDisplayUsername).withAncestor(userItemMatcher); + + return { + userItem: element(userItemMatcher), + userItemUsernameDisplay: element(userItemUsernameDisplayMatcher), + }; + } + + getUserAtIndex = (index) => { + return element(by.id(this.testID.userItem).withAncestor(by.id(this.testID.usersList))).atIndex(index); + } + + getUserByDisplayUsername = (displayUsername) => { + return element(by.text(displayUsername).withAncestor(by.id(this.testID.usersList))); + } + + getDisplayUsernameAtIndex = (index) => { + return element(by.id(this.testID.userItemDisplayUsername)).atIndex(index); + } + + toBeVisible = async () => { + await expect(this.channelAddMembersScreen).toBeVisible(); + + return this.channelAddMembersScreen; + } + + open = async () => { + // # Open more direct messages screen + await MainSidebar.openChannelMembersButton.tap(); + + return this.toBeVisible(); + } + + back = async () => { + await this.backButton.tap(); + await expect(this.channelAddMembersScreen).not.toBeVisible(); + } + + removeMembers = async (displayUsernameList, confirm = true) => { + displayUsernameList.forEach(async (displayUsername) => { + await this.getUserByDisplayUsername(`@${displayUsername}`).tap(); + }); + await wait(timeouts.ONE_SEC); + await this.removeButton.tap(); + const { + removeMembersTitle, + noButton, + yesButton, + } = Alert; + await expect(removeMembersTitle).toBeVisible(); + if (confirm) { + yesButton.tap(); + } else { + noButton.tap(); + } + await wait(timeouts.ONE_SEC); + await expect(this.channelAddMembersScreen).not.toBeVisible(); + } + + hasUserDisplayUsernameAtIndex = async (index, displayUsername) => { + await expect( + this.getDisplayUsernameAtIndex(index), + ).toHaveText(displayUsername); + } +} + +const channelAddMembersScreen = new ChannelAddMembersScreen(); +export default channelAddMembersScreen; diff --git a/detox/e2e/support/ui/screen/index.js b/detox/e2e/support/ui/screen/index.js index ad6c3be1d..0f74ca234 100644 --- a/detox/e2e/support/ui/screen/index.js +++ b/detox/e2e/support/ui/screen/index.js @@ -3,6 +3,7 @@ import AddReactionScreen from './add_reaction'; import ChannelInfoScreen from './channel_info'; +import ChannelAddMembersScreen from './channel_add_members'; import ChannelMembersScreen from './channel_members'; import ChannelNotificationPreferenceScreen from './channel_notification_preference'; import ChannelScreen from './channel'; @@ -33,6 +34,7 @@ import ThreadScreen from './thread'; export { AddReactionScreen, ChannelInfoScreen, + ChannelAddMembersScreen, ChannelMembersScreen, ChannelNotificationPreferenceScreen, ChannelScreen, diff --git a/detox/e2e/support/ui/screen/long_post.js b/detox/e2e/support/ui/screen/long_post.js index f27ba9fa3..72c4aa99c 100644 --- a/detox/e2e/support/ui/screen/long_post.js +++ b/detox/e2e/support/ui/screen/long_post.js @@ -14,6 +14,7 @@ class LongPostScreen { getPost = (postId, postMessage, postProfileOptions = {}) => { const { postItem, + postItemEmoji, postItemHeaderDateTime, postItemHeaderDisplayName, postItemHeaderGuestTag, @@ -28,6 +29,7 @@ class LongPostScreen { return { longPostItem: postItem, + longPostItemEmoji: postItemEmoji, longPostItemHeaderDateTime: postItemHeaderDateTime, longPostItemHeaderDisplayName: postItemHeaderDisplayName, longPostItemHeaderGuestTag: postItemHeaderGuestTag, diff --git a/detox/e2e/support/ui/screen/search.js b/detox/e2e/support/ui/screen/search.js index fb7320992..10f1ab677 100644 --- a/detox/e2e/support/ui/screen/search.js +++ b/detox/e2e/support/ui/screen/search.js @@ -20,6 +20,7 @@ class SearchScreen { searchOnSection: 'search.on_section', searchAfterSection: 'search.after_section', searchBeforeSection: 'search.before_section', + searchResultsList: 'search.results_list', } searchScreen = element(by.id(this.testID.searchScreen)); @@ -28,6 +29,7 @@ class SearchScreen { searchOnSection = element(by.id(this.testID.searchOnSection)); searchAfterSection = element(by.id(this.testID.searchAfterSection)); searchBeforeSection = element(by.id(this.testID.searchBeforeSection)); + searchResultsList = element(by.id(this.testID.searchResultsList)); searchFromModifier = 'from:'; searchInModifier = 'in:'; diff --git a/detox/e2e/support/ui/screen/search_result_post.js b/detox/e2e/support/ui/screen/search_result_post.js index 9acc75463..0c36db4f2 100644 --- a/detox/e2e/support/ui/screen/search_result_post.js +++ b/detox/e2e/support/ui/screen/search_result_post.js @@ -11,6 +11,7 @@ class SearchResultPostScreen { getPost = (postId, postMessage, postProfileOptions = {}) => { const { postItem, + postItemEmoji, postItemHeaderDateTime, postItemHeaderDisplayName, postItemHeaderGuestTag, @@ -25,6 +26,7 @@ class SearchResultPostScreen { return { searchResultPostItem: postItem, + searchResultPostItemEmoji: postItemEmoji, searchResultPostItemHeaderDateTime: postItemHeaderDateTime, searchResultPostItemHeaderDisplayName: postItemHeaderDisplayName, searchResultPostItemHeaderGuestTag: postItemHeaderGuestTag, diff --git a/detox/e2e/test/smoke_test/channel_add_members.e2e.js b/detox/e2e/test/smoke_test/channel_add_members.e2e.js new file mode 100644 index 000000000..4c6185cc0 --- /dev/null +++ b/detox/e2e/test/smoke_test/channel_add_members.e2e.js @@ -0,0 +1,63 @@ +// 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 { + ChannelAddMembersScreen, + ChannelInfoScreen, + ChannelScreen, +} from '@support/ui/screen'; +import { + Setup, + Team, + User, +} from '@support/server_api'; + +describe('Channel Add Members', () => { + let testChannel; + let testLastUser; + + beforeAll(async () => { + const {channel, team, user} = await Setup.apiInit(); + testChannel = channel; + + [...Array(100).keys()].forEach(async (key) => { + const {user: testOtherUser} = await User.apiCreateUser({prefix: `a-${key}-`}); + await Team.apiAddUserToTeam(testOtherUser.id, team.id); + }); + ({user: testLastUser} = await User.apiCreateUser({prefix: 'z-'})); + await Team.apiAddUserToTeam(testLastUser.id, team.id); + + // # Open channel screen + await ChannelScreen.open(user); + }); + + afterAll(async () => { + await ChannelScreen.logout(); + }); + + it('MM-T3197 should be able load users in long add members list', async () => { + const { + addMembersAction, + channelInfoScrollView, + } = ChannelInfoScreen; + + // # Open add members screen + await ChannelScreen.goToChannel(testChannel.display_name); + await ChannelInfoScreen.open(); + await channelInfoScrollView.scrollTo('bottom'); + await addMembersAction.tap(); + + // * Verify user can scroll down multiple times until last user is seen + await waitFor(ChannelAddMembersScreen.getUserByDisplayUsername(`@${testLastUser.username}`)).toBeVisible().whileElement(by.id(ChannelAddMembersScreen.testID.usersList)).scroll(500, 'down'); + await ChannelAddMembersScreen.back(); + + // # Close channel info screen + await ChannelInfoScreen.close(); + }); +}); diff --git a/detox/e2e/test/smoke_test/manage_members.e2e.js b/detox/e2e/test/smoke_test/channel_manage_members.e2e.js similarity index 98% rename from detox/e2e/test/smoke_test/manage_members.e2e.js rename to detox/e2e/test/smoke_test/channel_manage_members.e2e.js index ad90204c7..880881a4c 100644 --- a/detox/e2e/test/smoke_test/manage_members.e2e.js +++ b/detox/e2e/test/smoke_test/channel_manage_members.e2e.js @@ -20,7 +20,7 @@ import { } from '@support/server_api'; import {getRandomId} from '@support/utils'; -describe('Manage Members', () => { +describe('Channel Manage Members', () => { const searchTerm = getRandomId(); const { removeMembers, diff --git a/detox/e2e/test/smoke_test/message_posting.e2e.js b/detox/e2e/test/smoke_test/message_posting.e2e.js index c7b4016f7..ffb31365f 100644 --- a/detox/e2e/test/smoke_test/message_posting.e2e.js +++ b/detox/e2e/test/smoke_test/message_posting.e2e.js @@ -78,4 +78,15 @@ describe('Message Posting', () => { const {postListPostItemImage} = await getPostListPostItem(post.id); await expect(postListPostItemImage).toBeVisible(); }); + + it('MM-T3225 should be able to post a jumbo emoji', async () => { + // # Post a jumbo emoji + const message = ':fox_face:'; + await postMessage(message, {quickReplace: true}); + + // * Verify message is posted + const {post} = await Post.apiGetLastPostInChannel(testChannel.id); + const {postListPostItem} = await getPostListPostItem(post.id); + await expect(postListPostItem).toBeVisible(); + }); }); diff --git a/detox/e2e/test/smoke_test/search.e2e.js b/detox/e2e/test/smoke_test/search.e2e.js index 2681f5715..da7e92eb5 100644 --- a/detox/e2e/test/smoke_test/search.e2e.js +++ b/detox/e2e/test/smoke_test/search.e2e.js @@ -33,6 +33,7 @@ describe('Search', () => { getSearchResultPostItem, searchFromModifier, searchInModifier, + searchInput, } = SearchScreen; let testMessage; let testPartialSearchTerm; @@ -174,6 +175,36 @@ describe('Search', () => { // # Go back to channel await SearchScreen.cancel(); }); + + it('MM-T3239 should be able to scroll through long list of search results', async () => { + // # Post messages + const keyword = 'qa'; + const firstMessage = `${Date.now().toString()} ${keyword} first`; + const firstPost = await Post.apiCreatePost({ + channelId: testChannel.id, + message: firstMessage, + }); + [...Array(50).keys()].forEach(async () => { + const message = `${Date.now().toString()} ${keyword}`; + await Post.apiCreatePost({ + channelId: testChannel.id, + message, + }); + }); + + // # Perform search on keyword + await SearchScreen.open(); + await searchInput.clearText(); + await searchInput.typeText(keyword); + await searchInput.tapReturnKey(); + + // * Verify user can scroll down multiple times until first matching post is seen + const {searchResultPostItem} = await getSearchResultPostItem(firstPost.post.id, firstMessage); + await waitFor(searchResultPostItem).toBeVisible().whileElement(by.id(SearchScreen.testID.searchResultsList)).scroll(1000, 'down'); + + // # Go back to channel + await SearchScreen.cancel(); + }); }); async function postMessageAndSearchFrom(testMessage, testUser, atMentionSuggestionList, testPartialSearchTerm) { diff --git a/detox/e2e/test/smoke_test/select_team.e2e.js b/detox/e2e/test/smoke_test/select_team.e2e.js new file mode 100644 index 000000000..0a27cbd49 --- /dev/null +++ b/detox/e2e/test/smoke_test/select_team.e2e.js @@ -0,0 +1,56 @@ +// 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 { + ChannelScreen, + LoginScreen, + SelectTeamScreen, +} from '@support/ui/screen'; +import { + Channel, + Team, + User, +} from '@support/server_api'; + +describe('Select Team', () => { + const {getTeamByDisplayName} = SelectTeamScreen; + let testTeam; + + beforeAll(async () => { + await Team.apiPatchTeams({allow_open_invite: false}); + const {user} = await User.apiCreateUser(); + const {team} = await Team.apiCreateTeam(); + testTeam = team; + await Team.apiPatchTeam(testTeam.id, {allow_open_invite: true}); + + // # Login + await LoginScreen.open(); + await LoginScreen.login(user); + }); + + afterAll(async () => { + await ChannelScreen.logout(); + await Team.apiPatchTeams({allow_open_invite: true}); + }); + + it('MM-T3274 user with no teams should be able to select a team', async () => { + // * Verify select team screen is displayed + await SelectTeamScreen.toBeVisible(); + + // # Tap on team to join + await waitFor(getTeamByDisplayName(testTeam.display_name)).toBeVisible().whileElement(by.id(SelectTeamScreen.testID.teamsList)).scroll(500, 'down'); + const team = await getTeamByDisplayName(testTeam.display_name); + await team.tap(); + + // * Verify redirect to default channel of joined team + const {channel} = await Channel.apiGetChannelByName(testTeam.name, 'town-square'); + await ChannelScreen.toBeVisible(); + await expect(ChannelScreen.channelNavBarTitle).toHaveText(channel.display_name); + }); +});