From 5fd7981d07f64e17c251415e7963d9463033936c Mon Sep 17 00:00:00 2001 From: Gerzon Z Date: Wed, 2 Jun 2021 09:44:19 -0400 Subject: [PATCH 1/8] [NEW] Convert/Move Channel to Team (#3164) * Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Added SelectListView and logic for leaving team * Added addTeamMember and removeTeamMember * Minor tweak * Added deleteTeam function * Minor tweak * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Remove unnecesary prop * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * Minor refactor to ListItem, add SelectListView to ModalStack, update handleLeaveTeam * Minor tweaks * Update SelectListView * Update handleLeaveTeam, remove unnecessary method, add story * Minor tweak * Minor visual tweaks * Update SelectListView.js * Update index.js * Update RoomMembersView * Updated SelectListView, RoomActionsView, leaveTeam method and string translations * Update SelectListVIew * Minor tweak * Update SelectListView * Minor tweak * Minor tweaks * Fix for List.Item subtitles being pushed down by title's flex * Minor tweaks * Update RoomActionsView * Use showConfirmationAlert and showErrorAlert * Remove addTeamMember, update removeTeamMember * Update Alert * Minor tweaks * Minor tweaks * Minor tweak * Update showActionSheet on RoomMembersView * Remove team main from query and move code around * Fetch roles * Update RoomMembersView and SelectListView * Update rocketchat.js * Updated leaveTeam and handleRemoveFromTeam * Fix validation * Remove unnecessary function * Update RoomActionsView * Update en.json * updated deleteTeam function and permissions * Added showConfirmationAlert * Added string translations for teams * Fix permission * Added moveChannelToTeam and convertToTeam functionality * Fix SelectListView RadioButton * Fix moveToTeam * Added searchBar to SelectListVIew * Update RoomView , SelectListVIew and string translation for error Co-authored-by: Diego Mello --- app/containers/RoomHeader/index.js | 5 +- app/i18n/locales/en.json | 12 +- app/lib/methods/getPermissions.js | 1 + app/lib/rocketchat.js | 14 +++ app/views/RoomActionsView/index.js | 190 ++++++++++++++++++++++++++++- app/views/RoomView/RightButtons.js | 4 + app/views/RoomView/index.js | 7 +- app/views/SelectListView.js | 53 ++++++-- 8 files changed, 270 insertions(+), 16 deletions(-) diff --git a/app/containers/RoomHeader/index.js b/app/containers/RoomHeader/index.js index 4eeab701f..7d4d22de8 100644 --- a/app/containers/RoomHeader/index.js +++ b/app/containers/RoomHeader/index.js @@ -32,7 +32,7 @@ class RoomHeaderContainer extends Component { shouldComponentUpdate(nextProps) { const { - type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height + type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height, teamMain } = this.props; if (nextProps.type !== type) { return true; @@ -67,6 +67,9 @@ class RoomHeaderContainer extends Component { if (nextProps.onPress !== onPress) { return true; } + if (nextProps.teamMain !== teamMain) { + return true; + } return false; } diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 29a3b2422..090bf5cd0 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -14,7 +14,7 @@ "error-delete-protected-role": "Cannot delete a protected role", "error-department-not-found": "Department not found", "error-direct-message-file-upload-not-allowed": "File sharing not allowed in direct messages", - "error-duplicate-channel-name": "A channel with name {{channel_name}} exists", + "error-duplicate-channel-name": "A channel with name {{room_name}} exists", "error-email-domain-blacklisted": "The email domain is blacklisted", "error-email-send-failed": "Error trying to send email: {{message}}", "error-save-image": "Error while saving image", @@ -182,6 +182,7 @@ "delete": "delete", "Delete": "Delete", "DELETE": "DELETE", + "move": "move", "deleting_room": "deleting room", "description": "description", "Description": "Description", @@ -731,6 +732,7 @@ "invalid-room": "Invalid room", "You_are_leaving_the_team": "You are leaving the team '{{team}}'", "Leave_Team": "Leave Team", + "Select_Team": "Select Team", "Select_Team_Channels": "Select the Team's channels you would like to leave.", "Cannot_leave": "Cannot leave", "Cannot_remove": "Cannot remove", @@ -746,8 +748,16 @@ "Remove_Member": "Remove Member", "leaving_team": "leaving team", "removing_team": "removing from team", + "moving_channel_to_team": "moving channel to team", "deleting_team": "deleting team", "member-does-not-exist": "Member does not exist", + "Convert": "Convert", + "Convert_to_Team": "Convert to Team", + "Convert_to_Team_Warning": "This can't be undone. Once you convert a channel to a team, you can not turn it back to a channel.", + "Move_to_Team": "Move to Team", + "Move_Channel_to_Team": "Move Channel to Team", + "Move_Channel_Paragraph": "Moving a channel inside a team means that this channel will be added in the team’s context, however, all channel’s members, which are not members of the respective team, will still have access to this channel, but will not be added as team’s members. \n\nAll channel’s management will still be made by the owners of this channel.\n\nTeam’s members and even team’s owners, if not a member of this channel, can not have access to the channel’s content. \n\nPlease notice that the Team’s owner will be able remove members from the Channel.", + "Move_to_Team_Warning": "After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?", "Load_More": "Load More", "Load_Newer": "Load Newer", "Load_Older": "Load Older" diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index 99a18a6c0..2b7da4765 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -17,6 +17,7 @@ const PERMISSIONS = [ 'archive-room', 'auto-translate', 'create-invite-links', + 'create-team', 'delete-c', 'delete-message', 'delete-p', diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 4774b4272..f174aa1eb 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -798,6 +798,20 @@ const RocketChat = { // RC 3.13.0 return this.sdk.get('teams.listRoomsOfUser', { teamId, userId }); }, + convertChannelToTeam({ rid, name, type }) { + const params = { + ...(type === 'c' + ? { + channelId: rid, + channelName: name + } + : { + roomId: rid, + roomName: name + }) + }; + return this.sdk.post(type === 'c' ? 'channels.convertToTeam' : 'groups.convertToTeam', params); + }, joinRoom(roomId, joinCode, type) { // TODO: join code // RC 0.48.0 diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 89d34ed07..7199843c8 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -5,8 +5,9 @@ import { } from 'react-native'; import { connect } from 'react-redux'; import isEmpty from 'lodash/isEmpty'; -import { compareServerVersion, methods } from '../../lib/utils'; +import { Q } from '@nozbe/watermelondb'; +import { compareServerVersion, methods } from '../../lib/utils'; import Touch from '../../utils/touch'; import { setLoading as setLoadingAction } from '../../actions/selectedUsers'; import { leaveRoom as leaveRoomAction, closeRoom as closeRoomAction } from '../../actions/room'; @@ -61,7 +62,9 @@ class RoomActionsView extends React.Component { editRoomPermission: PropTypes.array, toggleRoomE2EEncryptionPermission: PropTypes.array, viewBroadcastMemberListPermission: PropTypes.array, - transferLivechatGuestPermission: PropTypes.array + transferLivechatGuestPermission: PropTypes.array, + createTeamPermission: PropTypes.array, + addTeamChannelPermission: PropTypes.array } constructor(props) { @@ -83,7 +86,9 @@ class RoomActionsView extends React.Component { canForwardGuest: false, canReturnQueue: false, canEdit: false, - canToggleEncryption: false + canToggleEncryption: false, + canCreateTeam: false, + canAddChannelToTeam: false }; if (room && room.observe && room.rid) { this.roomObservable = room.observe(); @@ -132,9 +137,11 @@ class RoomActionsView extends React.Component { const canEdit = await this.canEdit(); const canToggleEncryption = await this.canToggleEncryption(); const canViewMembers = await this.canViewMembers(); + const canCreateTeam = await this.canCreateTeam(); + const canAddChannelToTeam = await this.canAddChannelToTeam(); this.setState({ - canAutoTranslate, canAddUser, canInviteUser, canEdit, canToggleEncryption, canViewMembers + canAutoTranslate, canAddUser, canInviteUser, canEdit, canToggleEncryption, canViewMembers, canCreateTeam, canAddChannelToTeam }); // livechat permissions @@ -210,6 +217,26 @@ class RoomActionsView extends React.Component { return canEdit; } + canCreateTeam = async() => { + const { room } = this.state; + const { createTeamPermission } = this.props; + const { rid } = room; + const permissions = await RocketChat.hasPermission([createTeamPermission], rid); + + const canCreateTeam = permissions[0]; + return canCreateTeam; + } + + canAddChannelToTeam = async() => { + const { room } = this.state; + const { addTeamChannelPermission } = this.props; + const { rid } = room; + const permissions = await RocketChat.hasPermission([addTeamChannelPermission], rid); + + const canAddChannelToTeam = permissions[0]; + return canAddChannelToTeam; + } + canToggleEncryption = async() => { const { room } = this.state; const { toggleRoomE2EEncryptionPermission } = this.props; @@ -464,6 +491,111 @@ class RoomActionsView extends React.Component { } } + handleConvertToTeam = async() => { + try { + const { room } = this.state; + const { navigation } = this.props; + const result = await RocketChat.convertChannelToTeam({ rid: room.rid, name: room.name, type: room.t }); + + if (result.success) { + navigation.navigate('RoomView'); + } + } catch (e) { + log(e); + } + } + + convertToTeam = () => { + showConfirmationAlert({ + title: I18n.t('Confirmation'), + message: I18n.t('Convert_to_Team_Warning'), + confirmationText: I18n.t('Convert'), + onPress: () => this.handleConvertToTeam() + }); + } + + handleMoveToTeam = async(selected) => { + try { + const { room } = this.state; + const { navigation } = this.props; + const result = await RocketChat.addRoomsToTeam({ teamId: selected.teamId, rooms: [room.rid] }); + if (result.success) { + navigation.navigate('RoomView'); + } + } catch (e) { + log(e); + showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('moving_channel_to_team') })); + } + } + + moveToTeam = async() => { + try { + const { navigation } = this.props; + const db = database.active; + const subCollection = db.get('subscriptions'); + const teamRooms = await subCollection.query( + Q.where('team_main', Q.notEq(null)) + ); + + if (teamRooms.length) { + navigation.navigate('SelectListView', { + title: 'Move_to_Team', + infoText: 'Move_Channel_Paragraph', + nextAction: () => { + navigation.push('SelectListView', { + title: 'Select_Team', + data: teamRooms, + isRadio: true, + isSearch: true, + onSearch: onChangeText => this.searchTeam(onChangeText), + nextAction: selected => showConfirmationAlert({ + title: I18n.t('Confirmation'), + message: I18n.t('Move_to_Team_Warning'), + confirmationText: I18n.t('Yes_action_it', { action: I18n.t('move') }), + onPress: () => this.handleMoveToTeam(selected) + }) + + }); + } + }); + } + } catch (e) { + log(e); + } + } + + searchTeam = async(onChangeText) => { + try { + const { addTeamChannelPermission, createTeamPermission } = this.props; + const QUERY_SIZE = 50; + const db = database.active; + const teams = await db.collections + .get('subscriptions') + .query( + Q.where('team_main', Q.notEq(null)), + Q.where('name', Q.like(`%${ onChangeText }%`)), + Q.experimentalTake(QUERY_SIZE), + Q.experimentalSortBy('room_updated_at', Q.desc) + ); + + const asyncFilter = async(teamArray) => { + const results = await Promise.all(teamArray.map(async(team) => { + const permissions = await RocketChat.hasPermission([addTeamChannelPermission, createTeamPermission], team.rid); + if (!permissions[0]) { + return false; + } + return true; + })); + + return teamArray.filter((_v, index) => results[index]); + }; + const teamsFiltered = await asyncFilter(teams); + return teamsFiltered; + } catch (e) { + log(e); + } + } + renderRoomInfo = () => { const { room, member } = this.state; const { @@ -635,6 +767,50 @@ class RoomActionsView extends React.Component { } } + teamChannelActions = (t, room) => { + const { canEdit, canCreateTeam, canAddChannelToTeam } = this.state; + const canConvertToTeam = canEdit && canCreateTeam && !room.teamMain; + const canMoveToTeam = canEdit && canAddChannelToTeam && !room.teamId; + + return ( + <> + {['c', 'p'].includes(t) && canConvertToTeam + ? ( + <> + this.onPressTouchable({ + event: this.convertToTeam + })} + testID='room-actions-convert-to-team' + left={() => } + showActionIndicator + /> + + + ) + : null} + + {['c', 'p'].includes(t) && canMoveToTeam + ? ( + <> + this.onPressTouchable({ + event: this.moveToTeam + })} + testID='room-actions-convert-to-team' + left={() => } + showActionIndicator + /> + + + ) + : null} + + ); + } + render() { const { room, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate, canForwardGuest, canReturnQueue @@ -836,6 +1012,8 @@ class RoomActionsView extends React.Component { ) : null} + { this.teamChannelActions(t, room) } + {['l'].includes(t) && !this.isOmnichannelPreview ? ( <> @@ -922,7 +1100,9 @@ const mapStateToProps = state => ({ editRoomPermission: state.permissions['edit-room'], toggleRoomE2EEncryptionPermission: state.permissions['toggle-room-e2e-encryption'], viewBroadcastMemberListPermission: state.permissions['view-broadcast-member-list'], - transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'] + transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'], + createTeamPermission: state.permissions['create-team'], + addTeamChannelPermission: state.permissions['add-team-channel'] }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/RoomView/RightButtons.js b/app/views/RoomView/RightButtons.js index f61488b5b..5b283b4ad 100644 --- a/app/views/RoomView/RightButtons.js +++ b/app/views/RoomView/RightButtons.js @@ -59,6 +59,10 @@ class RightButtonsContainer extends Component { const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state; + const { teamId } = this.props; + if (nextProps.teamId !== teamId) { + return true; + } if (nextState.isFollowingThread !== isFollowingThread) { return true; } diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index 18d123ec4..13de89c87 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -85,7 +85,7 @@ const stateAttrsUpdate = [ 'member', 'showingBlockingLoader' ]; -const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'tunread', 'muted', 'ignored', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles', 'bannerClosed', 'visitor', 'joinCodeRequired']; +const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'tunread', 'muted', 'ignored', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles', 'bannerClosed', 'visitor', 'joinCodeRequired', 'teamMain', 'teamId']; class RoomView extends React.Component { static propTypes = { @@ -254,7 +254,10 @@ class RoomView extends React.Component { this.setHeader(); } } - if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name)) && !this.tmid) { + if ((roomUpdate.teamMain !== prevState.roomUpdate.teamMain) || (roomUpdate.teamId !== prevState.roomUpdate.teamId)) { + this.setHeader(); + } + if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name) || (roomUpdate.teamMain !== prevState.roomUpdate.teamMain) || (roomUpdate.teamId !== prevState.roomUpdate.teamId)) && !this.tmid) { this.setHeader(); } if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) { diff --git a/app/views/SelectListView.js b/app/views/SelectListView.js index bcbb0923f..a3ad3f889 100644 --- a/app/views/SelectListView.js +++ b/app/views/SelectListView.js @@ -4,7 +4,9 @@ import { View, StyleSheet, FlatList, Text } from 'react-native'; import { connect } from 'react-redux'; +import { RadioButton } from 'react-native-ui-lib'; +import log from '../utils/log'; import * as List from '../containers/List'; import sharedStyles from './Styles'; import I18n from '../i18n'; @@ -14,6 +16,9 @@ import { themes } from '../constants/colors'; import { withTheme } from '../theme'; import SafeAreaView from '../containers/SafeAreaView'; import { animateNextTransition } from '../utils/layoutAnimation'; +import { ICON_SIZE } from '../containers/List/constants'; +import SearchBox from '../containers/SearchBox'; + const styles = StyleSheet.create({ buttonText: { @@ -38,8 +43,13 @@ class SelectListView extends React.Component { this.infoText = props.route?.params?.infoText; this.nextAction = props.route?.params?.nextAction; this.showAlert = props.route?.params?.showAlert; + this.isSearch = props.route?.params?.isSearch; + this.onSearch = props.route?.params?.onSearch; + this.isRadio = props.route?.params?.isRadio; this.state = { data, + dataFiltered: [], + isSearching: false, selected: [] }; this.setHeader(); @@ -75,6 +85,25 @@ class SelectListView extends React.Component { ); } + renderSearch = () => { + const { theme } = this.props; + return ( + + this.search(text)} testID='select-list-view-search' onCancelPress={() => this.setState({ isSearching: false })} /> + + ); + } + + search = async(text) => { + try { + this.setState({ isSearching: true }); + const result = await this.onSearch(text); + this.setState({ dataFiltered: result }); + } catch (e) { + log(e); + } + } + isChecked = (rid) => { const { selected } = this.state; return selected.includes(rid); @@ -84,7 +113,11 @@ class SelectListView extends React.Component { const { selected } = this.state; animateNextTransition(); - if (!this.isChecked(rid)) { + if (this.isRadio) { + if (!this.isChecked(rid)) { + this.setState({ selected: [rid] }, () => this.setHeader()); + } + } else if (!this.isChecked(rid)) { this.setState({ selected: [...selected, rid] }, () => this.setHeader()); } else { const filterSelected = selected.filter(el => el !== rid); @@ -94,9 +127,16 @@ class SelectListView extends React.Component { renderItem = ({ item }) => { const { theme } = this.props; - const icon = item.t === 'p' ? 'channel-private' : 'channel-public'; + const { selected } = this.state; + + const channelIcon = item.t === 'p' ? 'channel-private' : 'channel-public'; + const teamIcon = item.t === 'p' ? 'teams-private' : 'teams'; + const icon = item.teamMain ? teamIcon : channelIcon; const checked = this.isChecked(item.rid) ? 'check' : null; + const showRadio = () => ; + const showCheck = () => ; + return ( <> @@ -107,25 +147,24 @@ class SelectListView extends React.Component { onPress={() => (item.alert ? this.showAlert() : this.toggleItem(item.rid))} alert={item.alert} left={() => } - right={() => (checked ? : null)} + right={() => (this.isRadio ? showRadio() : showCheck())} /> ); } render() { - const { data } = this.state; + const { data, isSearching, dataFiltered } = this.state; const { theme } = this.props; - return ( item.rid} renderItem={this.renderItem} - ListHeaderComponent={this.renderInfoText} + ListHeaderComponent={this.isSearch ? this.renderSearch : this.renderInfoText} contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }} keyboardShouldPersistTaps='always' /> From c087780ccfa848be8ce744b8607195f510209e35 Mon Sep 17 00:00:00 2001 From: Gerzon Z Date: Fri, 4 Jun 2021 12:16:05 -0400 Subject: [PATCH 2/8] [TEST] E2E Tests for Teams (#3178) * Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Added SelectListView and logic for leaving team * Added addTeamMember and removeTeamMember * Minor tweak * Added deleteTeam function * Minor tweak * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Remove unnecesary prop * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * Minor refactor to ListItem, add SelectListView to ModalStack, update handleLeaveTeam * Minor tweaks * Update SelectListView * Update handleLeaveTeam, remove unnecessary method, add story * Minor tweak * Minor visual tweaks * Update SelectListView.js * Update index.js * Update RoomMembersView * Updated SelectListView, RoomActionsView, leaveTeam method and string translations * Update SelectListVIew * Minor tweak * Update SelectListView * Minor tweak * Minor tweaks * Fix for List.Item subtitles being pushed down by title's flex * Minor tweaks * Update RoomActionsView * Use showConfirmationAlert and showErrorAlert * Remove addTeamMember, update removeTeamMember * Update Alert * Minor tweaks * Minor tweaks * Minor tweak * Update showActionSheet on RoomMembersView * Remove team main from query and move code around * Fetch roles * Update RoomMembersView and SelectListView * Update rocketchat.js * Updated leaveTeam and handleRemoveFromTeam * Fix validation * Remove unnecessary function * Update RoomActionsView * Update en.json * updated deleteTeam function and permissions * Added showConfirmationAlert * Added string translations for teams * Fix permission * Added moveChannelToTeam and convertToTeam functionality * Fix SelectListView RadioButton * Fix moveToTeam * Added searchBar to SelectListVIew * Update RoomView , SelectListVIew and string translation for error * E2E for Teams * Fix tests and cleanup * Minor refactor * Wrong label * Move/convert * Fix convert Co-authored-by: Diego Mello --- .../__snapshots__/Storyshots.test.js.snap | 2 + app/containers/ActionSheet/Item.js | 4 +- app/containers/List/ListIcon.js | 7 +- app/i18n/locales/en.json | 1 - app/presentation/RoomItem/RoomItem.js | 2 +- app/presentation/RoomItem/Tag.js | 6 +- app/views/AddChannelTeamView.js | 2 +- app/views/AddExistingChannelView.js | 4 +- app/views/CreateChannelView.js | 4 +- app/views/RoomActionsView/index.js | 14 +- app/views/RoomMembersView/index.js | 21 +- app/views/SelectListView.js | 4 +- app/views/TeamChannelsView.js | 13 +- e2e/data/data.cloud.js | 10 + e2e/data/data.docker.js | 10 + e2e/helpers/app.js | 4 +- e2e/tests/assorted/05-joinpublicroom.spec.js | 5 - e2e/tests/room/02-room.spec.js | 12 +- e2e/tests/room/03-roomactions.spec.js | 64 ++-- e2e/tests/room/04-discussion.spec.js | 4 - e2e/tests/team/01-createteam.spec.js | 70 ++--- e2e/tests/team/02-team.spec.js | 296 ++++++++++++++++++ e2e/tests/team/03-moveconvert.spec.js | 89 ++++++ 23 files changed, 526 insertions(+), 122 deletions(-) create mode 100644 e2e/tests/team/02-team.spec.js create mode 100644 e2e/tests/team/03-moveconvert.spec.js diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index eec5b535a..52fa5d01d 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -58119,6 +58119,7 @@ exports[`Storyshots Room Item Tag 1`] = ` }, ] } + testID="auto-join-tag" > Auto-join @@ -59020,6 +59021,7 @@ exports[`Storyshots Room Item Tag 1`] = ` }, ] } + testID="auto-join-tag" > Auto-join diff --git a/app/containers/ActionSheet/Item.js b/app/containers/ActionSheet/Item.js index aa76da8bb..2cacd0855 100644 --- a/app/containers/ActionSheet/Item.js +++ b/app/containers/ActionSheet/Item.js @@ -18,6 +18,7 @@ export const Item = React.memo(({ item, hide, theme }) => { onPress={onPress} style={[styles.item, { backgroundColor: themes[theme].focusedBackground }]} theme={theme} + testID={item.testID} > @@ -42,7 +43,8 @@ Item.propTypes = { icon: PropTypes.string, danger: PropTypes.bool, onPress: PropTypes.func, - right: PropTypes.func + right: PropTypes.func, + testID: PropTypes.string }), hide: PropTypes.func, theme: PropTypes.string diff --git a/app/containers/List/ListIcon.js b/app/containers/List/ListIcon.js index 5ab6c3bc9..44941f2ca 100644 --- a/app/containers/List/ListIcon.js +++ b/app/containers/List/ListIcon.js @@ -18,13 +18,15 @@ const ListIcon = React.memo(({ theme, name, color, - style + style, + testID }) => ( )); @@ -33,7 +35,8 @@ ListIcon.propTypes = { theme: PropTypes.string, name: PropTypes.string, color: PropTypes.string, - style: PropTypes.object + style: PropTypes.object, + testID: PropTypes.string }; ListIcon.displayName = 'List.Icon'; diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 090bf5cd0..a8f8d9635 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -755,7 +755,6 @@ "Convert_to_Team": "Convert to Team", "Convert_to_Team_Warning": "This can't be undone. Once you convert a channel to a team, you can not turn it back to a channel.", "Move_to_Team": "Move to Team", - "Move_Channel_to_Team": "Move Channel to Team", "Move_Channel_Paragraph": "Moving a channel inside a team means that this channel will be added in the team’s context, however, all channel’s members, which are not members of the respective team, will still have access to this channel, but will not be added as team’s members. \n\nAll channel’s management will still be made by the owners of this channel.\n\nTeam’s members and even team’s owners, if not a member of this channel, can not have access to the channel’s content. \n\nPlease notice that the Team’s owner will be able remove members from the Channel.", "Move_to_Team_Warning": "After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?", "Load_More": "Load More", diff --git a/app/presentation/RoomItem/RoomItem.js b/app/presentation/RoomItem/RoomItem.js index 065e91331..2ac73a0bb 100644 --- a/app/presentation/RoomItem/RoomItem.js +++ b/app/presentation/RoomItem/RoomItem.js @@ -94,7 +94,7 @@ const RoomItem = ({ alert={alert} /> { - autoJoin ? : null + autoJoin ? : null } { +const Tag = React.memo(({ name, testID }) => { const { theme } = useTheme(); return ( @@ -16,6 +16,7 @@ const Tag = React.memo(({ name }) => { styles.tagText, { color: themes[theme].infoText } ]} numberOfLines={1} + testID={testID} > {name} @@ -24,7 +25,8 @@ const Tag = React.memo(({ name }) => { }); Tag.propTypes = { - name: PropTypes.string + name: PropTypes.string, + testID: PropTypes.string }; export default Tag; diff --git a/app/views/AddChannelTeamView.js b/app/views/AddChannelTeamView.js index fdb166caf..76439b412 100644 --- a/app/views/AddChannelTeamView.js +++ b/app/views/AddChannelTeamView.js @@ -51,7 +51,7 @@ const AddChannelTeamView = ({ navigation.navigate('AddExistingChannelView', { teamId, teamChannels })} - testID='add-channel-team-view-create-channel' + testID='add-channel-team-view-add-existing' left={() => } right={() => } theme={theme} diff --git a/app/views/AddExistingChannelView.js b/app/views/AddExistingChannelView.js index c1c94fec8..e7a590a54 100644 --- a/app/views/AddExistingChannelView.js +++ b/app/views/AddExistingChannelView.js @@ -61,7 +61,7 @@ class AddExistingChannelView extends React.Component { options.headerRight = () => selected.length > 0 && ( - + ); @@ -169,7 +169,7 @@ class AddExistingChannelView extends React.Component { title={RocketChat.getRoomTitle(item)} translateTitle={false} onPress={() => this.toggleChannel(item.rid)} - testID='add-existing-channel-view-item' + testID={`add-existing-channel-view-item-${ item.name }`} left={() => } right={() => (isChecked ? : null)} /> diff --git a/app/views/CreateChannelView.js b/app/views/CreateChannelView.js index 54f402c63..78f4ea42a 100644 --- a/app/views/CreateChannelView.js +++ b/app/views/CreateChannelView.js @@ -334,7 +334,7 @@ class CreateChannelView extends React.Component { keyboardVerticalOffset={128} > - + ({ + rid: team.teamId, + t: team.t, + name: team.name + })); navigation.navigate('SelectListView', { title: 'Move_to_Team', infoText: 'Move_Channel_Paragraph', nextAction: () => { navigation.push('SelectListView', { title: 'Select_Team', - data: teamRooms, + data, isRadio: true, isSearch: true, onSearch: onChangeText => this.searchTeam(onChangeText), @@ -554,7 +559,6 @@ class RoomActionsView extends React.Component { confirmationText: I18n.t('Yes_action_it', { action: I18n.t('move') }), onPress: () => this.handleMoveToTeam(selected) }) - }); } }); @@ -795,11 +799,11 @@ class RoomActionsView extends React.Component { ? ( <> this.onPressTouchable({ event: this.moveToTeam })} - testID='room-actions-convert-to-team' + testID='room-actions-move-to-team' left={() => } showActionIndicator /> diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js index 5819cf512..5f0deb326 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.js @@ -265,7 +265,8 @@ class RoomMembersView extends React.Component { options.push({ icon: 'ignore', title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'), - onPress: () => this.handleIgnore(selectedUser, !isIgnored) + onPress: () => this.handleIgnore(selectedUser, !isIgnored), + testID: 'action-sheet-ignore-user' }); } @@ -284,7 +285,8 @@ class RoomMembersView extends React.Component { confirmationText: I18n.t(userIsMuted ? 'Unmute' : 'Mute'), onPress: () => this.handleMute(selectedUser) }); - } + }, + testID: 'action-sheet-mute-user' }); } @@ -296,7 +298,8 @@ class RoomMembersView extends React.Component { icon: 'shield-check', title: I18n.t('Owner'), onPress: () => this.handleOwner(selectedUser, !isOwner), - right: () => + right: () => , + testID: 'action-sheet-set-owner' }); } @@ -308,7 +311,8 @@ class RoomMembersView extends React.Component { icon: 'shield-alt', title: I18n.t('Leader'), onPress: () => this.handleLeader(selectedUser, !isLeader), - right: () => + right: () => , + testID: 'action-sheet-set-leader' }); } @@ -320,7 +324,8 @@ class RoomMembersView extends React.Component { icon: 'shield', title: I18n.t('Moderator'), onPress: () => this.handleModerator(selectedUser, !isModerator), - right: () => + right: () => , + testID: 'action-sheet-set-moderator' }); } @@ -330,7 +335,8 @@ class RoomMembersView extends React.Component { icon: 'logout', danger: true, title: I18n.t('Remove_from_Team'), - onPress: () => this.handleRemoveFromTeam(selectedUser) + onPress: () => this.handleRemoveFromTeam(selectedUser), + testID: 'action-sheet-remove-from-team' }); } @@ -346,7 +352,8 @@ class RoomMembersView extends React.Component { confirmationText: I18n.t('Yes_remove_user'), onPress: () => this.handleRemoveUserFromRoom(selectedUser) }); - } + }, + testID: 'action-sheet-remove-from-room' }); } diff --git a/app/views/SelectListView.js b/app/views/SelectListView.js index a3ad3f889..5767df3f2 100644 --- a/app/views/SelectListView.js +++ b/app/views/SelectListView.js @@ -134,8 +134,8 @@ class SelectListView extends React.Component { const icon = item.teamMain ? teamIcon : channelIcon; const checked = this.isChecked(item.rid) ? 'check' : null; - const showRadio = () => ; - const showCheck = () => ; + const showRadio = () => ; + const showCheck = () => ; return ( <> diff --git a/app/views/TeamChannelsView.js b/app/views/TeamChannelsView.js index 15905a9ee..cee7fe13c 100644 --- a/app/views/TeamChannelsView.js +++ b/app/views/TeamChannelsView.js @@ -217,9 +217,9 @@ class TeamChannelsView extends React.Component { options.headerRight = () => ( { showCreate - ? navigation.navigate('AddChannelTeamView', { teamId: this.teamId, teamChannels: data })} /> + ? navigation.navigate('AddChannelTeamView', { teamId: this.teamId, teamChannels: data })} /> : null} - + ); navigation.setOptions(options); @@ -388,7 +388,8 @@ class TeamChannelsView extends React.Component { title: I18n.t('Auto-join'), icon: item.t === 'p' ? 'channel-private' : 'channel-public', onPress: () => this.toggleAutoJoin(item), - right: () => + right: () => , + testID: 'action-sheet-auto-join' }); } @@ -398,7 +399,8 @@ class TeamChannelsView extends React.Component { title: I18n.t('Remove_from_Team'), icon: 'close', danger: true, - onPress: () => this.remove(item) + onPress: () => this.remove(item), + testID: 'action-sheet-remove-from-team' }); } @@ -408,7 +410,8 @@ class TeamChannelsView extends React.Component { title: I18n.t('Delete'), icon: 'delete', danger: true, - onPress: () => this.delete(item) + onPress: () => this.delete(item), + testID: 'action-sheet-delete' }); } diff --git a/e2e/data/data.cloud.js b/e2e/data/data.cloud.js index 381d939f1..c69b72515 100644 --- a/e2e/data/data.cloud.js +++ b/e2e/data/data.cloud.js @@ -42,6 +42,11 @@ const data = { name: `detox-private-${ value }` } }, + teams: { + private: { + name: `detox-team-${ value }` + } + }, registeringUser: { username: `newuser${ value }`, password: `password${ value }`, @@ -57,6 +62,11 @@ const data = { password: `passwordthree${ value }`, email: `mobile+registeringthree${ value }@rocket.chat` }, + registeringUser4: { + username: `newuserfour${ value }`, + password: `passwordfour${ value }`, + email: `mobile+registeringfour${ value }@rocket.chat` + }, random: value } module.exports = data; diff --git a/e2e/data/data.docker.js b/e2e/data/data.docker.js index 77f9f82c0..1a6bb1569 100644 --- a/e2e/data/data.docker.js +++ b/e2e/data/data.docker.js @@ -42,6 +42,11 @@ const data = { name: `detox-private-${ value }` } }, + teams: { + private: { + name: `detox-team-${ value }` + } + }, registeringUser: { username: `newuser${ value }`, password: `password${ value }`, @@ -57,6 +62,11 @@ const data = { password: `passwordthree${ value }`, email: `mobile+registeringthree${ value }@rocket.chat` }, + registeringUser4: { + username: `newuserfour${ value }`, + password: `passwordfour${ value }`, + email: `mobile+registeringfour${ value }@rocket.chat` + }, random: value } module.exports = data; diff --git a/e2e/helpers/app.js b/e2e/helpers/app.js index af72f73d1..7c71c9f3e 100644 --- a/e2e/helpers/app.js +++ b/e2e/helpers/app.js @@ -67,7 +67,7 @@ async function starMessage(message){ await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); - await element(by.label('Star')).tap(); + await element(by.label('Star')).atIndex(0).tap(); await waitFor(element(by.id('action-sheet'))).not.toExist().withTimeout(5000); }; @@ -78,7 +78,7 @@ async function pinMessage(message){ await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); - await element(by.label('Pin')).tap(); + await element(by.label('Pin')).atIndex(0).tap(); await waitFor(element(by.id('action-sheet'))).not.toExist().withTimeout(5000); } diff --git a/e2e/tests/assorted/05-joinpublicroom.spec.js b/e2e/tests/assorted/05-joinpublicroom.spec.js index 9b92d33b3..15acbda9c 100644 --- a/e2e/tests/assorted/05-joinpublicroom.spec.js +++ b/e2e/tests/assorted/05-joinpublicroom.spec.js @@ -98,10 +98,6 @@ describe('Join public room', () => { await expect(element(by.id('room-actions-starred'))).toBeVisible(); }); - it('should have search', async() => { - await expect(element(by.id('room-actions-search'))).toBeVisible(); - }); - it('should have share', async() => { await expect(element(by.id('room-actions-share'))).toBeVisible(); }); @@ -150,7 +146,6 @@ describe('Join public room', () => { await expect(element(by.id('room-actions-files'))).toBeVisible(); await expect(element(by.id('room-actions-mentioned'))).toBeVisible(); await expect(element(by.id('room-actions-starred'))).toBeVisible(); - await expect(element(by.id('room-actions-search'))).toBeVisible(); await element(by.type('UIScrollView')).atIndex(1).swipe('down'); await expect(element(by.id('room-actions-share'))).toBeVisible(); await expect(element(by.id('room-actions-pinned'))).toBeVisible(); diff --git a/e2e/tests/room/02-room.spec.js b/e2e/tests/room/02-room.spec.js index 820536fb8..02d874963 100644 --- a/e2e/tests/room/02-room.spec.js +++ b/e2e/tests/room/02-room.spec.js @@ -168,7 +168,7 @@ describe('Room screen', () => { await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); - await element(by.label('Permalink')).tap(); + await element(by.label('Permalink')).atIndex(0).tap(); // TODO: test clipboard }); @@ -178,7 +178,7 @@ describe('Room screen', () => { await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); - await element(by.label('Copy')).tap(); + await element(by.label('Copy')).atIndex(0).tap(); // TODO: test clipboard }); @@ -191,7 +191,7 @@ describe('Room screen', () => { await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'slow', 0.5); - await waitFor(element(by.label('Unstar'))).toBeVisible().withTimeout(6000); + await waitFor(element(by.label('Unstar')).atIndex(0)).toBeVisible().withTimeout(6000); await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.8); }); @@ -243,7 +243,7 @@ describe('Room screen', () => { await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); - await element(by.label('Edit')).tap(); + await element(by.label('Edit')).atIndex(0).tap(); await element(by.id('messagebox-input')).typeText('ed'); await element(by.id('messagebox-send-message')).tap(); await waitFor(element(by.label(`${ data.random }edited (edited)`)).atIndex(0)).toExist().withTimeout(60000); @@ -255,7 +255,7 @@ describe('Room screen', () => { await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); - await element(by.label('Quote')).tap(); + await element(by.label('Quote')).atIndex(0).tap(); await element(by.id('messagebox-input')).typeText(`${ data.random }quoted`); await element(by.id('messagebox-send-message')).tap(); @@ -285,7 +285,7 @@ describe('Room screen', () => { await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await waitFor(element(by.label('Delete'))).toExist().withTimeout(1000); - await element(by.label('Delete')).tap(); + await element(by.label('Delete')).atIndex(0).tap(); const deleteAlertMessage = 'You will not be able to recover this message!'; await waitFor(element(by.text(deleteAlertMessage)).atIndex(0)).toExist().withTimeout(10000); diff --git a/e2e/tests/room/03-roomactions.spec.js b/e2e/tests/room/03-roomactions.spec.js index fcfc86711..204235705 100644 --- a/e2e/tests/room/03-roomactions.spec.js +++ b/e2e/tests/room/03-roomactions.spec.js @@ -77,10 +77,6 @@ describe('Room actions screen', () => { await expect(element(by.id('room-actions-starred'))).toExist(); }); - it('should have search', async() => { - await expect(element(by.id('room-actions-search'))).toExist(); - }); - it('should have share', async() => { await waitFor(element(by.id('room-actions-share'))).toExist(); await expect(element(by.id('room-actions-share'))).toExist(); @@ -147,10 +143,6 @@ describe('Room actions screen', () => { await expect(element(by.id('room-actions-starred'))).toExist(); }); - it('should have search', async() => { - await expect(element(by.id('room-actions-search'))).toExist(); - }); - it('should have share', async() => { await waitFor(element(by.id('room-actions-share'))).toExist(); await expect(element(by.id('room-actions-share'))).toExist(); @@ -229,7 +221,7 @@ describe('Room actions screen', () => { await element(by.label(`${ data.random }messageToStar`)).atIndex(0).longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); - await element(by.label('Unstar')).tap(); + await element(by.label('Unstar')).atIndex(0).tap(); await waitFor(element(by.label(`${ data.random }messageToStar`).withAncestor(by.id('starred-messages-view')))).toBeNotVisible().withTimeout(60000); await backToActions(); @@ -256,29 +248,29 @@ describe('Room actions screen', () => { await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); - await element(by.label('Unpin')).tap(); + await element(by.label('Unpin')).atIndex(0).tap(); await waitFor(element(by.label(`${ data.random }messageToPin`).withAncestor(by.id('pinned-messages-view')))).not.toExist().withTimeout(6000); await backToActions(); }); - it('should search and find a message', async() => { + // it('should search and find a message', async() => { - //Go back to room and send a message - await tapBack(); - await mockMessage('messageToFind'); + // //Go back to room and send a message + // await tapBack(); + // await mockMessage('messageToFind'); - //Back into Room Actions - await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + // //Back into Room Actions + // await element(by.id('room-header')).tap(); + // await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); - await element(by.id('room-actions-search')).tap(); - await waitFor(element(by.id('search-messages-view'))).toExist().withTimeout(2000); - await expect(element(by.id('search-message-view-input'))).toExist(); - await element(by.id('search-message-view-input')).replaceText(`/${ data.random }messageToFind/`); - await waitFor(element(by.label(`${ data.random }messageToFind`).withAncestor(by.id('search-messages-view')))).toExist().withTimeout(60000); - await backToActions(); - }); + // await element(by.id('room-actions-search')).tap(); + // await waitFor(element(by.id('search-messages-view'))).toExist().withTimeout(2000); + // await expect(element(by.id('search-message-view-input'))).toExist(); + // await element(by.id('search-message-view-input')).replaceText(`/${ data.random }messageToFind/`); + // await waitFor(element(by.label(`${ data.random }messageToFind`).withAncestor(by.id('search-messages-view')))).toExist().withTimeout(60000); + // await backToActions(); + // }); }); describe('Notification', async() => { @@ -419,46 +411,46 @@ describe('Room actions screen', () => { it('should set/remove as owner', async() => { await openActionSheet(user.username); - await element(by.label('Set as owner')).tap(); + await element(by.id('action-sheet-set-owner')).tap(); await waitForToast(); await openActionSheet(user.username); - await element(by.label('Remove as owner')).tap(); + await waitFor(element(by.id('action-sheet-set-owner-checked'))).toBeVisible().withTimeout(6000); + await element(by.id('action-sheet-set-owner')).tap(); await waitForToast(); await openActionSheet(user.username); - // Tests if Remove as owner worked - await waitFor(element(by.label('Set as owner'))).toExist().withTimeout(5000); + await waitFor(element(by.id('action-sheet-set-owner-unchecked'))).toBeVisible().withTimeout(60000); await closeActionSheet(); }); it('should set/remove as leader', async() => { await openActionSheet(user.username); - await element(by.label('Set as leader')).tap(); + await element(by.id('action-sheet-set-leader')).tap(); await waitForToast(); await openActionSheet(user.username); - await element(by.label('Remove as leader')).tap(); + await waitFor(element(by.id('action-sheet-set-leader-checked'))).toBeVisible().withTimeout(6000); + await element(by.id('action-sheet-set-leader')).tap(); await waitForToast(); await openActionSheet(user.username); - // Tests if Remove as leader worked - await waitFor(element(by.label('Set as leader'))).toExist().withTimeout(5000); + await waitFor(element(by.id('action-sheet-set-owner-unchecked'))).toBeVisible().withTimeout(60000); await closeActionSheet(); }); it('should set/remove as moderator', async() => { await openActionSheet(user.username); - await element(by.label('Set as moderator')).tap(); + await element(by.id('action-sheet-set-moderator')).tap(); await waitForToast(); await openActionSheet(user.username); - await element(by.label('Remove as moderator')).tap(); + await waitFor(element(by.id('action-sheet-set-moderator-checked'))).toBeVisible().withTimeout(6000); + await element(by.id('action-sheet-set-moderator')).tap(); await waitForToast(); await openActionSheet(user.username); - // Tests if Remove as moderator worked - await waitFor(element(by.label('Set as moderator'))).toExist().withTimeout(5000); + await waitFor(element(by.id('action-sheet-set-moderator-unchecked'))).toBeVisible().withTimeout(60000); await closeActionSheet(); }); diff --git a/e2e/tests/room/04-discussion.spec.js b/e2e/tests/room/04-discussion.spec.js index e5705b26b..fccc96185 100644 --- a/e2e/tests/room/04-discussion.spec.js +++ b/e2e/tests/room/04-discussion.spec.js @@ -102,10 +102,6 @@ describe('Discussion', () => { await expect(element(by.id('room-actions-starred'))).toBeVisible(); }); - it('should have search', async() => { - await expect(element(by.id('room-actions-search'))).toBeVisible(); - }); - it('should have share', async() => { await element(by.type('UIScrollView')).atIndex(1).swipe('up'); await expect(element(by.id('room-actions-share'))).toBeVisible(); diff --git a/e2e/tests/team/01-createteam.spec.js b/e2e/tests/team/01-createteam.spec.js index 4dfe17ca3..3c9a2260c 100644 --- a/e2e/tests/team/01-createteam.spec.js +++ b/e2e/tests/team/01-createteam.spec.js @@ -2,9 +2,9 @@ const { device, expect, element, by, waitFor } = require('detox'); const data = require('../../data'); -const { tapBack, sleep, navigateToLogin, login, tryTapping } = require('../../helpers/app'); - +const { navigateToLogin, login } = require('../../helpers/app'); +const teamName = `team-${ data.random }`; describe('Create team screen', () => { before(async() => { @@ -18,38 +18,18 @@ describe('Create team screen', () => { await element(by.id('rooms-list-view-create-channel')).tap(); }); - describe('Render', async() => { - it('should have team button', async() => { - await waitFor(element(by.id('new-message-view-create-channel'))).toBeVisible().withTimeout(2000); - }); - }) + it('should have team button', async() => { + await waitFor(element(by.id('new-message-view-create-team'))).toBeVisible().withTimeout(2000); + }); - describe('Usage', async() => { - it('should navigate to select users', async() => { - await element(by.id('new-message-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000); - }); - }) + it('should navigate to select users', async() => { + await element(by.id('new-message-view-create-team')).tap(); + await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000); + }); }); describe('Select Users', async() => { - it('should search users', async() => { - await element(by.id('select-users-view-search')).replaceText('rocket.cat'); - await waitFor(element(by.id(`select-users-view-item-rocket.cat`))).toBeVisible().withTimeout(10000); - }); - - it('should select/unselect user', async() => { - // Spotlight issues - await element(by.id('select-users-view-item-rocket.cat')).tap(); - await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000); - await element(by.id('selected-user-rocket.cat')).tap(); - await waitFor(element(by.id('selected-user-rocket.cat'))).toBeNotVisible().withTimeout(10000); - // Spotlight issues - await element(by.id('select-users-view-item-rocket.cat')).tap(); - await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000); - }); - - it('should create team', async() => { + it('should nav to create team', async() => { await element(by.id('selected-users-view-submit')).tap(); await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); }); @@ -64,19 +44,33 @@ describe('Create team screen', () => { }); it('should create private team', async() => { - const room = `private${ data.random }`; await element(by.id('create-channel-name')).replaceText(''); - await element(by.id('create-channel-name')).typeText(room); + await element(by.id('create-channel-name')).typeText(teamName); await element(by.id('create-channel-submit')).tap(); await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); await expect(element(by.id('room-view'))).toExist(); - await waitFor(element(by.id(`room-view-title-${ room }`))).toExist().withTimeout(6000); - await expect(element(by.id(`room-view-title-${ room }`))).toExist(); - await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000); - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(6000); - await expect(element(by.id(`rooms-list-view-item-${ room }`))).toExist(); + await waitFor(element(by.id(`room-view-title-${ teamName }`))).toExist().withTimeout(6000); + await expect(element(by.id(`room-view-title-${ teamName }`))).toExist(); }); }) }); + + describe('Delete Team', async() => { + it('should navigate to room info edit view', async() => { + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + await element(by.id('room-actions-info')).tap(); + await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); + }); + + it('should delete team', async() => { + await element(by.id('room-info-view-edit-button')).tap(); + await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); + await element(by.id('room-info-edit-view-delete')).tap(); + await waitFor(element(by.text('Yes, delete it!'))).toExist().withTimeout(5000); + await element(by.text('Yes, delete it!')).tap(); + await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${ teamName }`))).toBeNotVisible().withTimeout(60000); + }); + }); }); diff --git a/e2e/tests/team/02-team.spec.js b/e2e/tests/team/02-team.spec.js new file mode 100644 index 000000000..f6f4d0329 --- /dev/null +++ b/e2e/tests/team/02-team.spec.js @@ -0,0 +1,296 @@ +const { + device, expect, element, by, waitFor +} = require('detox'); +const data = require('../../data'); +const { navigateToLogin, login, tapBack, sleep, searchRoom } = require('../../helpers/app'); + +async function navigateToRoom(roomName) { + await searchRoom(`${ roomName }`); + await element(by.id(`rooms-list-view-item-${ roomName }`)).tap(); + await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); +} + +async function openActionSheet(username) { + await waitFor(element(by.id(`room-members-view-item-${ username }`))).toExist().withTimeout(5000); + await element(by.id(`room-members-view-item-${ username }`)).tap(); + await sleep(300); + await expect(element(by.id('action-sheet'))).toExist(); + await expect(element(by.id('action-sheet-handle'))).toBeVisible(); +} + +async function navigateToRoomActions() { + await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000); + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); +} + +async function backToActions() { + await tapBack(); + await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); +} +async function closeActionSheet() { + await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); +} + +async function waitForToast() { + await sleep(1000); +} + +describe('Team', () => { + const team = data.teams.private.name; + const user = data.users.alternate; + const room = `private${ data.random }`; + const existingRoom = data.groups.private.name; + + before(async() => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + await navigateToRoom(team); + }); + + describe('Team Room', async() => { + describe('Team Header', async() => { + it('should have actions button ', async() => { + await expect(element(by.id('room-header'))).toExist(); + }); + + it('should have team channels button ', async() => { + await expect(element(by.id('room-view-header-team-channels'))).toExist(); + }); + + it('should have threads button ', async() => { + await expect(element(by.id('room-view-header-threads'))).toExist(); + }); + + + it('should have threads button ', async() => { + await expect(element(by.id('room-view-search'))).toExist(); + }); + }); + + describe('Team Header Usage', async() => { + it('should navigate to team channels view', async() => { + await element(by.id('room-view-header-team-channels')).tap(); + await waitFor(element(by.id('team-channels-view'))).toExist().withTimeout(5000); + }); + }) + + describe('Team Channels Header', async() => { + it('should have actions button ', async() => { + await expect(element(by.id('room-header'))).toExist(); + }); + + it('should have team channels button ', async() => { + await expect(element(by.id('team-channels-view-create'))).toExist(); + }); + + it('should have threads button ', async() => { + await expect(element(by.id('team-channels-view-search'))).toExist(); + }); + }); + + describe('Team Channels Header Usage', async() => { + it('should navigate to add team channels view', async() => { + await element(by.id('team-channels-view-create')).tap(); + await waitFor(element(by.id('add-channel-team-view'))).toExist().withTimeout(5000); + }); + + it('should have create new button', async() => { + await waitFor(element(by.id('add-channel-team-view-create-channel'))).toExist().withTimeout(5000); + }); + + it('should add existing button', async() => { + await waitFor(element(by.id('add-channel-team-view-add-existing'))).toExist().withTimeout(5000); + }); + }) + + describe('Channels', async() => { + it('should create new channel for team', async() => { + + await element(by.id('add-channel-team-view-create-channel')).tap(); + + await element(by.id('select-users-view-search')).replaceText('rocket.cat'); + await element(by.id('select-users-view-item-rocket.cat')).tap(); + await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000); + await element(by.id('selected-users-view-submit')).tap(); + + await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); + await element(by.id('create-channel-name')).replaceText(''); + await element(by.id('create-channel-name')).typeText(room); + await element(by.id('create-channel-submit')).tap(); + + await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); + await expect(element(by.id('room-view'))).toExist(); + await expect(element(by.id('room-view-header-team-channels'))).toExist(); + await element(by.id('room-view-header-team-channels')).tap(); + + await waitFor(element(by.id('team-channels-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(6000); + await expect(element(by.id(`rooms-list-view-item-${ room }`))).toExist(); + await element(by.id(`rooms-list-view-item-${ room }`)).tap(); + await waitFor(element(by.id(`room-view-title-${ room }`))).toExist().withTimeout(60000); + await expect(element(by.id(`room-view-title-${ room }`))).toExist(); + await expect(element(by.id('room-view-header-team-channels'))).toExist(); + await expect(element(by.id('room-view-header-threads'))).toExist(); + await expect(element(by.id('room-view-search'))).toExist(); + await tapBack(); + }); + + it('should add existing channel to team', async() => { + + await element(by.id('team-channels-view-create')).tap(); + await waitFor(element(by.id('add-channel-team-view'))).toExist().withTimeout(5000); + + await element(by.id('add-channel-team-view-add-existing')).tap(); + await waitFor(element(by.id('add-existing-channel-view'))).toExist().withTimeout(60000) + await expect(element(by.id(`add-existing-channel-view-item-${ existingRoom }`))).toExist(); + await element(by.id(`add-existing-channel-view-item-${ existingRoom }`)).tap(); + await waitFor(element(by.id('add-existing-channel-view-submit'))).toExist().withTimeout(6000); + await element(by.id('add-existing-channel-view-submit')).tap(); + + await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); + await expect(element(by.id('room-view'))).toExist(); + await expect(element(by.id('room-view-header-team-channels'))).toExist(); + await element(by.id('room-view-header-team-channels')).tap(); + + await waitFor(element(by.id(`rooms-list-view-item-${ existingRoom }`))).toExist().withTimeout(10000); + }); + + it('should activate/deactivate auto-join to channel', async() => { + await element(by.id(`rooms-list-view-item-${ existingRoom }`)).atIndex(0).longPress(); + + await waitFor(element(by.id('action-sheet-auto-join'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('auto-join-unchecked'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('action-sheet-remove-from-team'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('action-sheet-delete'))).toBeVisible().withTimeout(5000); + + await element(by.id('auto-join-unchecked')).tap(); + await waitFor(element(by.id('auto-join-tag'))).toBeVisible().withTimeout(5000); + await element(by.id(`rooms-list-view-item-${ existingRoom }`)).atIndex(0).longPress(); + + await waitFor(element(by.id('auto-join-checked'))).toBeVisible().withTimeout(5000); + await element(by.id('auto-join-checked')).tap(); + await waitFor(element(by.id('auto-join-tag'))).toBeNotVisible().withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${ existingRoom }`))).toExist().withTimeout(6000); + }); + }) + + describe('Team actions', () => { + before(async() => { + await tapBack(); + await navigateToRoomActions(); + }); + + it('should add users to the team', async() => { + await waitFor(element(by.id('room-actions-add-user'))).toExist().withTimeout(10000); + await element(by.id('room-actions-add-user')).tap(); + + const rocketCat = 'rocket.cat'; + await element(by.id('select-users-view-search')).replaceText('rocket.cat'); + await waitFor(element(by.id(`select-users-view-item-${ rocketCat }`))).toExist().withTimeout(10000); + await element(by.id(`select-users-view-item-${ rocketCat }`)).tap(); + await waitFor(element(by.id(`selected-user-${ rocketCat }`))).toExist().withTimeout(5000); + + await waitFor(element(by.id('select-users-view-search'))).toExist().withTimeout(4000); + await element(by.id('select-users-view-search')).tap(); + await element(by.id('select-users-view-search')).replaceText(user.username); + await waitFor(element(by.id(`select-users-view-item-${ user.username }`))).toExist().withTimeout(10000); + await element(by.id(`select-users-view-item-${ user.username }`)).tap(); + await waitFor(element(by.id(`selected-user-${ user.username }`))).toExist().withTimeout(5000); + + await element(by.id('selected-users-view-submit')).tap(); + await sleep(300); + await waitFor(element(by.id('room-actions-members'))).toExist().withTimeout(10000); + await element(by.id('room-actions-members')).tap(); + await element(by.id('room-members-view-toggle-status')).tap(); + await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await backToActions(); + }); + + it('should try to leave to leave team and raise alert', async() => { + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-leave-channel'))).toExist().withTimeout(2000); + await element(by.id('room-actions-leave-channel')).tap(); + + await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${room}`))).toExist().withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${existingRoom}`))).toExist().withTimeout(2000); + await element(by.id(`select-list-view-item-${room}`)).tap(); + + await waitFor(element(by.label('You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.'))).toExist().withTimeout(2000); + await element(by.text('OK')).tap(); + await waitFor(element(by.id('select-list-view-submit'))).toExist().withTimeout(2000); + await element(by.id('select-list-view-submit')).tap(); + await waitFor(element(by.text('Last owner cannot be removed'))).toExist().withTimeout(8000); + await element(by.text('OK')).tap(); + await tapBack(); + await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); + }); + + describe('Room Members', async() => { + before(async() => { + await element(by.id('room-actions-members')).tap(); + await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000); + }); + + it('should show all users', async() => { + await element(by.id('room-members-view-toggle-status')).tap(); + await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + }); + + it('should filter user', async() => { + await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await element(by.id('room-members-view-search')).replaceText('rocket'); + await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toBeNotVisible().withTimeout(60000); + await element(by.id('room-members-view-search')).tap(); + await element(by.id('room-members-view-search')).clearText(''); + await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + }); + + it('should remove member from team', async() => { + await openActionSheet('rocket.cat'); + await element(by.id('action-sheet-remove-from-team')).tap(); + await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id(`select-list-view-item-${ room }`))).toExist().withTimeout(5000); + await element(by.id(`select-list-view-item-${ room }`)).tap(); + await waitFor(element(by.id(`${ room }-checked`))).toExist().withTimeout(5000); + await element(by.id(`select-list-view-item-${ room }`)).tap(); + await waitFor(element(by.id(`${ room }-unchecked`))).toExist().withTimeout(5000); + await element(by.id('select-list-view-submit')).tap(); + await waitFor(element(by.id('room-members-view-item-rocket.cat'))).toBeNotVisible().withTimeout(60000); + }); + + it('should set member as owner', async() => { + await openActionSheet(user.username); + await element(by.id('action-sheet-set-owner')).tap(); + await waitForToast(); + + await openActionSheet(user.username); + await waitFor(element(by.id('action-sheet-set-owner-checked'))).toBeVisible().withTimeout(6000); + await closeActionSheet(); + }); + + it('should leave team', async() => { + await tapBack(); + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-leave-channel'))).toExist().withTimeout(2000); + await element(by.id('room-actions-leave-channel')).tap(); + + await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${room}`))).toExist().withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${existingRoom}`))).toExist().withTimeout(2000); + await element(by.id(`select-list-view-item-${room}`)).tap(); + + await waitFor(element(by.label('You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.'))).toExist().withTimeout(2000); + await element(by.text('OK')).tap(); + await waitFor(element(by.id('select-list-view-submit'))).toExist().withTimeout(2000); + await element(by.id('select-list-view-submit')).tap(); + await waitFor(element(by.text(`You were removed from ${ team }`))).toExist().withTimeout(8000); + await element(by.text('OK')).tap(); + await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(5000); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/e2e/tests/team/03-moveconvert.spec.js b/e2e/tests/team/03-moveconvert.spec.js new file mode 100644 index 000000000..17143c609 --- /dev/null +++ b/e2e/tests/team/03-moveconvert.spec.js @@ -0,0 +1,89 @@ +const { + device, expect, element, by, waitFor +} = require('detox'); +const data = require('../../data'); +const { navigateToLogin, login, tapBack, searchRoom, sleep } = require('../../helpers/app'); + +const toBeConverted = `to-be-converted-${ data.random }`; +const toBeMoved = `to-be-moved-${ data.random }`; + +const createChannel = async(room) => { + await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(5000); + await element(by.id('new-message-view-create-channel')).tap(); + await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000); + await element(by.id('selected-users-view-submit')).tap(); + await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); + await element(by.id('create-channel-name')).typeText(room); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000); + await waitFor(element(by.id(`room-view-title-${ room }`))).toExist().withTimeout(60000); + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); +} + +async function navigateToRoom(room) { + await searchRoom(`${ room }`); + await element(by.id(`rooms-list-view-item-${ room }`)).tap(); + await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); +} + +async function navigateToRoomActions(room) { + await navigateToRoom(room); + await element(by.id('room-header')).tap(); + await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); +} + +describe('Move/Convert Team', () => { + before(async() => { + await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('Convert', async() => { + before(async() => { + await createChannel(toBeConverted); + }); + + it('should convert channel to a team', async() => { + await navigateToRoomActions(toBeConverted); + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-convert-to-team'))).toExist().withTimeout(2000); + await element(by.id('room-actions-convert-to-team')).tap(); + await waitFor(element(by.label('This can\'t be undone. Once you convert a channel to a team, you can not turn it back to a channel.'))).toExist().withTimeout(2000); + await element(by.text('Convert')).tap(); + await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); + await waitFor(element(by.id(`room-view-title-${ toBeConverted }`))).toExist().withTimeout(6000); + }); + + after(async() => { + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); + }) + }); + + describe('Move', async() => { + before(async() => { + await createChannel(toBeMoved); + }); + + it('should move channel to a team', async() => { + await navigateToRoomActions(toBeMoved); + await element(by.id('room-actions-scrollview')).scrollTo('bottom'); + await waitFor(element(by.id('room-actions-move-to-team'))).toExist().withTimeout(2000); + await element(by.id('room-actions-move-to-team')).tap(); + await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); + await element(by.id('select-list-view-submit')).tap(); + await sleep(2000); + await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${toBeConverted}`))).toExist().withTimeout(2000); + await element(by.id(`select-list-view-item-${toBeConverted}`)).tap(); + await element(by.id('select-list-view-submit')).atIndex(0).tap(); + await waitFor(element(by.label('After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?'))).toExist().withTimeout(2000); + await element(by.text('Yes, move it!')).tap(); + await waitFor(element(by.id('room-view-header-team-channels'))).toExist().withTimeout(10000); + }); + }) +}); From 91371e88d95138e60e435d573b25bcf5d236d8f6 Mon Sep 17 00:00:00 2001 From: Gerzon Z Date: Fri, 4 Jun 2021 14:07:26 -0400 Subject: [PATCH 3/8] [NEW] Add Teams to Directory (#3181) * Added Teams to DirectoryView * Fix icon * Minor tweaks * add tests Co-authored-by: Diego Mello --- app/i18n/locales/en.json | 1 + app/lib/rocketchat.js | 4 ++ app/presentation/DirectoryItem/index.js | 7 ++-- app/views/DirectoryView/Options.js | 6 +++ app/views/DirectoryView/index.js | 40 +++++++++++++++++-- .../assorted/09-joinfromdirectory.spec.js | 16 ++++++-- 6 files changed, 63 insertions(+), 11 deletions(-) diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index a8f8d9635..e23bb33b7 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -332,6 +332,7 @@ "My_servers": "My servers", "N_people_reacted": "{{n}} people reacted", "N_users": "{{n}} users", + "N_channels": "{{n}} channels", "name": "name", "Name": "Name", "Navigation_history": "Navigation history", diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index f174aa1eb..6e7b3bcb2 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -798,6 +798,10 @@ const RocketChat = { // RC 3.13.0 return this.sdk.get('teams.listRoomsOfUser', { teamId, userId }); }, + getTeamInfo({ teamId }) { + // RC 3.13.0 + return this.sdk.get('teams.info', { teamId }); + }, convertChannelToTeam({ rid, name, type }) { const params = { ...(type === 'c' diff --git a/app/presentation/DirectoryItem/index.js b/app/presentation/DirectoryItem/index.js index 75b944c5e..9f98969af 100644 --- a/app/presentation/DirectoryItem/index.js +++ b/app/presentation/DirectoryItem/index.js @@ -18,7 +18,7 @@ const DirectoryItemLabel = React.memo(({ text, theme }) => { }); const DirectoryItem = ({ - title, description, avatar, onPress, testID, style, rightLabel, type, rid, theme + title, description, avatar, onPress, testID, style, rightLabel, type, rid, theme, teamMain }) => ( - + {title} { description ? {description} : null } @@ -56,7 +56,8 @@ DirectoryItem.propTypes = { style: PropTypes.any, rightLabel: PropTypes.string, rid: PropTypes.string, - theme: PropTypes.string + theme: PropTypes.string, + teamMain: PropTypes.bool }; DirectoryItemLabel.propTypes = { diff --git a/app/views/DirectoryView/Options.js b/app/views/DirectoryView/Options.js index ad8de1da1..a88bf42be 100644 --- a/app/views/DirectoryView/Options.js +++ b/app/views/DirectoryView/Options.js @@ -64,6 +64,11 @@ export default class DirectoryOptions extends PureComponent { icon = 'channel-public'; } + if (itemType === 'teams') { + text = 'Teams'; + icon = 'teams'; + } + return ( changeType(itemType)} @@ -105,6 +110,7 @@ export default class DirectoryOptions extends PureComponent { {this.renderItem('channels')} {this.renderItem('users')} + {this.renderItem('teams')} {isFederationEnabled ? ( <> diff --git a/app/views/DirectoryView/index.js b/app/views/DirectoryView/index.js index f78e74a21..c5cacb64d 100644 --- a/app/views/DirectoryView/index.js +++ b/app/views/DirectoryView/index.js @@ -121,6 +121,8 @@ class DirectoryView extends React.Component { logEvent(events.DIRECTORY_SEARCH_USERS); } else if (type === 'channels') { logEvent(events.DIRECTORY_SEARCH_CHANNELS); + } else if (type === 'teams') { + logEvent(events.DIRECTORY_SEARCH_TEAMS); } } @@ -149,17 +151,34 @@ class DirectoryView extends React.Component { if (result.success) { this.goRoom({ rid: result.room._id, name: item.username, t: 'd' }); } - } else { + } else if (['p', 'c'].includes(item.t) && !item.teamMain) { const { room } = await RocketChat.getRoomInfo(item._id); this.goRoom({ rid: item._id, name: item.name, joinCodeRequired: room.joinCodeRequired, t: 'c', search: true }); + } else { + this.goRoom({ + rid: item._id, name: item.name, t: item.t, search: true, teamMain: item.teamMain, teamId: item.teamId + }); } } renderHeader = () => { const { type } = this.state; const { theme } = this.props; + let text = 'Users'; + let icon = 'user'; + + if (type === 'channels') { + text = 'Channels'; + icon = 'channel-public'; + } + + if (type === 'teams') { + text = 'Teams'; + icon = 'teams'; + } + return ( <> - - {type === 'users' ? I18n.t('Users') : I18n.t('Channels')} + + {I18n.t(text)} @@ -217,12 +236,25 @@ class DirectoryView extends React.Component { /> ); } + + if (type === 'teams') { + return ( + + ); + } return ( ); diff --git a/e2e/tests/assorted/09-joinfromdirectory.spec.js b/e2e/tests/assorted/09-joinfromdirectory.spec.js index 308c4db2c..039ef823c 100644 --- a/e2e/tests/assorted/09-joinfromdirectory.spec.js +++ b/e2e/tests/assorted/09-joinfromdirectory.spec.js @@ -32,16 +32,24 @@ describe('Join room from directory', () => { await navigateToRoom(data.channels.detoxpublic.name); }) - it('should back and tap directory', async() => { + it('should search user and navigate', async() => { await tapBack(); await element(by.id('rooms-list-view-directory')).tap(); - }) - - it('should search user and navigate', async() => { + await waitFor(element(by.id('directory-view'))).toExist().withTimeout(2000); await element(by.id('directory-view-dropdown')).tap(); await element(by.label('Users')).tap(); await element(by.label('Search by')).tap(); await navigateToRoom(data.users.alternate.username); }) + + it('should search user and navigate', async() => { + await tapBack(); + await element(by.id('rooms-list-view-directory')).tap(); + await waitFor(element(by.id('directory-view'))).toExist().withTimeout(2000); + await element(by.id('directory-view-dropdown')).tap(); + await element(by.label('Teams')).tap(); + await element(by.label('Search by')).tap(); + await navigateToRoom(data.teams.private.name); + }) }); }); From cf6ddf63528424535c516a26a2c2b770379b4c22 Mon Sep 17 00:00:00 2001 From: Gerzon Z Date: Fri, 4 Jun 2021 14:08:37 -0400 Subject: [PATCH 4/8] [CHORE] Add logEvents for Teams (#3182) * added events for team channels view and add existing channel view * add logevents for room actions view and room info edit view Co-authored-by: Diego Mello --- app/utils/log/events.js | 19 ++++++++++++++++--- app/views/AddExistingChannelView.js | 6 +++--- app/views/RoomActionsView/index.js | 7 +++++++ app/views/RoomInfoEditView/index.js | 2 ++ app/views/TeamChannelsView.js | 5 +++++ 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/utils/log/events.js b/app/utils/log/events.js index a6d2eaa80..a183735ba 100644 --- a/app/utils/log/events.js +++ b/app/utils/log/events.js @@ -100,8 +100,8 @@ export default { SELECTED_USERS_CREATE_GROUP_F: 'selected_users_create_group_f', // ADD EXISTING CHANNEL VIEW - EXISTING_CHANNEL_ADD_CHANNEL: 'existing_channel_add_channel', - EXISTING_CHANNEL_REMOVE_CHANNEL: 'existing_channel_remove_channel', + AEC_ADD_CHANNEL: 'aec_add_channel', + AEC_REMOVE_CHANNEL: 'aec_remove_channel', // CREATE CHANNEL VIEW CR_CREATE: 'cr_create', @@ -255,6 +255,13 @@ export default { RA_TOGGLE_BLOCK_USER_F: 'ra_toggle_block_user_f', RA_TOGGLE_ENCRYPTED: 'ra_toggle_encrypted', RA_TOGGLE_ENCRYPTED_F: 'ra_toggle_encrypted_f', + RA_LEAVE_TEAM: 'ra_leave_team', + RA_LEAVE_TEAM_F: 'ra_leave_team_f', + RA_CONVERT_TO_TEAM: 'ra_convert_to_team', + RA_CONVERT_TO_TEAM_F: 'ra_convert_to_team_f', + RA_MOVE_TO_TEAM: 'ra_move_to_team', + RA_MOVE_TO_TEAM_F: 'ra_move_to_team_f', + RA_SEARCH_TEAM: 'ra_search_team', // ROOM INFO VIEW RI_GO_RI_EDIT: 'ri_go_ri_edit', @@ -274,6 +281,8 @@ export default { RI_EDIT_TOGGLE_ARCHIVE_F: 'ri_edit_toggle_archive_f', RI_EDIT_DELETE: 'ri_edit_delete', RI_EDIT_DELETE_F: 'ri_edit_delete_f', + RI_EDIT_DELETE_TEAM: 'ri_edit_delete_team', + RI_EDIT_DELETE_TEAM_F: 'ri_edit_delete_team_f', // JITSI MEET VIEW JM_CONFERENCE_JOIN: 'jm_conference_join', @@ -327,5 +336,9 @@ export default { TC_SEARCH: 'tc_search', TC_CANCEL_SEARCH: 'tc_cancel_search', TC_GO_ACTIONS: 'tc_go_actions', - TC_GO_ROOM: 'tc_go_room' + TC_GO_ROOM: 'tc_go_room', + TC_DELETE_ROOM: 'tc_delete_room', + TC_DELETE_ROOM_F: 'tc_delete_room_f', + TC_TOGGLE_AUTOJOIN: 'tc_toggle_autojoin', + TC_TOGGLE_AUTOJOIN_F: 'tc_toggle_autojoin_f' }; diff --git a/app/views/AddExistingChannelView.js b/app/views/AddExistingChannelView.js index e7a590a54..0edced4b0 100644 --- a/app/views/AddExistingChannelView.js +++ b/app/views/AddExistingChannelView.js @@ -126,8 +126,8 @@ class AddExistingChannelView extends React.Component { goRoom({ item: result, isMasterDetail }); } } catch (e) { - showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {}); logEvent(events.CT_ADD_ROOM_TO_TEAM_F); + showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {}); this.setState({ loading: false }); } } @@ -151,10 +151,10 @@ class AddExistingChannelView extends React.Component { animateNextTransition(); if (!this.isChecked(rid)) { - logEvent(events.EXISTING_CHANNEL_ADD_CHANNEL); + logEvent(events.AEC_ADD_CHANNEL); this.setState({ selected: [...selected, rid] }, () => this.setHeader()); } else { - logEvent(events.EXISTING_CHANNEL_REMOVE_CHANNEL); + logEvent(events.AEC_REMOVE_CHANNEL); const filterSelected = selected.filter(el => el !== rid); this.setState({ selected: filterSelected }, () => this.setHeader()); } diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 74dc308fa..0748cf783 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -431,6 +431,7 @@ class RoomActionsView extends React.Component { } handleLeaveTeam = async(selected) => { + logEvent(events.RA_LEAVE_TEAM); try { const { room } = this.state; const { navigation, isMasterDetail } = this.props; @@ -444,6 +445,7 @@ class RoomActionsView extends React.Component { } } } catch (e) { + logEvent(events.RA_LEAVE_TEAM_F); log(e); showErrorAlert( e.data.error @@ -492,6 +494,7 @@ class RoomActionsView extends React.Component { } handleConvertToTeam = async() => { + logEvent(events.RA_CONVERT_TO_TEAM); try { const { room } = this.state; const { navigation } = this.props; @@ -501,6 +504,7 @@ class RoomActionsView extends React.Component { navigation.navigate('RoomView'); } } catch (e) { + logEvent(events.RA_CONVERT_TO_TEAM_F); log(e); } } @@ -515,6 +519,7 @@ class RoomActionsView extends React.Component { } handleMoveToTeam = async(selected) => { + logEvent(events.RA_MOVE_TO_TEAM); try { const { room } = this.state; const { navigation } = this.props; @@ -523,6 +528,7 @@ class RoomActionsView extends React.Component { navigation.navigate('RoomView'); } } catch (e) { + logEvent(events.RA_MOVE_TO_TEAM_F); log(e); showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('moving_channel_to_team') })); } @@ -569,6 +575,7 @@ class RoomActionsView extends React.Component { } searchTeam = async(onChangeText) => { + logEvent(events.RA_SEARCH_TEAM); try { const { addTeamChannelPermission, createTeamPermission } = this.props; const QUERY_SIZE = 50; diff --git a/app/views/RoomInfoEditView/index.js b/app/views/RoomInfoEditView/index.js index 90d8b28f5..8905e5ee1 100644 --- a/app/views/RoomInfoEditView/index.js +++ b/app/views/RoomInfoEditView/index.js @@ -293,6 +293,7 @@ class RoomInfoEditView extends React.Component { } handleDeleteTeam = async(selected) => { + logEvent(events.RI_EDIT_DELETE_TEAM); const { navigation, isMasterDetail } = this.props; const { room } = this.state; try { @@ -305,6 +306,7 @@ class RoomInfoEditView extends React.Component { } } } catch (e) { + logEvent(events.RI_EDIT_DELETE_TEAM_F); log(e); showErrorAlert( e.data.error diff --git a/app/views/TeamChannelsView.js b/app/views/TeamChannelsView.js index cee7fe13c..82e4ecdc3 100644 --- a/app/views/TeamChannelsView.js +++ b/app/views/TeamChannelsView.js @@ -301,6 +301,7 @@ class TeamChannelsView extends React.Component { }, 1000, true); toggleAutoJoin = async(item) => { + logEvent(events.TC_TOGGLE_AUTOJOIN); try { const { data } = this.state; const result = await RocketChat.updateTeamRoom({ roomId: item._id, isDefault: !item.teamDefault }); @@ -314,6 +315,7 @@ class TeamChannelsView extends React.Component { this.setState({ data: newData }); } } catch (e) { + logEvent(events.TC_TOGGLE_AUTOJOIN_F); log(e); } } @@ -338,6 +340,7 @@ class TeamChannelsView extends React.Component { } removeRoom = async(item) => { + logEvent(events.TC_DELETE_ROOM); try { const { data } = this.state; const result = await RocketChat.removeTeamRoom({ roomId: item._id, teamId: this.team.teamId }); @@ -346,11 +349,13 @@ class TeamChannelsView extends React.Component { this.setState({ data: newData }); } } catch (e) { + logEvent(events.TC_DELETE_ROOM_F); log(e); } } delete = (item) => { + logEvent(events.TC_DELETE_ROOM); const { deleteRoom } = this.props; Alert.alert( From 5f2aba3d74d4cb4db06f4547050d5e46ea20764f Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Fri, 4 Jun 2021 15:10:01 -0300 Subject: [PATCH 5/8] [FIX] Disable jitsi call for teams (#3183) Co-authored-by: Diego Mello --- app/views/RoomActionsView/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 0748cf783..0c7267d0a 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -681,7 +681,7 @@ class RoomActionsView extends React.Component { renderJitsi = () => { const { room } = this.state; const { jitsiEnabled } = this.props; - if (!jitsiEnabled) { + if (!jitsiEnabled || room.teamMain) { return null; } return ( From abf3945f32344834f27c789b51f92ed3d5eac470 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Fri, 4 Jun 2021 15:40:50 -0300 Subject: [PATCH 6/8] [FIX] Show alert `Not allowed` when click on a private channel that you don't be invited before (#3177) * [FIX] Showing only channel you joined * [FIX] How to get the params to mnavigation to other room from TeamChannelList * Show alert Not allowed when trying access private channel that you don't joined Co-authored-by: Diego Mello --- app/views/TeamChannelsView.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/views/TeamChannelsView.js b/app/views/TeamChannelsView.js index 82e4ecdc3..40c8c05ac 100644 --- a/app/views/TeamChannelsView.js +++ b/app/views/TeamChannelsView.js @@ -282,21 +282,20 @@ class TeamChannelsView extends React.Component { logEvent(events.TC_GO_ROOM); const { navigation, isMasterDetail } = this.props; try { - let params = {}; - if (item.rid) { - params = item; - } else { - const { room } = await RocketChat.getRoomInfo(item._id); - params = { - rid: item._id, name: RocketChat.getRoomTitle(room), joinCodeRequired: room.joinCodeRequired, t: room.t, teamId: room.teamId - }; - } + const { room } = await RocketChat.getRoomInfo(item._id); + const params = { + rid: item._id, name: RocketChat.getRoomTitle(room), joinCodeRequired: room.joinCodeRequired, t: room.t, teamId: room.teamId + }; if (isMasterDetail) { navigation.pop(); } goRoom({ item: params, isMasterDetail, navigationMethod: navigation.push }); } catch (e) { - // do nothing + if (e.data.error === 'not-allowed') { + showErrorAlert(I18n.t('error-not-allowed')); + } else { + showErrorAlert(e.data.error); + } } }, 1000, true); From fa00ef92efa45fef3938afbb92be52b97cb16358 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Fri, 4 Jun 2021 17:23:31 -0300 Subject: [PATCH 7/8] [IMPROVEMENT] Load team's rooms from local database on team leave (#3185) * [IMPROVEMENT] Search team list rooms of user in watermelon db * Minor nitpick Co-authored-by: Diego Mello --- app/views/RoomActionsView/index.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 0c7267d0a..77a4cc59b 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -461,11 +461,16 @@ class RoomActionsView extends React.Component { const { navigation } = this.props; try { - const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: room.u._id }); + const db = database.active; + const subCollection = db.get('subscriptions'); + const rooms = await subCollection.query( + Q.where('team_id', Q.eq(room.teamId)), + Q.where('team_main', Q.notEq(true)) + ); - if (result.rooms?.length) { - const teamChannels = result.rooms.map(r => ({ - rid: r._id, + if (rooms.length) { + const teamChannels = rooms.map(r => ({ + rid: r.id, name: r.name, teamId: r.teamId, alert: r.isLastOwner From 2502b27564926f3a15bfa0ed04ef9c9ede292c21 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Fri, 4 Jun 2021 17:53:39 -0300 Subject: [PATCH 8/8] [FIX] Option to prevent users from using Invisible status (#3186) * [FIX] Option to prevent users from using Invisible status * Added error to pt-BR Co-authored-by: Diego Mello --- app/constants/settings.js | 3 +++ app/i18n/locales/en.json | 1 + app/i18n/locales/pt-BR.json | 1 + app/views/StatusView.js | 14 +++++++++++--- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/constants/settings.js b/app/constants/settings.js index 9f0df8865..d684a11c1 100644 --- a/app/constants/settings.js +++ b/app/constants/settings.js @@ -193,5 +193,8 @@ export default { }, Allow_Save_Media_to_Gallery: { type: 'valueAsBoolean' + }, + Accounts_AllowInvisibleStatusOption: { + type: 'valueAsString' } }; diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index e23bb33b7..1f37628d6 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -79,6 +79,7 @@ "error-user-registration-disabled": "User registration is disabled", "error-user-registration-secret": "User registration is only allowed via Secret URL", "error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.", + "error-status-not-allowed": "Invisible status is disabled", "Actions": "Actions", "activity": "activity", "Activity": "Activity", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 7eca8b049..293959613 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -75,6 +75,7 @@ "error-user-registration-disabled": "O registro do usuário está desativado", "error-user-registration-secret": "O registro de usuário é permitido somente via URL secreta", "error-you-are-last-owner": "Você é o último proprietário da sala. Por favor defina um novo proprietário antes de sair.", + "error-status-not-allowed": "O status invisível está desativado", "Actions": "Ações", "activity": "atividade", "Activity": "Atividade", diff --git a/app/views/StatusView.js b/app/views/StatusView.js index 3f5a48c12..2ac358343 100644 --- a/app/views/StatusView.js +++ b/app/views/StatusView.js @@ -8,6 +8,7 @@ import * as List from '../containers/List'; import Status from '../containers/Status/Status'; import TextInput from '../containers/TextInput'; import EventEmitter from '../utils/events'; +import { showErrorAlert } from '../utils/info'; import Loading from '../containers/Loading'; import RocketChat from '../lib/rocketchat'; import log, { logEvent, events } from '../utils/log'; @@ -58,7 +59,8 @@ class StatusView extends React.Component { theme: PropTypes.string, navigation: PropTypes.object, isMasterDetail: PropTypes.bool, - setUser: PropTypes.func + setUser: PropTypes.func, + Accounts_AllowInvisibleStatusOption: PropTypes.bool } constructor(props) { @@ -168,6 +170,7 @@ class StatusView extends React.Component { setUser({ status: item.id }); } } catch (e) { + showErrorAlert(I18n.t(e.data.errorType)); logEvent(events.SET_STATUS_FAIL); log(e); } @@ -181,10 +184,14 @@ class StatusView extends React.Component { render() { const { loading } = this.state; + const { Accounts_AllowInvisibleStatusOption } = this.props; + + const status = Accounts_AllowInvisibleStatusOption ? STATUS : STATUS.filter(s => s.id !== 'offline'); + return ( item.id} renderItem={this.renderItem} ListHeaderComponent={this.renderHeader} @@ -199,7 +206,8 @@ class StatusView extends React.Component { const mapStateToProps = state => ({ user: getUserSelector(state), - isMasterDetail: state.app.isMasterDetail + isMasterDetail: state.app.isMasterDetail, + Accounts_AllowInvisibleStatusOption: state.settings.Accounts_AllowInvisibleStatusOption ?? true }); const mapDispatchToProps = dispatch => ({