MM-28539 Fix wrong participant count in GM info (#4887)

* Fix e2e flaky tests

* MM-28539 Fix wrong participant count in GM info

* Apply suggestions from code review

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>

* Apply suggestions from code review

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>

* Update detox/e2e/test/autocomplete/edit_post.e2e.js

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>

Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>
This commit is contained in:
Elias Nahum 2020-10-19 10:13:30 -03:00 committed by GitHub
parent 5579c1fc29
commit fbe2a9e575
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 188 additions and 42 deletions

View file

@ -186,6 +186,7 @@ export function goToScreen(name, title, passProps = {}, options = {}) {
backButton: {
color: theme.sidebarHeaderTextColor,
title: '',
testID: 'screen.back.button',
},
background: {
color: theme.sidebarHeaderBg,

View file

@ -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);
});
});
});

View file

@ -84,6 +84,7 @@ export default class ChannelIcon extends React.PureComponent {
<CompassIcon
name='archive-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
testID='channel_icon.archive'
/>
);
} else if (isBot) {
@ -91,6 +92,7 @@ export default class ChannelIcon extends React.PureComponent {
<CompassIcon
name='robot-happy'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: -1.5, top: -1}]}
testID='channel_icon.bot'
/>
);
} else if (hasDraft) {
@ -98,6 +100,7 @@ export default class ChannelIcon extends React.PureComponent {
<CompassIcon
name='pencil-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
testID='channel_icon.draft'
/>
);
} else if (type === General.OPEN_CHANNEL) {
@ -105,6 +108,7 @@ export default class ChannelIcon extends React.PureComponent {
<CompassIcon
name='globe'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
testID='channel_icon.public'
/>
);
} else if (type === General.PRIVATE_CHANNEL) {
@ -112,12 +116,17 @@ export default class ChannelIcon extends React.PureComponent {
<CompassIcon
name='lock-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
testID='channel_icon.private'
/>
);
} else if (type === General.GM_CHANNEL) {
const fontSize = (size - 10);
icon = (
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size + 1, height: size + 1}]}>
<Text style={[style.group, unreadGroup, activeGroup, {fontSize: (size - 4)}]}>
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size, height: size}]}>
<Text
style={[style.group, unreadGroup, activeGroup, {fontSize}]}
testID='channel_icon.gm_member_count'
>
{membersCount}
</Text>
</View>
@ -129,6 +138,7 @@ export default class ChannelIcon extends React.PureComponent {
<CompassIcon
name='clock'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.awayIndicator}]}
testID='channel_icon.away'
/>
);
break;
@ -137,6 +147,7 @@ export default class ChannelIcon extends React.PureComponent {
<CompassIcon
name='minus-circle'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.dndIndicator}]}
testID='channel_icon.dnd'
/>
);
break;
@ -145,6 +156,7 @@ export default class ChannelIcon extends React.PureComponent {
<CompassIcon
name='check-circle'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.onlineIndicator}]}
testID='channel_icon.online'
/>
);
break;
@ -153,6 +165,7 @@ export default class ChannelIcon extends React.PureComponent {
<CompassIcon
name='circle-outline'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: offlineColor}]}
testID='channel_icon.offline'
/>
);
break;

View file

@ -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}
>
<View style={[style.container, padding(this.props.isLandscape)]}>
{this.props.selectable &&

View file

@ -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}
/>
);
};

View file

@ -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}
>
<View style={style.profileContainer}>
<ProfilePicture

View file

@ -16,6 +16,7 @@ export default class KeyboardLayout extends PureComponent {
static propTypes = {
children: PropTypes.node,
style: CustomPropTypes.Style,
testID: PropTypes.string,
};
constructor(props) {
@ -60,7 +61,10 @@ export default class KeyboardLayout extends PureComponent {
}
return (
<View style={layoutStyle}>
<View
style={layoutStyle}
testID={this.props.testID}
>
{this.props.children}
</View>
);

View file

@ -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: {},
};

View file

@ -328,11 +328,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');

View file

@ -253,6 +253,7 @@ export default class SettingsSidebarBase extends PureComponent {
isDestructor={true}
onPress={this.logout}
separator={false}
testID='sidebar.settings.logout'
theme={theme}
/>
</View>

View file

@ -271,6 +271,7 @@ export default class SlideUpPanel extends PureComponent {
<Animated.View
style={[styles.backdrop, backdropStyle]}
pointerEvents='box-only'
testID='slide_up_panel'
/>
</PanGestureHandler>
</Animated.View>

View file

@ -184,6 +184,7 @@ export default class ChannelBase extends PureComponent {
leftButtons: [{
id: 'close-info',
icon: source,
testID: 'screen.channel_info.close',
}],
},
};

View file

@ -12,6 +12,7 @@ exports[`SettingDrawerButton should match, full snapshot 1`] = `
"width": 44,
}
}
testID="sidebar.settings.button"
>
<View
style={

View file

@ -70,6 +70,7 @@ export class SettingDrawerButton extends PureComponent {
accessibilityHint={accessibilityHint}
accessibilityLabel={accessibilityLabel}
accessibilityRole='button'
testID='sidebar.settings.button'
onPress={this.handlePress}
style={style.container}
>

View file

@ -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={

View file

@ -158,7 +158,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
<View style={[style.channelNameContainer, style.row, padding(isLandscape)]}>
<ChannelIcon
isInfo={true}
membersCount={memberCount - 1}
membersCount={memberCount}
size={24}
status={status}
theme={theme}

View file

@ -25,7 +25,7 @@ function mapStateToProps(state) {
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
const currentChannelCreatorName = displayUsername(currentChannelCreator, teammateNameDisplay);
const currentChannelStats = getCurrentChannelStats(state);
const currentChannelMemberCount = currentChannelStats && currentChannelStats.member_count;
let currentChannelMemberCount = currentChannelStats && currentChannelStats.member_count;
let currentChannelGuestCount = (currentChannelStats && currentChannelStats.guest_count) || 0;
const currentUserId = getCurrentUserId(state);
@ -46,6 +46,10 @@ function mapStateToProps(state) {
}
}
if (currentChannel.type === General.GM_CHANNEL) {
currentChannelMemberCount = currentChannel.display_name.split(',').length;
}
return {
currentChannel,
currentChannelCreatorName,

View file

@ -47,6 +47,7 @@ export default class EditPost extends PureComponent {
leftButton = {
id: 'close-edit-post',
testID: 'edit_post.close',
};
rightButton = {

View file

@ -350,6 +350,7 @@ export default class MoreDirectMessages extends PureComponent {
text: formatMessage({id: 'mobile.more_dms.start', defaultMessage: 'Start'}),
showAsAction: 'always',
enabled: startEnabled,
testID: START_BUTTON,
}],
});
};
@ -365,6 +366,7 @@ export default class MoreDirectMessages extends PureComponent {
selectable={true}
selected={selected}
enabled={true}
testID='more_dms.user'
/>
);
};
@ -475,7 +477,7 @@ export default class MoreDirectMessages extends PureComponent {
}
return (
<KeyboardLayout>
<KeyboardLayout testID='direct_channels_screen'>
<StatusBar/>
<View style={style.searchBar}>
<View style={padding(isLandscape)}>
@ -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}
/>
</KeyboardLayout>

View file

@ -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();
}

View file

@ -7,8 +7,7 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {toChannelScreen} from '@support/ui/screen';
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
@ -17,6 +16,10 @@ describe('Autocomplete', () => {
await toChannelScreen(user);
});
afterAll(async () => {
await logoutUser();
});
it('MM-T3390 should render autocomplete in channel header edit screen', async () => {
// # Open channel info modal
await element(by.id('channel.title.button')).tap();
@ -29,5 +32,11 @@ describe('Autocomplete', () => {
// * Expect autocomplete to render
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
// Go to previous screen
await element(by.id('screen.back.button')).tap();
// close channel info screen
await element(by.id('screen.channel_info.close')).tap();
});
});

View file

@ -7,8 +7,8 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {toChannelScreen} from '@support/ui/screen';
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {isAndroid, timeouts, wait} from '@support/utils';
import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
@ -17,6 +17,10 @@ describe('Autocomplete', () => {
await toChannelScreen(user);
});
afterAll(async () => {
await logoutUser();
});
it('MM-T3391 should render autocomplete in post edit screen', async () => {
const message = Date.now().toString();
@ -28,9 +32,19 @@ describe('Autocomplete', () => {
// # Tap the send button
await element(by.id('send_button')).tap();
// Explicitly wait on Android before verifying error message
if (isAndroid()) {
await wait(timeouts.ONE_SEC);
}
// # Open edit screen
await element(by.text(message)).longPress();
await element(by.text('Edit')).tap();
const slide = element(by.id('slide_up_panel'));
await slide.swipe('up');
const edit = element(by.text('Edit'));
await edit.tap();
// # Open autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
@ -38,5 +52,7 @@ describe('Autocomplete', () => {
// * Expect at_mention autocomplete to render
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await element(by.id('edit_post.close')).tap();
});
});

View file

@ -7,8 +7,7 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {toChannelScreen} from '@support/ui/screen';
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
@ -18,13 +17,17 @@ describe('Autocomplete', () => {
});
beforeEach(async () => {
await device.reloadReactNative();
// # Select post draft
await expect(element(by.id('channel_screen'))).toBeVisible();
await element(by.id('post_input')).clearText();
await element(by.id('post_input')).tap();
});
afterAll(async () => {
await element(by.id('post_input')).clearText();
await logoutUser();
});
it('MM-T3392_1 should render emoji_suggestion component', async () => {
// # Type ":" to activate emoji suggestions
await expect(element(by.id('autocomplete.emoji_suggestion.list'))).not.toExist();

View file

@ -7,8 +7,7 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {toChannelScreen} from '@support/ui/screen';
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
@ -24,6 +23,11 @@ describe('Autocomplete', () => {
await element(by.id('channel.search.button')).tap();
});
afterAll(async () => {
await device.reloadReactNative();
await logoutUser();
});
it('MM-T3393_1 should render at_mention component', async () => {
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
@ -47,8 +51,8 @@ describe('Autocomplete', () => {
it('MM-T3393_3 should render date_suggestion component', async () => {
await expect(element(by.id('autocomplete.date_suggestion'))).not.toExist();
// # Tap "before:" modifier
await element(by.id('search_before.section')).tap();
// # Tap "on:" modifier
await element(by.id('search_on.section')).tap();
// * Expect date suggestion to render
await expect(element(by.id('autocomplete.date_suggestion'))).toExist();

View file

@ -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();
});
});

View file

@ -7,7 +7,7 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {toChannelScreen} from '@support/ui/screen';
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Setup} from '@support/server_api';
@ -18,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();

View file

@ -9,7 +9,7 @@
import jestExpect 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', () => {
@ -22,6 +22,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();

View file

@ -9,7 +9,7 @@
import {Setup, System} 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', () => {
@ -25,6 +25,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();
@ -49,11 +53,11 @@ describe('On boarding', () => {
// # Explicitly wait on Android before verifying error message
if (isAndroid()) {
await wait(timeouts.ONE_MIN);
await wait(timeouts.ONE_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('Please enter a valid server URL');
});
@ -61,7 +65,9 @@ describe('On boarding', () => {
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();
@ -74,11 +80,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.');
});

View file

@ -7,8 +7,7 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {toChannelScreen} from '@support/ui/screen';
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Setup, Channel} from '@support/server_api';
describe('Unread channels', () => {
@ -29,6 +28,10 @@ describe('Unread channels', () => {
await toChannelScreen(user);
});
afterAll(async () => {
await logoutUser();
});
it('MM-T3187 Unread channels sort at top', async () => {
// # Open channel drawer (with at least one unread channel)
await element(by.id('channel_drawer.button')).tap();
@ -57,5 +60,6 @@ describe('Unread channels', () => {
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(2)).toHaveText('Off-Topic');
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(3)).toHaveText('Town Square');
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(4)).toHaveText(zChannel.display_name);
await element(by.text(aChannel.display_name)).tap();
});
});