MM-30286 Detox/E2E: Add e2e test for MM-T3249 and added useful helpers (#4990)

* MM-30286 Detox/E2E: Add e2e test for MM-T3249 and added useful helper functions

* Update app/components/sidebars/main/channels_list/channels_list.js

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* Add team icon content helper query

* Fixed reset for teams

* MM-30286 Detox/E2E: Add e2e test for MM-T3235 (#5003)

* MM-30286 Detox/E2E: Add e2e test for MM-T3255 (#5006)

* Fix merge issues

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Joseph Baylon 2020-12-15 17:24:45 -08:00 committed by GitHub
parent 8d9ed361f8
commit 7aebfd0b13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1403 additions and 162 deletions

View file

@ -15,6 +15,7 @@ exports[`Badge should match snapshot 1`] = `
},
]
}
testID="badge"
>
<View
style={
@ -70,6 +71,7 @@ exports[`Badge should match snapshot 1`] = `
},
]
}
testID="badge.unread_count"
>
99+
</Text>

View file

@ -21,6 +21,7 @@ export default class Badge extends PureComponent {
};
static propTypes = {
testID: PropTypes.string,
containerStyle: ViewPropTypes.style,
count: PropTypes.number.isRequired,
extraPaddingHorizontal: PropTypes.number,
@ -108,12 +109,15 @@ export default class Badge extends PureComponent {
};
renderText = () => {
const {containerStyle, count, style} = this.props;
const {testID, containerStyle, count, style} = this.props;
const unreadCountTestID = `${testID}.unread_count`;
const unreadIndicatorID = `${testID}.unread_indicator`;
let unreadCount = null;
let unreadIndicator = null;
if (count < 0) {
unreadIndicator = (
<View
testID={unreadIndicatorID}
style={[styles.text, this.props.countStyle]}
onLayout={this.onLayout}
/>
@ -127,6 +131,7 @@ export default class Badge extends PureComponent {
unreadCount = (
<View style={styles.verticalAlign}>
<Text
testID={unreadCountTestID}
style={[styles.text, this.props.countStyle]}
onLayout={this.onLayout}
>
@ -137,7 +142,10 @@ export default class Badge extends PureComponent {
}
return (
<View style={[styles.badgeContainer, containerStyle]}>
<View
testID={testID}
style={[styles.badgeContainer, containerStyle]}
>
<View
ref={this.setBadgeRef}
style={[styles.badge, style, {opacity: 0}]}

View file

@ -9,6 +9,7 @@ import Badge from './badge';
describe('Badge', () => {
const baseProps = {
testID: 'badge',
count: 100,
countStyle: {color: '#145dbf', fontSize: 10},
style: {backgroundColor: '#ffffff'},

View file

@ -160,13 +160,24 @@ export default class Markdown extends PureComponent {
renderText = ({context, literal}) => {
if (context.indexOf('image') !== -1) {
// If this text is displayed, it will be styled by the image component
return <Text>{literal}</Text>;
return (
<Text testID='markdown_text'>
{literal}
</Text>
);
}
// Construct the text style based off of the parents of this node since RN's inheritance is limited
const style = this.computeTextStyle(this.props.baseTextStyle, context);
return <Text style={style}>{literal}</Text>;
return (
<Text
testID='markdown_text'
style={style}
>
{literal}
</Text>
);
};
renderCodeSpan = ({context, literal}) => {

View file

@ -320,6 +320,7 @@ export default class Post extends PureComponent {
}
const replyBarStyle = this.replyBarStyle();
const rightColumnStyle = [style.rightColumn, (commentedOnPost && isLastReply && style.rightColumnPadding)];
const itemTestID = `${testID}.${post.id}`;
return (
<View
@ -327,6 +328,7 @@ export default class Post extends PureComponent {
style={[style.postStyle, highlighted]}
>
<TouchableWithFeedback
testID={itemTestID}
onPress={this.handlePress}
onLongPress={this.showPostOptions}
delayLongPress={200}

View file

@ -22,6 +22,7 @@ exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] =
],
]
}
testID="markdown_text"
>
{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}
</Text>
@ -51,6 +52,7 @@ exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
],
]
}
testID="markdown_text"
>
{username} updated the channel header from: {oldHeader} to: {newHeader}
</Text>
@ -88,6 +90,7 @@ exports[`renderSystemMessage uses renderer for OLD archived channel without a us
],
]
}
testID="markdown_text"
>
{username} archived the channel
</Text>
@ -128,6 +131,7 @@ exports[`renderSystemMessage uses renderer for archived channel 2`] = `
],
]
}
testID="markdown_text"
>
{username} archived the channel
</Text>
@ -157,6 +161,7 @@ exports[`renderSystemMessage uses renderer for unarchived channel 1`] = `
],
]
}
testID="markdown_text"
>
{username} unarchived the channel
</Text>

View file

@ -549,7 +549,7 @@ export default class PostList extends PureComponent {
}
function PostComponent({testID, postId, highlightPostId, lastPostIndex, index, ...postProps}) {
const postTestID = `${testID}.post.${postId}`;
const postTestID = `${testID}.post`;
return (
<Post

View file

@ -217,7 +217,7 @@ export default class Search extends PureComponent {
render() {
const {testID, backgroundColor, inputHeight, inputStyle, placeholderTextColor, tintColorSearch, cancelButtonStyle, tintColorDelete, titleCancelColor, searchBarRightMargin, containerHeight} = this.props;
const searchClearButtonTestID = `${testID}.search.clear.button`;
const searchBackButtonTestID = `${testID}.search.back.button`;
const searchCancelButtonTestID = `${testID}.search.cancel.button`;
const searchInputTestID = `${testID}.search.input`;
const searchBarStyle = getSearchBarStyle(
backgroundColor,
@ -263,7 +263,7 @@ export default class Search extends PureComponent {
(
<TouchableWithoutFeedback onPress={this.onCancel}>
<CompassIcon
testID={searchBackButtonTestID}
testID={searchCancelButtonTestID}
name='arrow-left'
size={this.props.backArrowSize}
color={searchBarStyle.clearIconColorAndroid}
@ -281,6 +281,7 @@ export default class Search extends PureComponent {
// Making sure the icon won't change depending on whether the input is in focus on Android devices
cancelIcon = (
<CompassIcon
testID={searchCancelButtonTestID}
name='arrow-left'
size={25}
color={searchBarStyle.clearIconColorAndroid}

View file

@ -30,6 +30,7 @@ exports[`ChannelItem should match snapshot 1`] = `
undefined,
]
}
testID="main.sidebar.channels_list.list.channel_item.channel_id"
>
<ChannelIcon
channelId="channel_id"
@ -144,6 +145,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
},
]
}
testID="main.sidebar.channels_list.list.channel_item.channel_id"
>
<ChannelIcon
channelId="channel_id"
@ -257,6 +259,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
},
]
}
testID="main.sidebar.channels_list.list.channel_item.channel_id"
>
<ChannelIcon
channelId="channel_id"
@ -370,6 +373,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
},
]
}
testID="main.sidebar.channels_list.list.channel_item.channel_id"
>
<ChannelIcon
channelId="channel_id"
@ -472,6 +476,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
undefined,
]
}
testID="main.sidebar.channels_list.list.channel_item.channel_id"
>
<ChannelIcon
channelId="channel_id"
@ -575,6 +580,7 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
undefined,
]
}
testID="main.sidebar.channels_list.list.channel_item.channel_id"
>
<ChannelIcon
channelId="channel_id"
@ -678,6 +684,7 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
undefined,
]
}
testID="main.sidebar.channels_list.list.channel_item.channel_id"
>
<ChannelIcon
channelId="channel_id"
@ -785,6 +792,7 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
undefined,
]
}
testID="main.sidebar.channels_list.list.channel_item.channel_id"
>
<ChannelIcon
channelId="channel_id"
@ -890,6 +898,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
undefined,
]
}
testID="main.sidebar.channels_list.list.channel_item.channel_id"
>
<ChannelIcon
channelId="channel_id"
@ -990,6 +999,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
"position": "relative",
}
}
testID="main.sidebar.channels_list.list.channel_item.badge"
/>
</View>
</View>

View file

@ -134,8 +134,11 @@ export default class ChannelItem extends PureComponent {
let badge;
if (mentions) {
const badgeTestID = `${testID}.badge`;
badge = (
<Badge
testID={badgeTestID}
containerStyle={style.badgeContainer}
style={style.badge}
countStyle={style.mention}
@ -167,6 +170,7 @@ export default class ChannelItem extends PureComponent {
/>
);
const itemTestID = `${testID}.${channelId}`;
const displayNameTestID = `${testID}.display_name`;
return (
@ -179,13 +183,16 @@ export default class ChannelItem extends PureComponent {
style={[style.container, mutedStyle]}
>
{extraBorder}
<View style={[style.item, extraItemStyle]}>
<View
testID={itemTestID}
style={[style.item, extraItemStyle]}
>
{icon}
<Text
testID={displayNameTestID}
style={[style.text, extraTextStyle]}
ellipsizeMode='tail'
numberOfLines={1}
testID={displayNameTestID}
>
{channelDisplayName}
</Text>

View file

@ -99,6 +99,7 @@ export default class ChannelsList extends PureComponent {
const filteredListTestID = `${testID}.filtered_list`;
const listTestID = `${testID}.list`;
const searchBarTestID = `${testID}.search_bar`;
const switchTeamsButtonTestID = `${testID}.switch_teams.button`;
let list;
if (searching) {
@ -126,7 +127,15 @@ export default class ChannelsList extends PureComponent {
fontSize: 15,
};
const leftComponent = onShowTeams ? <SwitchTeamsButton onShowTeams={onShowTeams}/> : null;
let leftComponent;
if (onShowTeams) {
leftComponent = (
<SwitchTeamsButton
testID={switchTeamsButtonTestID}
onShowTeams={onShowTeams}
/>
);
}
const title = (
<View

View file

@ -0,0 +1,95 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SwitchTeamsButton should match snapshot 1`] = `
<View
testID="main.sidebar.channels_list.switch_teams.button"
>
<ForwardRef
onPress={[Function]}
underlayColor="rgba(17,83,171,0.5)"
>
<View
style={
Object {
"alignItems": "center",
"backgroundColor": "#ffffff",
"borderRadius": 2,
"flexDirection": "row",
"height": 36,
"justifyContent": "center",
"marginRight": 12,
"width": 57,
}
}
testID="main.sidebar.channels_list.switch_teams.button.current-team-id"
>
<CompassIcon
name="chevron-left"
size={24}
style={
Object {
"color": "#1153ab",
"left": -2,
"top": 1,
}
}
/>
<Connect(TeamIcon)
styleContainer={
Object {
"alignItems": "center",
"height": 24,
"justifyContent": "center",
"width": 24,
}
}
styleImage={
Object {
"height": 24,
"width": 24,
}
}
styleText={
Object {
"fontFamily": "Open Sans",
"fontSize": 14,
}
}
teamId="current-team-id"
testID="main.sidebar.channels_list.switch_teams.button.team_icon"
/>
</View>
</ForwardRef>
<Badge
containerStyle={
Object {
"borderColor": "#145dbf",
"borderRadius": 14,
"borderWidth": 2,
"position": "absolute",
"right": 0,
"top": -9,
}
}
count={1}
countStyle={
Object {
"color": "#145dbf",
"fontSize": 10,
"fontWeight": "bold",
}
}
extraPaddingHorizontal={10}
minHeight={20}
minWidth={20}
style={
Object {
"backgroundColor": "#ffffff",
"height": 20,
"padding": 3,
}
}
testID="main.sidebar.channels_list.switch_teams.button.badge"
/>
</View>
`;

View file

@ -16,6 +16,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
export default class SwitchTeamsButton extends React.PureComponent {
static propTypes = {
testID: PropTypes.string,
currentTeamId: PropTypes.string,
onShowTeams: PropTypes.func.isRequired,
mentionCount: PropTypes.number.isRequired,
@ -29,6 +30,7 @@ export default class SwitchTeamsButton extends React.PureComponent {
render() {
const {
testID,
currentTeamId,
mentionCount,
teamsCount,
@ -49,9 +51,11 @@ export default class SwitchTeamsButton extends React.PureComponent {
const minWidth = lowMentionCount ? 8 : 20;
const badgeStyle = lowMentionCount ? styles.smallBadge : styles.badge;
const containerStyle = lowMentionCount ? styles.smallBadgeContainer : styles.badgeContainer;
const badgeTestID = `${testID}.badge`;
const badge = (
<Badge
testID={badgeTestID}
containerStyle={containerStyle}
style={badgeStyle}
countStyle={styles.mention}
@ -60,19 +64,26 @@ export default class SwitchTeamsButton extends React.PureComponent {
/>
);
const itemTestID = `${testID}.${currentTeamId}`;
const teamIconTestID = `${testID}.team_icon`;
return (
<View>
<View testID={testID}>
<TouchableHighlight
onPress={this.showTeams}
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
>
<View style={styles.switcherContainer}>
<View
testID={itemTestID}
style={styles.switcherContainer}
>
<CompassIcon
name='chevron-left'
size={24}
style={styles.switcherArrow}
/>
<TeamIcon
testID={teamIconTestID}
teamId={currentTeamId}
styleContainer={styles.teamIconContainer}
styleText={styles.teamIconText}

View file

@ -0,0 +1,26 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import Preferences from '@mm-redux/constants/preferences';
import SwitchTeamsButton from './switch_teams_button';
describe('SwitchTeamsButton', () => {
const baseProps = {
onShowTeams: jest.fn(),
testID: 'main.sidebar.channels_list.switch_teams.button',
currentTeamId: 'current-team-id',
mentionCount: 1,
teamsCount: 2,
theme: Preferences.THEMES.default,
};
test('should match snapshot', () => {
const wrapper = shallow(<SwitchTeamsButton {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
});

View file

@ -217,6 +217,7 @@ export default class MainSidebarBase extends Component {
style={style.swiperContent}
>
<TeamsList
testID='main.sidebar.teams_list'
closeMainSidebar={this.closeMainSidebar}
/>
</View>

View file

@ -36,6 +36,7 @@ const VIEWABILITY_CONFIG = {
export default class TeamsList extends PureComponent {
static propTypes = {
testID: PropTypes.string,
actions: PropTypes.shape({
handleTeamChange: PropTypes.func.isRequired,
}).isRequired,
@ -124,8 +125,12 @@ export default class TeamsList extends PureComponent {
};
renderItem = ({item}) => {
const {testID} = this.props;
const teamsListItemTestID = `${testID}.flat_list.teams_list_item`;
return (
<TeamsListItem
testID={teamsListItemTestID}
currentUrl={this.state.serverUrl}
selectTeam={this.selectTeam}
teamId={item}
@ -134,7 +139,8 @@ export default class TeamsList extends PureComponent {
};
render() {
const {hasOtherJoinableTeams, teamIds, theme} = this.props;
const {testID, hasOtherJoinableTeams, teamIds, theme} = this.props;
const flatListTestID = `${testID}.flat_list`;
const styles = getStyleSheet(theme);
let moreAction;
@ -156,6 +162,7 @@ export default class TeamsList extends PureComponent {
return (
<SafeAreaView
testID={testID}
edges={['left']}
style={styles.container}
>
@ -168,6 +175,7 @@ export default class TeamsList extends PureComponent {
{moreAction}
</View>
<FlatList
testID={flatListTestID}
extraData={this.state.serverUrl}
contentContainerStyle={this.listContentPadding()}
data={teamIds}

View file

@ -0,0 +1,115 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TeamsListItem should match snapshot 1`] = `
<View
style={
Object {
"marginTop": 10,
}
}
testID="main.sidebar.teams_list.flat_list.teams_list_item"
>
<ForwardRef
onPress={[Function]}
underlayColor="rgba(69,120,191,0.5)"
>
<View
style={
Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"marginHorizontal": 16,
"paddingVertical": 10,
}
}
testID="main.sidebar.teams_list.flat_list.teams_list_item.team-id"
>
<View>
<Connect(TeamIcon)
styleContainer={
Object {
"backgroundColor": "#ffffff",
"height": 40,
"width": 40,
}
}
styleText={
Object {
"fontSize": 18,
}
}
teamId="team-id"
testID="main.sidebar.teams_list.flat_list.teams_list_item.team_icon"
/>
<Badge
containerStyle={
Object {
"borderColor": "#145dbf",
"borderRadius": 14,
"borderWidth": 2,
"position": "absolute",
"right": -12,
"top": -10,
}
}
count={1}
countStyle={
Object {
"color": "#145dbf",
"fontSize": 10,
"fontWeight": "bold",
}
}
extraPaddingHorizontal={10}
minHeight={20}
minWidth={20}
style={
Object {
"backgroundColor": "#ffffff",
"height": 20,
"padding": 3,
}
}
testID="main.sidebar.teams_list.flat_list.teams_list_item.badge"
/>
</View>
<View
style={
Object {
"flex": 1,
"flexDirection": "column",
"marginLeft": 10,
}
}
>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={
Object {
"color": "#ffffff",
"fontSize": 18,
}
}
testID="main.sidebar.teams_list.flat_list.teams_list_item.display_name"
>
display-name
</Text>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={
Object {
"color": "rgba(255,255,255,0.5)",
"fontSize": 12,
}
}
>
current-url/name
</Text>
</View>
</View>
</ForwardRef>
</View>
`;

View file

@ -17,6 +17,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
export default class TeamsListItem extends React.PureComponent {
static propTypes = {
testID: PropTypes.string,
currentTeamId: PropTypes.string.isRequired,
currentUrl: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
@ -33,6 +34,7 @@ export default class TeamsListItem extends React.PureComponent {
render() {
const {
testID,
currentTeamId,
currentUrl,
displayName,
@ -47,9 +49,11 @@ export default class TeamsListItem extends React.PureComponent {
const minWidth = lowMentionCount ? 8 : 20;
const badgeStyle = lowMentionCount ? styles.smallBadge : styles.badge;
const containerStyle = lowMentionCount ? styles.smallBadgeContainer : styles.badgeContainer;
const badgeTestID = `${testID}.badge`;
const badge = (
<Badge
testID={badgeTestID}
containerStyle={containerStyle}
countStyle={styles.mention}
count={mentionCount}
@ -60,8 +64,13 @@ export default class TeamsListItem extends React.PureComponent {
let current;
if (teamId === currentTeamId) {
const currentTestID = `${testID}.current`;
current = (
<View style={styles.checkmarkContainer}>
<View
testID={currentTestID}
style={styles.checkmarkContainer}
>
<CompassIcon
name='check'
style={styles.checkmark}
@ -70,15 +79,26 @@ export default class TeamsListItem extends React.PureComponent {
);
}
const itemTestID = `${testID}.${teamId}`;
const displayNameTestID = `${testID}.display_name`;
const teamIconTestID = `${testID}.team_icon`;
return (
<View style={styles.teamWrapper}>
<View
testID={testID}
style={styles.teamWrapper}
>
<TouchableHighlight
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
onPress={this.selectTeam}
>
<View style={styles.teamContainer}>
<View
testID={itemTestID}
style={styles.teamContainer}
>
<View>
<TeamIcon
testID={teamIconTestID}
teamId={teamId}
styleContainer={styles.teamIconContainer}
styleText={styles.teamIconText}
@ -87,6 +107,7 @@ export default class TeamsListItem extends React.PureComponent {
</View>
<View style={styles.teamNameContainer}>
<Text
testID={displayNameTestID}
numberOfLines={1}
ellipsizeMode='tail'
style={styles.teamName}

View file

@ -0,0 +1,29 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import Preferences from '@mm-redux/constants/preferences';
import TeamsListItem from './teams_list_item';
describe('TeamsListItem', () => {
const baseProps = {
selectTeam: jest.fn(),
testID: 'main.sidebar.teams_list.flat_list.teams_list_item',
currentTeamId: 'current-team-id',
currentUrl: 'current-url',
displayName: 'display-name',
mentionCount: 1,
name: 'name',
teamId: 'team-id',
theme: Preferences.THEMES.default,
};
test('should match snapshot', () => {
const wrapper = shallow(<TeamsListItem {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
});

View file

@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TeamIcon should match snapshot 1`] = `
<View
style={
Array [
Object {
"alignItems": "center",
"backgroundColor": "#ffffff",
"borderRadius": 2,
"height": 30,
"justifyContent": "center",
"width": 30,
},
Object {},
]
}
testID="team_icon"
>
<FastImage
onError={[Function]}
source={
Object {
"uri": "/api/v4/teams/team-id/image?_=1",
}
}
style={
Array [
Object {
"borderRadius": 2,
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
Object {},
]
}
testID="team_icon.content"
/>
</View>
`;

View file

@ -12,6 +12,7 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
export default class TeamIcon extends React.PureComponent {
static propTypes = {
testID: PropTypes.string,
displayName: PropTypes.string,
lastIconUpdate: PropTypes.number,
teamId: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
@ -51,6 +52,7 @@ export default class TeamIcon extends React.PureComponent {
render() {
const {
testID,
displayName,
lastIconUpdate,
teamId,
@ -59,19 +61,23 @@ export default class TeamIcon extends React.PureComponent {
styleText,
styleImage,
} = this.props;
const contentTestID = `${testID}.content`;
const styles = getStyleSheet(theme);
let teamIconContent;
if (this.state.imageError || !lastIconUpdate) {
teamIconContent = (
<Text style={[styles.text, styleText]}>
<Text
testID={contentTestID}
style={[styles.text, styleText]}
>
{displayName?.substr(0, 2).toUpperCase()}
</Text>
);
} else {
teamIconContent = (
<FastImage
testID={contentTestID}
style={[styles.image, styleImage]}
source={{uri: Client4.getTeamIconUrl(teamId, lastIconUpdate)}}
onError={this.handleImageError}
@ -80,7 +86,10 @@ export default class TeamIcon extends React.PureComponent {
}
return (
<View style={[styles.container, styleContainer]}>
<View
testID={testID}
style={[styles.container, styleContainer]}
>
{teamIconContent}
</View>
);

View file

@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import Preferences from '@mm-redux/constants/preferences';
import TeamIcon from './team_icon';
describe('TeamIcon', () => {
const baseProps = {
testID: 'team_icon',
displayName: 'display-name',
lastIconUpdate: 1,
teamId: 'team-id',
styleContainer: {},
styleText: {},
styleImage: {},
theme: Preferences.THEMES.default,
};
test('should match snapshot', () => {
const wrapper = shallow(<TeamIcon {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
});

View file

@ -103,6 +103,7 @@ exports[`MainSidebarDrawerButton should match, full snapshot 2`] = `
"padding": 3,
}
}
testID="main_sidebar_drawer.button.badge"
/>
</View>
</View>

View file

@ -96,6 +96,7 @@ export default class MainSidebarDrawerButton extends PureComponent {
badge = (
<Badge
testID='main_sidebar_drawer.button.badge'
containerStyle={containerStyle}
style={badgeStyle}
countStyle={style.mention}

View file

@ -342,7 +342,7 @@ exports[`channelInfo should match snapshot 1`] = `
</React.Fragment>
<Connect(Pinned)
channelId="1234"
testID="channel_info.pinned.action"
testID="channel_info.pinned_messages.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -841,7 +841,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
</React.Fragment>
<Connect(Pinned)
channelId="1234"
testID="channel_info.pinned.action"
testID="channel_info.pinned_messages.action"
theme={
Object {
"awayIndicator": "#ffbc42",
@ -1200,7 +1200,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
/>
<Connect(Pinned)
channelId="1234"
testID="channel_info.pinned.action"
testID="channel_info.pinned_messages.action"
theme={
Object {
"awayIndicator": "#ffbc42",

View file

@ -138,7 +138,7 @@ export default class ChannelInfo extends PureComponent {
</>
}
<Pinned
testID='channel_info.pinned.action'
testID='channel_info.pinned_messages.action'
channelId={currentChannel.id}
theme={theme}
/>

View file

@ -327,7 +327,7 @@ LongPost {
shouldRenderReplyButton={false}
showAddReaction={false}
showLongPost={true}
testID="long_post.post.post-id"
testID="long_post.post"
/>
</ScrollView>
<View

View file

@ -115,7 +115,6 @@ export default class LongPost extends PureComponent {
theme,
} = this.props;
const style = getStyleSheet(theme);
const testID = `long_post.post.${postId}`;
return (
<SafeAreaView
@ -156,7 +155,7 @@ export default class LongPost extends PureComponent {
</View>
<ScrollView style={style.postList}>
<Post
testID={testID}
testID='long_post.post'
postId={postId}
shouldRenderReplyButton={false}
onPress={this.handlePress}

View file

@ -13,6 +13,7 @@ exports[`Permalink should match snapshot 1`] = `
"marginTop": 20,
}
}
testID="permalink.screen"
>
<withAnimatable(View)
animation="zoomIn"

View file

@ -334,6 +334,7 @@ export default class Permalink extends PureComponent {
footerColor='transparent'
>
<View
testID='permalink.screen'
style={style.container}
>
<Animatable.View
@ -380,6 +381,7 @@ export default class Permalink extends PureComponent {
onPress={this.handlePress}
>
<FormattedText
testID='permalink.search.jump'
id='mobile.search.jump'
defautMessage='Jump to recent messages'
style={style.jump}

View file

@ -14,6 +14,7 @@ exports[`PinnedPosts should match snapshot 1`] = `
"flex": 1,
}
}
testID="pinned_messages.screen"
>
<Connect(StatusBar) />
<Connect(ChannelLoader)
@ -36,6 +37,7 @@ exports[`PinnedPosts should match snapshot when component waiting for response 1
"flex": 1,
}
}
testID="pinned_messages.screen"
>
<Connect(StatusBar) />
<Connect(ChannelLoader)
@ -58,6 +60,7 @@ exports[`PinnedPosts should match snapshot when getPinnedPosts failed 1`] = `
"flex": 1,
}
}
testID="pinned_messages.screen"
>
<Connect(StatusBar) />
<Connect(FailedNetworkAction)

View file

@ -224,6 +224,7 @@ export default class PinnedPosts extends PureComponent {
return (
<SafeAreaView
testID='pinned_messages.screen'
edges={['bottom', 'left', 'right']}
style={style.container}
>

View file

@ -224,8 +224,8 @@ export default class RecentMentions extends PureComponent {
return (
<SafeAreaView
style={style.container}
testID='recent_mentions.screen'
style={style.container}
>
<StatusBar/>
{component}

View file

@ -7,6 +7,7 @@ exports[`SavedPosts should match snapshot 1`] = `
"flex": 1,
}
}
testID="saved_messages.screen"
>
<Connect(StatusBar) />
<Connect(ChannelLoader)
@ -22,6 +23,7 @@ exports[`SavedPosts should match snapshot when component waiting for response 1`
"flex": 1,
}
}
testID="saved_messages.screen"
>
<Connect(StatusBar) />
<Connect(ChannelLoader)
@ -37,6 +39,7 @@ exports[`SavedPosts should match snapshot when getFlaggedPosts failed 1`] = `
"flex": 1,
}
}
testID="saved_messages.screen"
>
<Connect(StatusBar) />
<Connect(FailedNetworkAction)

View file

@ -228,7 +228,10 @@ export default class SavedPosts extends PureComponent {
}
return (
<SafeAreaView style={style.container}>
<SafeAreaView
testID='saved_messages.screen'
style={style.container}
>
<StatusBar/>
{component}
</SafeAreaView>

View file

@ -15,6 +15,6 @@ exports[`SearchResultPost should match snapshot 1`] = `
showFullDate={false}
skipFlaggedHeader={false}
skipPinnedHeader={false}
testID="search_result_post.post.post-id"
testID="search_result_post.post"
/>
`;

View file

@ -42,11 +42,9 @@ export default class SearchResultPost extends PureComponent {
postComponentProps.skipPinnedHeader = this.props.skipPinnedHeader;
}
const testID = `search_result_post.post.${this.props.postId}`;
return (
<Post
testID={testID}
testID='search_result_post.post'
postId={this.props.postId}
{...postComponentProps}
isSearchResult={true}

View file

@ -82,7 +82,7 @@ export const apiAddUserToChannel = async (userId, channelId) => {
* See https://api.mattermost.com/#tag/channels/paths/~1channels~1{channel_id}~1members~1{user_id}/delete
* @param {string} channelId - The channel ID
* @param {string} userId - The user ID to be removed from channel
* @return {Object} returns status on success or {error, status} on error
* @return {Object} returns {status} on success or {error, status} on error
*/
export const apiDeleteUserFromChannel = async (channelId, userId) => {
try {
@ -90,7 +90,7 @@ export const apiDeleteUserFromChannel = async (channelId, userId) => {
`/api/v4/channels/${channelId}/members/${userId}`,
);
return response;
return {status: response.status};
} catch (err) {
return getResponseFromError(err);
}

View file

@ -0,0 +1,67 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class ChannelsList {
testID = {
channelsList: 'main.sidebar.channels_list.list',
channelItem: 'main.sidebar.channels_list.list.channel_item',
channelItemDisplayName: 'main.sidebar.channels_list.list.channel_item.display_name',
filteredChannelsList: 'main.sidebar.channels_list.filtered_list',
filteredChannelItem: 'main.sidebar.channels_list.filtered_list.channel_item',
filteredChannelItemDisplayName: 'main.sidebar.channels_list.filtered_list.channel_item.display_name',
switchTeamsButton: 'main.sidebar.channels_list.switch_teams.button',
switchTeamsButtonBadge: 'main.sidebar.channels_list.switch_teams.button.badge',
switchTeamsButtonBadgeUnreadCount: 'main.sidebar.channels_list.switch_teams.button.badge.unread_count',
switchTeamsButtonBadgeUnreadIndicator: 'main.sidebar.channels_list.switch_teams.button.badge.unread_indicator',
}
channelsList = element(by.id(this.testID.channelsList));
filteredChannelsList = element(by.id(this.testID.channelsList));
switchTeamsButton = element(by.id(this.testID.switchTeamsButton));
switchTeamsButtonBadge = element(by.id(this.testID.switchTeamsButtonBadge));
switchTeamsButtonBadgeUnreadCount = element(by.id(this.testID.switchTeamsButtonBadgeUnreadCount));
switchTeamsButtonBadgeUnreadIndicator = element(by.id(this.testID.switchTeamsButtonBadgeUnreadIndicator));
getChannelItem = (channelId, displayName) => {
const channelItemTestID = `${this.testID.channelItem}.${channelId}`;
const baseMatcher = by.id(channelItemTestID);
const channelItemMatcher = displayName ? baseMatcher.withDescendant(by.text(displayName)) : baseMatcher;
const channelItemDisplayNameMatcher = by.id(this.testID.channelItemDisplayName).withAncestor(channelItemMatcher);
return {
channelItem: element(channelItemMatcher),
channelItemDisplayName: element(channelItemDisplayNameMatcher),
};
}
getChannelByDisplayName = (displayName) => {
return element(by.text(displayName).withAncestor(by.id(this.testID.channelsList)));
}
getChannelDisplayNameAtIndex = (index) => {
return element(by.id(this.testID.channelItemDisplayName)).atIndex(index);
}
getFilteredChannelItem = (channelId, displayName) => {
const filteredChannelItemTestID = `${this.testID.filteredChannelItem}.${channelId}`;
const baseMatcher = by.id(filteredChannelItemTestID);
const filteredChannelItemMatcher = displayName ? baseMatcher.withDescendant(by.text(displayName)) : baseMatcher;
const filteredChannelItemDisplayNameMatcher = by.id(this.testID.filteredChannelItemDisplayName).withAncestor(filteredChannelItemMatcher);
return {
channelItem: element(filteredChannelItemMatcher),
channelItemDisplayName: element(filteredChannelItemDisplayNameMatcher),
};
}
getFilteredChannelByDisplayName = (displayName) => {
return element(by.text(displayName).withAncestor(by.id(this.testID.filteredChannelsList)));
}
getFilteredChannelDisplayNameAtIndex = (index) => {
return element(by.id(this.testID.filteredChannelItemDisplayName)).atIndex(index);
}
}
const channelsList = new ChannelsList();
export default channelsList;

View file

@ -7,9 +7,12 @@ import EditChannelInfo from './edit_channel_info';
import FileQuickAction from './file_quick_action';
import ImageQuickAction from './image_quick_action';
import InputQuickAction from './input_quick_action';
import ChannelsList from './channels_list';
import TeamsList from './teams_list';
import MainSidebar from './main_sidebar';
import Post from './post';
import PostDraft from './post_draft';
import PostList from './post_list';
import PostOptions from './post_options';
import RecentItem from './recent_item';
import SearchBar from './search_bar';
@ -23,9 +26,12 @@ export {
FileQuickAction,
ImageQuickAction,
InputQuickAction,
ChannelsList,
TeamsList,
MainSidebar,
Post,
PostDraft,
PostList,
PostOptions,
RecentItem,
SearchBar,

View file

@ -1,27 +1,73 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import ChannelsList from './channels_list';
import TeamsList from './teams_list';
class MainSidebar {
testID = {
mainSidebar: 'main.sidebar',
channelsList: 'main.sidebar.channels_list',
channelItemDisplayName: 'main.sidebar.channels_list.list.channel_item.display_name',
filteredChannelItemDisplayName: 'main.sidebar.channels_list.filtered_list.channel_item.display_name',
openMoreChannelsButton: 'action_button_sidebar.channels',
openCreatePrivateChannelButton: 'action_button_sidebar.pg',
openMoreDirectMessagesButton: 'action_button_sidebar.direct',
}
mainSidebar = element(by.id(this.testID.mainSidebar));
channelsList = element(by.id(this.testID.channelsList));
channelItemDisplayName = element(by.id(this.testID.channelItemDisplayName));
filteredChannelItemDisplayName = element(by.id(this.testID.filteredChannelItemDisplayName));
openMoreChannelsButton = element(by.id(this.testID.openMoreChannelsButton));
openCreatePrivateChannelButton = element(by.id(this.testID.openCreatePrivateChannelButton));
openMoreDirectMessagesButton = element(by.id(this.testID.openMoreDirectMessagesButton));
// convenience props
channelsList = ChannelsList.channelsList;
filteredChannelsList = ChannelsList.filteredChannelsList;
switchTeamsButton = ChannelsList.switchTeamsButton;
switchTeamsButtonBadge = ChannelsList.switchTeamsButtonBadge;
switchTeamsButtonBadgeUnreadCount = ChannelsList.switchTeamsButtonBadgeUnreadCount;
switchTeamsButtonBadgeUnreadIndicator = ChannelsList.switchTeamsButtonBadgeUnreadIndicator;
teamsList = TeamsList.teamsList;
getChannel = (channelId, displayName) => {
return ChannelsList.getChannelItem(channelId, displayName);
}
getChannelByDisplayName = (displayName) => {
return element(by.text(displayName).withAncestor(by.id(this.testID.channelsList)));
return ChannelsList.getChannelByDisplayName(displayName);
}
getChannelDisplayNameAtIndex = (index) => {
return ChannelsList.getChannelDisplayNameAtIndex(index);
}
getFilteredChannel = (channelId, displayName) => {
return ChannelsList.getFilteredChannelItem(channelId, displayName);
}
getFilteredChannelByDisplayName = (displayName) => {
return ChannelsList.getFilteredChannelByDisplayName(displayName);
}
getFilteredChannelDisplayNameAtIndex = (index) => {
return ChannelsList.getFilteredChannelDisplayNameAtIndex(index);
}
getTeam = (teamId, displayName) => {
return TeamsList.getTeamItem(teamId, displayName);
}
getTeamByDisplayName = (displayName) => {
return TeamsList.getTeamByDisplayName(displayName);
}
getTeamBadgeUnreadCountAtIndex = (index) => {
return TeamsList.getTeamBadgeUnreadCountAtIndex(index);
}
getTeamDisplayNameAtIndex = (index) => {
return TeamsList.getTeamDisplayNameAtIndex(index);
}
getTeamIconContentAtIndex = (index) => {
return TeamsList.getTeamIconContentAtIndex(index);
}
toBeVisible = async () => {
@ -30,16 +76,58 @@ class MainSidebar {
return this.mainSidebar;
}
hasChannelAtIndex = async (index, channelDisplayName) => {
closeTeamSidebar = async () => {
// # Close team sidebar
await this.swipeLeft();
await expect(this.teamsList).not.toBeVisible();
await expect(this.channelsList).toBeVisible();
await this.toBeVisible();
}
openTeamSidebar = async () => {
// # Open team sidebar
await this.switchTeamsButton.tap();
await expect(this.channelsList).not.toBeVisible();
await expect(this.teamsList).toBeVisible();
await this.toBeVisible();
}
swipeLeft = async () => {
await this.mainSidebar.swipe('left');
}
swipeRight = async () => {
await this.mainSidebar.swipe('right');
}
hasChannelDisplayNameAtIndex = async (index, channelDisplayName) => {
await expect(
element(by.id(this.testID.channelItemDisplayName)).atIndex(index),
this.getChannelDisplayNameAtIndex(index),
).toHaveText(channelDisplayName);
}
hasFilteredChannelAtIndex = async (index, filteredChannelItemDisplayName) => {
hasFilteredChannelDisplayNameAtIndex = async (index, channelDisplayName) => {
await expect(
element(by.id(this.testID.filteredChannelItemDisplayName)).atIndex(index),
).toHaveText(filteredChannelItemDisplayName);
this.getFilteredChannelDisplayNameAtIndex(index),
).toHaveText(channelDisplayName);
}
hasTeamBadgeUnreadCountAtIndex = async (index, teamBadUnreadCount) => {
await expect(
this.getTeamBadgeUnreadCountAtIndex(index),
).toHaveText(teamBadUnreadCount);
}
hasTeamDisplayNameAtIndex = async (index, teamDisplayName) => {
await expect(
this.getTeamDisplayNameAtIndex(index),
).toHaveText(teamDisplayName);
}
hasTeamIconContentAtIndex = async (index, teamIconContent) => {
await expect(
this.getTeamIconContentAtIndex(index),
).toHaveText(teamIconContent);
}
}

View file

@ -3,23 +3,27 @@
class Post {
testID = {
postPrefix: 'post.',
postHeaderReply: 'post_header.reply',
markdownText: 'markdown_text',
}
getPost = (postTypePrefix, postId, text) => {
const postTestID = `${postTypePrefix}${this.testID.postPrefix}${postId}`;
if (text) {
return {
postItem: element(by.id(postTestID).withDescendant(by.text(text))),
postItemHeaderReply: element(by.id(this.testID.postHeaderReply).withAncestor(by.id(postTestID).withDescendant(by.text(text)))),
};
}
getPost = (postItemSourceTestID, postId, postMessage) => {
const postTestID = `${postItemSourceTestID}.${postId}`;
const baseMatcher = by.id(postTestID);
const postItemMatcher = postMessage ? baseMatcher.withDescendant(by.text(postMessage)) : baseMatcher;
const postItemHeaderReplyMatcher = by.id(this.testID.postHeaderReply).withAncestor(postItemMatcher);
const postItemMessageMatcher = by.id(this.testID.markdownText).withAncestor(postItemMatcher);
return {
postItem: element(by.id(postTestID)),
postItemHeaderReply: element(by.id(this.testID.postHeaderReply).withAncestor(by.id(postTestID))),
postItem: element(postItemMatcher),
postItemHeaderReply: element(postItemHeaderReplyMatcher),
postItemMessage: element(postItemMessageMatcher),
};
}
getPostMessage = (postItemSourceTestID) => {
return element(by.id(this.testID.markdownText).withAncestor(by.id(postItemSourceTestID)));
}
}
const post = new Post();

View file

@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import Post from './post';
class PostList {
constructor(screenPrefix) {
this.testID = {
postListPostItem: `${screenPrefix}post_list.post`,
};
}
getPost = (postId, postMessage) => {
const {postItem, postItemHeaderReply, postItemMessage} = Post.getPost(this.testID.postListPostItem, postId, postMessage);
return {
postListPostItem: postItem,
postListPostItemHeaderReply: postItemHeaderReply,
postListPostItemMessage: postItemMessage,
};
}
getPostMessageAtIndex = (index) => {
return Post.getPostMessage(this.testID.postListPostItem).atIndex(index);
}
}
export default PostList;

View file

@ -1,11 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {isAndroid} from '@support/utils';
class SearchBar {
testID = {
searchBarSuffix: 'search_bar',
searchInputSuffix: 'search_bar.search.input',
backButtonSuffix: 'search_bar.search.back.button',
cancelButtonSuffix: 'search_bar.search.cancel.button',
clearButtonSuffix: 'search_bar.search.clear.button',
}
@ -17,11 +19,10 @@ class SearchBar {
return element(by.id(`${screenPrefix}${this.testID.searchInputSuffix}`)).atIndex(0);
}
getBackButton = (screenPrefix) => {
return element(by.id(`${screenPrefix}${this.testID.backButtonSuffix}`)).atIndex(0);
}
getCancelButton = (screenPrefix) => {
if (isAndroid()) {
return element(by.id(`${screenPrefix}${this.testID.cancelButtonSuffix}`)).atIndex(0);
}
return element(by.text('Cancel').withAncestor(by.id(`${screenPrefix}${this.testID.searchBarSuffix}`))).atIndex(0);
}

View file

@ -27,6 +27,11 @@ class SettingsSidebar {
return this.settingsSidebar;
}
tapLogoutAction = async () => {
await this.logoutAction.tap();
await expect(this.settingsSidebar).not.toBeVisible();
}
}
const settingsSidebar = new SettingsSidebar();

View file

@ -0,0 +1,61 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class TeamsList {
testID = {
teamsList: 'main.sidebar.teams_list',
teamItem: 'main.sidebar.teams_list.flat_list.teams_list_item',
teamItemBadge: 'main.sidebar.teams_list.flat_list.teams_list_item.badge',
teamItemBadgeUnreadCount: 'main.sidebar.teams_list.flat_list.teams_list_item.badge.unread_count',
teamItemBadgeUnreadIndicator: 'main.sidebar.teams_list.flat_list.teams_list_item.badge.unread_indicator',
teamItemCurrent: 'main.sidebar.teams_list.flat_list.teams_list_item.current',
teamItemDisplayName: 'main.sidebar.teams_list.flat_list.teams_list_item.display_name',
teamItemIcon: 'main.sidebar.teams_list.flat_list.teams_list_item.team_icon',
teamItemIconContent: 'main.sidebar.teams_list.flat_list.teams_list_item.team_icon.content',
}
teamsList = element(by.id(this.testID.teamsList));
getTeamItem = (teamId, displayName) => {
const teamItemTestID = `${this.testID.teamItem}.${teamId}`;
const baseMatcher = by.id(teamItemTestID);
const teamItemMatcher = displayName ? baseMatcher.withDescendant(by.text(displayName)) : baseMatcher;
const teamItemBadgeMatcher = by.id(this.testID.teamItemBadge).withAncestor(teamItemMatcher);
const teamItemBadgeUnreadCountMatcher = by.id(this.testID.teamItemBadgeUnreadCount).withAncestor(teamItemMatcher);
const teamItemBadgeUnreadIndicatorMatcher = by.id(this.testID.teamItemBadgeUnreadIndicator).withAncestor(teamItemMatcher);
const teamItemCurrentMatcher = by.id(this.testID.teamItemCurrent).withAncestor(teamItemMatcher);
const teamItemDisplayNameMatcher = by.id(this.testID.teamItemDisplayName).withAncestor(teamItemMatcher);
const teamItemIconMatcher = by.id(this.testID.teamItemIcon).withAncestor(teamItemMatcher);
const teamItemIconContentMatcher = by.id(this.testID.teamItemIconContent).withAncestor(teamItemMatcher);
return {
teamItem: element(teamItemMatcher),
teamItemBadge: element(teamItemBadgeMatcher),
teamItemBadgeUnreadCount: element(teamItemBadgeUnreadCountMatcher),
teamItemBadgeUnreadIndicator: element(teamItemBadgeUnreadIndicatorMatcher),
teamItemCurrent: element(teamItemCurrentMatcher),
teamItemDisplayName: element(teamItemDisplayNameMatcher),
teamItemIcon: element(teamItemIconMatcher),
teamItemIconContent: element(teamItemIconContentMatcher),
};
}
getTeamByDisplayName = (displayName) => {
return element(by.text(displayName).withAncestor(by.id(this.testID.teamsList)));
}
getTeamBadgeUnreadCountAtIndex = (index) => {
return element(by.id(this.testID.teamItemBadgeUnreadCount)).atIndex(index);
}
getTeamDisplayNameAtIndex = (index) => {
return element(by.id(this.testID.teamItemDisplayName)).atIndex(index);
}
getTeamIconContentAtIndex = (index) => {
return element(by.id(this.testID.teamItemIconContent)).atIndex(index);
}
}
const teamsList = new TeamsList();
export default teamsList;

View file

@ -8,6 +8,7 @@ import {
InputQuickAction,
MainSidebar,
PostDraft,
PostList,
PostOptions,
SendButton,
SettingsSidebar,
@ -15,15 +16,18 @@ import {
import {
LoginScreen,
LongPostScreen,
PostListScreen,
SelectServerScreen,
} from '@support/ui/screen';
import {isAndroid} from '@support/utils';
class ChannelScreen {
testID = {
channelScreenPrefix: 'channel.',
channelScreen: 'channel.screen',
mainSidebarDrawerButton: 'main_sidebar_drawer.button',
mainSidebarDrawerButtonBadge: 'main_sidebar_drawer.button.badge',
mainSidebarDrawerButtonBadgeUnreadCount: 'main_sidebar_drawer.button.badge.unread_count',
mainSidebarDrawerButtonBadgeUnreadIndicator: 'main_sidebar_drawer.button.badge.unread_indicator',
channelIntro: 'channel_intro.beginning.text',
channelNavBarTitle: 'channel.nav_bar.title',
channelSearchButton: 'channel.search.button',
@ -33,6 +37,9 @@ class ChannelScreen {
channelScreen = element(by.id(this.testID.channelScreen));
mainSidebarDrawerButton = element(by.id(this.testID.mainSidebarDrawerButton));
mainSidebarDrawerButtonBadge = element(by.id(this.testID.mainSidebarDrawerButtonBadge));
mainSidebarDrawerButtonBadgeUnreadCount = element(by.id(this.testID.mainSidebarDrawerButtonBadgeUnreadCount));
mainSidebarDrawerButtonBadgeUnreadIndicator = element(by.id(this.testID.mainSidebarDrawerButtonBadgeUnreadIndicator));
channelIntro = element(by.id(this.testID.channelIntro));
channelNavBarTitle = element(by.id(this.testID.channelNavBarTitle));
channelSearchButton = element(by.id(this.testID.channelSearchButton));
@ -57,12 +64,22 @@ class ChannelScreen {
sendButton = SendButton.getSendButton(this.testID.channelScreenPrefix);
sendButtonDisabled = SendButton.getSendButtonDisabled(this.testID.channelScreenPrefix);
getLongPostPostItem = (postId, text) => {
postList = new PostList(this.testID.channelScreenPrefix);
getLongPostItem = (postId, text) => {
return LongPostScreen.getPost(postId, text);
}
getLongPostMessage = () => {
return LongPostScreen.getPostMessage();
}
getPostListPostItem = (postId, text) => {
return PostListScreen.getPost(this.testID.channelScreenPrefix, postId, text);
return this.postList.getPost(postId, text);
}
getPostMessageAtIndex = (index) => {
return this.postList.getPostMessageAtIndex(index);
}
toBeVisible = async () => {
@ -81,13 +98,42 @@ class ChannelScreen {
logout = async () => {
await this.openSettingsSidebar();
await SettingsSidebar.logoutAction.tap();
await SettingsSidebar.tapLogoutAction();
await SelectServerScreen.toBeVisible();
}
closeMainSidebar = async () => {
if (isAndroid()) {
// # Close main sidebar
await this.swipeLeft();
await this.toBeVisible();
} else {
// # iOS workaround for now
await device.reloadReactNative();
}
}
closeSettingsSidebar = async () => {
if (isAndroid()) {
// # Close settings sidebar
await this.swipeRight();
await this.toBeVisible();
} else {
// # iOS workaround for now
await device.reloadReactNative();
}
}
closeTeamSidebar = async () => {
// # Close team sidebar
await MainSidebar.closeTeamSidebar();
await this.closeMainSidebar();
}
openMainSidebar = async () => {
// # Open main sidebar
await this.mainSidebarDrawerButton.tap();
await expect(MainSidebar.channelsList).toBeVisible();
await MainSidebar.toBeVisible();
}
@ -97,6 +143,12 @@ class ChannelScreen {
await SettingsSidebar.toBeVisible();
}
openTeamSidebar = async () => {
// # Open team sidebar
await this.openMainSidebar();
await MainSidebar.openTeamSidebar();
}
openPostOptionsFor = async (postId, text) => {
const {postListPostItem} = await this.getPostListPostItem(postId, text);
await expect(postListPostItem).toBeVisible();
@ -113,12 +165,32 @@ class ChannelScreen {
await this.tapSendButton();
}
swipeLeft = async () => {
await this.channelScreen.swipe('left');
}
swipeRight = async () => {
await this.channelScreen.swipe('right');
}
tapSendButton = async () => {
// # Tap send button
await this.sendButton.tap();
await expect(this.sendButton).not.toExist();
await expect(this.sendButtonDisabled).toBeVisible();
}
hasLongPostMessage = async (postMessage) => {
await expect(
this.getLongPostMessage(),
).toHaveText(postMessage);
}
hasPostMessageAtIndex = async (index, postMessage) => {
await expect(
this.getPostMessageAtIndex(index),
).toHaveText(postMessage);
}
}
const channelScreen = new ChannelScreen();

View file

@ -13,7 +13,7 @@ class ChannelInfoScreen {
mutePreferenceAction: 'channel_info.mute.action',
ignoreMentionsPreferenceAction: 'channel_info.ignore_mentions.action',
notificationPreferenceAction: 'channel_info.notification_preference.action',
pinnedAction: 'channel_info.pinned.action',
pinnedMessagesAction: 'channel_info.pinned_messages.action',
manageMembersAction: 'channel_info.manage_members.action',
addMembersAction: 'channel_info.add_members.action',
convertPrivateAction: 'channel_info.convert_private.action',
@ -27,7 +27,7 @@ class ChannelInfoScreen {
mutePreferenceAction = element(by.id(this.testID.mutePreferenceAction));
ignoreMentionsPreferenceAction = element(by.id(this.testID.ignoreMentionsPreferenceAction));
notificationPreferenceAction = element(by.id(this.testID.notificationPreferenceAction));
pinnedAction = element(by.id(this.testID.pinnedAction));
pinnedMessagesAction = element(by.id(this.testID.pinnedMessagesAction));
manageMembersAction = element(by.id(this.testID.manageMembersAction));
addMembersAction = element(by.id(this.testID.addMembersAction));
convertPrivateAction = element(by.id(this.testID.convertPrivateAction));

View file

@ -16,8 +16,10 @@ import MoreDirectMessagesScreen from './more_direct_messages';
import NotificationScreen from './notification';
import NotificationSettingsMobileScreen from './notification_settings_mobile';
import NotificationSettingsScreen from './notification_settings';
import PostListScreen from './post_list';
import PermalinkScreen from './permalink';
import PinnedMessagesScreen from './pinned_messages';
import RecentMentionsScreen from './recent_mentions';
import SavedMessagesScreen from './saved_messages';
import SearchResultPostScreen from './search_result_post';
import SearchScreen from './search';
import SelectServerScreen from './select_server';
@ -40,8 +42,10 @@ export {
NotificationScreen,
NotificationSettingsMobileScreen,
NotificationSettingsScreen,
PostListScreen,
PermalinkScreen,
PinnedMessagesScreen,
RecentMentionsScreen,
SavedMessagesScreen,
SearchResultPostScreen,
SearchScreen,
SelectServerScreen,

View file

@ -5,16 +5,21 @@ import {Post} from '@support/ui/component';
class LongPostScreen {
testID = {
longPostScreenPrefix: 'long_post.',
longPostItem: 'long_post.post',
}
getPost = (postId, text) => {
const {postItem, postItemHeaderReply} = Post.getPost(this.testID.longPostScreenPrefix, postId, text);
getPost = (postId, postMessage) => {
const {postItem, postItemHeaderReply, postItemMessage} = Post.getPost(this.testID.longPostItem, postId, postMessage);
return {
longPostItem: postItem,
longPostItemHeaderReply: postItemHeaderReply,
longPostItemMessage: postItemMessage,
};
}
getPostMessage = () => {
return Post.getPostMessage(this.testID.longPostItem);
}
}
const longPostScreen = new LongPostScreen();

View file

@ -0,0 +1,61 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {PostList} from '@support/ui/component';
import {LongPostScreen} from '@support/ui/screen';
class PermalinkScreen {
testID = {
permalinkScreenPrefix: 'permalink.',
permalinkScreen: 'permalink.screen',
searchJump: 'permalink.search.jump',
}
permalinkScreen = element(by.id(this.testID.permalinkScreen));
searchJump = element(by.id(this.testID.searchJump));
postList = new PostList(this.testID.permalinkScreenPrefix);
getLongPostItem = (postId, text) => {
return LongPostScreen.getPost(postId, text);
}
getLongPostMessage = () => {
return LongPostScreen.getPostMessage();
}
getPostListPostItem = (postId, text) => {
return this.postList.getPost(postId, text);
}
getPostMessageAtIndex = (index) => {
return this.postList.getPostMessageAtIndex(index);
}
toBeVisible = async () => {
await expect(this.permalinkScreen).toBeVisible();
return this.permalinkScreen;
}
jumpToRecentMessages = async () => {
// # Jump to recent messages
await this.searchJump.tap();
await expect(this.permalinkScreen).not.toBeVisible();
}
hasLongPostMessage = async (postMessage) => {
await expect(
this.getLongPostMessage(),
).toHaveText(postMessage);
}
hasPostMessageAtIndex = async (index, postMessage) => {
await expect(
this.getPostMessageAtIndex(index),
).toHaveText(postMessage);
}
}
const permalinkScreen = new PermalinkScreen();
export default permalinkScreen;

View file

@ -0,0 +1,42 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
ChannelInfoScreen,
SearchResultPostScreen,
} from '@support/ui/screen';
class PinnedMessagesScreen {
testID = {
pinnedMessagesScreen: 'pinned_messages.screen',
backButton: 'screen.back.button',
}
pinnedMessagesScreen = element(by.id(this.testID.pinnedMessagesScreen));
backButton = element(by.id(this.testID.backButton));
getSearchResultPostItem = (postId, text) => {
return SearchResultPostScreen.getPost(postId, text);
}
toBeVisible = async () => {
await expect(this.pinnedMessagesScreen).toBeVisible();
return this.pinnedMessagesScreen;
}
open = async () => {
// # Open pinned messages screen
await ChannelInfoScreen.pinnedMessagesAction.tap();
return this.toBeVisible();
}
back = async () => {
await this.backButton.tap();
await expect(this.pinnedMessagesScreen).not.toBeVisible();
}
}
const pinnedMessagesScreen = new PinnedMessagesScreen();
export default pinnedMessagesScreen;

View file

@ -1,21 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Post} from '@support/ui/component';
class PostListScreen {
testID = {
postListScreenPrefix: 'post_list.',
}
getPost = (screenPrefix, postId, text) => {
const {postItem, postItemHeaderReply} = Post.getPost(`${screenPrefix}${this.testID.postListScreenPrefix}`, postId, text);
return {
postListPostItem: postItem,
postListPostItemHeaderReply: postItemHeaderReply,
};
}
}
const postListScreen = new PostListScreen();
export default postListScreen;

View file

@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
PostOptions,
SettingsSidebar,
} from '@support/ui/component';
import {SearchResultPostScreen} from '@support/ui/screen';
class SavedMessagesScreen {
testID = {
savedMessagesScreen: 'saved_messages.screen',
closeSettingsButton: 'close.settings.button',
}
savedMessagesScreen = element(by.id(this.testID.savedMessagesScreen));
closeSettingsButton = element(by.id(this.testID.closeSettingsButton));
getSearchResultPostItem = (postId, text) => {
return SearchResultPostScreen.getPost(postId, text);
}
toBeVisible = async () => {
await expect(this.savedMessagesScreen).toBeVisible();
return this.savedMessagesScreen;
}
open = async () => {
// # Open saved messages screen
await SettingsSidebar.savedMessagesAction.tap();
return this.toBeVisible();
}
close = async () => {
await this.closeSettingsButton.tap();
await expect(this.savedMessagesScreen).not.toBeVisible();
}
openPostOptionsFor = async (postId, text) => {
const {searchResultPostItem} = await this.getSearchResultPostItem(postId, text);
await expect(searchResultPostItem).toBeVisible();
// # Open post options
await searchResultPostItem.longPress();
await PostOptions.toBeVisible();
}
}
const savedMessagesScreen = new SavedMessagesScreen();
export default savedMessagesScreen;

View file

@ -9,7 +9,6 @@ import {
ChannelScreen,
SearchResultPostScreen,
} from '@support/ui/screen';
import {isAndroid} from '@support/utils';
class SearchScreen {
testID = {
@ -38,7 +37,6 @@ class SearchScreen {
// convenience props
searchBar = SearchBar.getSearchBar(this.testID.searchScreenPrefix);
searchInput = SearchBar.getSearchInput(this.testID.searchScreenPrefix);
backButton = SearchBar.getBackButton(this.testID.searchScreenPrefix);
cancelButton = SearchBar.getCancelButton(this.testID.searchScreenPrefix);
clearButton = SearchBar.getClearButton(this.testID.searchScreenPrefix);
@ -50,6 +48,10 @@ class SearchScreen {
return SearchResultPostScreen.getPost(postId, text);
}
getSearchResultPostMessageAtIndex = (index) => {
return SearchResultPostScreen.getPostMessageAtIndex(index);
}
toBeVisible = async () => {
await expect(this.searchScreen).toBeVisible();
@ -63,17 +65,20 @@ class SearchScreen {
return this.toBeVisible();
}
back = async () => {
if (isAndroid()) {
await this.backButton.tap();
} else {
await this.cancelButton.tap();
}
cancel = async () => {
await this.cancelButton.tap();
await expect(this.searchScreen).not.toBeVisible();
}
clear = async () => {
await this.clearButton.tap();
await expect(this.clearButton).not.toExist();
}
hasSearchResultPostMessageAtIndex = async (index, postMessage) => {
await expect(
this.getSearchResultPostMessageAtIndex(index),
).toHaveText(postMessage);
}
}

View file

@ -5,16 +5,21 @@ import {Post} from '@support/ui/component';
class SearchResultPostScreen {
testID = {
searchResultPostScreenPrefix: 'search_result_post.',
searchResultPostItem: 'search_result_post.post',
}
getPost = (postId, text) => {
const {postItem, postItemHeaderReply} = Post.getPost(this.testID.searchResultPostScreenPrefix, postId, text);
getPost = (postId, postMessage) => {
const {postItem, postItemHeaderReply, postItemMessage} = Post.getPost(this.testID.searchResultPostItem, postId, postMessage);
return {
searchResultPostItem: postItem,
searchResultPostItemHeaderReply: postItemHeaderReply,
searchResultPostItemMessage: postItemMessage,
};
}
getPostMessageAtIndex = (index) => {
return Post.getPostMessage(this.testID.searchResultPostItem).atIndex(index);
}
}
const searchResultPostScreen = new SearchResultPostScreen();

View file

@ -49,6 +49,18 @@ class SelectTeamScreen {
getTeamIconContentAtIndex = (index) => {
return element(by.id(this.testID.teamItemIconContent)).atIndex(index);
}
hasTeamDisplayNameAtIndex = async (index, teamDisplayName) => {
await expect(
this.getTeamDisplayNameAtIndex(index),
).toHaveText(teamDisplayName);
}
hasTeamIconContentAtIndex = async (index, teamIconContent) => {
await expect(
this.getTeamIconContentAtIndex(index),
).toHaveText(teamIconContent);
}
}
const selectTeamScreen = new SelectTeamScreen();

View file

@ -7,12 +7,10 @@ import {
ImageQuickAction,
InputQuickAction,
PostDraft,
PostList,
SendButton,
} from '@support/ui/component';
import {
LongPostScreen,
PostListScreen,
} from '@support/ui/screen';
import {LongPostScreen} from '@support/ui/screen';
import {timeouts, wait} from '@support/utils';
class ThreadScreen {
@ -43,12 +41,22 @@ class ThreadScreen {
sendButton = SendButton.getSendButton(this.testID.threadScreenPrefix);
sendButtonDisabled = SendButton.getSendButtonDisabled(this.testID.threadScreenPrefix);
getLongPostPostItem = (postId, text) => {
postList = new PostList(this.testID.threadScreenPrefix);
getLongPostItem = (postId, text) => {
return LongPostScreen.getPost(postId, text);
}
getLongPostMessage = () => {
return LongPostScreen.getPostMessage();
}
getPostListPostItem = (postId, text) => {
return PostListScreen.getPost(this.testID.threadScreenPrefix, postId, text);
return this.postList.getPost(postId, text);
}
getPostMessageAtIndex = (index) => {
return this.postList.getPostMessageAtIndex(index);
}
toBeVisible = async () => {
@ -76,6 +84,18 @@ class ThreadScreen {
await expect(this.sendButton).not.toExist();
await expect(this.sendButtonDisabled).toBeVisible();
}
hasLongPostMessage = async (postMessage) => {
await expect(
this.getLongPostMessage(),
).toHaveText(postMessage);
}
hasPostMessageAtIndex = async (index, postMessage) => {
await expect(
this.getPostMessageAtIndex(index),
).toHaveText(postMessage);
}
}
const threadScreen = new ThreadScreen();

View file

@ -37,6 +37,24 @@ export const capitalize = (text) => {
return text.charAt(0).toUpperCase() + text.slice(1);
};
/**
* @param {map} testIDMap - map of testIDs
* @return {map} map of testID matchers
*/
export const testIDMatcherMap = (testIDMap) => {
const testIDFilter = (k, v) => {
const key = k.toLowerCase();
if (key.endsWith('prefix') ||
key.endsWith('suffix') ||
v.startsWith('.') ||
v.endsWith('.')) {
return false;
}
return true;
};
return testIDMap.filter(([k, v]) => testIDFilter(k, v)).map(([k, v]) => [k, by.id(v)]);
};
const SECOND = 1000;
const MINUTE = 60 * 1000;

View file

@ -34,7 +34,7 @@ describe('Autocomplete', () => {
});
afterAll(async () => {
await device.reloadReactNative();
await SearchScreen.cancel();
await ChannelScreen.logout();
});

View file

@ -0,0 +1,92 @@
// 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 {MainSidebar} from '@support/ui/component';
import {ChannelScreen} from '@support/ui/screen';
import {
Channel,
Post,
Team,
User,
} from '@support/server_api';
describe('Mention Badges', () => {
let testChannel1;
let testChannel2;
let testTeam1;
let testTeam2;
let testUser1;
let testUser2;
beforeAll(async () => {
({user: testUser1} = await User.apiCreateUser());
({team: testTeam1} = await Team.apiCreateTeam({prefix: 'team-a'}));
({channel: testChannel1} = await Channel.apiGetChannelByName(testTeam1.name, 'town-square'));
({user: testUser2} = await User.apiCreateUser());
({team: testTeam2} = await Team.apiCreateTeam({prefix: 'team-b'}));
({channel: testChannel2} = await Channel.apiGetChannelByName(testTeam2.name, 'town-square'));
await Team.apiAddUserToTeam(testUser1.id, testTeam1.id);
await Team.apiAddUserToTeam(testUser1.id, testTeam2.id);
await Team.apiAddUserToTeam(testUser2.id, testTeam1.id);
await Team.apiAddUserToTeam(testUser2.id, testTeam2.id);
// # Open channel screen
await ChannelScreen.open(testUser1);
});
afterAll(async () => {
await ChannelScreen.logout();
});
it('MM-T3255 should display mention badges after an at-mention', async () => {
const {
channelNavBarTitle,
mainSidebarDrawerButtonBadgeUnreadCount,
closeMainSidebar,
openTeamSidebar,
} = ChannelScreen;
const {
closeTeamSidebar,
switchTeamsButtonBadgeUnreadCount,
} = MainSidebar;
// * Verify team 1 main sidebar drawer button badge does not exist
await expect(mainSidebarDrawerButtonBadgeUnreadCount).not.toExist();
// # Post an at-mention message to user 1 by user 2
const testMessage = `Mention @${testUser1.username}`;
await User.apiLogin(testUser2);
await Post.apiCreatePost({
channelId: testChannel2.id,
message: testMessage,
});
// * Verify team 1 main sidebar drawer button badge count is 1
await expect(channelNavBarTitle).toHaveText(testChannel1.display_name);
await expect(mainSidebarDrawerButtonBadgeUnreadCount).toHaveText('1');
// * Verify team 2 item badge count is 1
await openTeamSidebar();
const {teamItemBadgeUnreadCount: team2ItemBadgeUnreadCount} = await MainSidebar.getTeam(testTeam2.id);
await expect(team2ItemBadgeUnreadCount).toHaveText('1');
// * Verify team 1 item badge count does not exist
const {teamItemBadgeUnreadCount: team1ItemBadgeUnreadCount} = await MainSidebar.getTeam(testTeam1.id);
await expect(team1ItemBadgeUnreadCount).not.toExist();
// * Verify switch teams sidebar button badge count is 1
await closeTeamSidebar();
await expect(switchTeamsButtonBadgeUnreadCount).toHaveText('1');
// # Close main sidebar
await closeMainSidebar();
});
});

View file

@ -10,6 +10,7 @@
import {Autocomplete} from '@support/ui/component';
import {
ChannelScreen,
PermalinkScreen,
SearchScreen,
ThreadScreen,
} from '@support/ui/screen';
@ -42,39 +43,9 @@ describe('Search', () => {
it('MM-T3236 search on sender, select from autocomplete, reply from search results', async () => {
const testMessage = Date.now().toString();
const {
searchFromModifier,
searchFromSection,
searchInput,
} = SearchScreen;
// # Post a message
await ChannelScreen.postMessage(testMessage);
// # Open search screen
await SearchScreen.open();
// # Tap "from:" modifier
await searchInput.clearText();
await searchFromSection.tap();
// # Type beginning of search term
const searchTerm = testUser.first_name;
await searchInput.typeText(searchTerm);
// # Select user from autocomplete
await expect(atMentionSuggestionList).toExist();
const userAtMentionAutocomplete = await Autocomplete.getAtMentionItem(testUser.id);
await userAtMentionAutocomplete.tap();
// # Search user
await searchInput.tapReturnKey();
await expect(atMentionSuggestionList).not.toExist();
// * Verify recent search item is displayed
const searchTerms = `${searchFromModifier} ${testUser.username}`;
const recentSearchItem = await SearchScreen.getRecentSearchItem(searchTerms);
await expect(recentSearchItem).toBeVisible();
// # Post message and search on sender
await postMessageAndSearchFrom(testMessage, testUser, atMentionSuggestionList);
// * Verify search result post has the message
const lastPost = await Post.apiGetLastPostInChannel(testChannel.id);
@ -86,7 +57,7 @@ describe('Search', () => {
// # Open reply thread from search result post item
const replyTestMessage = `reply-${testMessage}`;
searchResultPostItemHeaderReply.tap();
await searchResultPostItemHeaderReply.tap();
// # Post a reply message
await ThreadScreen.toBeVisible();
@ -99,6 +70,89 @@ describe('Search', () => {
// # Go back to channel
await ThreadScreen.back();
await SearchScreen.back();
await SearchScreen.cancel();
});
it('MM-T3235 search on text, jump to result', async () => {
const testMessage = Date.now().toString();
// # Post message and search on text
await postMessageAndSearchText(testMessage);
// # Open permalink from search result post item
const lastPost = await Post.apiGetLastPostInChannel(testChannel.id);
const {searchResultPostItem} = await SearchScreen.getSearchResultPostItem(lastPost.post.id, testMessage);
await expect(searchResultPostItem).toBeVisible();
await searchResultPostItem.tap();
// * Verify permalink post list has the message
await PermalinkScreen.toBeVisible();
const {postListPostItem: permalinkPostItem} = await PermalinkScreen.getPostListPostItem(lastPost.post.id, testMessage);
await waitFor(permalinkPostItem).toBeVisible();
// # Jump to recent messages
await PermalinkScreen.jumpToRecentMessages();
// * Verify user is on channel where message is posted
await expect(ChannelScreen.channelNavBarTitle).toHaveText(testChannel.display_name);
const {postListPostItem: channelPostItem} = await ChannelScreen.getPostListPostItem(lastPost.post.id, testMessage);
await expect(channelPostItem).toBeVisible();
});
});
async function postMessageAndSearchFrom(testMessage, testUser, atMentionSuggestionList) {
const {
searchFromModifier,
searchFromSection,
searchInput,
} = SearchScreen;
// # Post a message
await ChannelScreen.postMessage(testMessage);
// # Open search screen
await SearchScreen.open();
// # Tap "from:" modifier
await searchInput.clearText();
await searchFromSection.tap();
// # Type beginning of search term
const searchTerm = testUser.first_name;
await searchInput.typeText(searchTerm);
// # Select user from autocomplete
await expect(atMentionSuggestionList).toExist();
const userAtMentionAutocomplete = await Autocomplete.getAtMentionItem(testUser.id);
await userAtMentionAutocomplete.tap();
// # Search user
await searchInput.tapReturnKey();
await expect(atMentionSuggestionList).not.toExist();
// * Verify recent search item is displayed
const searchTerms = `${searchFromModifier} ${testUser.username}`;
const recentSearchItem = await SearchScreen.getRecentSearchItem(searchTerms);
await expect(recentSearchItem).toBeVisible();
}
async function postMessageAndSearchText(testMessage) {
const {searchInput} = SearchScreen;
// # Post a message
await ChannelScreen.postMessage(testMessage);
// # Open search screen
await SearchScreen.open();
// # Type beginning of search term
await searchInput.clearText();
await searchInput.typeText(testMessage);
// # Search text
await searchInput.tapReturnKey();
// * Verify recent search item is displayed
const recentSearchItem = await SearchScreen.getRecentSearchItem(testMessage);
await expect(recentSearchItem).toBeVisible();
}

View file

@ -0,0 +1,96 @@
// 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 {MainSidebar} from '@support/ui/component';
import {ChannelScreen} from '@support/ui/screen';
import {
Channel,
Setup,
Team,
} from '@support/server_api';
describe('Teams', () => {
let testTeam1;
let testTeam1Channel;
let testTeam2;
let testTeam2Channel;
beforeAll(async () => {
const {team, user} = await Setup.apiInit({teamOptions: {prefix: 'team-a'}});
testTeam1 = team;
const {channel} = await Channel.apiGetChannelByName(testTeam1.name, 'town-square');
testTeam1Channel = channel;
({team: testTeam2} = await Team.apiCreateTeam({prefix: 'team-b'}));
await Team.apiAddUserToTeam(user.id, testTeam2.id);
({channel: testTeam2Channel} = await Channel.apiGetChannelByName(testTeam2.name, 'town-square'));
// # Open channel screen
await ChannelScreen.open(user);
});
afterAll(async () => {
await ChannelScreen.logout();
});
it('MM-T3249 should be able to switch between teams', async () => {
const {
channelNavBarTitle,
closeTeamSidebar,
openTeamSidebar,
} = ChannelScreen;
const {getTeamByDisplayName} = MainSidebar;
// * Verify user is on team 1 channel
await expect(channelNavBarTitle).toHaveText(testTeam1Channel.display_name);
// * Verify team 1 in team sidebar
await openTeamSidebar();
await verifyTeamSidebar(testTeam1);
// # Tap on team 1
await getTeamByDisplayName(testTeam1.display_name).tap();
// * Verify user is on team 1 channel
await ChannelScreen.toBeVisible();
await expect(channelNavBarTitle).toHaveText(testTeam1Channel.display_name);
// # Tap on team 2
await openTeamSidebar();
await getTeamByDisplayName(testTeam2.display_name).tap();
// * Verify user is on team 2 channel
await ChannelScreen.toBeVisible();
await expect(channelNavBarTitle).toHaveText(testTeam2Channel.display_name);
// * Verify team 2 in team sidebar
await openTeamSidebar();
await verifyTeamSidebar(testTeam2);
// # Close team sidebar
await closeTeamSidebar();
});
});
async function verifyTeamSidebar(testTeam) {
const {
teamItem,
teamItemCurrent,
teamItemDisplayName,
teamItemIcon,
} = await MainSidebar.getTeam(testTeam.id, testTeam.display_name);
// * Verify team is current
await expect(teamItem).toBeVisible();
await expect(teamItemCurrent).toBeVisible();
await expect(teamItemDisplayName).toBeVisible();
await expect(teamItemIcon).toBeVisible();
}

View file

@ -38,31 +38,37 @@ describe('Unread channels', () => {
});
it('MM-T3187 Unread channels sort at top', async () => {
const {openMainSidebar} = ChannelScreen;
const {
getChannelByDisplayName,
hasChannelDisplayNameAtIndex,
} = MainSidebar;
// # Open main sidebar (with at least one unread channel)
await ChannelScreen.openMainSidebar();
await openMainSidebar();
// * Verify unread channel(s) display at top of channel list (with mentions first, if any), in alphabetical order, with title "Unreads"
await expect(element(by.text('UNREADS'))).toBeVisible();
await MainSidebar.hasChannelAtIndex(0, aChannel.display_name);
await MainSidebar.hasChannelAtIndex(1, newChannel.display_name);
await MainSidebar.hasChannelAtIndex(2, zChannel.display_name);
await hasChannelDisplayNameAtIndex(0, aChannel.display_name);
await hasChannelDisplayNameAtIndex(1, newChannel.display_name);
await hasChannelDisplayNameAtIndex(2, zChannel.display_name);
// # Tap an unread channel to view it
await MainSidebar.getChannelByDisplayName(aChannel.display_name).tap();
await ChannelScreen.openMainSidebar();
await MainSidebar.getChannelByDisplayName(newChannel.display_name).tap();
await ChannelScreen.openMainSidebar();
await MainSidebar.getChannelByDisplayName(zChannel.display_name).tap();
await getChannelByDisplayName(aChannel.display_name).tap();
await openMainSidebar();
await getChannelByDisplayName(newChannel.display_name).tap();
await openMainSidebar();
await getChannelByDisplayName(zChannel.display_name).tap();
// * Channel you just read is no longer listed in Unreads
await ChannelScreen.openMainSidebar();
await openMainSidebar();
await expect(element(by.text('UNREADS'))).not.toBeVisible();
await expect(element(by.text('PUBLIC CHANNELS'))).toBeVisible();
await MainSidebar.hasChannelAtIndex(0, aChannel.display_name);
await MainSidebar.hasChannelAtIndex(1, newChannel.display_name);
await MainSidebar.hasChannelAtIndex(2, 'Off-Topic');
await MainSidebar.hasChannelAtIndex(3, 'Town Square');
await MainSidebar.hasChannelAtIndex(4, zChannel.display_name);
await MainSidebar.getChannelByDisplayName(aChannel.display_name).tap();
await hasChannelDisplayNameAtIndex(0, aChannel.display_name);
await hasChannelDisplayNameAtIndex(1, newChannel.display_name);
await hasChannelDisplayNameAtIndex(2, 'Off-Topic');
await hasChannelDisplayNameAtIndex(3, 'Town Square');
await hasChannelDisplayNameAtIndex(4, zChannel.display_name);
await getChannelByDisplayName(aChannel.display_name).tap();
});
});

View file

@ -36,6 +36,7 @@ describe('Select Team', () => {
afterAll(async () => {
await ChannelScreen.logout();
await Team.apiPatchTeams({allow_open_invite: true});
});
it('MM-T3619 should be able to select a team', async () => {

View file

@ -53,6 +53,7 @@ const ChannelItem = ({onSelect, selected, channel}: ChannelItemProps) => {
<View style={styles.item}>
{channelTypes[channel.type] || PublicChannel}
<Text
testID='share_extension.channel_item.display_name'
style={[styles.text]}
ellipsizeMode='tail'
numberOfLines={1}

View file

@ -44,6 +44,7 @@ const TeamItem = ({onSelect, selected, team}: TeamItemProps) => {
<View style={styles.container}>
<View style={styles.item}>
<TeamIcon
testID='share_extension.team_item.team_icon'
teamId={team.id}
styleContainer={styles.teamIconContainer}
styleText={styles.teamIconText}

View file

@ -73,6 +73,7 @@ const ChannelList = ({intl}: ChannnelListProps) => {
);
const renderItem = ({item}: SectionListRenderItemInfo<Channel>) => (
<ChannelItem
testID='share_extension.channel_list.channel_item'
channel={item}
onSelect={onSelectChannel}
selected={item.id === currentChannelId}

View file

@ -73,6 +73,7 @@ const TeamList = () => {
return (
<FlatList
testID='share_extension.team_list.screen'
data={teams}
ItemSeparatorComponent={renderItemSeparator}
renderItem={renderItem}