[MM-10594] Add feature to combine join/add/leave/remove to channel/team system messages (#1682)
* add feature to combine system messages Signed-off-by: Saturnino Abril <saturnino.abril@gmail.com> * updated copyright to 2015 Signed-off-by: Saturnino Abril <saturnino.abril@gmail.com> * updated per comments Signed-off-by: Saturnino Abril <saturnino.abril@gmail.com>
This commit is contained in:
parent
e72bb48d98
commit
1e4ee0716f
11 changed files with 896 additions and 0 deletions
|
|
@ -0,0 +1,79 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CombinedSystemMessage should match snapshot 1`] = `
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="{firstUser} "
|
||||
id="mobile.combined_system_message.first_user"
|
||||
values={
|
||||
Object {
|
||||
"firstUser": "@user1",
|
||||
"secondUser": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="added to the team"
|
||||
id="mobile.combined_system_message.added_to_team"
|
||||
/>
|
||||
</Text>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage=" by {actor}."
|
||||
id="mobile.combined_system_message.by_actor"
|
||||
values={
|
||||
Object {
|
||||
"actor": "@user2",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Text>
|
||||
`;
|
||||
|
||||
exports[`CombinedSystemMessage should match snapshot 2`] = `
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="{firstUser} and {secondUser} were "
|
||||
id="combined_system_message.first_user_and_second_user_were"
|
||||
values={
|
||||
Object {
|
||||
"firstUser": "first_user",
|
||||
"secondUser": "second_user",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="removed from the team"
|
||||
id="combined_system_message.removed_from_team"
|
||||
/>
|
||||
</Text>
|
||||
.
|
||||
</Text>
|
||||
`;
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LastUsers should match snapshot 1`] = `
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="E"
|
||||
id="e"
|
||||
values={
|
||||
Object {
|
||||
"lastUser": "User Two",
|
||||
"users": "User One",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="x"
|
||||
id="x"
|
||||
/>
|
||||
</Text>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="p"
|
||||
id="p"
|
||||
values={
|
||||
Object {
|
||||
"actor": "actor",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Text>
|
||||
`;
|
||||
|
||||
exports[`LastUsers should match snapshot 2`] = `
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="{firstUser} and "
|
||||
id="mobile.combined_system_message.first_user_and"
|
||||
values={
|
||||
Object {
|
||||
"firstUser": "User One",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
onPress={[Function]}
|
||||
style={1}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="{numOthers} others "
|
||||
id="mobile.combined_system_message.others"
|
||||
values={
|
||||
Object {
|
||||
"numOthers": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Text>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="were "
|
||||
id="mobile.combined_system_message.were"
|
||||
/>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="added to the team"
|
||||
id="mobile.combined_system_message.added_to_team"
|
||||
/>
|
||||
</Text>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage=" by {actor}."
|
||||
id="mobile.combined_system_message.by_actor"
|
||||
values={
|
||||
Object {
|
||||
"actor": "actor",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Text>
|
||||
`;
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
import LastUsers from './last_users';
|
||||
|
||||
const {
|
||||
JOIN_CHANNEL, ADD_TO_CHANNEL, REMOVE_FROM_CHANNEL, LEAVE_CHANNEL,
|
||||
JOIN_TEAM, ADD_TO_TEAM, REMOVE_FROM_TEAM, LEAVE_TEAM,
|
||||
} = Posts.POST_TYPES;
|
||||
|
||||
const postTypeMessage = {
|
||||
[JOIN_CHANNEL]: {
|
||||
one: {
|
||||
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.joined_channel'],
|
||||
defaultMessage: ['{firstUser} ', 'joined the channel'],
|
||||
},
|
||||
two: {
|
||||
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.joined_channel'],
|
||||
defaultMessage: ['{firstUser} and {secondUser} ', 'joined the channel'],
|
||||
},
|
||||
many_expanded: {
|
||||
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.joined_channel'],
|
||||
defaultMessage: ['{users} and {lastUser} ', 'joined the channel'],
|
||||
},
|
||||
},
|
||||
[ADD_TO_CHANNEL]: {
|
||||
one: {
|
||||
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['{firstUser} ', 'added to the channel', ' by {actor}.'],
|
||||
},
|
||||
one_you: {
|
||||
id: ['mobile.combined_system_message.you_were', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['You were ', 'added to the channel', ' by {actor}.'],
|
||||
},
|
||||
two: {
|
||||
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['{firstUser} and {secondUser} ', 'added to the channel', ' by {actor}.'],
|
||||
},
|
||||
many_expanded: {
|
||||
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['{users} and {lastUser} ', 'added to the channel', ' by {actor}.'],
|
||||
},
|
||||
},
|
||||
[REMOVE_FROM_CHANNEL]: {
|
||||
one: {
|
||||
id: ['mobile.combined_system_message.first_user_was', 'mobile.combined_system_message.removed_from_channel'],
|
||||
defaultMessage: ['{firstUser} was ', 'removed from the channel'],
|
||||
},
|
||||
one_you: {
|
||||
id: ['mobile.combined_system_message.you_were', 'mobile.combined_system_message.removed_from_channel'],
|
||||
defaultMessage: ['You were ', 'removed from the channel'],
|
||||
},
|
||||
two: {
|
||||
id: ['mobile.combined_system_message.first_user_and_second_user_were', 'mobile.combined_system_message.removed_from_channel'],
|
||||
defaultMessage: ['{firstUser} and {secondUser} were ', 'removed from the channel'],
|
||||
},
|
||||
many_expanded: {
|
||||
id: ['mobile.combined_system_message.users_and_last_user_were', 'mobile.combined_system_message.removed_from_channel'],
|
||||
defaultMessage: ['{users} and {lastUser} were ', 'removed from the channel'],
|
||||
},
|
||||
},
|
||||
[LEAVE_CHANNEL]: {
|
||||
one: {
|
||||
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.left_channel'],
|
||||
defaultMessage: ['{firstUser} ', 'left the channel'],
|
||||
},
|
||||
two: {
|
||||
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.left_channel'],
|
||||
defaultMessage: ['{firstUser} and {secondUser} ', 'left the channel'],
|
||||
},
|
||||
many_expanded: {
|
||||
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.left_channel'],
|
||||
defaultMessage: ['{users} and {lastUser} ', 'left the channel'],
|
||||
},
|
||||
},
|
||||
[JOIN_TEAM]: {
|
||||
one: {
|
||||
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.joined_team'],
|
||||
defaultMessage: ['{firstUser} ', 'joined the team'],
|
||||
},
|
||||
two: {
|
||||
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.joined_team'],
|
||||
defaultMessage: ['{firstUser} and {secondUser} ', 'joined the team'],
|
||||
},
|
||||
many_expanded: {
|
||||
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.joined_team'],
|
||||
defaultMessage: ['{users} and {lastUser} ', 'joined the team'],
|
||||
},
|
||||
},
|
||||
[ADD_TO_TEAM]: {
|
||||
one: {
|
||||
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['{firstUser} ', 'added to the team', ' by {actor}.'],
|
||||
},
|
||||
one_you: {
|
||||
id: ['mobile.combined_system_message.you_were', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['You were ', 'added to the team', ' by {actor}.'],
|
||||
},
|
||||
two: {
|
||||
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['{firstUser} and {secondUser} ', 'added to the team', ' by {actor}.'],
|
||||
},
|
||||
many_expanded: {
|
||||
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['{users} and {lastUser} ', 'added to the team', ' by {actor}.'],
|
||||
},
|
||||
},
|
||||
[REMOVE_FROM_TEAM]: {
|
||||
one: {
|
||||
id: ['mobile.combined_system_message.first_user_was', 'mobile.combined_system_message.removed_from_team'],
|
||||
defaultMessage: ['{firstUser} was ', 'removed from the team'],
|
||||
},
|
||||
one_you: {
|
||||
id: ['mobile.combined_system_message.you_were', 'mobile.combined_system_message.removed_from_team'],
|
||||
defaultMessage: ['You were ', 'removed from the team'],
|
||||
},
|
||||
two: {
|
||||
id: ['mobile.combined_system_message.first_user_and_second_user_were', 'mobile.combined_system_message.removed_from_team'],
|
||||
defaultMessage: ['{firstUser} and {secondUser} were ', 'removed from the team'],
|
||||
},
|
||||
many_expanded: {
|
||||
id: ['mobile.combined_system_message.users_and_last_user_were', 'mobile.combined_system_message.removed_from_team'],
|
||||
defaultMessage: ['{users} and {lastUser} were ', 'removed from the team'],
|
||||
},
|
||||
},
|
||||
[LEAVE_TEAM]: {
|
||||
one: {
|
||||
id: ['mobile.combined_system_message.first_user', 'mobile.combined_system_message.left_team'],
|
||||
defaultMessage: ['{firstUser} ', 'left the team'],
|
||||
},
|
||||
two: {
|
||||
id: ['mobile.combined_system_message.first_user_and_second_user', 'mobile.combined_system_message.left_team'],
|
||||
defaultMessage: ['{firstUser} and {secondUser} ', 'left the team'],
|
||||
},
|
||||
many_expanded: {
|
||||
id: ['mobile.combined_system_message.users_and_last_user', 'mobile.combined_system_message.left_team'],
|
||||
defaultMessage: ['{users} and {lastUser} ', 'left the team'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default class CombinedSystemMessage extends React.PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
getProfilesByIds: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
allUserIds: PropTypes.array.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
linkStyle: CustomPropTypes.Style,
|
||||
messageData: PropTypes.array.isRequired,
|
||||
teammateNameDisplay: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
userProfiles: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadUserProfiles();
|
||||
}
|
||||
|
||||
loadUserProfiles = async () => {
|
||||
const {
|
||||
actions,
|
||||
allUserIds,
|
||||
} = this.props;
|
||||
const {data: userProfiles} = await actions.getProfilesByIds(allUserIds);
|
||||
|
||||
this.setState({userProfiles});
|
||||
}
|
||||
|
||||
getAllUsersDisplayName = () => {
|
||||
const {userProfiles} = this.state;
|
||||
const {
|
||||
allUserIds,
|
||||
currentUserId,
|
||||
teammateNameDisplay,
|
||||
} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
const usersDisplayName = userProfiles.reduce((acc, user) => {
|
||||
acc[user.id] = displayUsername(user, teammateNameDisplay, true);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const includesCurrentUser = allUserIds.includes(currentUserId);
|
||||
if (includesCurrentUser) {
|
||||
usersDisplayName[currentUserId] = formatMessage({id: 'mobile.combined_system_message.you', defaultMessage: 'You'});
|
||||
}
|
||||
|
||||
return usersDisplayName;
|
||||
}
|
||||
|
||||
getDisplayNameByIds = (userIds = []) => {
|
||||
const {currentUserId} = this.props;
|
||||
const usersDisplayName = this.getAllUsersDisplayName();
|
||||
|
||||
const displayNames = userIds.
|
||||
filter((userId) => {
|
||||
return userId !== currentUserId;
|
||||
}).
|
||||
map((userId) => {
|
||||
return usersDisplayName[userId];
|
||||
});
|
||||
|
||||
const includesCurrentUser = userIds.includes(currentUserId);
|
||||
if (includesCurrentUser) {
|
||||
displayNames.unshift(usersDisplayName[currentUserId]);
|
||||
}
|
||||
|
||||
return displayNames;
|
||||
}
|
||||
|
||||
renderSystemMessage(postType, userIds, actorId, style) {
|
||||
const usersDisplayName = this.getDisplayNameByIds(userIds);
|
||||
let actorDisplayName = actorId ? this.getDisplayNameByIds([actorId])[0] : '';
|
||||
if (actorDisplayName && actorId === this.props.currentUserId) {
|
||||
actorDisplayName = actorDisplayName.toLowerCase();
|
||||
}
|
||||
|
||||
const firstUser = usersDisplayName[0];
|
||||
const numOthers = usersDisplayName.length - 1;
|
||||
|
||||
let formattedMessage;
|
||||
if (numOthers === 0) {
|
||||
formattedMessage = this.renderFormattedMessage(
|
||||
postTypeMessage[postType].one,
|
||||
firstUser,
|
||||
null,
|
||||
actorDisplayName,
|
||||
style,
|
||||
);
|
||||
|
||||
if (
|
||||
userIds[0] === this.props.currentUserId &&
|
||||
postTypeMessage[postType].one_you
|
||||
) {
|
||||
formattedMessage = this.renderFormattedMessage(
|
||||
postTypeMessage[postType].one_you,
|
||||
null,
|
||||
null,
|
||||
actorDisplayName,
|
||||
style,
|
||||
);
|
||||
}
|
||||
} else if (numOthers === 1) {
|
||||
formattedMessage = this.renderFormattedMessage(
|
||||
postTypeMessage[postType].two,
|
||||
firstUser,
|
||||
usersDisplayName[1],
|
||||
actorDisplayName,
|
||||
style,
|
||||
);
|
||||
} else {
|
||||
formattedMessage = (
|
||||
<LastUsers
|
||||
actor={actorDisplayName}
|
||||
expandedLocale={postTypeMessage[postType].many_expanded}
|
||||
postType={postType}
|
||||
style={style}
|
||||
userDisplayNames={usersDisplayName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return formattedMessage;
|
||||
}
|
||||
|
||||
renderFormattedMessage = (localeFormat, firstUser, secondUser, actor, style) => {
|
||||
return (
|
||||
<Text>
|
||||
<FormattedText
|
||||
id={localeFormat.id[0]}
|
||||
defaultMessage={localeFormat.defaultMessage[0]}
|
||||
values={{
|
||||
firstUser,
|
||||
secondUser,
|
||||
}}
|
||||
/>
|
||||
<Text style={style.activityType}>
|
||||
<FormattedText
|
||||
id={localeFormat.id[1]}
|
||||
defaultMessage={localeFormat.defaultMessage[1]}
|
||||
/>
|
||||
</Text>
|
||||
{localeFormat.id[2] ? (
|
||||
<FormattedText
|
||||
id={localeFormat.id[2]}
|
||||
defaultMessage={localeFormat.defaultMessage[2]}
|
||||
values={{actor}}
|
||||
/>
|
||||
) : ('.')
|
||||
}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
linkStyle,
|
||||
messageData,
|
||||
theme,
|
||||
} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{messageData.map(({postType, userIds, actorId}) => {
|
||||
return (
|
||||
<React.Fragment key={postType + actorId}>
|
||||
{this.renderSystemMessage(postType, userIds, actorId, {activityType: style.activityType, link: linkStyle})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
activityType: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {configure} from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
configure({adapter: new Adapter()});
|
||||
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
|
||||
import CombinedSystemMessage from './combined_system_message';
|
||||
|
||||
describe('CombinedSystemMessage', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
getProfilesByIds: () => {}, // eslint-disable-line no-empty-function
|
||||
},
|
||||
allUserIds: ['user_id_1', 'user_id_2', 'user_id_3'],
|
||||
currentUserId: 'user_id_3',
|
||||
linkStyle: 1,
|
||||
messageData: [
|
||||
{postType: Posts.POST_TYPES.ADD_TO_TEAM, userIds: ['user_id_1'], actorId: 'user_id_2'},
|
||||
],
|
||||
teammateNameDisplay: 'username',
|
||||
theme: {centerChannelColor: '#aaa'},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {getProfilesByIds: jest.fn(() => Promise.resolve({data: true}))},
|
||||
};
|
||||
const wrapper = shallowWithIntl(
|
||||
<CombinedSystemMessage {...props}/>
|
||||
);
|
||||
wrapper.setState({userProfiles: [{id: 'user_id_1', username: 'user1'}, {id: 'user_id_2', username: 'user2'}, {id: 'user_id_3', username: 'user3'}]});
|
||||
|
||||
const {postType, userIds, actorId} = baseProps.messageData[0];
|
||||
expect(wrapper.instance().renderSystemMessage(postType, userIds, actorId, {activityType: {fontSize: 14}}, 1)).toMatchSnapshot();
|
||||
|
||||
// on componentDidMount
|
||||
expect(props.actions.getProfilesByIds).toHaveBeenCalledTimes(1);
|
||||
expect(props.actions.getProfilesByIds).toHaveBeenCalledWith(props.allUserIds);
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {getProfilesByIds: jest.fn(() => Promise.resolve({data: true}))},
|
||||
};
|
||||
const localeFormat = {
|
||||
id: ['combined_system_message.first_user_and_second_user_were', 'combined_system_message.removed_from_team'],
|
||||
defaultMessage: ['{firstUser} and {secondUser} were ', 'removed from the team'],
|
||||
};
|
||||
const wrapper = shallowWithIntl(
|
||||
<CombinedSystemMessage {...props}/>
|
||||
);
|
||||
|
||||
wrapper.setState({userProfiles: [{id: 'user_id_1', username: 'user1'}, {id: 'user_id_2', username: 'user2'}, {id: 'user_id_3', username: 'user3'}]});
|
||||
expect(wrapper.instance().renderFormattedMessage(localeFormat, 'first_user', 'second_user', 'actor', {activityType: {fontSize: 14}})).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
29
app/components/combined_system_message/index.js
Normal file
29
app/components/combined_system_message/index.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {getProfilesByIds} from 'mattermost-redux/actions/users';
|
||||
|
||||
import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import CombinedSystemMessage from './combined_system_message';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
currentUserId: getCurrentUserId(state),
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getProfilesByIds,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CombinedSystemMessage);
|
||||
159
app/components/combined_system_message/last_users.js
Normal file
159
app/components/combined_system_message/last_users.js
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
const typeMessage = {
|
||||
[Posts.POST_TYPES.ADD_TO_CHANNEL]: {
|
||||
id: ['mobile.combined_system_message.were', 'mobile.combined_system_message.added_to_channel', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['were ', 'added to the channel', ' by {actor}.'],
|
||||
},
|
||||
[Posts.POST_TYPES.JOIN_CHANNEL]: {
|
||||
id: ['', 'mobile.combined_system_message.joined_channel'],
|
||||
defaultMessage: ['', 'joined the channel'],
|
||||
},
|
||||
[Posts.POST_TYPES.LEAVE_CHANNEL]: {
|
||||
id: ['', 'mobile.combined_system_message.left_channel', ''],
|
||||
defaultMessage: ['', 'left the channel'],
|
||||
},
|
||||
[Posts.POST_TYPES.REMOVE_FROM_CHANNEL]: {
|
||||
id: ['mobile.combined_system_message.were', 'mobile.combined_system_message.removed_from_channel'],
|
||||
defaultMessage: ['were ', 'removed from the channel'],
|
||||
},
|
||||
[Posts.POST_TYPES.ADD_TO_TEAM]: {
|
||||
id: ['mobile.combined_system_message.were', 'mobile.combined_system_message.added_to_team', 'mobile.combined_system_message.by_actor'],
|
||||
defaultMessage: ['were ', 'added to the team', ' by {actor}.'],
|
||||
},
|
||||
[Posts.POST_TYPES.JOIN_TEAM]: {
|
||||
id: ['', 'mobile.combined_system_message.joined_team'],
|
||||
defaultMessage: ['', 'joined the team'],
|
||||
},
|
||||
[Posts.POST_TYPES.LEAVE_TEAM]: {
|
||||
id: ['', 'mobile.combined_system_message.left_team'],
|
||||
defaultMessage: ['', 'left the team'],
|
||||
},
|
||||
[Posts.POST_TYPES.REMOVE_FROM_TEAM]: {
|
||||
id: ['', 'mobile.combined_system_message.removed_from_team'],
|
||||
defaultMessage: ['were ', 'removed from the team'],
|
||||
},
|
||||
};
|
||||
|
||||
export default class LastUsers extends React.PureComponent {
|
||||
static propTypes = {
|
||||
actor: PropTypes.string,
|
||||
expandedLocale: PropTypes.object.isRequired,
|
||||
postType: PropTypes.string.isRequired,
|
||||
style: PropTypes.object.isRequired,
|
||||
userDisplayNames: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
expand: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleOnClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({expand: true});
|
||||
}
|
||||
|
||||
renderExpandedView = (expandedLocale, userDisplayNames, actor, lastIndex, style) => {
|
||||
return (
|
||||
<Text>
|
||||
<FormattedText
|
||||
id={expandedLocale.id[0]}
|
||||
defaultMessage={expandedLocale.defaultMessage[0]}
|
||||
values={{
|
||||
users: userDisplayNames.slice(0, lastIndex).join(', '),
|
||||
lastUser: userDisplayNames[lastIndex],
|
||||
}}
|
||||
/>
|
||||
<Text style={style.activityType}>
|
||||
<FormattedText
|
||||
id={expandedLocale.id[1]}
|
||||
defaultMessage={expandedLocale.defaultMessage[1]}
|
||||
/>
|
||||
</Text>
|
||||
{expandedLocale.id[2] ? (
|
||||
<FormattedText
|
||||
id={expandedLocale.id[2]}
|
||||
defaultMessage={expandedLocale.defaultMessage[2]}
|
||||
values={{actor}}
|
||||
/>
|
||||
) : ('.')
|
||||
}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
renderCollapsedView = (postType, userDisplayNames, actor, lastIndex, style) => {
|
||||
return (
|
||||
<Text>
|
||||
<FormattedText
|
||||
id={'mobile.combined_system_message.first_user_and'}
|
||||
defaultMessage={'{firstUser} and '}
|
||||
values={{firstUser: userDisplayNames[0]}}
|
||||
/>
|
||||
<Text
|
||||
style={style.link}
|
||||
onPress={this.handleOnClick}
|
||||
>
|
||||
<FormattedText
|
||||
id={'mobile.combined_system_message.others'}
|
||||
defaultMessage={'{numOthers} others '}
|
||||
values={{numOthers: lastIndex}}
|
||||
/>
|
||||
</Text>
|
||||
{typeMessage[postType].id[0] &&
|
||||
<FormattedText
|
||||
id={typeMessage[postType].id[0]}
|
||||
defaultMessage={typeMessage[postType].defaultMessage[0]}
|
||||
/>
|
||||
}
|
||||
<Text style={style.activityType}>
|
||||
<FormattedText
|
||||
id={typeMessage[postType].id[1]}
|
||||
defaultMessage={typeMessage[postType].defaultMessage[1]}
|
||||
/>
|
||||
</Text>
|
||||
{typeMessage[postType].id[2] ? (
|
||||
<FormattedText
|
||||
id={typeMessage[postType].id[2]}
|
||||
defaultMessage={typeMessage[postType].defaultMessage[2]}
|
||||
values={{actor}}
|
||||
/>
|
||||
) : ('.')
|
||||
}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {expand} = this.state;
|
||||
const {
|
||||
actor,
|
||||
expandedLocale,
|
||||
postType,
|
||||
userDisplayNames,
|
||||
style,
|
||||
} = this.props;
|
||||
|
||||
const lastIndex = userDisplayNames.length - 1;
|
||||
|
||||
if (expand) {
|
||||
return this.renderExpandedView(expandedLocale, userDisplayNames, actor, lastIndex, style);
|
||||
}
|
||||
|
||||
return this.renderCollapsedView(postType, userDisplayNames, actor, lastIndex, style);
|
||||
}
|
||||
}
|
||||
45
app/components/combined_system_message/last_users.test.js
Normal file
45
app/components/combined_system_message/last_users.test.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {configure} from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
configure({adapter: new Adapter()});
|
||||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import LastUsers from './last_users';
|
||||
|
||||
describe('LastUsers', () => {
|
||||
const baseProps = {
|
||||
actor: 'actor',
|
||||
expandedLocale: {id: 'expanded_locale_id', defaultMessage: 'Expanded Locale'},
|
||||
postType: Posts.POST_TYPES.ADD_TO_TEAM,
|
||||
style: {activityType: {fontSize: 14}, link: 1},
|
||||
userDisplayNames: ['User One', 'User Two'],
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<LastUsers {...baseProps}/>
|
||||
);
|
||||
|
||||
const expanded = wrapper.instance().renderExpandedView(baseProps.expandedLocale, baseProps.userDisplayNames, baseProps.actor, 1, baseProps.style);
|
||||
expect(expanded).toMatchSnapshot();
|
||||
|
||||
const collapsed = wrapper.instance().renderCollapsedView(baseProps.postType, baseProps.userDisplayNames, baseProps.actor, 1, baseProps.style);
|
||||
expect(collapsed).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match state on handleOnClick', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<LastUsers {...baseProps}/>
|
||||
);
|
||||
|
||||
wrapper.setState({expand: false});
|
||||
wrapper.instance().handleOnClick({preventDefault: jest.fn()});
|
||||
expect(wrapper.state('expand')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -66,6 +66,7 @@ function mapStateToProps(state, ownProps) {
|
|||
|
||||
return {
|
||||
postProps: post.props || {},
|
||||
postType: post.type || '',
|
||||
fileIds: post.file_ids,
|
||||
hasBeenDeleted: post.state === Posts.POST_DELETED,
|
||||
hasBeenEdited: isEdited(post),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import {intlShape} from 'react-intl';
|
|||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
|
||||
import CombinedSystemMessage from 'app/components/combined_system_message';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Markdown from 'app/components/markdown';
|
||||
import OptionsContext from 'app/components/options_context';
|
||||
|
|
@ -65,6 +68,7 @@ export default class PostBody extends PureComponent {
|
|||
onPress: PropTypes.func,
|
||||
postId: PropTypes.string.isRequired,
|
||||
postProps: PropTypes.object,
|
||||
postType: PropTypes.string,
|
||||
renderReplyBar: PropTypes.func,
|
||||
showAddReaction: PropTypes.bool,
|
||||
showLongPost: PropTypes.bool.isRequired,
|
||||
|
|
@ -401,6 +405,8 @@ export default class PostBody extends PureComponent {
|
|||
onFailedPostPress,
|
||||
onPermalinkPress,
|
||||
onPress,
|
||||
postProps,
|
||||
postType,
|
||||
renderReplyBar,
|
||||
theme,
|
||||
toggleSelected,
|
||||
|
|
@ -434,6 +440,20 @@ export default class PostBody extends PureComponent {
|
|||
body = (<View>{messageComponent}</View>);
|
||||
} else if (isPostAddChannelMember) {
|
||||
messageComponent = this.renderAddChannelMember(style, textStyles);
|
||||
} else if (postType === Posts.POST_TYPES.COMBINED_USER_ACTIVITY) {
|
||||
const {allUserIds, messageData} = postProps.user_activity;
|
||||
messageComponent = (
|
||||
<View style={style.row}>
|
||||
<View style={style.flex}>
|
||||
<CombinedSystemMessage
|
||||
allUserIds={allUserIds}
|
||||
linkStyle={textStyles.link}
|
||||
messageData={messageData}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
} else if (message.length) {
|
||||
messageComponent = (
|
||||
<View style={style.row}>
|
||||
|
|
|
|||
|
|
@ -2125,6 +2125,26 @@
|
|||
"mobile.client_upgrade.no_upgrade_subtitle": "You already have the latest version.",
|
||||
"mobile.client_upgrade.no_upgrade_title": "Your App Is Up to Date",
|
||||
"mobile.client_upgrade.upgrade": "Update",
|
||||
"mobile.combined_system_message.first_user": "{firstUser} ",
|
||||
"mobile.combined_system_message.first_user_was": "{firstUser} was ",
|
||||
"mobile.combined_system_message.first_user_and_second_user": "{firstUser} and {secondUser} ",
|
||||
"mobile.combined_system_message.first_user_and_second_user_were": "{firstUser} and {secondUser} were ",
|
||||
"mobile.combined_system_message.users_and_last_user": "{users} and {lastUser} ",
|
||||
"mobile.combined_system_message.users_and_last_user_were": "{users} and {lastUser} were ",
|
||||
"mobile.combined_system_message.by_actor": " by {actor}.",
|
||||
"mobile.combined_system_message.you": "You",
|
||||
"mobile.combined_system_message.you_were": "You were ",
|
||||
"mobile.combined_system_message.were": "were ",
|
||||
"mobile.combined_system_message.first_user_and": "{firstUser} and ",
|
||||
"mobile.combined_system_message.others": "{numOthers} others ",
|
||||
"mobile.combined_system_message.joined_channel": "joined the channel",
|
||||
"mobile.combined_system_message.added_to_channel": "added to the channel",
|
||||
"mobile.combined_system_message.removed_from_channel": "removed from the channel",
|
||||
"mobile.combined_system_message.left_channel": "left the channel",
|
||||
"mobile.combined_system_message.joined_team": "joined the team",
|
||||
"mobile.combined_system_message.added_to_team": "added to the team",
|
||||
"mobile.combined_system_message.removed_from_team": "removed from the team",
|
||||
"mobile.combined_system_message.left_team": "left the team",
|
||||
"mobile.commands.error_title": "Error Executing Command",
|
||||
"mobile.components.channels_list_view.yourChannels": "Your channels:",
|
||||
"mobile.components.error_list.dismiss_all": "Dismiss All",
|
||||
|
|
|
|||
22
test/intl-test-helper.js
Normal file
22
test/intl-test-helper.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {IntlProvider, intlShape} from 'react-intl';
|
||||
import {mount, shallow} from 'enzyme';
|
||||
|
||||
const intlProvider = new IntlProvider({locale: 'en'}, {});
|
||||
const {intl} = intlProvider.getChildContext();
|
||||
|
||||
export function shallowWithIntl(node, {context} = {}) {
|
||||
return shallow(React.cloneElement(node, {intl}), {
|
||||
context: Object.assign({}, context, {intl}),
|
||||
});
|
||||
}
|
||||
|
||||
export function mountWithIntl(node, {context, childContextTypes} = {}) {
|
||||
return mount(React.cloneElement(node, {intl}), {
|
||||
context: Object.assign({}, context, {intl}),
|
||||
childContextTypes: Object.assign({}, {intl: intlShape}, childContextTypes),
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue