diff --git a/app/components/__snapshots__/badge.test.js.snap b/app/components/__snapshots__/badge.test.js.snap index 4ba6eb7c7..bf77c93db 100644 --- a/app/components/__snapshots__/badge.test.js.snap +++ b/app/components/__snapshots__/badge.test.js.snap @@ -15,6 +15,7 @@ exports[`Badge should match snapshot 1`] = ` }, ] } + testID="badge" > 99+ diff --git a/app/components/badge.js b/app/components/badge.js index 34064e69a..5b39f6c41 100644 --- a/app/components/badge.js +++ b/app/components/badge.js @@ -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 = ( @@ -127,6 +131,7 @@ export default class Badge extends PureComponent { unreadCount = ( @@ -137,7 +142,10 @@ export default class Badge extends PureComponent { } return ( - + { const baseProps = { + testID: 'badge', count: 100, countStyle: {color: '#145dbf', fontSize: 10}, style: {backgroundColor: '#ffffff'}, diff --git a/app/components/markdown/markdown.js b/app/components/markdown/markdown.js index ee2240d40..5ab9e1442 100644 --- a/app/components/markdown/markdown.js +++ b/app/components/markdown/markdown.js @@ -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 {literal}; + return ( + + {literal} + + ); } // 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 {literal}; + return ( + + {literal} + + ); }; renderCodeSpan = ({context, literal}) => { diff --git a/app/components/post/post.js b/app/components/post/post.js index 4c972814d..0315ec65c 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -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 ( {username} updated the channel display name from: {oldDisplayName} to: {newDisplayName} @@ -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} @@ -88,6 +90,7 @@ exports[`renderSystemMessage uses renderer for OLD archived channel without a us ], ] } + testID="markdown_text" > {username} archived the channel @@ -128,6 +131,7 @@ exports[`renderSystemMessage uses renderer for archived channel 2`] = ` ], ] } + testID="markdown_text" > {username} archived the channel @@ -157,6 +161,7 @@ exports[`renderSystemMessage uses renderer for unarchived channel 1`] = ` ], ] } + testID="markdown_text" > {username} unarchived the channel diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index e17c4361f..5d607e411 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -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 ( diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js index a2dd6621a..369c063ae 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js @@ -134,8 +134,11 @@ export default class ChannelItem extends PureComponent { let badge; if (mentions) { + const badgeTestID = `${testID}.badge`; + badge = ( ); + 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} - + {icon} {channelDisplayName} diff --git a/app/components/sidebars/main/channels_list/channels_list.js b/app/components/sidebars/main/channels_list/channels_list.js index 15b7c24d8..d0c0dcac5 100644 --- a/app/components/sidebars/main/channels_list/channels_list.js +++ b/app/components/sidebars/main/channels_list/channels_list.js @@ -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 ? : null; + let leftComponent; + if (onShowTeams) { + leftComponent = ( + + ); + } const title = ( + + + + + + + + +`; diff --git a/app/components/sidebars/main/channels_list/switch_teams_button/switch_teams_button.js b/app/components/sidebars/main/channels_list/switch_teams_button/switch_teams_button.js index 268f75dbe..3dbfcfa86 100644 --- a/app/components/sidebars/main/channels_list/switch_teams_button/switch_teams_button.js +++ b/app/components/sidebars/main/channels_list/switch_teams_button/switch_teams_button.js @@ -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 = ( ); + const itemTestID = `${testID}.${currentTeamId}`; + const teamIconTestID = `${testID}.team_icon`; + return ( - + - + { + 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(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/components/sidebars/main/main_sidebar_base.js b/app/components/sidebars/main/main_sidebar_base.js index 935fe42ac..058c8fd62 100644 --- a/app/components/sidebars/main/main_sidebar_base.js +++ b/app/components/sidebars/main/main_sidebar_base.js @@ -217,6 +217,7 @@ export default class MainSidebarBase extends Component { style={style.swiperContent} > diff --git a/app/components/sidebars/main/teams_list/teams_list.js b/app/components/sidebars/main/teams_list/teams_list.js index a5c8e9525..479a26006 100644 --- a/app/components/sidebars/main/teams_list/teams_list.js +++ b/app/components/sidebars/main/teams_list/teams_list.js @@ -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 ( @@ -168,6 +175,7 @@ export default class TeamsList extends PureComponent { {moreAction} + + + + + + + + + display-name + + + current-url/name + + + + + +`; diff --git a/app/components/sidebars/main/teams_list/teams_list_item/teams_list_item.js b/app/components/sidebars/main/teams_list/teams_list_item/teams_list_item.js index b1856b7a9..3adae01f7 100644 --- a/app/components/sidebars/main/teams_list/teams_list_item/teams_list_item.js +++ b/app/components/sidebars/main/teams_list/teams_list_item/teams_list_item.js @@ -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 = ( + + - + { + 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(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/components/team_icon/__snapshots__/team_icon.test.js.snap b/app/components/team_icon/__snapshots__/team_icon.test.js.snap new file mode 100644 index 000000000..24b21bcbf --- /dev/null +++ b/app/components/team_icon/__snapshots__/team_icon.test.js.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TeamIcon should match snapshot 1`] = ` + + + +`; diff --git a/app/components/team_icon/team_icon.js b/app/components/team_icon/team_icon.js index 529376512..bd0cdde8f 100644 --- a/app/components/team_icon/team_icon.js +++ b/app/components/team_icon/team_icon.js @@ -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 = ( - + {displayName?.substr(0, 2).toUpperCase()} ); } else { teamIconContent = ( + {teamIconContent} ); diff --git a/app/components/team_icon/team_icon.test.js b/app/components/team_icon/team_icon.test.js new file mode 100644 index 000000000..4c93fffae --- /dev/null +++ b/app/components/team_icon/team_icon.test.js @@ -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(); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); +}); diff --git a/app/screens/channel/channel_nav_bar/main_sidebar_drawer_button/__snapshots__/main_sidebar_drawer_button.test.js.snap b/app/screens/channel/channel_nav_bar/main_sidebar_drawer_button/__snapshots__/main_sidebar_drawer_button.test.js.snap index 1905433e6..a6b540e18 100644 --- a/app/screens/channel/channel_nav_bar/main_sidebar_drawer_button/__snapshots__/main_sidebar_drawer_button.test.js.snap +++ b/app/screens/channel/channel_nav_bar/main_sidebar_drawer_button/__snapshots__/main_sidebar_drawer_button.test.js.snap @@ -103,6 +103,7 @@ exports[`MainSidebarDrawerButton should match, full snapshot 2`] = ` "padding": 3, } } + testID="main_sidebar_drawer.button.badge" /> diff --git a/app/screens/channel/channel_nav_bar/main_sidebar_drawer_button/main_sidebar_drawer_button.js b/app/screens/channel/channel_nav_bar/main_sidebar_drawer_button/main_sidebar_drawer_button.js index 12d70000d..c04509d7b 100644 --- a/app/screens/channel/channel_nav_bar/main_sidebar_drawer_button/main_sidebar_drawer_button.js +++ b/app/screens/channel/channel_nav_bar/main_sidebar_drawer_button/main_sidebar_drawer_button.js @@ -96,6 +96,7 @@ export default class MainSidebarDrawerButton extends PureComponent { badge = ( } diff --git a/app/screens/long_post/__snapshots__/long_post.test.js.snap b/app/screens/long_post/__snapshots__/long_post.test.js.snap index 40f667de0..c23320656 100644 --- a/app/screens/long_post/__snapshots__/long_post.test.js.snap +++ b/app/screens/long_post/__snapshots__/long_post.test.js.snap @@ -327,7 +327,7 @@ LongPost { shouldRenderReplyButton={false} showAddReaction={false} showLongPost={true} - testID="long_post.post.post-id" + testID="long_post.post" /> diff --git a/app/screens/recent_mentions/recent_mentions.js b/app/screens/recent_mentions/recent_mentions.js index 41e0c9e76..831258986 100644 --- a/app/screens/recent_mentions/recent_mentions.js +++ b/app/screens/recent_mentions/recent_mentions.js @@ -224,8 +224,8 @@ export default class RecentMentions extends PureComponent { return ( {component} diff --git a/app/screens/saved_posts/__snapshots__/saved_posts.test.js.snap b/app/screens/saved_posts/__snapshots__/saved_posts.test.js.snap index 7de6e8f4f..b0cfa3c64 100644 --- a/app/screens/saved_posts/__snapshots__/saved_posts.test.js.snap +++ b/app/screens/saved_posts/__snapshots__/saved_posts.test.js.snap @@ -7,6 +7,7 @@ exports[`SavedPosts should match snapshot 1`] = ` "flex": 1, } } + testID="saved_messages.screen" > + {component} diff --git a/app/screens/search/search_result_post/__snapshots__/search_result_post.test.js.snap b/app/screens/search/search_result_post/__snapshots__/search_result_post.test.js.snap index e71bd7b1d..7e8032c90 100644 --- a/app/screens/search/search_result_post/__snapshots__/search_result_post.test.js.snap +++ b/app/screens/search/search_result_post/__snapshots__/search_result_post.test.js.snap @@ -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" /> `; diff --git a/app/screens/search/search_result_post/search_result_post.js b/app/screens/search/search_result_post/search_result_post.js index 15c06cf50..373fcf1f9 100644 --- a/app/screens/search/search_result_post/search_result_post.js +++ b/app/screens/search/search_result_post/search_result_post.js @@ -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 ( { * 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); } diff --git a/detox/e2e/support/ui/component/channels_list.js b/detox/e2e/support/ui/component/channels_list.js new file mode 100644 index 000000000..b541f1869 --- /dev/null +++ b/detox/e2e/support/ui/component/channels_list.js @@ -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; diff --git a/detox/e2e/support/ui/component/index.js b/detox/e2e/support/ui/component/index.js index 3e05b638c..6578873fb 100644 --- a/detox/e2e/support/ui/component/index.js +++ b/detox/e2e/support/ui/component/index.js @@ -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, diff --git a/detox/e2e/support/ui/component/main_sidebar.js b/detox/e2e/support/ui/component/main_sidebar.js index c0fb9e98c..cf650be01 100644 --- a/detox/e2e/support/ui/component/main_sidebar.js +++ b/detox/e2e/support/ui/component/main_sidebar.js @@ -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); } } diff --git a/detox/e2e/support/ui/component/post.js b/detox/e2e/support/ui/component/post.js index d78c2ee96..7f809570f 100644 --- a/detox/e2e/support/ui/component/post.js +++ b/detox/e2e/support/ui/component/post.js @@ -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(); diff --git a/detox/e2e/support/ui/component/post_list.js b/detox/e2e/support/ui/component/post_list.js new file mode 100644 index 000000000..f923f1d06 --- /dev/null +++ b/detox/e2e/support/ui/component/post_list.js @@ -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; diff --git a/detox/e2e/support/ui/component/search_bar.js b/detox/e2e/support/ui/component/search_bar.js index 90e6b85b5..32e815f5b 100644 --- a/detox/e2e/support/ui/component/search_bar.js +++ b/detox/e2e/support/ui/component/search_bar.js @@ -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); } diff --git a/detox/e2e/support/ui/component/settings_sidebar.js b/detox/e2e/support/ui/component/settings_sidebar.js index 8262bf4aa..93b28781d 100644 --- a/detox/e2e/support/ui/component/settings_sidebar.js +++ b/detox/e2e/support/ui/component/settings_sidebar.js @@ -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(); diff --git a/detox/e2e/support/ui/component/teams_list.js b/detox/e2e/support/ui/component/teams_list.js new file mode 100644 index 000000000..9f96ac866 --- /dev/null +++ b/detox/e2e/support/ui/component/teams_list.js @@ -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; diff --git a/detox/e2e/support/ui/screen/channel.js b/detox/e2e/support/ui/screen/channel.js index 1c3147d86..f488c3560 100644 --- a/detox/e2e/support/ui/screen/channel.js +++ b/detox/e2e/support/ui/screen/channel.js @@ -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(); diff --git a/detox/e2e/support/ui/screen/channel_info.js b/detox/e2e/support/ui/screen/channel_info.js index d208da170..025094e3d 100644 --- a/detox/e2e/support/ui/screen/channel_info.js +++ b/detox/e2e/support/ui/screen/channel_info.js @@ -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)); diff --git a/detox/e2e/support/ui/screen/index.js b/detox/e2e/support/ui/screen/index.js index 9a87f0fd4..7c7f67dee 100644 --- a/detox/e2e/support/ui/screen/index.js +++ b/detox/e2e/support/ui/screen/index.js @@ -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, diff --git a/detox/e2e/support/ui/screen/long_post.js b/detox/e2e/support/ui/screen/long_post.js index 7624e9c02..2fc203402 100644 --- a/detox/e2e/support/ui/screen/long_post.js +++ b/detox/e2e/support/ui/screen/long_post.js @@ -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(); diff --git a/detox/e2e/support/ui/screen/permalink.js b/detox/e2e/support/ui/screen/permalink.js new file mode 100644 index 000000000..cbf4cce73 --- /dev/null +++ b/detox/e2e/support/ui/screen/permalink.js @@ -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; diff --git a/detox/e2e/support/ui/screen/pinned_messages.js b/detox/e2e/support/ui/screen/pinned_messages.js new file mode 100644 index 000000000..e83af8ebd --- /dev/null +++ b/detox/e2e/support/ui/screen/pinned_messages.js @@ -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; diff --git a/detox/e2e/support/ui/screen/post_list.js b/detox/e2e/support/ui/screen/post_list.js deleted file mode 100644 index 4a86a5719..000000000 --- a/detox/e2e/support/ui/screen/post_list.js +++ /dev/null @@ -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; diff --git a/detox/e2e/support/ui/screen/saved_messages.js b/detox/e2e/support/ui/screen/saved_messages.js new file mode 100644 index 000000000..b80afeff8 --- /dev/null +++ b/detox/e2e/support/ui/screen/saved_messages.js @@ -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; diff --git a/detox/e2e/support/ui/screen/search.js b/detox/e2e/support/ui/screen/search.js index 7ad9f8ae0..aa3f36df8 100644 --- a/detox/e2e/support/ui/screen/search.js +++ b/detox/e2e/support/ui/screen/search.js @@ -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); } } diff --git a/detox/e2e/support/ui/screen/search_result_post.js b/detox/e2e/support/ui/screen/search_result_post.js index 75fb1322d..e07b42ac5 100644 --- a/detox/e2e/support/ui/screen/search_result_post.js +++ b/detox/e2e/support/ui/screen/search_result_post.js @@ -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(); diff --git a/detox/e2e/support/ui/screen/select_team.js b/detox/e2e/support/ui/screen/select_team.js index 4ecf32f69..00d8eacde 100644 --- a/detox/e2e/support/ui/screen/select_team.js +++ b/detox/e2e/support/ui/screen/select_team.js @@ -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(); diff --git a/detox/e2e/support/ui/screen/thread.js b/detox/e2e/support/ui/screen/thread.js index 5d41e121e..e8781a5a9 100644 --- a/detox/e2e/support/ui/screen/thread.js +++ b/detox/e2e/support/ui/screen/thread.js @@ -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(); diff --git a/detox/e2e/support/utils.js b/detox/e2e/support/utils.js index 4218a0f6f..c6009d83e 100644 --- a/detox/e2e/support/utils.js +++ b/detox/e2e/support/utils.js @@ -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; diff --git a/detox/e2e/test/autocomplete/search.e2e.js b/detox/e2e/test/autocomplete/search.e2e.js index 5c109b6f2..3d9c81c31 100644 --- a/detox/e2e/test/autocomplete/search.e2e.js +++ b/detox/e2e/test/autocomplete/search.e2e.js @@ -34,7 +34,7 @@ describe('Autocomplete', () => { }); afterAll(async () => { - await device.reloadReactNative(); + await SearchScreen.cancel(); await ChannelScreen.logout(); }); diff --git a/detox/e2e/test/smoke_test/mention_badges.e2e.js b/detox/e2e/test/smoke_test/mention_badges.e2e.js new file mode 100644 index 000000000..675d70662 --- /dev/null +++ b/detox/e2e/test/smoke_test/mention_badges.e2e.js @@ -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(); + }); +}); diff --git a/detox/e2e/test/smoke_test/search.e2e.js b/detox/e2e/test/smoke_test/search.e2e.js index 2d3f2782f..e393793d9 100644 --- a/detox/e2e/test/smoke_test/search.e2e.js +++ b/detox/e2e/test/smoke_test/search.e2e.js @@ -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(); +} diff --git a/detox/e2e/test/smoke_test/teams.e2e.js b/detox/e2e/test/smoke_test/teams.e2e.js new file mode 100644 index 000000000..c85bf9b3e --- /dev/null +++ b/detox/e2e/test/smoke_test/teams.e2e.js @@ -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(); +} diff --git a/detox/e2e/test/smoke_test/unread_channels.e2e.js b/detox/e2e/test/smoke_test/unread_channels.e2e.js index 3c6a88679..2dc7c2f74 100644 --- a/detox/e2e/test/smoke_test/unread_channels.e2e.js +++ b/detox/e2e/test/smoke_test/unread_channels.e2e.js @@ -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(); }); }); diff --git a/detox/e2e/test/teams/select_team.e2e.js b/detox/e2e/test/teams/select_team.e2e.js index 8747dfade..a87168acf 100644 --- a/detox/e2e/test/teams/select_team.e2e.js +++ b/detox/e2e/test/teams/select_team.e2e.js @@ -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 () => { diff --git a/share_extension/components/channel_item.tsx b/share_extension/components/channel_item.tsx index 4fa9aebbf..ee32a1776 100644 --- a/share_extension/components/channel_item.tsx +++ b/share_extension/components/channel_item.tsx @@ -53,6 +53,7 @@ const ChannelItem = ({onSelect, selected, channel}: ChannelItemProps) => { {channelTypes[channel.type] || PublicChannel} { { ); const renderItem = ({item}: SectionListRenderItemInfo) => ( { return (