diff --git a/app/components/channel_icon.js b/app/components/channel_icon.js index 4e3b2f3a1..e7d26299c 100644 --- a/app/components/channel_icon.js +++ b/app/components/channel_icon.js @@ -26,6 +26,7 @@ export default class ChannelIcon extends React.PureComponent { type: PropTypes.string.isRequired, isArchived: PropTypes.bool.isRequired, isBot: PropTypes.bool.isRequired, + testID: PropTypes.string, }; static defaultProps = { @@ -48,6 +49,7 @@ export default class ChannelIcon extends React.PureComponent { type, isArchived, isBot, + testID, } = this.props; const style = getStyleSheet(theme); @@ -84,7 +86,7 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (isBot) { @@ -92,7 +94,7 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (hasDraft) { @@ -100,7 +102,7 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (type === General.OPEN_CHANNEL) { @@ -108,7 +110,7 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (type === General.PRIVATE_CHANNEL) { @@ -116,7 +118,7 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (type === General.GM_CHANNEL) { @@ -125,7 +127,7 @@ export default class ChannelIcon extends React.PureComponent { {membersCount} @@ -138,7 +140,7 @@ export default class ChannelIcon extends React.PureComponent { ); break; @@ -147,7 +149,7 @@ export default class ChannelIcon extends React.PureComponent { ); break; @@ -156,7 +158,7 @@ export default class ChannelIcon extends React.PureComponent { ); break; @@ -165,7 +167,7 @@ export default class ChannelIcon extends React.PureComponent { ); break; diff --git a/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap b/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap index d190621d7..64236e85b 100644 --- a/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap +++ b/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap @@ -15,6 +15,7 @@ exports[`UserListRow should match snapshot 1`] = ` enabled={true} id="21345" onPress={[Function]} + testID="custom_list.user_item" > @user @@ -147,6 +150,7 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated enabled={true} id="21345" onPress={[Function]} + testID="custom_list.user_item" > @user @@ -420,6 +429,7 @@ exports[`UserListRow should match snapshot for guest user 1`] = ` enabled={true} id="21345" onPress={[Function]} + testID="custom_list.user_item" > @user diff --git a/app/components/custom_list/user_list_row/user_list_row.js b/app/components/custom_list/user_list_row/user_list_row.js index a3af41feb..e42c0bde7 100644 --- a/app/components/custom_list/user_list_row/user_list_row.js +++ b/app/components/custom_list/user_list_row/user_list_row.js @@ -63,6 +63,9 @@ export default class UserListRow extends React.PureComponent { const teammateDisplay = displayUsername(user, teammateNameDisplay); const showTeammateDisplay = teammateDisplay !== username; + const testID = this.props.testID; + const itemTestID = `${testID}.${id}`; + const displayUsernameTestID = `${testID}.display_username`; return ( @@ -72,7 +75,7 @@ export default class UserListRow extends React.PureComponent { enabled={enabled} selectable={selectable} selected={selected} - testID={this.props.testID} + testID={testID} > - + {usernameDisplay} diff --git a/app/components/custom_list/user_list_row/user_list_test.test.js b/app/components/custom_list/user_list_row/user_list_test.test.js index 927a7eabf..594b49e65 100644 --- a/app/components/custom_list/user_list_row/user_list_test.test.js +++ b/app/components/custom_list/user_list_row/user_list_test.test.js @@ -29,6 +29,7 @@ describe('UserListRow', () => { }, theme: Preferences.THEMES.default, teammateNameDisplay: 'test', + testID: 'custom_list.user_item', }; test('should match snapshot', () => { diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap index a8fa555d2..83694769d 100644 --- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap +++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap @@ -43,6 +43,7 @@ exports[`ChannelItem should match snapshot 1`] = ` membersCount={1} size={16} status="online" + testID="main.sidebar.channels_list.list.channel_item.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -158,6 +159,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you membersCount={1} size={16} status="online" + testID="main.sidebar.channels_list.list.channel_item.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -272,6 +274,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you membersCount={1} size={16} status="online" + testID="main.sidebar.channels_list.list.channel_item.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -386,6 +389,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh membersCount={1} size={16} status="online" + testID="main.sidebar.channels_list.list.channel_item.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -489,6 +493,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes membersCount={1} size={16} status="online" + testID="main.sidebar.channels_list.list.channel_item.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -593,6 +598,7 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe membersCount={1} size={16} status="online" + testID="main.sidebar.channels_list.list.channel_item.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -697,6 +703,7 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = ` membersCount={1} size={16} status="online" + testID="main.sidebar.channels_list.list.channel_item.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -805,6 +812,7 @@ exports[`ChannelItem should match snapshot with draft 1`] = ` membersCount={1} size={16} status="online" + testID="main.sidebar.channels_list.list.channel_item.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -911,6 +919,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = ` membersCount={1} size={16} status="online" + testID="main.sidebar.channels_list.list.channel_item.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js index 369c063ae..82254c1f8 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js @@ -167,6 +167,7 @@ export default class ChannelItem extends PureComponent { type={channel.type} isArchived={isArchived} isBot={isBot} + testID={`${testID}.channel_icon`} /> ); diff --git a/app/screens/channel_info/__snapshots__/channel_info.test.js.snap b/app/screens/channel_info/__snapshots__/channel_info.test.js.snap index d9e8a6c2a..dbc61d281 100644 --- a/app/screens/channel_info/__snapshots__/channel_info.test.js.snap +++ b/app/screens/channel_info/__snapshots__/channel_info.test.js.snap @@ -40,6 +40,7 @@ exports[`channelInfo should match snapshot 1`] = ` onPermalinkPress={[Function]} purpose="Purpose" status="status" + testID="channel_info.header" theme={ Object { "awayIndicator": "#ffbc42", diff --git a/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap b/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap index e65e5574e..791ebe124 100644 --- a/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap +++ b/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap @@ -35,6 +35,7 @@ exports[`channel_info_header should match snapshot 1`] = ` membersCount={3} size={24} status="status" + testID="channel_info.header.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -77,6 +78,7 @@ exports[`channel_info_header should match snapshot 1`] = ` "fontWeight": "600", } } + testID="channel_info.header.display_name" > Channel name @@ -338,6 +340,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is membersCount={3} size={24} status="status" + testID="channel_info.header.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -380,6 +383,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is "fontWeight": "600", } } + testID="channel_info.header.display_name" > Channel name @@ -669,6 +673,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its membersCount={3} size={24} status="status" + testID="channel_info.header.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -711,6 +716,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its "fontWeight": "600", } } + testID="channel_info.header.display_name" > Channel name @@ -972,6 +978,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = ` membersCount={3} size={24} status="status" + testID="channel_info.header.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -1014,6 +1021,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = ` "fontWeight": "600", } } + testID="channel_info.header.display_name" > Channel name @@ -1303,6 +1311,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`] membersCount={3} size={24} status="status" + testID="channel_info.header.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -1345,6 +1354,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`] "fontWeight": "600", } } + testID="channel_info.header.display_name" > Channel name @@ -1627,6 +1637,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu membersCount={3} size={24} status="status" + testID="channel_info.header.channel_icon" theme={ Object { "awayIndicator": "#ffbc42", @@ -1669,6 +1680,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu "fontWeight": "600", } } + testID="channel_info.header.display_name" > Channel name diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js index 9fcc3af88..4c7bed6b0 100644 --- a/app/screens/channel_info/channel_info.js +++ b/app/screens/channel_info/channel_info.js @@ -205,6 +205,7 @@ export default class ChannelInfo extends PureComponent { isTeammateGuest={isTeammateGuest} hasGuests={currentChannelGuestCount > 0} isGroupConstrained={currentChannel.group_constrained} + testID='channel_info.header' /> } diff --git a/app/screens/channel_info/channel_info_header.js b/app/screens/channel_info/channel_info_header.js index b6689c8e3..78cccbd4c 100644 --- a/app/screens/channel_info/channel_info_header.js +++ b/app/screens/channel_info/channel_info_header.js @@ -42,6 +42,7 @@ export default class ChannelInfoHeader extends React.PureComponent { isTeammateGuest: PropTypes.bool.isRequired, hasGuests: PropTypes.bool.isRequired, isGroupConstrained: PropTypes.bool, + testID: PropTypes.string, timeZone: PropTypes.string, }; @@ -140,6 +141,7 @@ export default class ChannelInfoHeader extends React.PureComponent { isArchived, isBot, isGroupConstrained, + testID, timeZone, } = this.props; @@ -162,11 +164,13 @@ export default class ChannelInfoHeader extends React.PureComponent { type={type} isArchived={isArchived} isBot={isBot} + testID={`${testID}.channel_icon`} /> {displayName} diff --git a/app/screens/channel_info/channel_info_header.test.js b/app/screens/channel_info/channel_info_header.test.js index ac6731981..83f0ee8c4 100644 --- a/app/screens/channel_info/channel_info_header.test.js +++ b/app/screens/channel_info/channel_info_header.test.js @@ -43,6 +43,7 @@ describe('channel_info_header', () => { isTeammateGuest: false, hasGuests: false, isGroupConstrained: false, + testID: 'channel_info.header', }; test('should match snapshot', async () => { diff --git a/app/screens/more_dms/more_dms.js b/app/screens/more_dms/more_dms.js index 6ff6e30ed..134bd5a36 100644 --- a/app/screens/more_dms/more_dms.js +++ b/app/screens/more_dms/more_dms.js @@ -364,7 +364,7 @@ export default class MoreDirectMessages extends PureComponent { selectable={true} selected={selected} enabled={true} - testID='more_direct_messages.user' + testID='more_direct_messages.custom_list.user_item' /> ); }; @@ -516,7 +516,7 @@ export default class MoreDirectMessages extends PureComponent { onLoadMore={this.getProfiles} onRowPress={this.handleSelectProfile} renderItem={this.renderItem} - testID='more_direct_messages.list' + testID='more_direct_messages.custom_list' theme={theme} /> diff --git a/detox/e2e/support/ui/screen/channel_info.js b/detox/e2e/support/ui/screen/channel_info.js index d5dcb5eef..119cb5e93 100644 --- a/detox/e2e/support/ui/screen/channel_info.js +++ b/detox/e2e/support/ui/screen/channel_info.js @@ -10,7 +10,8 @@ class ChannelInfoScreen { channelInfoScreen: 'channel_info.screen', channelInfoScrollView: 'channel_info.scroll_view', closeChannelInfoButton: 'close.channel_info.button', - channelIconGMMemberCount: 'channel_icon.gm_member_count', + headerChannelIconGMMemberCount: 'channel_info.header.channel_icon.gm_member_count', + headerDisplayName: 'channel_info.header.display_name', favoritePreferenceAction: 'channel_info.favorite.action', favoriteSwitchFalse: 'channel_info.favorite.action.switch.false', favoriteSwitchTrue: 'channel_info.favorite.action.switch.true', @@ -33,7 +34,8 @@ class ChannelInfoScreen { channelInfoScreen = element(by.id(this.testID.channelInfoScreen)); channelInfoScrollView = element(by.id(this.testID.channelInfoScrollView)); closeChannelInfoButton = element(by.id(this.testID.closeChannelInfoButton)); - channelIconGMMemberCount = element(by.id(this.testID.channelIconGMMemberCount)); + headerChannelIconGMMemberCount = element(by.id(this.testID.headerChannelIconGMMemberCount)); + headerDisplayName = element(by.id(this.testID.headerDisplayName)); favoritePreferenceAction = element(by.id(this.testID.favoritePreferenceAction)); favoriteSwitchFalse = element(by.id(this.testID.favoriteSwitchFalse)); favoriteSwitchTrue = element(by.id(this.testID.favoriteSwitchTrue)); diff --git a/detox/e2e/support/ui/screen/more_direct_messages.js b/detox/e2e/support/ui/screen/more_direct_messages.js index 677a8e2f8..55c3d0059 100644 --- a/detox/e2e/support/ui/screen/more_direct_messages.js +++ b/detox/e2e/support/ui/screen/more_direct_messages.js @@ -1,21 +1,53 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {MainSidebar} from '@support/ui/component'; +import { + MainSidebar, + SearchBar, +} from '@support/ui/component'; class MoreDirectMessagesScreen { testID = { moreDirectMessagesScreen: 'more_direct_messages.screen', + moreDirectMessagesPrefix: 'more_direct_messages.', startButton: 'more_direct_messages.start.button', - list: 'more_direct_messages.list', - user: 'more_direct_messages.user', + usersList: 'more_direct_messages.custom_list', + userItem: 'more_direct_messages.custom_list.user_item', + userItemDisplayUsername: 'more_direct_messages.custom_list.user_item.display_username', } moreDirectMessagesScreen = element(by.id(this.testID.moreDirectMessagesScreen)); startButton = element(by.id(this.testID.startButton)); + usersList = element(by.id(this.testID.usersList)); + + // convenience props + searchBar = SearchBar.getSearchBar(this.testID.moreDirectMessagesPrefix); + searchInput = SearchBar.getSearchInput(this.testID.moreDirectMessagesPrefix); + cancelButton = SearchBar.getCancelButton(this.testID.moreDirectMessagesPrefix); + clearButton = SearchBar.getClearButton(this.testID.moreDirectMessagesPrefix); + + 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.user).withAncestor(by.id(this.testID.list))).atIndex(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 () => { @@ -30,6 +62,12 @@ class MoreDirectMessagesScreen { return this.toBeVisible(); } + + hasUserDisplayUsernameAtIndex = async (index, displayUsername) => { + await expect( + this.getDisplayUsernameAtIndex(index), + ).toHaveText(displayUsername); + } } const moreDirectMessagesScreen = new MoreDirectMessagesScreen(); diff --git a/detox/e2e/test/channel_info/header.e2e.js b/detox/e2e/test/channel_info/header.e2e.js index 73b0b3513..d249dac7c 100644 --- a/detox/e2e/test/channel_info/header.e2e.js +++ b/detox/e2e/test/channel_info/header.e2e.js @@ -52,7 +52,7 @@ describe('Channel Info Header', () => { await ChannelInfoScreen.open(); // * Verify GM member count is 3 - await expect(element(by.id(ChannelInfoScreen.testID.channelIconGMMemberCount)).atIndex(0)).toHaveText('3'); + await expect(ChannelInfoScreen.headerChannelIconGMMemberCount).toHaveText('3'); // # Close channel info screen await ChannelInfoScreen.close(); diff --git a/detox/e2e/test/smoke_test/group_message_channel.e2e.js b/detox/e2e/test/smoke_test/group_message_channel.e2e.js new file mode 100644 index 000000000..74faf2521 --- /dev/null +++ b/detox/e2e/test/smoke_test/group_message_channel.e2e.js @@ -0,0 +1,90 @@ +// 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 { + ChannelInfoScreen, + ChannelScreen, + MoreDirectMessagesScreen, +} from '@support/ui/screen'; +import { + User, + Setup, + Team, +} from '@support/server_api'; +import {getRandomId} from '@support/utils'; + +describe('Group Message Channel', () => { + const searchTerm = getRandomId(); + let testUser; + let testOtherUser1; + let testOtherUser2; + + beforeAll(async () => { + const {user, team} = await Setup.apiInit({userOptions: {prefix: `${searchTerm}-3-`}}); + testUser = user; + + ({user: testOtherUser1} = await User.apiCreateUser({prefix: `${searchTerm}-1-`})); + await Team.apiAddUserToTeam(testOtherUser1.id, team.id); + + ({user: testOtherUser2} = await User.apiCreateUser({prefix: `${searchTerm}-2-`})); + await Team.apiAddUserToTeam(testOtherUser2.id, team.id); + + // # Open channel screen + await ChannelScreen.open(testUser); + }); + + afterAll(async () => { + await ChannelScreen.logout(); + }); + + it('MM-T3213 should be able to create a GM channel and post a message to the group', async () => { + const { + openMainSidebar, + postMessage, + } = ChannelScreen; + const { + getUserByDisplayUsername, + hasUserDisplayUsernameAtIndex, + getUserAtIndex, + searchInput, + startButton, + } = MoreDirectMessagesScreen; + + // # Open more direct messages screen + await openMainSidebar(); + await MoreDirectMessagesScreen.open(); + + // * Verify all 3 users are listed + await searchInput.typeText(searchTerm); + await hasUserDisplayUsernameAtIndex(0, `@${testOtherUser1.username}`); + await hasUserDisplayUsernameAtIndex(1, `@${testOtherUser2.username}`); + await hasUserDisplayUsernameAtIndex(2, `@${testUser.username} - you`); + + // # Select profiles of other 2 users + await getUserAtIndex(0).tap(); + await searchInput.typeText(searchTerm); + await getUserAtIndex(1).tap(); + + // * Verify current user is not displayed in the list + await expect(getUserByDisplayUsername(`@${testUser.username} - you`)).not.toBeVisible(); + + // # Create a GM with selected profiles + await startButton.tap(); + + // * Verify GM channel is created + await ChannelInfoScreen.open(); + await expect(ChannelInfoScreen.headerDisplayName).toHaveText(`${testOtherUser1.username}, ${testOtherUser2.username}`); + await ChannelInfoScreen.close(); + + // * Verify current user can post message to GM channel + const message = Date.now().toString(); + await postMessage(message); + await expect(element(by.text(message))).toBeVisible(); + }); +});