Chore: Migrate redux module room to typescript (#3636)

* chore: migrate redux module room to typescript and remove dispatch on dependencies

* chore: add tests to redux module room

* chore: create ERoomType and use on implemention

* chore: update enum name

* fix test id
This commit is contained in:
Gleidson Daniel Silva 2022-02-18 10:46:19 -03:00 committed by GitHub
parent eb38761a37
commit d73886bd60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 227 additions and 121 deletions

View File

@ -1,62 +0,0 @@
import * as types from './actionsTypes';
export function subscribeRoom(rid) {
return {
type: types.ROOM.SUBSCRIBE,
rid
};
}
export function unsubscribeRoom(rid) {
return {
type: types.ROOM.UNSUBSCRIBE,
rid
};
}
export function leaveRoom(roomType, room, selected) {
return {
type: types.ROOM.LEAVE,
room,
roomType,
selected
};
}
export function deleteRoom(roomType, room, selected) {
return {
type: types.ROOM.DELETE,
room,
roomType,
selected
};
}
export function closeRoom(rid) {
return {
type: types.ROOM.CLOSE,
rid
};
}
export function forwardRoom(rid, transferData) {
return {
type: types.ROOM.FORWARD,
transferData,
rid
};
}
export function removedRoom() {
return {
type: types.ROOM.REMOVED
};
}
export function userTyping(rid, status = true) {
return {
type: types.ROOM.USER_TYPING,
rid,
status
};
}

109
app/actions/room.ts Normal file
View File

@ -0,0 +1,109 @@
import { Action } from 'redux';
import { ERoomType } from '../definitions/ERoomType';
import { ROOM } from './actionsTypes';
// TYPE RETURN RELATED
type ISelected = Record<string, string>;
export interface ITransferData {
roomId: string;
userId?: string;
departmentId?: string;
}
// ACTION RETURN RELATED
interface IBaseReturn extends Action {
rid: string;
}
type TSubscribeRoom = IBaseReturn;
type TUnsubscribeRoom = IBaseReturn;
type TCloseRoom = IBaseReturn;
type TRoom = Record<string, any>;
interface ILeaveRoom extends Action {
roomType: ERoomType;
room: TRoom;
selected?: ISelected;
}
interface IDeleteRoom extends Action {
roomType: ERoomType;
room: TRoom;
selected?: ISelected;
}
interface IForwardRoom extends Action {
transferData: ITransferData;
rid: string;
}
interface IUserTyping extends Action {
rid: string;
status: boolean;
}
export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & TCloseRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping;
export function subscribeRoom(rid: string): TSubscribeRoom {
return {
type: ROOM.SUBSCRIBE,
rid
};
}
export function unsubscribeRoom(rid: string): TUnsubscribeRoom {
return {
type: ROOM.UNSUBSCRIBE,
rid
};
}
export function leaveRoom(roomType: ERoomType, room: TRoom, selected?: ISelected): ILeaveRoom {
return {
type: ROOM.LEAVE,
room,
roomType,
selected
};
}
export function deleteRoom(roomType: ERoomType, room: TRoom, selected?: ISelected): IDeleteRoom {
return {
type: ROOM.DELETE,
room,
roomType,
selected
};
}
export function closeRoom(rid: string): TCloseRoom {
return {
type: ROOM.CLOSE,
rid
};
}
export function forwardRoom(rid: string, transferData: ITransferData): IForwardRoom {
return {
type: ROOM.FORWARD,
transferData,
rid
};
}
export function removedRoom(): Action {
return {
type: ROOM.REMOVED
};
}
export function userTyping(rid: string, status = true): IUserTyping {
return {
type: ROOM.USER_TYPING,
rid,
status
};
}

View File

@ -0,0 +1,7 @@
export enum ERoomType {
p = 'group',
c = 'channel',
d = 'direct',
t = 'team',
l = 'omnichannel'
}

View File

@ -24,6 +24,7 @@ import { ICreateDiscussion } from '../../reducers/createDiscussion';
import { IEncryption } from '../../reducers/encryption'; import { IEncryption } from '../../reducers/encryption';
import { IInviteLinks } from '../../reducers/inviteLinks'; import { IInviteLinks } from '../../reducers/inviteLinks';
import { IRoles } from '../../reducers/roles'; import { IRoles } from '../../reducers/roles';
import { IRoom } from '../../reducers/room';
import { ISelectedUsers } from '../../reducers/selectedUsers'; import { ISelectedUsers } from '../../reducers/selectedUsers';
import { IServer } from '../../reducers/server'; import { IServer } from '../../reducers/server';
import { ISettings } from '../../reducers/settings'; import { ISettings } from '../../reducers/settings';
@ -39,7 +40,7 @@ export interface IApplicationState {
selectedUsers: ISelectedUsers; selectedUsers: ISelectedUsers;
app: IApp; app: IApp;
createChannel: ICreateChannel; createChannel: ICreateChannel;
room: any; room: IRoom;
rooms: any; rooms: any;
sortPreferences: any; sortPreferences: any;
share: IShare; share: IShare;

58
app/reducers/room.test.ts Normal file
View File

@ -0,0 +1,58 @@
import { closeRoom, deleteRoom, forwardRoom, leaveRoom, removedRoom, subscribeRoom, unsubscribeRoom } from '../actions/room';
import { ERoomType } from '../definitions/ERoomType';
import { mockedStore } from './mockedStore';
import { initialState } from './room';
describe('test room reducer', () => {
it('should return initial state', () => {
const state = mockedStore.getState().room;
expect(state).toEqual(initialState);
});
it('should return modified store after subscribeRoom', () => {
mockedStore.dispatch(subscribeRoom('GENERAL'));
const state = mockedStore.getState().room;
expect(state.rooms).toEqual(['GENERAL']);
});
it('should return empty store after remove unsubscribeRoom', () => {
mockedStore.dispatch(unsubscribeRoom('GENERAL'));
const state = mockedStore.getState().room;
expect(state.rooms).toEqual([]);
});
it('should return initial state after leaveRoom', () => {
mockedStore.dispatch(leaveRoom(ERoomType.c, { rid: ERoomType.c }));
const { rid, isDeleting } = mockedStore.getState().room;
expect(rid).toEqual(ERoomType.c);
expect(isDeleting).toEqual(true);
});
it('should return initial state after deleteRoom', () => {
mockedStore.dispatch(deleteRoom(ERoomType.l, { rid: ERoomType.l }));
const { rid, isDeleting } = mockedStore.getState().room;
expect(rid).toEqual(ERoomType.l);
expect(isDeleting).toEqual(true);
});
it('should return initial state after closeRoom', () => {
mockedStore.dispatch(closeRoom('CLOSING'));
const { rid, isDeleting } = mockedStore.getState().room;
expect(rid).toEqual('CLOSING');
expect(isDeleting).toEqual(true);
});
it('should return initial state after forwardRoom', () => {
const transferData = { roomId: 'FORWARDING' };
mockedStore.dispatch(forwardRoom('FORWARDING', transferData));
const { rid, isDeleting } = mockedStore.getState().room;
expect(rid).toEqual('FORWARDING');
expect(isDeleting).toEqual(true);
});
it('should return loading after call removedRoom', () => {
mockedStore.dispatch(removedRoom());
const { isDeleting } = mockedStore.getState().room;
expect(isDeleting).toEqual(false);
});
});

View File

@ -1,12 +1,21 @@
import { TActionsRoom } from '../actions/room';
import { ROOM } from '../actions/actionsTypes'; import { ROOM } from '../actions/actionsTypes';
const initialState = { export type IRoomRecord = string[];
rid: null,
export interface IRoom {
rid: string;
isDeleting: boolean;
rooms: IRoomRecord;
}
export const initialState: IRoom = {
rid: '',
isDeleting: false, isDeleting: false,
rooms: [] rooms: []
}; };
export default function (state = initialState, action) { export default function (state = initialState, action: TActionsRoom): IRoom {
switch (action.type) { switch (action.type) {
case ROOM.SUBSCRIBE: case ROOM.SUBSCRIBE:
return { return {

View File

@ -260,7 +260,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
/> />
</HeaderButton.Container> </HeaderButton.Container>
), ),
headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} />, headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} testID='team-channels-view-search-header' />,
headerTitleContainerStyle: { headerTitleContainerStyle: {
left: headerTitlePosition.left, left: headerTitlePosition.left,
right: headerTitlePosition.right right: headerTitlePosition.right

View File

@ -1,20 +1,17 @@
import React, { useEffect, useState } from 'react';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import { StyleSheet, View } from 'react-native';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import React, { useEffect, useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { useDispatch } from 'react-redux';
import I18n from '../i18n'; import { forwardRoom, ITransferData } from '../actions/room';
import { withTheme } from '../theme';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import RocketChat from '../lib/rocketchat';
import OrSeparator from '../containers/OrSeparator'; import OrSeparator from '../containers/OrSeparator';
import Input from '../containers/UIKit/MultiSelect/Input'; import Input from '../containers/UIKit/MultiSelect/Input';
import { forwardRoom as forwardRoomAction } from '../actions/room'; import { IBaseScreen, IRoom } from '../definitions';
import { IRoom } from '../definitions'; import I18n from '../i18n';
import RocketChat from '../lib/rocketchat';
import { ChatsStackParamList } from '../stacks/types'; import { ChatsStackParamList } from '../stacks/types';
import { withTheme } from '../theme';
import { IOptionsField } from './NotificationPreferencesView/options'; import { IOptionsField } from './NotificationPreferencesView/options';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -23,33 +20,26 @@ const styles = StyleSheet.create({
padding: 16 padding: 16
} }
}); });
interface ITransferData {
roomId: string;
userId?: string;
departmentId?: string;
}
interface IUser { interface IUser {
username: string; username: string;
_id: string; _id: string;
} }
interface IForwardLivechatViewProps {
navigation: StackNavigationProp<ChatsStackParamList, 'ForwardLivechatView'>; interface IParsedData {
route: RouteProp<ChatsStackParamList, 'ForwardLivechatView'>; label: string;
theme: string; value: string;
forwardRoom: (rid: string, transferData: ITransferData) => void;
} }
const COUNT_DEPARTMENT = 50; const COUNT_DEPARTMENT = 50;
const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForwardLivechatViewProps) => { const ForwardLivechatView = ({ navigation, route, theme }: IBaseScreen<ChatsStackParamList, 'ForwardLivechatView'>) => {
const [departments, setDepartments] = useState<IOptionsField[]>([]); const [departments, setDepartments] = useState<IParsedData[]>([]);
const [departmentId, setDepartment] = useState(''); const [departmentId, setDepartment] = useState('');
const [departmentTotal, setDepartmentTotal] = useState(0); const [departmentTotal, setDepartmentTotal] = useState(0);
const [users, setUsers] = useState<IOptionsField[]>([]); const [users, setUsers] = useState<IOptionsField[]>([]);
const [userId, setUser] = useState(); const [userId, setUser] = useState();
const [room, setRoom] = useState<IRoom>({} as IRoom); const [room, setRoom] = useState<IRoom>({} as IRoom);
const dispatch = useDispatch();
const rid = route.params?.rid; const rid = route.params?.rid;
@ -57,7 +47,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
try { try {
const result = await RocketChat.getDepartments({ count: COUNT_DEPARTMENT, text, offset }); const result = await RocketChat.getDepartments({ count: COUNT_DEPARTMENT, text, offset });
if (result.success) { if (result.success) {
const parsedDepartments: IOptionsField[] = result.departments.map(department => ({ const parsedDepartments: IParsedData[] = result.departments.map(department => ({
label: department.name, label: department.name,
value: department._id value: department._id
})); }));
@ -116,7 +106,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
transferData.departmentId = departmentId; transferData.departmentId = departmentId;
} }
forwardRoom(rid, transferData); dispatch(forwardRoom(rid, transferData));
}; };
useEffect(() => { useEffect(() => {
@ -171,8 +161,4 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
); );
}; };
const mapDispatchToProps = (dispatch: Dispatch) => ({ export default withTheme(ForwardLivechatView);
forwardRoom: (rid: string, transferData: ITransferData) => dispatch(forwardRoomAction(rid, transferData))
});
export default connect(null, mapDispatchToProps)(withTheme(ForwardLivechatView));

View File

@ -7,8 +7,8 @@ import { Q } from '@nozbe/watermelondb';
import { compareServerVersion } from '../../lib/utils'; import { compareServerVersion } from '../../lib/utils';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { setLoading as setLoadingAction } from '../../actions/selectedUsers'; import { setLoading } from '../../actions/selectedUsers';
import { closeRoom as closeRoomAction, leaveRoom as leaveRoomAction } from '../../actions/room'; import { closeRoom, leaveRoom } from '../../actions/room';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import Status from '../../containers/Status'; import Status from '../../containers/Status';
@ -334,9 +334,9 @@ class RoomActionsView extends React.Component {
const { const {
room: { rid } room: { rid }
} = this.state; } = this.state;
const { closeRoom } = this.props; const { dispatch } = this.props;
closeRoom(rid); dispatch(closeRoom(rid));
}; };
returnLivechat = () => { returnLivechat = () => {
@ -375,16 +375,16 @@ class RoomActionsView extends React.Component {
addUser = async () => { addUser = async () => {
const { room } = this.state; const { room } = this.state;
const { setLoadingInvite, navigation } = this.props; const { dispatch, navigation } = this.props;
const { rid } = room; const { rid } = room;
try { try {
setLoadingInvite(true); dispatch(setLoading(true));
await RocketChat.addUsersToRoom(rid); await RocketChat.addUsersToRoom(rid);
navigation.pop(); navigation.pop();
} catch (e) { } catch (e) {
log(e); log(e);
} finally { } finally {
setLoadingInvite(false); dispatch(setLoading(false));
} }
}; };
@ -458,12 +458,12 @@ class RoomActionsView extends React.Component {
leaveChannel = () => { leaveChannel = () => {
const { room } = this.state; const { room } = this.state;
const { leaveRoom } = this.props; const { dispatch } = this.props;
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: RocketChat.getRoomTitle(room) }), message: I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
onPress: () => leaveRoom('channel', room) onPress: () => dispatch(leaveRoom('channel', room))
}); });
}; };
@ -522,7 +522,7 @@ class RoomActionsView extends React.Component {
leaveTeam = async () => { leaveTeam = async () => {
const { room } = this.state; const { room } = this.state;
const { navigation, leaveRoom } = this.props; const { navigation, dispatch } = this.props;
try { try {
const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: room.u._id }); const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: room.u._id });
@ -538,21 +538,21 @@ class RoomActionsView extends React.Component {
title: 'Leave_Team', title: 'Leave_Team',
data: teamChannels, data: teamChannels,
infoText: 'Select_Team_Channels', infoText: 'Select_Team_Channels',
nextAction: data => leaveRoom('team', room, data), nextAction: data => dispatch(leaveRoom('team', room, data)),
showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_leave')) showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_leave'))
}); });
} else { } else {
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }), message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
onPress: () => leaveRoom('team', room) onPress: () => dispatch(leaveRoom('team', room))
}); });
} }
} catch (e) { } catch (e) {
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }), message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
onPress: () => leaveRoom('team', room) onPress: () => dispatch(leaveRoom('team', room))
}); });
} }
}; };
@ -1242,10 +1242,4 @@ const mapStateToProps = state => ({
viewCannedResponsesPermission: state.permissions['view-canned-responses'] viewCannedResponsesPermission: state.permissions['view-canned-responses']
}); });
const mapDispatchToProps = dispatch => ({ export default connect(mapStateToProps)(withTheme(withDimensions(RoomActionsView)));
leaveRoom: (roomType, room, selected) => dispatch(leaveRoomAction(roomType, room, selected)),
closeRoom: rid => dispatch(closeRoomAction(rid)),
setLoadingInvite: loading => dispatch(setLoadingAction(loading))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(withDimensions(RoomActionsView)));

View File

@ -4,6 +4,7 @@ import React from 'react';
import { Alert, FlatList, Keyboard } from 'react-native'; import { Alert, FlatList, Keyboard } from 'react-native';
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { deleteRoom } from '../actions/room'; import { deleteRoom } from '../actions/room';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
@ -17,6 +18,7 @@ import SafeAreaView from '../containers/SafeAreaView';
import SearchHeader from '../containers/SearchHeader'; import SearchHeader from '../containers/SearchHeader';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { IApplicationState, IBaseScreen } from '../definitions'; import { IApplicationState, IBaseScreen } from '../definitions';
import { ERoomType } from '../definitions/ERoomType';
import { withDimensions } from '../dimensions'; import { withDimensions } from '../dimensions';
import I18n from '../i18n'; import I18n from '../i18n';
import database from '../lib/database'; import database from '../lib/database';
@ -48,7 +50,7 @@ const keyExtractor = (item: IItem) => item._id;
// This interface comes from request RocketChat.getTeamListRoom // This interface comes from request RocketChat.getTeamListRoom
interface IItem { interface IItem {
_id: string; _id: ERoomType;
fname: string; fname: string;
customFields: object; customFields: object;
broadcast: boolean; broadcast: boolean;
@ -97,6 +99,7 @@ interface ITeamChannelsViewProps extends IProps {
showActionSheet: (options: any) => void; showActionSheet: (options: any) => void;
showAvatar: boolean; showAvatar: boolean;
displayMode: string; displayMode: string;
dispatch: Dispatch;
} }
class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChannelsViewState> { class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChannelsViewState> {
private teamId: string; private teamId: string;
@ -438,7 +441,8 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan
{ {
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }), text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
style: 'destructive', style: 'destructive',
onPress: () => dispatch(deleteRoom(item._id, item.t)) // VERIFY ON PR
onPress: () => dispatch(deleteRoom(item._id, item))
} }
], ],
{ cancelable: false } { cancelable: false }