diff --git a/app/actions/navigation/index.js b/app/actions/navigation/index.js index 1ad522706..6d4ea2636 100644 --- a/app/actions/navigation/index.js +++ b/app/actions/navigation/index.js @@ -186,6 +186,7 @@ export function goToScreen(name, title, passProps = {}, options = {}) { backButton: { color: theme.sidebarHeaderTextColor, title: '', + testID: 'screen.back.button', }, background: { color: theme.sidebarHeaderBg, diff --git a/app/actions/navigation/index.test.js b/app/actions/navigation/index.test.js index de94c3bcb..0ef759de7 100644 --- a/app/actions/navigation/index.test.js +++ b/app/actions/navigation/index.test.js @@ -188,6 +188,7 @@ describe('@actions/navigation', () => { backButton: { color: theme.sidebarHeaderTextColor, title: '', + testID: 'screen.back.button', }, background: { color: theme.sidebarHeaderBg, @@ -494,4 +495,4 @@ describe('@actions/navigation', () => { expect(popToRoot).toHaveBeenCalledWith(topComponentId); expect(EventEmitter.emit).toHaveBeenCalledWith(NavigationTypes.NAVIGATION_DISMISS_AND_POP_TO_ROOT); }); -}); \ No newline at end of file +}); diff --git a/app/components/channel_icon.js b/app/components/channel_icon.js index 6117b99ae..4e3b2f3a1 100644 --- a/app/components/channel_icon.js +++ b/app/components/channel_icon.js @@ -84,6 +84,7 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (isBot) { @@ -91,6 +92,7 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (hasDraft) { @@ -98,6 +100,7 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (type === General.OPEN_CHANNEL) { @@ -105,6 +108,7 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (type === General.PRIVATE_CHANNEL) { @@ -112,12 +116,17 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (type === General.GM_CHANNEL) { + const fontSize = (size - 10); icon = ( - - + + {membersCount} @@ -129,6 +138,7 @@ export default class ChannelIcon extends React.PureComponent { ); break; @@ -137,6 +147,7 @@ export default class ChannelIcon extends React.PureComponent { ); break; @@ -145,6 +156,7 @@ export default class ChannelIcon extends React.PureComponent { ); break; @@ -153,6 +165,7 @@ export default class ChannelIcon extends React.PureComponent { ); break; diff --git a/app/components/custom_list/custom_list_row.js b/app/components/custom_list/custom_list_row.js index b152f0ed3..4e02cac7a 100644 --- a/app/components/custom_list/custom_list_row.js +++ b/app/components/custom_list/custom_list_row.js @@ -22,6 +22,7 @@ export default class CustomListRow extends React.PureComponent { children: CustomPropTypes.Children, item: PropTypes.object, isLandscape: PropTypes.bool.isRequired, + testID: PropTypes.string, }; static defaultProps = { @@ -35,6 +36,7 @@ export default class CustomListRow extends React.PureComponent { touchable={Boolean(this.props.enabled && this.props.onPress)} onPress={this.props.onPress} style={style.touchable} + testID={this.props.testID} > {this.props.selectable && diff --git a/app/components/custom_list/index.js b/app/components/custom_list/index.js index 33db287a5..5a2b1a371 100644 --- a/app/components/custom_list/index.js +++ b/app/components/custom_list/index.js @@ -33,6 +33,7 @@ export default class CustomList extends PureComponent { theme: PropTypes.object.isRequired, shouldRenderSeparator: PropTypes.bool, isLandscape: PropTypes.bool.isRequired, + testID: PropTypes.string, }; static defaultProps = { @@ -148,6 +149,7 @@ export default class CustomList extends PureComponent { scrollEventThrottle={60} style={style.list} stickySectionHeadersEnabled={true} + testID={this.props.testID} /> ); }; @@ -201,6 +203,7 @@ export default class CustomList extends PureComponent { sections={data} style={style.list} stickySectionHeadersEnabled={false} + testID={this.props.testID} /> ); }; 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 fec7302a2..3785b9745 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 @@ -24,6 +24,7 @@ export default class UserListRow extends React.PureComponent { theme: PropTypes.object.isRequired, user: PropTypes.object.isRequired, teammateNameDisplay: PropTypes.string.isRequired, + testID: PropTypes.string, ...CustomListRow.propTypes, }; @@ -73,6 +74,7 @@ export default class UserListRow extends React.PureComponent { selectable={selectable} selected={selected} isLandscape={isLandscape} + testID={this.props.testID} > + {this.props.children} ); diff --git a/app/components/post_body/post_body.js b/app/components/post_body/post_body.js index d760b8fde..1500993a6 100644 --- a/app/components/post_body/post_body.js +++ b/app/components/post_body/post_body.js @@ -72,7 +72,7 @@ export default class PostBody extends PureComponent { shouldRenderJumboEmoji: PropTypes.bool.isRequired, theme: PropTypes.object, location: PropTypes.string, - mentionKeys: PropTypes.array.isRequired, + mentionKeys: PropTypes.array, }; static defaultProps = { @@ -80,6 +80,7 @@ export default class PostBody extends PureComponent { onFailedPostPress: emptyFunction, onPress: emptyFunction, replyBarStyle: [], + mentionKeys: [], message: '', postProps: {}, }; diff --git a/app/components/sidebars/main/channels_list/list/list.js b/app/components/sidebars/main/channels_list/list/list.js index 0022e904c..a376c451f 100644 --- a/app/components/sidebars/main/channels_list/list/list.js +++ b/app/components/sidebars/main/channels_list/list/list.js @@ -327,11 +327,7 @@ export default class List extends PureComponent { renderSectionHeader = ({section}) => { const {styles, isLandscape} = this.props; const {intl} = this.context; - const { - action, - defaultMessage, - id, - } = section; + const {action, defaultMessage, id} = section; const anchor = (id === 'sidebar.types.recent' || id === 'mobile.channel_list.channels'); diff --git a/app/components/sidebars/settings/settings_sidebar_base.js b/app/components/sidebars/settings/settings_sidebar_base.js index 7669aa2f9..7bdd86326 100644 --- a/app/components/sidebars/settings/settings_sidebar_base.js +++ b/app/components/sidebars/settings/settings_sidebar_base.js @@ -253,6 +253,7 @@ export default class SettingsSidebarBase extends PureComponent { isDestructor={true} onPress={this.logout} separator={false} + testID='sidebar.settings.logout' theme={theme} /> diff --git a/app/components/slide_up_panel/slide_up_panel.js b/app/components/slide_up_panel/slide_up_panel.js index 610da04be..1d52198f0 100644 --- a/app/components/slide_up_panel/slide_up_panel.js +++ b/app/components/slide_up_panel/slide_up_panel.js @@ -271,6 +271,7 @@ export default class SlideUpPanel extends PureComponent { diff --git a/app/screens/channel/channel_base.js b/app/screens/channel/channel_base.js index bf3f85787..dab9ef319 100644 --- a/app/screens/channel/channel_base.js +++ b/app/screens/channel/channel_base.js @@ -184,6 +184,7 @@ export default class ChannelBase extends PureComponent { leftButtons: [{ id: 'close-info', icon: source, + testID: 'screen.channel_info.close', }], }, }; diff --git a/app/screens/channel/channel_nav_bar/__snapshots__/settings_drawer_button.test.js.snap b/app/screens/channel/channel_nav_bar/__snapshots__/settings_drawer_button.test.js.snap index 3a18e4496..333fd3215 100644 --- a/app/screens/channel/channel_nav_bar/__snapshots__/settings_drawer_button.test.js.snap +++ b/app/screens/channel/channel_nav_bar/__snapshots__/settings_drawer_button.test.js.snap @@ -12,6 +12,7 @@ exports[`SettingDrawerButton should match, full snapshot 1`] = ` "width": 44, } } + testID="sidebar.settings.button" > 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 82a94d19f..92d4c5aa0 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 @@ -33,7 +33,7 @@ exports[`channel_info_header should match snapshot 1`] = ` isBot={false} isInfo={true} isUnread={false} - membersCount={2} + membersCount={3} size={24} status="status" theme={ @@ -343,7 +343,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is isBot={false} isInfo={true} isUnread={false} - membersCount={2} + membersCount={3} size={24} status="status" theme={ @@ -684,7 +684,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its isBot={false} isInfo={true} isUnread={false} - membersCount={2} + membersCount={3} size={24} status="status" theme={ @@ -994,7 +994,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = ` isBot={false} isInfo={true} isUnread={false} - membersCount={2} + membersCount={3} size={24} status="status" theme={ @@ -1335,7 +1335,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`] isBot={false} isInfo={true} isUnread={false} - membersCount={2} + membersCount={3} size={24} status="status" theme={ @@ -1667,7 +1667,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu isBot={false} isInfo={true} isUnread={false} - membersCount={2} + membersCount={3} size={24} status="status" theme={ diff --git a/app/screens/channel_info/channel_info_header.js b/app/screens/channel_info/channel_info_header.js index fa8fa3961..3c427f386 100644 --- a/app/screens/channel_info/channel_info_header.js +++ b/app/screens/channel_info/channel_info_header.js @@ -158,7 +158,7 @@ export default class ChannelInfoHeader extends React.PureComponent { ); }; @@ -475,7 +477,7 @@ export default class MoreDirectMessages extends PureComponent { } return ( - + @@ -517,6 +519,7 @@ export default class MoreDirectMessages extends PureComponent { onLoadMore={this.getProfiles} onRowPress={this.handleSelectProfile} renderItem={this.renderItem} + testID='more_dms.list' theme={theme} /> diff --git a/detox/e2e/support/ui/screen.js b/detox/e2e/support/ui/screen.js index 75c0facc2..020e53583 100644 --- a/detox/e2e/support/ui/screen.js +++ b/detox/e2e/support/ui/screen.js @@ -54,3 +54,9 @@ export async function toChannelScreen(user) { await fulfillSelectServerScreen(serverUrl); await fulfillLoginScreen(user); } + +export async function logoutUser() { + await element(by.id('sidebar.settings.button')).tap(); + await element(by.text('Logout')).tap(); + await waitFor(element(by.id('select_server_screen'))).toBeVisible(); +} diff --git a/detox/e2e/test/channel_info/header.e2e.js b/detox/e2e/test/channel_info/header.e2e.js new file mode 100644 index 000000000..0f3bdd243 --- /dev/null +++ b/detox/e2e/test/channel_info/header.e2e.js @@ -0,0 +1,54 @@ +// 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 {logoutUser, toChannelScreen} from '@support/ui/screen'; +import {timeouts, wait} from '@support/utils'; + +import {Setup} from '@support/server_api'; + +describe('Channel Info Header', () => { + beforeAll(async () => { + const {user} = await Setup.apiInit(); + await toChannelScreen(user); + }); + + afterAll(async () => { + await logoutUser(); + }); + + it('MM-T3406 should render correct GM member count in channel info header', async () => { + // # Open drawer + await element(by.id('channel_drawer.button')).tap(); + await expect(element(by.text('DIRECT MESSAGES'))).toBeVisible(); + + // # Open Direct Channels screen + await element(by.id('action_button_sidebar.direct')).tap(); + await expect(element(by.id('direct_channels_screen'))).toBeVisible(); + + // # Wait for some profiles to load + await wait(timeouts.ONE_SEC); + + // # Select 3 profiles + await element(by.id('more_dms.user').withAncestor(by.id('more_dms.list'))).atIndex(0).tap(); + await element(by.id('more_dms.user').withAncestor(by.id('more_dms.list'))).atIndex(1).tap(); + await element(by.id('more_dms.user').withAncestor(by.id('more_dms.list'))).atIndex(2).tap(); + + // # Create a GM with selected profiles + await element(by.id('start-conversation')).tap(); + + // # Open channel info modal + await element(by.id('channel.title.button')).tap(); + + // * Verify GM member count is 3 + await expect(element(by.id('channel_icon.gm_member_count')).atIndex(0)).toHaveText('3'); + + // # Close channel info screen + await element(by.id('screen.channel_info.close')).tap(); + }); +}); diff --git a/detox/e2e/test/messaging/message_posting.e2e.js b/detox/e2e/test/messaging/message_posting.e2e.js index 58de38737..c5eece06a 100644 --- a/detox/e2e/test/messaging/message_posting.e2e.js +++ b/detox/e2e/test/messaging/message_posting.e2e.js @@ -1,7 +1,13 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {toChannelScreen} from '@support/ui/screen'; +// ******************************************************************* +// - [#] 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 {logoutUser, toChannelScreen} from '@support/ui/screen'; import {Setup} from '@support/server_api'; @@ -12,6 +18,10 @@ describe('Messaging', () => { await toChannelScreen(user); }); + afterAll(async () => { + await logoutUser(); + }); + it('should post a message on tap to paper send button', async () => { await expect(element(by.id('channel_screen'))).toBeVisible(); await expect(element(by.id('post_input'))).toExist(); diff --git a/detox/e2e/test/messaging/tap_send_button_repeatedly.e2e.js b/detox/e2e/test/messaging/tap_send_button_repeatedly.e2e.js index ce7a3bbd5..a472738d5 100644 --- a/detox/e2e/test/messaging/tap_send_button_repeatedly.e2e.js +++ b/detox/e2e/test/messaging/tap_send_button_repeatedly.e2e.js @@ -3,7 +3,7 @@ import nodeExpect from 'expect'; -import {toChannelScreen} from '@support/ui/screen'; +import {logoutUser, toChannelScreen} from '@support/ui/screen'; import {Channel, Post, Setup} from '@support/server_api'; describe('Messaging', () => { @@ -16,6 +16,10 @@ describe('Messaging', () => { await toChannelScreen(user); }); + afterAll(async () => { + await logoutUser(); + }); + it('MM-T109 User can\'t send the same message repeatedly', async () => { const message = Date.now().toString(); diff --git a/detox/e2e/test/on_boarding/login.e2e.js b/detox/e2e/test/on_boarding/login.e2e.js index 9149b26f2..4fc35ec29 100644 --- a/detox/e2e/test/on_boarding/login.e2e.js +++ b/detox/e2e/test/on_boarding/login.e2e.js @@ -3,7 +3,7 @@ import {Setup} from '@support/server_api'; import {serverUrl} from '@support/test_config'; -import {fulfillSelectServerScreen} from '@support/ui/screen'; +import {fulfillSelectServerScreen, logoutUser} from '@support/ui/screen'; import {isAndroid, timeouts, wait} from '@support/utils'; describe('On boarding', () => { @@ -17,6 +17,10 @@ describe('On boarding', () => { await device.reloadReactNative(); }); + afterAll(async () => { + await logoutUser(); + }); + it('should show Select server screen on initial load', async () => { // Verify basic elements on Select Server screen await expect(element(by.id('select_server_screen'))).toBeVisible(); @@ -24,11 +28,38 @@ describe('On boarding', () => { await expect(element(by.id('connect_button'))).toBeVisible(); }); + it('MM-T3383 should show error on empty server URL', async () => { + await expect(element(by.id('select_server_screen'))).toBeVisible(); + + // # Enter an empty server URL + await element(by.id('server_url_input')).typeText(' '); + + // # Tap anywhere to hide keyboard + await element(by.text('Enter Server URL')).tap(); + + // * Verify that the error message does not exist + await waitFor(element(by.id('error_text'))).not.toExist().withTimeout(timeouts.HALF_SEC); + + // # Tap connect button + await element(by.id('connect_button')).tap(); + + // # Explicitly wait on Android before verifying error message + if (isAndroid()) { + await wait(timeouts.ONE_SEC); + } + + // * Verify error message + await waitFor(element(by.id('error_text'))).toBeVisible().withTimeout(timeouts.ONE_SEC); + await expect(element(by.id('error_text'))).toHaveText('Please enter a valid server URL'); + }); + it('should show error on invalid server URL', async () => { await expect(element(by.id('select_server_screen'))).toBeVisible(); // Enter invalid server URL - await element(by.id('server_url_input')).typeText('http://invalid:8065'); + const input = element(by.id('server_url_input')); + await input.clearText(); + await input.typeText('http://invalid:8065'); // Tap anywhere to hide keyboard await element(by.text('Enter Server URL')).tap(); @@ -41,11 +72,11 @@ describe('On boarding', () => { // Explicitly wait on Android before verifying error message if (isAndroid()) { - await wait(timeouts.ONE_MIN); + await wait(timeouts.TWO_SEC); } // Verify error message - await waitFor(element(by.id('error_text'))).toBeVisible().withTimeout(timeouts.ONE_MIN); + await waitFor(element(by.id('error_text'))).toBeVisible().withTimeout(timeouts.ONE_SEC); await expect(element(by.id('error_text'))).toHaveText('Cannot connect to the server. Please check your server URL and internet connection.'); });