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.');
});