Commit f27aa4bb authored by Saturnino Abril's avatar Saturnino Abril Committed by Carlos Tadeu Panato Junior

fix missing users' status indicators on more DM modal (#1279)

parent 725552c0
......@@ -3,7 +3,12 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getProfiles, getProfilesInTeam, searchProfiles} from 'mattermost-redux/actions/users';
import {
getProfiles,
getProfilesInTeam,
getStatusesByIds,
searchProfiles,
} from 'mattermost-redux/actions/users';
import {
getCurrentUserId,
getProfiles as selectProfiles,
......@@ -62,6 +67,7 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
getProfiles,
getProfilesInTeam,
getStatusesByIds,
searchProfiles,
setModalSearchTerm,
}, dispatch),
......
......@@ -48,6 +48,7 @@ export default class MoreDirectChannels extends React.Component {
actions: PropTypes.shape({
getProfiles: PropTypes.func.isRequired,
getProfilesInTeam: PropTypes.func.isRequired,
getStatusesByIds: PropTypes.func.isRequired,
searchProfiles: PropTypes.func.isRequired,
setModalSearchTerm: PropTypes.func.isRequired,
}).isRequired,
......@@ -83,6 +84,7 @@ export default class MoreDirectChannels extends React.Component {
componentDidMount() {
this.getUserProfiles();
this.loadProfilesMissingStatus(this.props.users, this.props.statuses);
}
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase
......@@ -109,6 +111,23 @@ export default class MoreDirectChannels extends React.Component {
);
}
}
if (
this.props.users.length !== nextProps.users.length ||
Object.keys(this.props.statuses).length !== Object.keys(nextProps.statuses).length
) {
this.loadProfilesMissingStatus(nextProps.users, nextProps.statuses);
}
}
loadProfilesMissingStatus = (users = [], statuses = {}) => {
const missingStatusByIds = users.
filter((user) => !statuses[user.id]).
map((user) => user.id);
if (missingStatusByIds.length > 0) {
this.props.actions.getStatusesByIds(missingStatusByIds);
}
}
handleHide = () => {
......@@ -282,7 +301,7 @@ export default class MoreDirectChannels extends React.Component {
}
handleSubmitImmediatelyOn = (value) => {
return value.id === this.props.currentUserId || value.delete_at;
return value.id === this.props.currentUserId || Boolean(value.delete_at);
}
render() {
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/MoreDirectChannels should match renderOption snapshot 1`] = `
<div
className="more-modal__row clickable more-modal__row--selected"
onClick={[Function]}
>
<ProfilePicture
hasMention={false}
height="32"
isRHS={false}
src="/api/v4/users/user_id_1/image"
status="online"
width="32"
/>
<div
className="more-modal__details"
>
<div
className="more-modal__name"
>
@undefined
</div>
<div
className="more-modal__description"
/>
</div>
<div
className="more-modal__actions"
>
<div
className="more-modal__actions--round"
>
<i
className="fa fa-plus"
/>
</div>
</div>
</div>
`;
exports[`components/MoreDirectChannels should match snapshot 1`] = `
<Modal
animation={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="more-modal more-direct-channels"
dialogComponentClass={[Function]}
enforceFocus={true}
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={true}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
>
<ModalTitle
bsClass="modal-title"
componentClass="h4"
>
<FormattedMessage
defaultMessage="Direct Messages"
id="more_direct_channels.title"
values={Object {}}
/>
</ModalTitle>
</ModalHeader>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<MultiSelect
buttonSubmitText="Go"
handleAdd={[Function]}
handleDelete={[Function]}
handleInput={[Function]}
handlePageChange={[Function]}
handleSubmit={[Function]}
key="moreDirectChannelsList"
loading={true}
maxValues={7}
numRemainingText={
<FormattedMessage
defaultMessage="Use ↑↓ to browse, ↵ to select. You can add {num, number} more {num, plural, one {person} other {people}}. "
id="multiselect.numPeopleRemaining"
values={
Object {
"num": 5,
}
}
/>
}
optionRenderer={[Function]}
options={
Array [
Object {
"delete_at": 0,
"id": "user_id_1",
},
Object {
"delete_at": 0,
"id": "user_id_2",
},
Object {
"delete_at": 0,
"id": "user_id_3",
},
]
}
perPage={50}
saving={false}
submitImmediatelyOn={[Function]}
valueKey="id"
valueRenderer={[Function]}
values={
Array [
Object {
"id": "user_id_1",
},
Object {
"id": "user_id_2",
},
]
}
/>
</ModalBody>
</Modal>
`;
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import MoreDirectChannels from 'components/more_direct_channels/more_direct_channels.jsx';
jest.useFakeTimers();
describe('components/MoreDirectChannels', () => {
function emptyFunction() {} //eslint-disable-line no-empty-function
const baseProps = {
currentUserId: 'current_user_id',
currentTeamId: 'team_id',
currentTeamName: 'team_name',
searchTerm: '',
users: [{id: 'user_id_1', delete_at: 0}, {id: 'user_id_2', delete_at: 0}, {id: 'user_id_3', delete_at: 0}],
statuses: {user_id_1: 'online', user_id_2: 'away'},
currentChannelMembers: [{id: 'user_id_1'}, {id: 'user_id_2'}],
isExistingChannel: false,
restrictDirectMessage: 'any',
onModalDismissed: emptyFunction,
onHide: emptyFunction,
actions: {
getProfiles: jest.fn(() => {
return new Promise((resolve) => {
process.nextTick(() => resolve());
});
}),
getProfilesInTeam: emptyFunction,
getStatusesByIds: emptyFunction,
searchProfiles: emptyFunction,
setModalSearchTerm: emptyFunction,
},
};
test('should match snapshot', () => {
const props = {...baseProps, actions: {...baseProps.actions, getStatusesByIds: jest.fn()}};
const wrapper = shallow(<MoreDirectChannels {...props}/>);
expect(wrapper).toMatchSnapshot();
// on componentDidMount
expect(props.actions.getProfiles).toHaveBeenCalledTimes(1);
expect(props.actions.getProfiles).toBeCalledWith(0, 100);
expect(props.actions.getStatusesByIds).toHaveBeenCalledTimes(1);
expect(props.actions.getStatusesByIds).toBeCalledWith(['user_id_3']);
// on componentWillReceiveProps
wrapper.setProps({statuses: {user_id_1: 'online', user_id_2: 'away', user_id_3: 'offline'}});
expect(props.actions.getStatusesByIds).toHaveBeenCalledTimes(1);
});
test('should call actions.getStatusesByIds on loadProfilesMissingStatus', () => {
const props = {...baseProps, actions: {...baseProps.actions, getStatusesByIds: jest.fn()}};
const wrapper = shallow(<MoreDirectChannels {...props}/>);
wrapper.instance().loadProfilesMissingStatus(props.users, props.statuses);
expect(props.actions.getStatusesByIds).toHaveBeenCalledTimes(2);
expect(props.actions.getStatusesByIds).toBeCalledWith(['user_id_3']);
props.statuses = {user_id_1: 'online', user_id_2: 'away', user_id_3: 'offline'};
wrapper.instance().loadProfilesMissingStatus(props.users, props.statuses);
expect(props.actions.getStatusesByIds).toHaveBeenCalledTimes(2);
});
test('should call actions.setModalSearchTerm and match state on handleHide', () => {
const props = {...baseProps, actions: {...baseProps.actions, setModalSearchTerm: jest.fn()}};
const wrapper = shallow(<MoreDirectChannels {...props}/>);
wrapper.setState({show: true});
wrapper.instance().handleHide();
expect(props.actions.setModalSearchTerm).toHaveBeenCalledTimes(1);
expect(props.actions.setModalSearchTerm).toBeCalledWith('');
expect(wrapper.state('show')).toEqual(false);
});
test('should match state on setUsersLoadingState', () => {
const props = {...baseProps};
const wrapper = shallow(<MoreDirectChannels {...props}/>);
wrapper.setState({loadingUsers: true});
wrapper.instance().setUsersLoadingState(false);
expect(wrapper.state('loadingUsers')).toEqual(false);
wrapper.setState({loadingUsers: false});
wrapper.instance().setUsersLoadingState(true);
expect(wrapper.state('loadingUsers')).toEqual(true);
});
test('should call on search', () => {
const props = {...baseProps, actions: {...baseProps.actions, setModalSearchTerm: jest.fn()}};
const wrapper = shallow(<MoreDirectChannels {...props}/>);
wrapper.instance().search('user_search');
expect(props.actions.setModalSearchTerm).toHaveBeenCalledTimes(1);
expect(props.actions.setModalSearchTerm).toBeCalledWith('user_search');
});
test('should match state on handleDelete', () => {
const props = {...baseProps};
const wrapper = shallow(<MoreDirectChannels {...props}/>);
wrapper.setState({values: [{id: 'user_id_1'}]});
wrapper.instance().handleDelete([{id: 'user_id_2'}]);
expect(wrapper.state('values')).toEqual([{id: 'user_id_2'}]);
});
test('should match renderOption snapshot', () => {
const props = {...baseProps};
const wrapper = shallow(<MoreDirectChannels {...props}/>);
expect(wrapper.instance().renderOption({id: 'user_id_1', delete_at: 0}, true, jest.fn())).toMatchSnapshot();
});
test('should match output on renderValue', () => {
const wrapper = shallow(<MoreDirectChannels {...baseProps}/>);
expect(wrapper.instance().renderValue({id: 'user_id_2', username: 'username'})).toEqual('username');
});
test('should match output on handleSubmitImmediatelyOn', () => {
const wrapper = shallow(<MoreDirectChannels {...baseProps}/>);
expect(wrapper.instance().handleSubmitImmediatelyOn({id: 'current_user_id', delete_at: 0})).toEqual(true);
expect(wrapper.instance().handleSubmitImmediatelyOn({id: 'user_id_2', delete_at: 123})).toEqual(true);
expect(wrapper.instance().handleSubmitImmediatelyOn({id: 'user_id_2', delete_at: 0})).toEqual(false);
});
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment