From 757a673416ff92f495e7d6df042ff2648f39bbaf Mon Sep 17 00:00:00 2001 From: A C SREEDHAR REDDY Date: Thu, 3 Dec 2020 02:07:04 +0530 Subject: [PATCH] MM-18769 Removed deprecated lifecycles (#4983) * select_team : updated snapshot and removed deprecated lifecycles * removed deprecated lifecycle methods * Added e2e for MM-T3619 * Fix lint issue Co-authored-by: Joseph Baylon --- .../client_upgrade_listener.js | 18 ++- .../__snapshots__/select_team.test.js.snap | 14 +- app/screens/select_team/select_team.js | 57 +++++--- detox/e2e/support/server_api/channel.js | 38 +++++ detox/e2e/support/server_api/team.js | 131 +++++++++++++++++- detox/e2e/support/ui/screen/index.js | 2 + detox/e2e/support/ui/screen/select_team.js | 55 ++++++++ detox/e2e/test/smoke_test/login_ldap.e2e.js | 2 +- detox/e2e/test/teams/select_team.e2e.js | 52 +++++++ 9 files changed, 334 insertions(+), 35 deletions(-) create mode 100644 detox/e2e/support/ui/screen/select_team.js create mode 100644 detox/e2e/test/teams/select_team.e2e.js diff --git a/app/components/client_upgrade_listener/client_upgrade_listener.js b/app/components/client_upgrade_listener/client_upgrade_listener.js index 5013971bd..c2ac366d8 100644 --- a/app/components/client_upgrade_listener/client_upgrade_listener.js +++ b/app/components/client_upgrade_listener/client_upgrade_listener.js @@ -62,17 +62,21 @@ export default class ClientUpgradeListener extends PureComponent { } } - componentWillReceiveProps(nextProps) { - const {forceUpgrade, latestVersion, minVersion} = this.props; - const {latestVersion: nextLatestVersion, minVersion: nextMinVersion, lastUpgradeCheck} = nextProps; + setTop(top) { + this.setState({top}); + } + + componentDidUpdate(prevProps) { + const {forceUpgrade, latestVersion, minVersion} = prevProps; + const {latestVersion: nextLatestVersion, minVersion: nextMinVersion, lastUpgradeCheck} = this.props; const versionMismatch = latestVersion !== nextLatestVersion || minVersion !== nextMinVersion; if (versionMismatch && (forceUpgrade || Date.now() - lastUpgradeCheck > UPDATE_TIMEOUT)) { - this.checkUpgrade(minVersion, latestVersion, nextProps.isLandscape); - } else if (this.props.isLandscape !== nextProps.isLandscape && + this.checkUpgrade(minVersion, latestVersion, this.props.isLandscape); + } else if (prevProps.isLandscape !== this.props.isLandscape && isUpgradeAvailable(this.state.upgradeType) && DeviceTypes.IS_IPHONE_WITH_INSETS) { - const newTop = nextProps.isLandscape ? 45 : 100; - this.setState({top: new Animated.Value(newTop)}); + const newTop = this.props.isLandscape ? 45 : 100; + this.setTop(new Animated.Value(newTop)); } } diff --git a/app/screens/select_team/__snapshots__/select_team.test.js.snap b/app/screens/select_team/__snapshots__/select_team.test.js.snap index 6f792a6b7..883147cca 100644 --- a/app/screens/select_team/__snapshots__/select_team.test.js.snap +++ b/app/screens/select_team/__snapshots__/select_team.test.js.snap @@ -42,6 +42,7 @@ exports[`SelectTeam should match snapshot for teams 1`] = ` "flex": 1, } } + testID="select_team.screen" > diff --git a/app/screens/select_team/select_team.js b/app/screens/select_team/select_team.js index 305e946ca..a26c22d0e 100644 --- a/app/screens/select_team/select_team.js +++ b/app/screens/select_team/select_team.js @@ -26,6 +26,7 @@ import {resetToChannel, dismissModal} from 'app/actions/navigation'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {t} from 'app/utils/i18n'; +import memoize from 'memoize-one'; const TEAMS_PER_PAGE = 50; @@ -68,12 +69,6 @@ export default class SelectTeam extends PureComponent { this.getTeams(); } - componentWillReceiveProps(nextProps) { - if (this.props.teams !== nextProps.teams) { - this.buildData(nextProps); - } - } - navigationButtonPressed({buttonId}) { const {logout} = this.props.actions; @@ -98,17 +93,15 @@ export default class SelectTeam extends PureComponent { }); } - buildData = (props) => { - if (props.teams.length) { - this.setState({teams: props.teams}); - } else { - const teams = [{ - id: t('mobile.select_team.no_teams'), - defaultMessage: 'There are no available teams for you to join.', - }]; - this.setState({teams}); + memoizedTeams = memoize((teams) => { + if (teams.length) { + return teams; } - }; + return [{ + id: t('mobile.select_team.no_teams'), + defaultMessage: 'There are no available teams for you to join.', + }]; + }) close = () => { dismissModal(); @@ -164,6 +157,7 @@ export default class SelectTeam extends PureComponent { + this.onSelectTeam(item))} > - + + + @@ -256,6 +270,7 @@ export default class SelectTeam extends PureComponent { { } }; +/** + * Remove user from channel. + * 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 + */ +export const apiDeleteUserFromChannel = async (channelId, userId) => { + try { + const response = await client.delete( + `/api/v4/channels/${channelId}/members/${userId}`, + ); + + return response; + } catch (err) { + return getResponseFromError(err); + } +}; + +/** + * Get channels for user. + * See https://api.mattermost.com/#tag/channels/paths/~1users~1{user_id}~1teams~1{team_id}~1channels/get + * @param {string} userId - The user ID + * @param {string} teamId - The team ID the user belongs to + * @return {Object} returns {channels} on success or {error, status} on error + */ +export const apiGetChannelsForUser = async (userId, teamId) => { + try { + const response = await client.get(`/api/v4/users/${userId}/teams/${teamId}/channels`); + + return {channels: response.data}; + } catch (err) { + return getResponseFromError(err); + } +}; + function generateRandomChannel(teamId, type, prefix) { const randomId = getRandomId(); @@ -93,7 +129,9 @@ function generateRandomChannel(teamId, type, prefix) { export const Channel = { apiAddUserToChannel, apiCreateChannel, + apiDeleteUserFromChannel, apiGetChannelByName, + apiGetChannelsForUser, }; export default Channel; diff --git a/detox/e2e/support/server_api/team.js b/detox/e2e/support/server_api/team.js index ad34124fe..7a52c9b02 100644 --- a/detox/e2e/support/server_api/team.js +++ b/detox/e2e/support/server_api/team.js @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import jestExpect from 'expect'; import {capitalize, getRandomId} from '@support/utils'; import client from './client'; @@ -60,12 +61,80 @@ export const apiAddUserToTeam = async (userId, teamId) => { }; /** - * Get team members for user. - * See https://api.mattermost.com/#tag/teams/paths/~1users~1{user_id}~1teams~1members/get - * @param {string} userId + * Delete a team. + * See https://api.mattermost.com/#tag/teams/paths/~1teams~1{team_id}/delete + * @param {string} teamId - The team ID + * @return {Object} returns {status} on success or {error, status} on error + */ +export const apiDeleteTeam = async (teamId) => { + try { + const response = await client.delete( + `/api/v4/teams/${teamId}`, + ); + + return {status: response.status}; + } catch (err) { + return getResponseFromError(err); + } +}; + +/** + * Delete teams. + * @param {Array} teams - array of teams + */ +export const apiDeleteTeams = async (teams = []) => { + let teamArray = teams; + if (!teamArray.length > 0) { + ({teams: teamArray} = await Team.apiGetTeams()); + } + + teamArray.forEach(async (team) => { + const {status} = await Team.apiDeleteTeam(team.id); + jestExpect(status).toEqual(200); + }); +}; + +/** + * Remove user from team. + * See https://api.mattermost.com/#tag/teams/paths/~1teams~1{team_id}~1members~1{user_id}/delete + * @param {string} teamId - The team ID + * @param {string} userId - The user ID to be removed from team + * @return {Object} returns {status} on success or {error, status} on error + */ +export const apiDeleteUserFromTeam = async (teamId, userId) => { + try { + const response = await client.delete( + `/api/v4/teams/${teamId}/members/${userId}`, + ); + + return {status: response.status}; + } catch (err) { + return getResponseFromError(err); + } +}; + +/** + * Get teams. + * See https://api.mattermost.com/#tag/teams/paths/~1teams/get * @return {Object} returns {teams} on success or {error, status} on error */ -export const apiGetTeamMembersForUser = async (userId = 'me') => { +export const apiGetTeams = async () => { + try { + const response = await client.get('/api/v4/teams'); + + return {teams: response.data}; + } catch (err) { + return getResponseFromError(err); + } +}; + +/** + * Get teams for user. + * See https://api.mattermost.com/#tag/teams/paths/~1users~1{user_id}~1teams/get + * @param {string} userId - The user ID + * @return {Object} returns {teams} on success or {error, status} on error + */ +export const apiGetTeamsForUser = async (userId = 'me') => { try { const response = await client.get(`/api/v4/users/${userId}/teams`); @@ -75,6 +144,52 @@ export const apiGetTeamMembersForUser = async (userId = 'me') => { } }; +/** + * Patch a team. + * See https://api.mattermost.com/#tag/teams/paths/~1teams~1{team_id}~1patch/put + * @param {string} teamId - The team ID + * @param {string} patch.display_name - Display name + * @param {string} patch.description - Description + * @param {string} patch.company_name - Company name + * @param {string} patch.allowed_domains - Allowed domains + * @param {boolean} patch.allow_open_invite - Allow open invite + * @param {boolean} patch.group_constrained - Group constrained + * @return {Object} returns {team} on success or {error, status} on error + */ +export const apiPatchTeam = async (teamId, teamData) => { + try { + const response = await client.put( + `/api/v4/teams/${teamId}/patch`, + teamData, + ); + + return {team: response.data}; + } catch (err) { + return getResponseFromError(err); + } +}; + +/** + * Patch teams. + * @param {string} patch.display_name - Display name + * @param {string} patch.description - Description + * @param {string} patch.company_name - Company name + * @param {string} patch.allowed_domains - Allowed domains + * @param {boolean} patch.allow_open_invite - Allow open invite + * @param {boolean} patch.group_constrained - Group constrained + * @param {Array} teams - array of teams + */ +export const apiPatchTeams = async (teamData, teams = []) => { + let teamArray = teams; + if (!teamArray.length > 0) { + ({teams: teamArray} = await Team.apiGetTeams()); + } + + teamArray.forEach(async (team) => { + await Team.apiPatchTeam(team.id, teamData); + }); +}; + function generateRandomTeam(type, prefix) { const randomId = getRandomId(); @@ -88,7 +203,13 @@ function generateRandomTeam(type, prefix) { export const Team = { apiAddUserToTeam, apiCreateTeam, - apiGetTeamMembersForUser, + apiDeleteTeam, + apiDeleteTeams, + apiDeleteUserFromTeam, + apiGetTeams, + apiGetTeamsForUser, + apiPatchTeam, + apiPatchTeams, }; export default Team; diff --git a/detox/e2e/support/ui/screen/index.js b/detox/e2e/support/ui/screen/index.js index ba15d56f3..9a87f0fd4 100644 --- a/detox/e2e/support/ui/screen/index.js +++ b/detox/e2e/support/ui/screen/index.js @@ -21,6 +21,7 @@ import RecentMentionsScreen from './recent_mentions'; import SearchResultPostScreen from './search_result_post'; import SearchScreen from './search'; import SelectServerScreen from './select_server'; +import SelectTeamScreen from './select_team'; import ThreadScreen from './thread'; export { @@ -44,5 +45,6 @@ export { SearchResultPostScreen, SearchScreen, SelectServerScreen, + SelectTeamScreen, ThreadScreen, }; diff --git a/detox/e2e/support/ui/screen/select_team.js b/detox/e2e/support/ui/screen/select_team.js new file mode 100644 index 000000000..4ecf32f69 --- /dev/null +++ b/detox/e2e/support/ui/screen/select_team.js @@ -0,0 +1,55 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class SelectTeamScreen { + testID = { + selectTeamScreen: 'select_team.screen', + noTeams: 'select_team.no_teams', + teamsList: 'select_team.custom_list', + teamItem: 'select_team.custom_list.team_item', + teamItemDisplayName: 'select_team.custom_list.team_item.display_name', + teamItemIcon: 'select_team.custom_list.team_item.team_icon', + teamItemIconContent: 'select_team.custom_list.team_item.team_icon.content', + } + + selectTeamScreen = element(by.id(this.testID.selectTeamScreen)); + noTeams = element(by.id(this.testID.noTeams)); + teamsList = element(by.id(this.testID.teamsList)); + + toBeVisible = async () => { + await expect(this.selectTeamScreen).toBeVisible(); + + return this.selectTeamScreen; + } + + getTeamItem = (teamId, displayName) => { + const teamItemTestID = `${this.testID.teamItem}.${teamId}`; + const baseMatcher = by.id(teamItemTestID); + const teamItemMatcher = displayName ? baseMatcher.withDescendant(by.text(displayName)) : baseMatcher; + 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), + teamItemDisplayName: element(teamItemDisplayNameMatcher), + teamItemIcon: element(teamItemIconMatcher), + teamItemIconContent: element(teamItemIconContentMatcher), + }; + } + + getTeamByDisplayName = (displayName) => { + return element(by.text(displayName).withAncestor(by.id(this.testID.teamsList))); + } + + getTeamDisplayNameAtIndex = (index) => { + return element(by.id(this.testID.teamItemDisplayName)).atIndex(index); + } + + getTeamIconContentAtIndex = (index) => { + return element(by.id(this.testID.teamItemIconContent)).atIndex(index); + } +} + +const selectTeamScreen = new SelectTeamScreen(); +export default selectTeamScreen; diff --git a/detox/e2e/test/smoke_test/login_ldap.e2e.js b/detox/e2e/test/smoke_test/login_ldap.e2e.js index 52144f99d..fe0f2df16 100644 --- a/detox/e2e/test/smoke_test/login_ldap.e2e.js +++ b/detox/e2e/test/smoke_test/login_ldap.e2e.js @@ -65,7 +65,7 @@ async function ensureUserHasTeam(ldapUser) { // # Login as sysadmin and ensure LDAP user is member of at least one team await User.apiAdminLogin(); const {user} = await User.apiGetUserByUsername(ldapUser.username); - const {teams} = await Team.apiGetTeamMembersForUser(user.id); + const {teams} = await Team.apiGetTeamsForUser(user.id); if (!teams?.length) { const {team} = await Setup.apiInit(); diff --git a/detox/e2e/test/teams/select_team.e2e.js b/detox/e2e/test/teams/select_team.e2e.js new file mode 100644 index 000000000..8747dfade --- /dev/null +++ b/detox/e2e/test/teams/select_team.e2e.js @@ -0,0 +1,52 @@ +// 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 { + ChannelScreen, + LoginScreen, + SelectTeamScreen, +} from '@support/ui/screen'; +import { + Channel, + Team, + User, +} from '@support/server_api'; + +describe('Select Team', () => { + let testTeam; + + beforeAll(async () => { + await Team.apiPatchTeams({allow_open_invite: false}); + const {user} = await User.apiCreateUser(); + const {team} = await Team.apiCreateTeam(); + testTeam = team; + await Team.apiPatchTeam(testTeam.id, {allow_open_invite: true}); + + // # Open select team screen + await LoginScreen.open(); + await LoginScreen.login(user); + await SelectTeamScreen.toBeVisible(); + }); + + afterAll(async () => { + await ChannelScreen.logout(); + }); + + it('MM-T3619 should be able to select a team', async () => { + // # Tap on team to join + const team = await SelectTeamScreen.getTeamByDisplayName(testTeam.display_name); + await team.tap(); + + // * Verify redirect to default channel of joined team + const {channel} = await Channel.apiGetChannelByName(testTeam.name, 'town-square'); + const {channelNavBarTitle} = ChannelScreen; + await ChannelScreen.toBeVisible(); + await expect(channelNavBarTitle).toHaveText(channel.display_name); + }); +});