(cherry picked from commit e442275c6f)
Co-authored-by: Chetanya Kandhari <chetanya.kandhari@brightscout.com>
This commit is contained in:
parent
0d83ac4c93
commit
abc2f30ef3
66 changed files with 3896 additions and 140 deletions
66
app/actions/views/custom_status.ts
Normal file
66
app/actions/views/custom_status.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Client4} from '@client/rest';
|
||||
import {logError} from '@mm-redux/actions/errors';
|
||||
import {UserTypes} from '@mm-redux/action_types';
|
||||
import {getCurrentUser} from '@mm-redux/selectors/entities/common';
|
||||
import {ActionFunc, DispatchFunc, batchActions, GetStateFunc} from '@mm-redux/types/actions';
|
||||
import {UserCustomStatus} from '@mm-redux/types/users';
|
||||
|
||||
export function setCustomStatus(customStatus: UserCustomStatus): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const user = getCurrentUser(getState());
|
||||
if (!user.props) {
|
||||
user.props = {};
|
||||
}
|
||||
|
||||
const oldCustomStatus = user.props.customStatus;
|
||||
user.props.customStatus = JSON.stringify(customStatus);
|
||||
dispatch({type: UserTypes.RECEIVED_ME, data: user});
|
||||
|
||||
try {
|
||||
await Client4.updateCustomStatus(customStatus);
|
||||
} catch (error) {
|
||||
user.props.customStatus = oldCustomStatus;
|
||||
dispatch(batchActions([
|
||||
{type: UserTypes.RECEIVED_ME, data: user},
|
||||
logError(error),
|
||||
]));
|
||||
return {error};
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function unsetCustomStatus(): ActionFunc {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
try {
|
||||
await Client4.unsetCustomStatus();
|
||||
} catch (error) {
|
||||
dispatch(logError(error));
|
||||
return {error};
|
||||
}
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function removeRecentCustomStatus(customStatus: UserCustomStatus): ActionFunc {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
try {
|
||||
await Client4.removeRecentCustomStatus(customStatus);
|
||||
} catch (error) {
|
||||
dispatch(logError(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
setCustomStatus,
|
||||
unsetCustomStatus,
|
||||
removeRecentCustomStatus,
|
||||
};
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import {analytics} from '@init/analytics';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {UserProfile, UserStatus} from '@mm-redux/types/users';
|
||||
import {UserCustomStatus, UserProfile, UserStatus} from '@mm-redux/types/users';
|
||||
import {buildQueryString, isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
|
@ -43,6 +43,9 @@ export interface ClientUsersMix {
|
|||
getStatusesByIds: (userIds: string[]) => Promise<UserStatus[]>;
|
||||
getStatus: (userId: string) => Promise<UserStatus>;
|
||||
updateStatus: (status: UserStatus) => Promise<UserStatus>;
|
||||
updateCustomStatus: (customStatus: UserCustomStatus) => Promise<{status: string}>;
|
||||
unsetCustomStatus: () => Promise<{status: string}>;
|
||||
removeRecentCustomStatus: (customStatus: UserCustomStatus) => Promise<{status: string}>;
|
||||
}
|
||||
|
||||
const ClientUsers = (superclass: any) => class extends superclass {
|
||||
|
|
@ -393,6 +396,27 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
|||
{method: 'put', body: JSON.stringify(status)},
|
||||
);
|
||||
};
|
||||
|
||||
updateCustomStatus = (customStatus: UserCustomStatus) => {
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute('me')}/status/custom`,
|
||||
{method: 'put', body: JSON.stringify(customStatus)},
|
||||
);
|
||||
};
|
||||
|
||||
unsetCustomStatus = () => {
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute('me')}/status/custom`,
|
||||
{method: 'delete'},
|
||||
);
|
||||
};
|
||||
|
||||
removeRecentCustomStatus = (customStatus: UserCustomStatus) => {
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute('me')}/status/custom/recent/delete`,
|
||||
{method: 'post', body: JSON.stringify(customStatus)},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientUsers;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/custom_status/clear_button should match snapshot 1`] = `
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
},
|
||||
Object {
|
||||
"height": 40,
|
||||
"width": 40,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<CompassIcon
|
||||
name="close-circle"
|
||||
size={20}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": 1000,
|
||||
"color": "rgba(61,60,64,0.52)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/custom_status/custom_status_emoji should match snapshot 1`] = `
|
||||
<Text
|
||||
testID="custom_status_emoji.calendar"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
undefined,
|
||||
Object {
|
||||
"fontSize": 16,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
📆
|
||||
</Text>
|
||||
</Text>
|
||||
`;
|
||||
|
||||
exports[`components/custom_status/custom_status_emoji should match snapshot with props 1`] = `
|
||||
<Text
|
||||
testID="custom_status_emoji.calendar"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
undefined,
|
||||
Object {
|
||||
"fontSize": 34,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
📆
|
||||
</Text>
|
||||
</Text>
|
||||
`;
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/custom_status/custom_status_text should match snapshot 1`] = `
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"fontSize": 17,
|
||||
"includeFontPadding": false,
|
||||
"textAlignVertical": "center",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
In a meeting
|
||||
</Text>
|
||||
`;
|
||||
|
||||
exports[`components/custom_status/custom_status_text should match snapshot with empty text 1`] = `
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"fontSize": 17,
|
||||
"includeFontPadding": false,
|
||||
"textAlignVertical": "center",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
||||
</Text>
|
||||
`;
|
||||
36
app/components/custom_status/clear_button.test.tsx
Normal file
36
app/components/custom_status/clear_button.test.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {TouchableOpacity} from 'react-native';
|
||||
|
||||
import ClearButton from '@components/custom_status/clear_button';
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
describe('components/custom_status/clear_button', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.default,
|
||||
handlePress: jest.fn(),
|
||||
};
|
||||
|
||||
it('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<ClearButton
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should call handlePress when press event is fired', () => {
|
||||
const wrapper = shallow(
|
||||
<ClearButton
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
|
||||
wrapper.find(TouchableOpacity).simulate('press');
|
||||
expect(baseProps.handlePress).toBeCalled();
|
||||
});
|
||||
});
|
||||
59
app/components/custom_status/clear_button.tsx
Normal file
59
app/components/custom_status/clear_button.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {TouchableOpacity} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {Theme} from '@mm-redux/types/preferences';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
interface Props {
|
||||
handlePress: () => void;
|
||||
size?: number;
|
||||
containerSize?: number;
|
||||
theme: Theme;
|
||||
testID?: string;
|
||||
iconName: string,
|
||||
}
|
||||
|
||||
const ClearButton = ({handlePress, iconName, size, containerSize, theme, testID}: Props) => {
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={preventDoubleTap(handlePress)}
|
||||
style={[style.container, {height: containerSize, width: containerSize}]}
|
||||
testID={testID}
|
||||
>
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
size={size}
|
||||
style={style.button}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
ClearButton.defaultProps = {
|
||||
size: 20,
|
||||
containerSize: 40,
|
||||
iconName: 'close-circle',
|
||||
};
|
||||
|
||||
export default ClearButton;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
button: {
|
||||
borderRadius: 1000,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.52),
|
||||
},
|
||||
};
|
||||
});
|
||||
45
app/components/custom_status/custom_status_emoji.test.tsx
Normal file
45
app/components/custom_status/custom_status_emoji.test.tsx
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 CustomStatusEmoji from '@components/custom_status/custom_status_emoji';
|
||||
import * as CustomStatusSelectors from '@selectors/custom_status';
|
||||
import {renderWithRedux} from 'test/testing_library';
|
||||
|
||||
jest.mock('@selectors/custom_status');
|
||||
|
||||
describe('components/custom_status/custom_status_emoji', () => {
|
||||
const getCustomStatus = () => {
|
||||
return {
|
||||
emoji: 'calendar',
|
||||
text: 'In a meeting',
|
||||
};
|
||||
};
|
||||
(CustomStatusSelectors.makeGetCustomStatus as jest.Mock).mockReturnValue(getCustomStatus);
|
||||
it('should match snapshot', () => {
|
||||
const wrapper = renderWithRedux(
|
||||
<CustomStatusEmoji/>,
|
||||
);
|
||||
expect(wrapper.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot with props', () => {
|
||||
const wrapper = renderWithRedux(
|
||||
<CustomStatusEmoji
|
||||
emojiSize={34}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not render when getCustomStatus returns null', () => {
|
||||
(CustomStatusSelectors.makeGetCustomStatus as jest.Mock).mockReturnValue(() => null);
|
||||
const wrapper = renderWithRedux(
|
||||
<CustomStatusEmoji/>,
|
||||
);
|
||||
|
||||
expect(wrapper.toJSON()).toBeNull();
|
||||
});
|
||||
});
|
||||
46
app/components/custom_status/custom_status_emoji.tsx
Normal file
46
app/components/custom_status/custom_status_emoji.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Text, TextStyle} from 'react-native';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import Emoji from '@components/emoji';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import {makeGetCustomStatus} from '@selectors/custom_status';
|
||||
|
||||
interface ComponentProps {
|
||||
emojiSize?: number;
|
||||
userID?: string;
|
||||
style?: TextStyle;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const CustomStatusEmoji = ({emojiSize, userID, style, testID}: ComponentProps) => {
|
||||
const getCustomStatus = makeGetCustomStatus();
|
||||
const customStatus = useSelector((state: GlobalState) => {
|
||||
return getCustomStatus(state, userID);
|
||||
});
|
||||
if (!customStatus?.emoji) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const testIdPrefix = testID ? `${testID}.` : '';
|
||||
return (
|
||||
<Text
|
||||
style={style}
|
||||
testID={`${testIdPrefix}custom_status_emoji.${customStatus.emoji}`}
|
||||
>
|
||||
<Emoji
|
||||
size={emojiSize}
|
||||
emojiName={customStatus.emoji}
|
||||
/>
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
CustomStatusEmoji.defaultProps = {
|
||||
emojiSize: 16,
|
||||
};
|
||||
|
||||
export default CustomStatusEmoji;
|
||||
35
app/components/custom_status/custom_status_text.test.tsx
Normal file
35
app/components/custom_status/custom_status_text.test.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import CustomStatusText from '@components/custom_status/custom_status_text';
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
describe('components/custom_status/custom_status_text', () => {
|
||||
const baseProps = {
|
||||
text: 'In a meeting',
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
it('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<CustomStatusText
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot with empty text', () => {
|
||||
const wrapper = shallow(
|
||||
<CustomStatusText
|
||||
{...baseProps}
|
||||
text={''}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
40
app/components/custom_status/custom_status_text.tsx
Normal file
40
app/components/custom_status/custom_status_text.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Text, TextStyle} from 'react-native';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import type {Theme} from '@mm-redux/types/preferences';
|
||||
|
||||
interface ComponentProps {
|
||||
text: string | typeof FormattedText;
|
||||
theme: Theme;
|
||||
textStyle?: TextStyle;
|
||||
ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
|
||||
numberOfLines?: number;
|
||||
}
|
||||
|
||||
const CustomStatusText = ({text, theme, textStyle, ellipsizeMode, numberOfLines}: ComponentProps) => (
|
||||
<Text
|
||||
style={[getStyleSheet(theme).label, textStyle]}
|
||||
ellipsizeMode={ellipsizeMode}
|
||||
numberOfLines={numberOfLines}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export default CustomStatusText;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
label: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 17,
|
||||
textAlignVertical: 'center',
|
||||
includeFontPadding: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import CustomStatusEmoji from '@components/custom_status/custom_status_emoji';
|
||||
import FormattedTime from '@components/formatted_time';
|
||||
import {CHANNEL, THREAD} from '@constants/screen';
|
||||
import {Posts} from '@mm-redux/constants';
|
||||
|
|
@ -30,6 +31,7 @@ type HeaderProps = {
|
|||
shouldRenderReplyButton?: boolean;
|
||||
theme: Theme;
|
||||
userTimezone?: string | null;
|
||||
isCustomStatusEnabled: boolean;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
|
@ -52,12 +54,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
opacity: 0.5,
|
||||
flex: 1,
|
||||
},
|
||||
customStatusEmoji: {
|
||||
color: theme.centerChannelColor,
|
||||
marginRight: 4,
|
||||
marginTop: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const Header = ({
|
||||
commentCount, displayName, location, isBot, isGuest,
|
||||
isMilitaryTime, post, rootPostAuthor, shouldRenderReplyButton, theme, userTimezone,
|
||||
isMilitaryTime, post, rootPostAuthor, shouldRenderReplyButton, theme, userTimezone, isCustomStatusEnabled,
|
||||
}: HeaderProps) => {
|
||||
const style = getStyleSheet(theme);
|
||||
const pendingPostStyle = isPostPendingOrFailed(post) ? style.pendingPost : undefined;
|
||||
|
|
@ -66,6 +73,7 @@ const Header = ({
|
|||
const isSystemPost = isSystemMessage(post);
|
||||
const isReplyPost = Boolean(post.root_id && (!isPostEphemeral(post) || post.state === Posts.POST_DELETED));
|
||||
const showReply = !isReplyPost && (location !== THREAD) && (shouldRenderReplyButton || (!rootPostAuthor && commentCount > 0));
|
||||
const showCustomStatusEmoji = Boolean(isCustomStatusEnabled && displayName && !(isSystemPost || isBot || isAutoResponse || isWebHook));
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -80,6 +88,12 @@ const Header = ({
|
|||
theme={theme}
|
||||
userId={post.user_id}
|
||||
/>
|
||||
{showCustomStatusEmoji && (
|
||||
<CustomStatusEmoji
|
||||
userID={post.user_id}
|
||||
style={style.customStatusEmoji}
|
||||
/>
|
||||
)}
|
||||
{!isSystemPost &&
|
||||
<HeaderTag
|
||||
isAutoResponder={isAutoResponse}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {getBool} from '@mm-redux/selectors/entities/preferences';
|
|||
import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone';
|
||||
import {getUser, getCurrentUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getUserCurrentTimezone} from '@mm-redux/utils/timezone_utils';
|
||||
import {isCustomStatusEnabled} from '@selectors/custom_status';
|
||||
import {postUserDisplayName} from '@utils/post';
|
||||
import {isGuest} from '@utils/users';
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ function mapStateToProps() {
|
|||
isGuest: isGuest(author),
|
||||
isMilitaryTime: getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time'),
|
||||
userTimezone: enableTimezone ? getUserCurrentTimezone(currentUser.timezone) : undefined,
|
||||
isCustomStatusEnabled: isCustomStatusEnabled(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ exports[`ChannelItem should match snapshot 1`] = `
|
|||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"flex": 1,
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
|
|
@ -98,6 +97,8 @@ exports[`ChannelItem should match snapshot 1`] = `
|
|||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
"maxWidth": "70%",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -208,7 +209,6 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"flex": 1,
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
|
|
@ -218,6 +218,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -328,7 +329,6 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"flex": 1,
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
|
|
@ -338,6 +338,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -448,7 +449,6 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
|
|||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"flex": 1,
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
|
|
@ -458,6 +458,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
|
|||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -557,7 +558,6 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
|
|||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"flex": 1,
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
|
|
@ -568,6 +568,8 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
|
|||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
"maxWidth": "70%",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -667,7 +669,6 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
|
|||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"flex": 1,
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
|
|
@ -678,6 +679,8 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
|
|||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
"maxWidth": "70%",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -777,7 +780,6 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
|
|||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"flex": 1,
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
|
|
@ -788,6 +790,8 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
|
|||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
"maxWidth": "70%",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -804,6 +808,137 @@ exports[`ChannelItem should match snapshot for no displayName 1`] = `null`;
|
|||
|
||||
exports[`ChannelItem should match snapshot for showUnreadForMsgs 1`] = `null`;
|
||||
|
||||
exports[`ChannelItem should match snapshot with custom status emoji 1`] = `
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(69,120,191,0.5)"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
testID="main.sidebar.channels_list.list.channel_item"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"paddingLeft": 16,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
testID="main.sidebar.channels_list.list.channel_item.channel_id"
|
||||
>
|
||||
<ChannelIcon
|
||||
channelId="channel_id"
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isInfo={false}
|
||||
isUnread={true}
|
||||
membersCount={1}
|
||||
size={24}
|
||||
statusStyle={
|
||||
Object {
|
||||
"backgroundColor": "#145dbf",
|
||||
"borderColor": "transparent",
|
||||
}
|
||||
}
|
||||
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
type="O"
|
||||
userId="currentUser"
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"marginLeft": 13,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
"maxWidth": "70%",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="main.sidebar.channels_list.list.channel_item.display_name"
|
||||
>
|
||||
display_name
|
||||
</Text>
|
||||
<CustomStatusEmoji
|
||||
emojiSize={16}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"opacity": 0.6,
|
||||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
"maxWidth": "70%",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="display_name"
|
||||
userID="currentUser"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot with draft 1`] = `
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
|
|
@ -891,7 +1026,6 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
|
|||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"flex": 1,
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
|
|
@ -902,6 +1036,8 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
|
|||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
"maxWidth": "70%",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -1003,7 +1139,6 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
|||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"flex": 1,
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
|
|
@ -1014,6 +1149,8 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
|||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
"maxWidth": "70%",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ import {
|
|||
TouchableHighlight,
|
||||
Text,
|
||||
View,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import Badge from '@components/badge';
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import CustomStatusEmoji from '@components/custom_status/custom_status_emoji';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
|
@ -37,6 +39,7 @@ export default class ChannelItem extends PureComponent {
|
|||
theme: PropTypes.object.isRequired,
|
||||
unreadMsgs: PropTypes.number.isRequired,
|
||||
isSearchResult: PropTypes.bool,
|
||||
customStatusEnabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -175,6 +178,15 @@ export default class ChannelItem extends PureComponent {
|
|||
const itemTestID = `${testID}.${channelId}`;
|
||||
const displayNameTestID = `${testID}.display_name`;
|
||||
|
||||
const customStatus = this.props.teammateId && this.props.customStatusEnabled ?
|
||||
(
|
||||
<CustomStatusEmoji
|
||||
userID={this.props.teammateId}
|
||||
style={[style.emoji, extraTextStyle]}
|
||||
testID={displayName}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
|
|
@ -198,6 +210,7 @@ export default class ChannelItem extends PureComponent {
|
|||
>
|
||||
{channelDisplayName}
|
||||
</Text>
|
||||
{customStatus}
|
||||
{badge}
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -234,16 +247,22 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
paddingRight: 10,
|
||||
marginLeft: 13,
|
||||
maxWidth: '80%',
|
||||
flex: 1,
|
||||
alignSelf: 'center',
|
||||
fontFamily: 'Open Sans',
|
||||
},
|
||||
textActive: {
|
||||
color: theme.sidebarTextActiveColor,
|
||||
opacity: 1,
|
||||
},
|
||||
textUnread: {
|
||||
opacity: 1,
|
||||
color: theme.sidebarUnreadText,
|
||||
fontWeight: '500',
|
||||
maxWidth: '70%',
|
||||
},
|
||||
emoji: {
|
||||
color: changeOpacity(theme.sidebarText, 0.6),
|
||||
opacity: Platform.OS === 'ios' ? 0.6 : 1,
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBg,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ describe('ChannelItem', () => {
|
|||
unreadMsgs: 1,
|
||||
isSearchResult: false,
|
||||
isBot: false,
|
||||
customStatusEnabled: true,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
|
@ -207,6 +208,18 @@ describe('ChannelItem', () => {
|
|||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with custom status emoji', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<ChannelItem
|
||||
{...baseProps}
|
||||
teammateId={baseProps.currentUserId}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Should call onPress', () => {
|
||||
const onSelectChannel = jest.fn();
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {getTheme, getTeammateNameDisplaySetting} from '@mm-redux/selectors/entit
|
|||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getUserIdFromChannelName, isChannelMuted} from '@mm-redux/utils/channel_utils';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import {isCustomStatusEnabled} from '@selectors/custom_status';
|
||||
import {getDraftForChannel} from '@selectors/views';
|
||||
import {isGuest as isGuestUser} from '@utils/users';
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ function makeMapStateToProps() {
|
|||
teammateId,
|
||||
theme: getTheme(state),
|
||||
unreadMsgs,
|
||||
customStatusEnabled: isCustomStatusEnabled(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,9 @@ exports[`DrawerItem should match snapshot 1`] = `
|
|||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#ffffff",
|
||||
"flexDirection": "row",
|
||||
"height": 50,
|
||||
"minHeight": 50,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
@ -51,9 +50,10 @@ exports[`DrawerItem should match snapshot 1`] = `
|
|||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"paddingBottom": 14,
|
||||
"paddingTop": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
|
|
@ -64,7 +64,6 @@ exports[`DrawerItem should match snapshot 1`] = `
|
|||
Array [
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"flex": 1,
|
||||
"fontSize": 17,
|
||||
"includeFontPadding": false,
|
||||
"textAlignVertical": "center",
|
||||
|
|
@ -92,3 +91,84 @@ exports[`DrawerItem should match snapshot 1`] = `
|
|||
</View>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`DrawerItem should match snapshot without separator and centered false 1`] = `
|
||||
<ForwardRef
|
||||
onPress={[MockFunction]}
|
||||
testID="test-id"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 50,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"height": 50,
|
||||
"justifyContent": "center",
|
||||
"marginLeft": 5,
|
||||
"width": 45,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CompassIcon
|
||||
name="icon-name"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.64)",
|
||||
"fontSize": 24,
|
||||
},
|
||||
Object {
|
||||
"color": "#fd5960",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
"paddingBottom": 14,
|
||||
"paddingTop": 14,
|
||||
}
|
||||
}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="default message"
|
||||
id="i18-id"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"fontSize": 17,
|
||||
"includeFontPadding": false,
|
||||
"textAlignVertical": "center",
|
||||
},
|
||||
Object {
|
||||
"color": "#fd5960",
|
||||
},
|
||||
Object {},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -15,3 +15,19 @@ exports[`SettingsSidebar should match snapshot 1`] = `
|
|||
useNativeAnimations={true}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`SettingsSidebar should match snapshot with custom status enabled 1`] = `
|
||||
<DrawerLayoutAdapter
|
||||
drawerPosition="right"
|
||||
drawerWidth={710}
|
||||
forwardRef={
|
||||
Object {
|
||||
"current": null,
|
||||
}
|
||||
}
|
||||
onDrawerClose={[Function]}
|
||||
onDrawerOpen={[Function]}
|
||||
renderNavigationView={[Function]}
|
||||
useNativeAnimations={true}
|
||||
/>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -87,11 +87,11 @@ export default class DrawerItem extends PureComponent {
|
|||
onPress={onPress}
|
||||
>
|
||||
<View style={style.container}>
|
||||
{icon &&
|
||||
<View style={style.iconContainer}>
|
||||
{icon}
|
||||
</View>
|
||||
}
|
||||
{icon && (
|
||||
<View style={style.iconContainer}>
|
||||
{icon}
|
||||
</View>
|
||||
)}
|
||||
<View style={style.wrapper}>
|
||||
<View style={style.labelContainer}>
|
||||
{label}
|
||||
|
|
@ -107,10 +107,9 @@ export default class DrawerItem extends PureComponent {
|
|||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flexDirection: 'row',
|
||||
height: 50,
|
||||
minHeight: 50,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 45,
|
||||
|
|
@ -127,9 +126,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
flex: 1,
|
||||
},
|
||||
labelContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
paddingTop: 14,
|
||||
paddingBottom: 14,
|
||||
},
|
||||
centerLabel: {
|
||||
textAlign: 'center',
|
||||
|
|
@ -137,7 +137,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
label: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
textAlignVertical: 'center',
|
||||
includeFontPadding: false,
|
||||
|
|
|
|||
|
|
@ -28,4 +28,17 @@ describe('DrawerItem', () => {
|
|||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot without separator and centered false', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
centered: false,
|
||||
separator: false,
|
||||
};
|
||||
const wrapper = shallowWithIntl(
|
||||
<DrawerItem {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,23 +4,32 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {unsetCustomStatus} from '@actions/views/custom_status';
|
||||
import {setStatus} from '@mm-redux/actions/users';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUser, getStatusForUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {isCustomStatusEnabled, makeGetCustomStatus} from '@selectors/custom_status';
|
||||
|
||||
import {logout} from 'app/actions/views/user';
|
||||
|
||||
import SettingsSidebar from './settings_sidebar';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentUser = getCurrentUser(state) || {};
|
||||
const status = getStatusForUserId(state, currentUser.id);
|
||||
function makeMapStateToProps() {
|
||||
const getCustomStatus = makeGetCustomStatus();
|
||||
return (state) => {
|
||||
const currentUser = getCurrentUser(state) || {};
|
||||
const status = getStatusForUserId(state, currentUser.id);
|
||||
|
||||
return {
|
||||
currentUser,
|
||||
locale: currentUser?.locale,
|
||||
status,
|
||||
theme: getTheme(state),
|
||||
const customStatusEnabled = isCustomStatusEnabled(state);
|
||||
const customStatus = customStatusEnabled ? getCustomStatus(state) : undefined;
|
||||
return {
|
||||
currentUser,
|
||||
locale: currentUser?.locale,
|
||||
status,
|
||||
theme: getTheme(state),
|
||||
isCustomStatusEnabled: customStatusEnabled,
|
||||
customStatus,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -29,8 +38,9 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
logout,
|
||||
setStatus,
|
||||
unsetCustomStatus,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, null, {forwardRef: true})(SettingsSidebar);
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps, null, {forwardRef: true})(SettingsSidebar);
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ export default class SettingsDrawerAndroid extends SettingsSidebarBase {
|
|||
this.goToSettingsScreeen(intl);
|
||||
});
|
||||
|
||||
goToCustomStatus = preventDoubleTap(() => {
|
||||
const {intl} = this.providerRef.getChildContext();
|
||||
this.goToCustomStatusScreen(intl);
|
||||
})
|
||||
|
||||
renderNavigationView = () => {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
|
|||
|
|
@ -111,6 +111,11 @@ export default class SettingsDrawer extends SettingsSidebarBase {
|
|||
this.goToSettingsScreeen(intl);
|
||||
});
|
||||
|
||||
goToCustomStatus = preventDoubleTap(() => {
|
||||
const {intl} = this.context;
|
||||
this.goToCustomStatusScreen(intl);
|
||||
})
|
||||
|
||||
renderNavigationView = () => {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
|
|||
|
|
@ -9,16 +9,24 @@ import Preferences from '@mm-redux/constants/preferences';
|
|||
import SettingsSidebar from './settings_sidebar.ios';
|
||||
|
||||
describe('SettingsSidebar', () => {
|
||||
const customStatus = {
|
||||
emoji: 'calendar',
|
||||
text: 'In a meeting',
|
||||
};
|
||||
|
||||
const baseProps = {
|
||||
actions: {
|
||||
logout: jest.fn(),
|
||||
setStatus: jest.fn(),
|
||||
unsetCustomStatus: jest.fn(),
|
||||
},
|
||||
currentUser: {
|
||||
id: 'user-id',
|
||||
},
|
||||
status: 'offline',
|
||||
theme: Preferences.THEMES.default,
|
||||
isCustomStatusEnabled: false,
|
||||
customStatus,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
|
@ -28,4 +36,15 @@ describe('SettingsSidebar', () => {
|
|||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot with custom status enabled', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<SettingsSidebar
|
||||
{...baseProps}
|
||||
isCustomStatusEnabled={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,11 +10,16 @@ import EventEmitter from '@mm-redux/utils/event_emitter';
|
|||
|
||||
import {showModal, showModalOverCurrentContext, dismissModal} from '@actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomStatusText from '@components/custom_status/custom_status_text';
|
||||
import ClearButton from '@components/custom_status/clear_button';
|
||||
import Emoji from '@components/emoji';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import UserStatus from '@components/user_status';
|
||||
import {NavigationTypes} from '@constants';
|
||||
import {NavigationTypes, CustomStatus} from '@constants';
|
||||
import {t} from '@utils/i18n';
|
||||
import {confirmOutOfOfficeDisabled} from '@utils/status';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {t} from '@utils/i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import DrawerItem from './drawer_item';
|
||||
import UserInfo from './user_info';
|
||||
|
|
@ -25,10 +30,13 @@ export default class SettingsSidebarBase extends PureComponent {
|
|||
actions: PropTypes.shape({
|
||||
logout: PropTypes.func.isRequired,
|
||||
setStatus: PropTypes.func.isRequired,
|
||||
unsetCustomStatus: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
currentUser: PropTypes.object.isRequired,
|
||||
status: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isCustomStatusEnabled: PropTypes.bool.isRequired,
|
||||
customStatus: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -36,14 +44,47 @@ export default class SettingsSidebarBase extends PureComponent {
|
|||
status: 'offline',
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showStatus: props.isCustomStatusEnabled,
|
||||
showRetryMessage: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
EventEmitter.on(NavigationTypes.CLOSE_SETTINGS_SIDEBAR, this.closeSettingsSidebar);
|
||||
EventEmitter.on(CustomStatus.SET_CUSTOM_STATUS_FAILURE, this.handleSetCustomStatusFailure);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
this.handleCustomStatusChange(prevProps.customStatus, this.props.customStatus);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
EventEmitter.off(NavigationTypes.CLOSE_SETTINGS_SIDEBAR, this.closeSettingsSidebar);
|
||||
EventEmitter.off(CustomStatus.SET_CUSTOM_STATUS_FAILURE, this.handleSetCustomStatusFailure);
|
||||
}
|
||||
|
||||
handleSetCustomStatusFailure = () => {
|
||||
this.setState({
|
||||
showRetryMessage: true,
|
||||
});
|
||||
}
|
||||
|
||||
handleCustomStatusChange = (prevCustomStatus, customStatus) => {
|
||||
const isStatusSet = Boolean(customStatus?.emoji);
|
||||
if (isStatusSet) {
|
||||
const isStatusChanged = prevCustomStatus?.emoji !== customStatus.emoji || prevCustomStatus?.text !== customStatus.text;
|
||||
if (isStatusChanged) {
|
||||
this.setState({
|
||||
showStatus: true,
|
||||
showRetryMessage: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
confirmResetBase = (status, intl) => {
|
||||
|
|
@ -123,6 +164,11 @@ export default class SettingsSidebarBase extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
goToCustomStatusScreen = (intl) => {
|
||||
this.closeSettingsSidebar();
|
||||
showModal('CustomStatus', intl.formatMessage({id: 'mobile.routes.custom_status', defaultMessage: 'Set a Status'}));
|
||||
}
|
||||
|
||||
logout = preventDoubleTap(() => {
|
||||
const {logout} = this.props.actions;
|
||||
this.closeSettingsSidebar();
|
||||
|
|
@ -185,6 +231,102 @@ export default class SettingsSidebarBase extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
clearCustomStatus = async () => {
|
||||
this.setState({showStatus: false, showRetryMessage: false});
|
||||
const {error} = await this.props.actions.unsetCustomStatus();
|
||||
if (error) {
|
||||
this.setState({showStatus: true, showRetryMessage: true});
|
||||
}
|
||||
}
|
||||
|
||||
renderCustomStatus = () => {
|
||||
const {isCustomStatusEnabled, customStatus, theme} = this.props;
|
||||
const {showStatus, showRetryMessage} = this.state;
|
||||
|
||||
if (!isCustomStatusEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const isStatusSet = customStatus?.emoji && showStatus;
|
||||
|
||||
const customStatusEmoji = (
|
||||
<View
|
||||
testID={`custom_status.emoji.${isStatusSet ? customStatus.emoji : 'default'}`}
|
||||
>
|
||||
{isStatusSet ? (
|
||||
<Emoji
|
||||
emojiName={customStatus.emoji}
|
||||
size={20}
|
||||
/>
|
||||
) : (
|
||||
<CompassIcon
|
||||
name='emoticon-happy-outline'
|
||||
size={24}
|
||||
style={style.customStatusIcon}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
|
||||
const clearButton = isStatusSet ?
|
||||
(
|
||||
<ClearButton
|
||||
handlePress={this.clearCustomStatus}
|
||||
theme={theme}
|
||||
testID='settings.sidebar.custom_status.action.clear'
|
||||
/>
|
||||
) : null;
|
||||
|
||||
const retryMessage = showRetryMessage ?
|
||||
(
|
||||
<FormattedText
|
||||
id='custom_status.failure_message'
|
||||
defaultMessage='Failed to update status. Try again'
|
||||
style={style.retryMessage}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
const text = isStatusSet ? customStatus.text : (
|
||||
<FormattedText
|
||||
id='mobile.routes.custom_status'
|
||||
defaultMessage='Set a Status'
|
||||
/>
|
||||
);
|
||||
|
||||
const labelComponent = (
|
||||
<>
|
||||
<View
|
||||
style={style.customStatusTextContainer}
|
||||
>
|
||||
<CustomStatusText
|
||||
text={text}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
{retryMessage}
|
||||
{clearButton &&
|
||||
<View
|
||||
style={style.clearButton}
|
||||
>
|
||||
{clearButton}
|
||||
</View>
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<DrawerItem
|
||||
testID='settings.sidebar.custom_status.action'
|
||||
labelComponent={labelComponent}
|
||||
leftComponent={customStatusEmoji}
|
||||
separator={false}
|
||||
onPress={this.goToCustomStatus}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderOptions = (style) => {
|
||||
const {currentUser, theme} = this.props;
|
||||
|
||||
|
|
@ -207,10 +349,11 @@ export default class SettingsSidebarBase extends PureComponent {
|
|||
testID='settings.sidebar.status.action'
|
||||
labelComponent={this.renderUserStatusLabel(currentUser.id)}
|
||||
leftComponent={this.renderUserStatusIcon(currentUser.id)}
|
||||
separator={false}
|
||||
separator={this.props.isCustomStatusEnabled}
|
||||
onPress={this.handleSetStatus}
|
||||
theme={theme}
|
||||
/>
|
||||
{this.renderCustomStatus()}
|
||||
</View>
|
||||
<View style={style.separator}/>
|
||||
<View style={style.block}>
|
||||
|
|
@ -276,3 +419,22 @@ export default class SettingsSidebarBase extends PureComponent {
|
|||
return; // eslint-disable-line no-useless-return
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
customStatusTextContainer: {
|
||||
width: '70%',
|
||||
},
|
||||
customStatusIcon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
},
|
||||
clearButton: {
|
||||
position: 'absolute',
|
||||
top: 4,
|
||||
right: 14,
|
||||
},
|
||||
retryMessage: {
|
||||
color: theme.errorTextColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
return {
|
||||
label: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
textAlignVertical: 'center',
|
||||
includeFontPadding: false,
|
||||
|
|
|
|||
5
app/constants/custom_status.ts
Normal file
5
app/constants/custom_status.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const CUSTOM_STATUS_TEXT_CHARACTER_LIMIT = 100;
|
||||
export const SET_CUSTOM_STATUS_FAILURE = 'set_custom_status_failure';
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import AttachmentTypes from './attachment';
|
||||
import CustomPropTypes from './custom_prop_types';
|
||||
import * as CustomStatus from './custom_status';
|
||||
import DeepLinkTypes from './deep_linking';
|
||||
import DeviceTypes from './device';
|
||||
import ListTypes from './list';
|
||||
|
|
@ -21,4 +22,5 @@ export {
|
|||
Types,
|
||||
ViewTypes,
|
||||
WebsocketEvents,
|
||||
CustomStatus,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ const Preferences: Dictionary<any> = {
|
|||
INTERVAL_HOUR: 60 * 60,
|
||||
INTERVAL_IMMEDIATE: 30,
|
||||
|
||||
CATEGORY_CUSTOM_STATUS: 'custom_status',
|
||||
NAME_CUSTOM_STATUS_TUTORIAL_STATE: 'custom_status_tutorial_state',
|
||||
NAME_RECENT_CUSTOM_STATUSES: 'recent_custom_statuses',
|
||||
CUSTOM_STATUS_MODAL_VIEWED: 'custom_status_modal_viewed',
|
||||
|
||||
// "immediate" is a 30 second interval
|
||||
INTERVAL_NEVER: 0,
|
||||
INTERVAL_NOT_SET: -1,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export type Config = {
|
|||
EnableCustomBrand: string;
|
||||
EnableCustomEmoji: string;
|
||||
EnableCustomTermsOfService: string;
|
||||
EnableCustomUserStatuses: string;
|
||||
EnableDeveloper: string;
|
||||
EnableDiagnostics: string;
|
||||
EnableEmailBatching: string;
|
||||
|
|
|
|||
1
app/mm-redux/types/module.d.ts
vendored
1
app/mm-redux/types/module.d.ts
vendored
|
|
@ -6,3 +6,4 @@ declare module 'redux-action-buffer';
|
|||
declare module 'react-intl';
|
||||
declare module 'react-native-local-auth';
|
||||
declare module 'react-native-cookies';
|
||||
declare module 'react-native-keyboard-aware-scrollview';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import {Channel} from './channels';
|
||||
import {Team} from './teams';
|
||||
import {PostType} from './posts';
|
||||
import {$ID, IDMappedObjects, RelationOneToMany, RelationOneToOne} from './utilities';
|
||||
import {$ID, IDMappedObjects, RelationOneToMany, RelationOneToOne, Dictionary} from './utilities';
|
||||
export type UserNotifyProps = {
|
||||
auto_responder_active?: 'true' | 'false';
|
||||
auto_responder_message?: string;
|
||||
|
|
@ -37,6 +37,7 @@ export type UserProfile = {
|
|||
roles: string;
|
||||
remote_id?: string;
|
||||
locale: string;
|
||||
props: Dictionary<string>;
|
||||
notify_props: UserNotifyProps;
|
||||
terms_of_service_id: string;
|
||||
terms_of_service_create_at: number;
|
||||
|
|
@ -78,3 +79,8 @@ export type UserStatus = {
|
|||
last_activity_at: number;
|
||||
active_channel?: string;
|
||||
}
|
||||
|
||||
export type UserCustomStatus = {
|
||||
emoji: string;
|
||||
text: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,19 @@ exports[`ChannelTitle should match snapshot 1`] = `
|
|||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
},
|
||||
Object {
|
||||
"width": "90%",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
|
|
@ -63,14 +68,19 @@ exports[`ChannelTitle should match snapshot when is DM and has guests and the te
|
|||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
},
|
||||
Object {
|
||||
"width": "80%",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
|
|
@ -100,6 +110,20 @@ exports[`ChannelTitle should match snapshot when is DM and has guests and the te
|
|||
}
|
||||
}
|
||||
/>
|
||||
<CustomStatusEmoji
|
||||
emojiSize={16}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": undefined,
|
||||
"marginHorizontal": 1,
|
||||
},
|
||||
Object {
|
||||
"marginHorizontal": 5,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
|
|
@ -140,14 +164,19 @@ exports[`ChannelTitle should match snapshot when is DM and has guests but the te
|
|||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
},
|
||||
Object {
|
||||
"width": "80%",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
|
|
@ -177,6 +206,20 @@ exports[`ChannelTitle should match snapshot when is DM and has guests but the te
|
|||
}
|
||||
}
|
||||
/>
|
||||
<CustomStatusEmoji
|
||||
emojiSize={16}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": undefined,
|
||||
"marginHorizontal": 1,
|
||||
},
|
||||
Object {
|
||||
"marginHorizontal": 5,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
|
@ -192,14 +235,19 @@ exports[`ChannelTitle should match snapshot when isChannelShared is true 1`] = `
|
|||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
},
|
||||
Object {
|
||||
"width": "90%",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
|
|
@ -263,14 +311,19 @@ exports[`ChannelTitle should match snapshot when isSelfDMChannel is true 1`] = `
|
|||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
},
|
||||
Object {
|
||||
"width": "90%",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
|
|
@ -311,3 +364,74 @@ exports[`ChannelTitle should match snapshot when isSelfDMChannel is true 1`] = `
|
|||
</View>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`ChannelTitle should match snapshot with custom status emoji 1`] = `
|
||||
<ForwardRef
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="channel.title.button"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"position": "relative",
|
||||
"top": -1,
|
||||
},
|
||||
Object {
|
||||
"width": "80%",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"flex": 0,
|
||||
"flexShrink": 1,
|
||||
"fontSize": 18,
|
||||
"fontWeight": "bold",
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
testID="channel.nav_bar.title"
|
||||
>
|
||||
My Channel
|
||||
</Text>
|
||||
<CompassIcon
|
||||
name="chevron-down"
|
||||
size={24}
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"marginHorizontal": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<CustomStatusEmoji
|
||||
emojiSize={16}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": undefined,
|
||||
"marginHorizontal": 1,
|
||||
},
|
||||
Object {
|
||||
"marginHorizontal": 5,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ import {General} from '@mm-redux/constants';
|
|||
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomStatusEmoji from '@components/custom_status/custom_status_emoji';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {t} from '@utils/i18n';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
export default class ChannelTitle extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -32,6 +33,8 @@ export default class ChannelTitle extends PureComponent {
|
|||
isSelfDMChannel: PropTypes.bool.isRequired,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object,
|
||||
teammateId: PropTypes.string,
|
||||
customStatusEnabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -144,6 +147,7 @@ export default class ChannelTitle extends PureComponent {
|
|||
}
|
||||
|
||||
let mutedIcon;
|
||||
let wrapperWidth = 90;
|
||||
if (isChannelMuted) {
|
||||
mutedIcon = (
|
||||
<CompassIcon
|
||||
|
|
@ -152,6 +156,20 @@ export default class ChannelTitle extends PureComponent {
|
|||
name='bell-off-outline'
|
||||
/>
|
||||
);
|
||||
wrapperWidth -= 10;
|
||||
}
|
||||
|
||||
const customStatus = this.props.channelType === General.DM_CHANNEL && this.props.customStatusEnabled ?
|
||||
(
|
||||
<CustomStatusEmoji
|
||||
userID={this.props.teammateId}
|
||||
emojiSize={16}
|
||||
style={[style.icon, style.emoji]}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
if (customStatus) {
|
||||
wrapperWidth -= 10;
|
||||
}
|
||||
|
||||
let channelIcon;
|
||||
|
|
@ -176,7 +194,7 @@ export default class ChannelTitle extends PureComponent {
|
|||
style={style.container}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={style.wrapper}>
|
||||
<View style={[style.wrapper, {width: `${wrapperWidth}%`}]}>
|
||||
{this.archiveIcon(style)}
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
|
|
@ -188,6 +206,7 @@ export default class ChannelTitle extends PureComponent {
|
|||
</Text>
|
||||
{channelIcon}
|
||||
{icon}
|
||||
{customStatus}
|
||||
{mutedIcon}
|
||||
</View>
|
||||
{hasGuestsText}
|
||||
|
|
@ -213,6 +232,9 @@ const getStyle = makeStyleSheetFromTheme((theme) => {
|
|||
color: theme.sidebarHeaderTextColor,
|
||||
marginHorizontal: 1,
|
||||
},
|
||||
emoji: {
|
||||
marginHorizontal: 5,
|
||||
},
|
||||
text: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 18,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ describe('ChannelTitle', () => {
|
|||
hasGuests: false,
|
||||
canHaveSubtitle: false,
|
||||
isSelfDMChannel: false,
|
||||
customStatusEnabled: true,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
|
@ -83,4 +84,15 @@ describe('ChannelTitle', () => {
|
|||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with custom status emoji', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<ChannelTitle
|
||||
{...baseProps}
|
||||
channelType={General.DM_CHANNEL}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {getCurrentChannel, getMyCurrentChannelMembership, getCurrentChannelStats
|
|||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getUserIdFromChannelName, isChannelMuted} from '@mm-redux/utils/channel_utils';
|
||||
import {isCustomStatusEnabled} from '@selectors/custom_status';
|
||||
|
||||
import {isGuest} from 'app/utils/users';
|
||||
|
||||
|
|
@ -21,8 +22,9 @@ function mapStateToProps(state) {
|
|||
|
||||
let isTeammateGuest = false;
|
||||
let isSelfDMChannel = false;
|
||||
let teammateId;
|
||||
if (currentChannel && currentChannel.type === General.DM_CHANNEL) {
|
||||
const teammateId = getUserIdFromChannelName(currentUserId, currentChannel.name);
|
||||
teammateId = getUserIdFromChannelName(currentUserId, currentChannel.name);
|
||||
const teammate = getUser(state, teammateId);
|
||||
isTeammateGuest = isGuest(teammate);
|
||||
isSelfDMChannel = currentUserId === currentChannel.teammate_id;
|
||||
|
|
@ -39,6 +41,8 @@ function mapStateToProps(state) {
|
|||
isGuest: isTeammateGuest,
|
||||
isSelfDMChannel,
|
||||
theme: getTheme(state),
|
||||
teammateId,
|
||||
customStatusEnabled: isCustomStatusEnabled(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ exports[`channelInfo should match snapshot 1`] = `
|
|||
hasGuests={false}
|
||||
header=""
|
||||
isArchived={false}
|
||||
isCustomStatusEnabled={false}
|
||||
isGroupConstrained={false}
|
||||
isTeammateGuest={false}
|
||||
memberCount={2}
|
||||
|
|
|
|||
|
|
@ -1940,3 +1940,379 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
|||
</Text>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`channel_info_header should match snapshot with custom status enabled 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderBottomColor": undefined,
|
||||
"borderBottomWidth": 1,
|
||||
"marginBottom": 40,
|
||||
"paddingVertical": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"paddingVertical": 10,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 15,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isInfo={true}
|
||||
isUnread={false}
|
||||
membersCount={3}
|
||||
size={24}
|
||||
testID="channel_info.header.channel_icon"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
type="D"
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flex": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"marginLeft": 13,
|
||||
}
|
||||
}
|
||||
testID="channel_info.header.display_name"
|
||||
>
|
||||
Channel name
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"paddingHorizontal": 15,
|
||||
},
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"paddingVertical": 10,
|
||||
"position": "relative",
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="channel_info.header.custom_status"
|
||||
>
|
||||
<Connect(Emoji)
|
||||
emojiName="calendar"
|
||||
size={20}
|
||||
testID="custom_status.emoji.calendar"
|
||||
textStyle={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"marginRight": 8,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<CustomStatusText
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
text="In a meeting"
|
||||
textStyle={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flex": 1,
|
||||
"fontSize": 15,
|
||||
"width": "80%",
|
||||
}
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"paddingHorizontal": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="Purpose"
|
||||
id="channel_info.purpose"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 13,
|
||||
"marginBottom": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 13,
|
||||
"lineHeight": 20,
|
||||
}
|
||||
}
|
||||
testID="channel_info.header.purpose"
|
||||
>
|
||||
Purpose string
|
||||
</Text>
|
||||
</View>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
onLongPress={[Function]}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"paddingHorizontal": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="Header"
|
||||
id="channel_info.header"
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 13,
|
||||
"marginBottom": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Markdown)
|
||||
baseTextStyle={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 13,
|
||||
"lineHeight": 20,
|
||||
}
|
||||
}
|
||||
blockStyles={
|
||||
Object {
|
||||
"adjacentParagraph": Object {
|
||||
"marginTop": 6,
|
||||
},
|
||||
"horizontalRule": Object {
|
||||
"backgroundColor": "#3d3c40",
|
||||
"height": 0.5,
|
||||
"marginVertical": 10,
|
||||
},
|
||||
"quoteBlockIcon": Object {
|
||||
"color": undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
disableAtChannelMentionHighlight={true}
|
||||
disableGallery={true}
|
||||
onChannelLinkPress={[MockFunction]}
|
||||
testID="channel_info.header.header"
|
||||
textStyles={
|
||||
Object {
|
||||
"code": Object {
|
||||
"alignSelf": "center",
|
||||
"backgroundColor": undefined,
|
||||
"fontFamily": "Menlo",
|
||||
},
|
||||
"codeBlock": Object {
|
||||
"fontFamily": "Menlo",
|
||||
},
|
||||
"del": Object {
|
||||
"textDecorationLine": "line-through",
|
||||
},
|
||||
"emph": Object {
|
||||
"fontStyle": "italic",
|
||||
},
|
||||
"error": Object {
|
||||
"color": "#fd5960",
|
||||
},
|
||||
"heading1": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading1Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading2": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading2Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading3": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading3Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading4": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading4Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading5": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading5Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"heading6": Object {
|
||||
"fontSize": 17,
|
||||
"fontWeight": "700",
|
||||
"lineHeight": 25,
|
||||
},
|
||||
"heading6Text": Object {
|
||||
"paddingBottom": 8,
|
||||
},
|
||||
"link": Object {
|
||||
"color": "#2389d7",
|
||||
},
|
||||
"mention": Object {
|
||||
"color": "#2389d7",
|
||||
},
|
||||
"mention_highlight": Object {
|
||||
"backgroundColor": "#ffe577",
|
||||
"color": "#166de0",
|
||||
},
|
||||
"strong": Object {
|
||||
"fontWeight": "bold",
|
||||
},
|
||||
"table_header_row": Object {
|
||||
"fontWeight": "700",
|
||||
},
|
||||
}
|
||||
}
|
||||
value="Header string"
|
||||
/>
|
||||
</View>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
"color": undefined,
|
||||
"flexDirection": "row",
|
||||
"fontSize": 12,
|
||||
"marginTop": 5,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 15,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="Created by {creator} on "
|
||||
id="mobile.routes.channelInfo.createdBy"
|
||||
values={
|
||||
Object {
|
||||
"creator": "Creator",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<FormattedDate
|
||||
format="LL"
|
||||
value={123}
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ export default class ChannelInfo extends PureComponent {
|
|||
isDirectMessage: PropTypes.bool.isRequired,
|
||||
teammateId: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
customStatus: PropTypes.object,
|
||||
isCustomStatusEnabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -168,6 +170,8 @@ export default class ChannelInfo extends PureComponent {
|
|||
teammateId,
|
||||
theme,
|
||||
isTeammateGuest,
|
||||
customStatus,
|
||||
isCustomStatusEnabled,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
@ -201,6 +205,8 @@ export default class ChannelInfo extends PureComponent {
|
|||
hasGuests={currentChannelGuestCount > 0}
|
||||
isGroupConstrained={currentChannel.group_constrained}
|
||||
testID='channel_info.header'
|
||||
customStatus={customStatus}
|
||||
isCustomStatusEnabled={isCustomStatusEnabled}
|
||||
/>
|
||||
}
|
||||
<View style={style.rowsContainer}>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ describe('channelInfo', () => {
|
|||
theme: Preferences.THEMES.default,
|
||||
isTeammateGuest: false,
|
||||
isDirectMessage: false,
|
||||
isLandscape: false,
|
||||
isCustomStatusEnabled: false,
|
||||
actions: {
|
||||
getChannelStats: jest.fn(),
|
||||
getCustomEmojisInText: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import Clipboard from '@react-native-community/clipboard';
|
|||
|
||||
import {popToRoot} from '@actions/navigation';
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import CustomStatusText from '@components/custom_status/custom_status_text';
|
||||
import Emoji from '@components/emoji';
|
||||
import FormattedDate from '@components/formatted_date';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import Markdown from '@components/markdown';
|
||||
|
|
@ -22,6 +24,7 @@ import BottomSheet from '@utils/bottom_sheet';
|
|||
import {t} from '@utils/i18n';
|
||||
import {getMarkdownTextStyles, getMarkdownBlockStyles} from '@utils/markdown';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
|
||||
export default class ChannelInfoHeader extends React.PureComponent {
|
||||
|
|
@ -42,6 +45,8 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
isGroupConstrained: PropTypes.bool,
|
||||
testID: PropTypes.string,
|
||||
timezone: PropTypes.string,
|
||||
customStatus: PropTypes.object,
|
||||
isCustomStatusEnabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -140,6 +145,8 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
isGroupConstrained,
|
||||
testID,
|
||||
timezone,
|
||||
customStatus,
|
||||
isCustomStatusEnabled,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
@ -173,6 +180,26 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
{displayName}
|
||||
</Text>
|
||||
</View>
|
||||
{isCustomStatusEnabled && type === General.DM_CHANNEL && customStatus?.emoji &&
|
||||
<View
|
||||
style={[style.row, style.customStatusContainer]}
|
||||
testID={`${testID}.custom_status`}
|
||||
>
|
||||
<Emoji
|
||||
emojiName={customStatus.emoji}
|
||||
size={20}
|
||||
textStyle={style.iconContainer}
|
||||
testID={`custom_status.emoji.${customStatus.emoji}`}
|
||||
/>
|
||||
<CustomStatusText
|
||||
text={customStatus.text}
|
||||
theme={theme}
|
||||
textStyle={style.customStatusText}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
{this.renderHasGuestText(style)}
|
||||
{purpose.length > 0 &&
|
||||
<View style={style.section}>
|
||||
|
|
@ -267,6 +294,22 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
color: theme.centerChannelColor,
|
||||
marginLeft: 13,
|
||||
},
|
||||
iconContainer: {
|
||||
marginRight: 8,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
customStatusContainer: {
|
||||
position: 'relative',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 10,
|
||||
},
|
||||
customStatusText: {
|
||||
flex: 1,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
width: '80%',
|
||||
},
|
||||
channelNameContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ describe('channel_info_header', () => {
|
|||
hasGuests: false,
|
||||
isGroupConstrained: false,
|
||||
testID: 'channel_info.header',
|
||||
isCustomStatusEnabled: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', async () => {
|
||||
|
|
@ -114,4 +115,22 @@ describe('channel_info_header', () => {
|
|||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with custom status enabled', async () => {
|
||||
const customStatus = {
|
||||
emoji: 'calendar',
|
||||
text: 'In a meeting',
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<ChannelInfoHeader
|
||||
{...baseProps}
|
||||
isCustomStatusEnabled={true}
|
||||
type={General.DM_CHANNEL}
|
||||
customStatus={customStatus}
|
||||
/>,
|
||||
{context: {intl: intlMock}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,47 +13,57 @@ import {getCurrentChannel, getCurrentChannelStats} from '@mm-redux/selectors/ent
|
|||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getUserIdFromChannelName} from '@mm-redux/utils/channel_utils';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import {makeGetCustomStatus, isCustomStatusEnabled} from '@selectors/custom_status';
|
||||
import {isGuest} from '@utils/users';
|
||||
|
||||
import ChannelInfo from './channel_info';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentChannel = getCurrentChannel(state) || {};
|
||||
const currentChannelCreator = getUser(state, currentChannel.creator_id);
|
||||
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
|
||||
const currentChannelCreatorName = displayUsername(currentChannelCreator, teammateNameDisplay);
|
||||
const currentChannelStats = getCurrentChannelStats(state);
|
||||
let currentChannelMemberCount = currentChannelStats && currentChannelStats.member_count;
|
||||
let currentChannelGuestCount = (currentChannelStats && currentChannelStats.guest_count) || 0;
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
function makeMapStateToProps() {
|
||||
const getCustomStatus = makeGetCustomStatus();
|
||||
return (state) => {
|
||||
const currentChannel = getCurrentChannel(state) || {};
|
||||
const currentChannelCreator = getUser(state, currentChannel.creator_id);
|
||||
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
|
||||
const currentChannelCreatorName = displayUsername(currentChannelCreator, teammateNameDisplay);
|
||||
const currentChannelStats = getCurrentChannelStats(state);
|
||||
let currentChannelMemberCount = currentChannelStats && currentChannelStats.member_count;
|
||||
let currentChannelGuestCount = (currentChannelStats && currentChannelStats.guest_count) || 0;
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
|
||||
let teammateId;
|
||||
let isTeammateGuest = false;
|
||||
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
|
||||
let teammateId;
|
||||
let isTeammateGuest = false;
|
||||
let customStatusEnabled;
|
||||
let customStatus;
|
||||
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
|
||||
|
||||
if (isDirectMessage) {
|
||||
teammateId = getUserIdFromChannelName(currentUserId, currentChannel.name);
|
||||
const teammate = getUser(state, teammateId);
|
||||
if (isGuest(teammate)) {
|
||||
isTeammateGuest = true;
|
||||
currentChannelGuestCount = 1;
|
||||
if (isDirectMessage) {
|
||||
teammateId = getUserIdFromChannelName(currentUserId, currentChannel.name);
|
||||
const teammate = getUser(state, teammateId);
|
||||
if (isGuest(teammate)) {
|
||||
isTeammateGuest = true;
|
||||
currentChannelGuestCount = 1;
|
||||
}
|
||||
customStatusEnabled = isCustomStatusEnabled(state);
|
||||
customStatus = customStatusEnabled ? getCustomStatus(state, teammateId) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentChannel.type === General.GM_CHANNEL) {
|
||||
currentChannelMemberCount = currentChannel.display_name.split(',').length;
|
||||
}
|
||||
if (currentChannel.type === General.GM_CHANNEL) {
|
||||
currentChannelMemberCount = currentChannel.display_name.split(',').length;
|
||||
}
|
||||
|
||||
return {
|
||||
currentChannel,
|
||||
currentChannelCreatorName,
|
||||
currentChannelGuestCount,
|
||||
currentChannelMemberCount,
|
||||
currentUserId,
|
||||
isTeammateGuest,
|
||||
isDirectMessage,
|
||||
teammateId,
|
||||
theme: getTheme(state),
|
||||
return {
|
||||
currentChannel,
|
||||
currentChannelCreatorName,
|
||||
currentChannelGuestCount,
|
||||
currentChannelMemberCount,
|
||||
currentUserId,
|
||||
isTeammateGuest,
|
||||
isDirectMessage,
|
||||
teammateId,
|
||||
theme: getTheme(state),
|
||||
customStatus,
|
||||
isCustomStatusEnabled: customStatusEnabled,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -67,4 +77,4 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelInfo);
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(ChannelInfo);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,235 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`screens/custom_status_modal should match snapshot 1`] = `
|
||||
<CustomStatusModal
|
||||
actions={
|
||||
Object {
|
||||
"removeRecentCustomStatus": [MockFunction],
|
||||
"setCustomStatus": [MockFunction],
|
||||
"unsetCustomStatus": [MockFunction],
|
||||
}
|
||||
}
|
||||
customStatus={
|
||||
Object {
|
||||
"emoji": "calendar",
|
||||
"text": "In a meeting",
|
||||
}
|
||||
}
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
recentCustomStatuses={
|
||||
Array [
|
||||
Object {
|
||||
"emoji": "calendar",
|
||||
"text": "In a meeting",
|
||||
},
|
||||
]
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`screens/custom_status_modal should match snapshot when user has no custom status set 1`] = `
|
||||
<CustomStatusModal
|
||||
actions={
|
||||
Object {
|
||||
"removeRecentCustomStatus": [MockFunction],
|
||||
"setCustomStatus": [MockFunction],
|
||||
"unsetCustomStatus": [MockFunction],
|
||||
}
|
||||
}
|
||||
customStatus={null}
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
recentCustomStatuses={
|
||||
Array [
|
||||
Object {
|
||||
"emoji": "calendar",
|
||||
"text": "In a meeting",
|
||||
},
|
||||
]
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`screens/custom_status_modal should match snapshot when user has no recent custom status 1`] = `
|
||||
<CustomStatusModal
|
||||
actions={
|
||||
Object {
|
||||
"removeRecentCustomStatus": [MockFunction],
|
||||
"setCustomStatus": [MockFunction],
|
||||
"unsetCustomStatus": [MockFunction],
|
||||
}
|
||||
}
|
||||
customStatus={
|
||||
Object {
|
||||
"emoji": "calendar",
|
||||
"text": "In a meeting",
|
||||
}
|
||||
}
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
recentCustomStatuses={Array []}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`screens/custom_status_suggestion should match snapshot 1`] = `
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
testID="custom_status_suggestion.In a meeting"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 50,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"height": 46,
|
||||
"left": 14,
|
||||
"marginRight": 6,
|
||||
"top": 12,
|
||||
"width": 45,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(Emoji)
|
||||
emojiName="calendar"
|
||||
size={20}
|
||||
/>
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
"paddingBottom": 14,
|
||||
"paddingTop": 14,
|
||||
"width": "70%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomStatusText
|
||||
text="In a meeting"
|
||||
textStyle={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
}
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</ForwardRef>
|
||||
`;
|
||||
|
||||
exports[`screens/custom_status_suggestion should match snapshot with separator and clear button 1`] = `
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
testID="custom_status_suggestion.In a meeting"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flexDirection": "row",
|
||||
"minHeight": 50,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"height": 46,
|
||||
"left": 14,
|
||||
"marginRight": 6,
|
||||
"top": 12,
|
||||
"width": 45,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(Emoji)
|
||||
emojiName="calendar"
|
||||
size={20}
|
||||
/>
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
"paddingBottom": 14,
|
||||
"paddingTop": 14,
|
||||
"width": "70%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomStatusText
|
||||
text="In a meeting"
|
||||
textStyle={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
}
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"position": "absolute",
|
||||
"right": 13,
|
||||
"top": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ClearButton
|
||||
containerSize={40}
|
||||
handlePress={[Function]}
|
||||
iconName="close"
|
||||
size={18}
|
||||
testID="custom_status_suggestion.clear.button"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</ForwardRef>
|
||||
`;
|
||||
57
app/screens/custom_status/custom_status_modal.test.tsx
Normal file
57
app/screens/custom_status/custom_status_modal.test.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import CustomStatusModal from '@screens/custom_status/custom_status_modal';
|
||||
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
describe('screens/custom_status_modal', () => {
|
||||
const customStatus = {
|
||||
emoji: 'calendar',
|
||||
text: 'In a meeting',
|
||||
};
|
||||
|
||||
const baseProps = {
|
||||
actions: {
|
||||
setCustomStatus: jest.fn(),
|
||||
unsetCustomStatus: jest.fn(),
|
||||
removeRecentCustomStatus: jest.fn(),
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
customStatus,
|
||||
recentCustomStatuses: [customStatus],
|
||||
};
|
||||
|
||||
it('should match snapshot', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<CustomStatusModal {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot when user has no custom status set', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<CustomStatusModal
|
||||
{...baseProps}
|
||||
customStatus={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot when user has no recent custom status', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<CustomStatusModal
|
||||
{...baseProps}
|
||||
recentCustomStatuses={[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
398
app/screens/custom_status/custom_status_modal.tsx
Normal file
398
app/screens/custom_status/custom_status_modal.tsx
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {intlShape, injectIntl} from 'react-intl';
|
||||
import {View, Text, TouchableOpacity, TextInput, Keyboard, KeyboardAvoidingView, Platform, ScrollView, StyleProp, ViewStyle} from 'react-native';
|
||||
import {Navigation, NavigationComponent, NavigationComponentProps, OptionsTopBarButton, Options, NavigationButtonPressedEvent} from 'react-native-navigation';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {dismissModal, showModal, mergeNavigationOptions} from '@actions/navigation';
|
||||
import Emoji from '@components/emoji';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import ClearButton from '@components/custom_status/clear_button';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import {CustomStatus, DeviceTypes} from '@constants';
|
||||
import {ActionFunc, ActionResult} from '@mm-redux/types/actions';
|
||||
import {Theme} from '@mm-redux/types/preferences';
|
||||
import {UserCustomStatus} from '@mm-redux/types/users';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import CustomStatusSuggestion from '@screens/custom_status/custom_status_suggestion';
|
||||
import {t} from '@utils/i18n';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type DefaultUserCustomStatus = {
|
||||
emoji: string;
|
||||
message: string;
|
||||
messageDefault: string;
|
||||
};
|
||||
|
||||
const defaultCustomStatusSuggestions: DefaultUserCustomStatus[] = [
|
||||
{emoji: 'calendar', message: t('custom_status.suggestions.in_a_meeting'), messageDefault: 'In a meeting'},
|
||||
{emoji: 'hamburger', message: t('custom_status.suggestions.out_for_lunch'), messageDefault: 'Out for lunch'},
|
||||
{emoji: 'sneezing_face', message: t('custom_status.suggestions.out_sick'), messageDefault: 'Out sick'},
|
||||
{emoji: 'house', message: t('custom_status.suggestions.working_from_home'), messageDefault: 'Working from home'},
|
||||
{emoji: 'palm_tree', message: t('custom_status.suggestions.on_a_vacation'), messageDefault: 'On a vacation'},
|
||||
];
|
||||
|
||||
interface Props extends NavigationComponentProps {
|
||||
intl: typeof intlShape;
|
||||
theme: Theme;
|
||||
customStatus: UserCustomStatus;
|
||||
recentCustomStatuses: UserCustomStatus[];
|
||||
isLandscape: boolean;
|
||||
actions: {
|
||||
setCustomStatus: (customStatus: UserCustomStatus) => Promise<ActionResult>;
|
||||
unsetCustomStatus: () => ActionFunc;
|
||||
removeRecentCustomStatus: (customStatus: UserCustomStatus) => ActionFunc;
|
||||
};
|
||||
}
|
||||
|
||||
type State = {
|
||||
emoji: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
class CustomStatusModal extends NavigationComponent<Props, State> {
|
||||
rightButton: OptionsTopBarButton = {
|
||||
id: 'update-custom-status',
|
||||
testID: 'custom_status.done.button',
|
||||
enabled: true,
|
||||
showAsAction: 'always',
|
||||
};
|
||||
|
||||
static options(): Options {
|
||||
return {
|
||||
topBar: {
|
||||
title: {
|
||||
alignment: 'center',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.rightButton.text = props.intl.formatMessage({id: 'mobile.custom_status.modal_confirm', defaultMessage: 'Done'});
|
||||
this.rightButton.color = props.theme.sidebarHeaderTextColor;
|
||||
|
||||
const options: Options = {
|
||||
topBar: {
|
||||
rightButtons: [this.rightButton],
|
||||
},
|
||||
};
|
||||
|
||||
mergeNavigationOptions(props.componentId, options);
|
||||
|
||||
this.state = {
|
||||
emoji: props.customStatus?.emoji,
|
||||
text: props.customStatus?.text || '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Navigation.events().bindComponent(this);
|
||||
}
|
||||
|
||||
navigationButtonPressed({buttonId}: NavigationButtonPressedEvent) {
|
||||
switch (buttonId) {
|
||||
case 'update-custom-status':
|
||||
this.handleSetStatus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleSetStatus = async () => {
|
||||
const {emoji, text} = this.state;
|
||||
const isStatusSet = emoji || text;
|
||||
const {customStatus} = this.props;
|
||||
if (isStatusSet) {
|
||||
const isStatusSame = customStatus?.emoji === emoji && customStatus?.text === text;
|
||||
if (!isStatusSame) {
|
||||
const status = {
|
||||
emoji: emoji || 'speech_balloon',
|
||||
text: text.trim(),
|
||||
};
|
||||
const {error} = await this.props.actions.setCustomStatus(status);
|
||||
if (error) {
|
||||
EventEmitter.emit(CustomStatus.SET_CUSTOM_STATUS_FAILURE);
|
||||
}
|
||||
}
|
||||
} else if (customStatus?.emoji) {
|
||||
this.props.actions.unsetCustomStatus();
|
||||
}
|
||||
Keyboard.dismiss();
|
||||
dismissModal();
|
||||
};
|
||||
|
||||
handleTextChange = (value: string) => this.setState({text: value});
|
||||
|
||||
handleRecentCustomStatusClear = (status: UserCustomStatus) => this.props.actions.removeRecentCustomStatus(status);
|
||||
|
||||
clearHandle = () => {
|
||||
this.setState({emoji: '', text: ''});
|
||||
};
|
||||
|
||||
handleSuggestionClick = (status: UserCustomStatus) => {
|
||||
const {emoji, text} = status;
|
||||
this.setState({emoji, text});
|
||||
};
|
||||
|
||||
renderRecentCustomStatuses = (style: Record<string, StyleProp<ViewStyle>>) => {
|
||||
const {recentCustomStatuses, theme} = this.props;
|
||||
const recentStatuses = recentCustomStatuses.map((status: UserCustomStatus, index: number) => (
|
||||
<CustomStatusSuggestion
|
||||
key={status.text}
|
||||
handleSuggestionClick={this.handleSuggestionClick}
|
||||
handleClear={this.handleRecentCustomStatusClear}
|
||||
emoji={status.emoji}
|
||||
text={status.text}
|
||||
theme={theme}
|
||||
separator={index !== recentCustomStatuses.length - 1}
|
||||
/>
|
||||
));
|
||||
|
||||
if (recentStatuses.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={style.separator}/>
|
||||
<View testID='custom_status.recents'>
|
||||
<FormattedText
|
||||
id='custom_status.suggestions.recent_title'
|
||||
defaultMessage='RECENT'
|
||||
style={style.title}
|
||||
/>
|
||||
<View style={style.block}>
|
||||
{recentStatuses}
|
||||
</View>
|
||||
</View >
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderCustomStatusSuggestions = (style: Record<string, StyleProp<ViewStyle>>) => {
|
||||
const {recentCustomStatuses, theme, intl} = this.props;
|
||||
const recentCustomStatusTexts = recentCustomStatuses.map((status: UserCustomStatus) => status.text);
|
||||
const customStatusSuggestions = defaultCustomStatusSuggestions.
|
||||
map((status) => ({
|
||||
emoji: status.emoji,
|
||||
text: intl.formatMessage({id: status.message, defaultMessage: status.messageDefault}),
|
||||
})).
|
||||
filter((status: UserCustomStatus) => !recentCustomStatusTexts.includes(status.text)).
|
||||
map((status: UserCustomStatus, index: number, arr: UserCustomStatus[]) => (
|
||||
<CustomStatusSuggestion
|
||||
key={status.text}
|
||||
handleSuggestionClick={this.handleSuggestionClick}
|
||||
emoji={status.emoji}
|
||||
text={status.text}
|
||||
theme={theme}
|
||||
separator={index !== arr.length - 1}
|
||||
/>
|
||||
));
|
||||
|
||||
if (customStatusSuggestions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={style.separator}/>
|
||||
<View testID='custom_status.suggestions'>
|
||||
<FormattedText
|
||||
id='custom_status.suggestions.title'
|
||||
defaultMessage='SUGGESTIONS'
|
||||
style={style.title}
|
||||
/>
|
||||
<View style={style.block}>
|
||||
{customStatusSuggestions}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
openEmojiPicker = () => {
|
||||
const {theme, intl} = this.props;
|
||||
CompassIcon.getImageSource('close', 24, theme.sidebarHeaderTextColor).then((source) => {
|
||||
const screen = 'AddReaction';
|
||||
const title = intl.formatMessage({id: 'mobile.custom_status.choose_emoji', defaultMessage: 'Choose an emoji'});
|
||||
const passProps = {
|
||||
closeButton: source,
|
||||
onEmojiPress: this.handleEmojiClick,
|
||||
};
|
||||
|
||||
showModal(screen, title, passProps);
|
||||
});
|
||||
};
|
||||
|
||||
handleEmojiClick = (emoji: string) => {
|
||||
dismissModal();
|
||||
this.setState({emoji});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {emoji, text} = this.state;
|
||||
const {theme, isLandscape} = this.props;
|
||||
|
||||
const isStatusSet = emoji || text;
|
||||
const style = getStyleSheet(theme);
|
||||
const customStatusEmoji = (
|
||||
<TouchableOpacity
|
||||
testID={`custom_status.emoji.${isStatusSet ? (emoji || 'speech_balloon') : 'default'}`}
|
||||
onPress={preventDoubleTap(this.openEmojiPicker)}
|
||||
style={style.iconContainer}
|
||||
>
|
||||
{isStatusSet ? (
|
||||
<Text style={style.emoji}>
|
||||
<Emoji
|
||||
emojiName={emoji || 'speech_balloon'}
|
||||
size={20}
|
||||
/>
|
||||
</Text>
|
||||
) : (
|
||||
<CompassIcon
|
||||
name='emoticon-happy-outline'
|
||||
size={24}
|
||||
style={style.icon}
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
const customStatusInput = (
|
||||
<View style={style.inputContainer}>
|
||||
<TextInput
|
||||
testID='custom_status.input'
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
blurOnSubmit={false}
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
keyboardType='default'
|
||||
maxLength={CustomStatus.CUSTOM_STATUS_TEXT_CHARACTER_LIMIT}
|
||||
onChangeText={this.handleTextChange}
|
||||
placeholder={this.props.intl.formatMessage({id: 'custom_status.set_status', defaultMessage: 'Set a Status'})}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
returnKeyType='go'
|
||||
style={style.input}
|
||||
secureTextEntry={false}
|
||||
underlineColorAndroid='transparent'
|
||||
value={text}
|
||||
/>
|
||||
{customStatusEmoji}
|
||||
{isStatusSet ? (
|
||||
<View
|
||||
style={style.clearButton}
|
||||
testID='custom_status.input.clear.button'
|
||||
>
|
||||
<ClearButton
|
||||
handlePress={this.clearHandle}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
|
||||
let keyboardOffset = DeviceTypes.IS_IPHONE_WITH_INSETS ? 110 : 60;
|
||||
if (isLandscape) {
|
||||
keyboardOffset = DeviceTypes.IS_IPHONE_WITH_INSETS ? 0 : 10;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
testID='custom_status.screen'
|
||||
style={style.container}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
style={style.container}
|
||||
keyboardVerticalOffset={keyboardOffset}
|
||||
enabled={Platform.OS === 'ios'}
|
||||
>
|
||||
<ScrollView
|
||||
bounces={false}
|
||||
>
|
||||
<StatusBar/>
|
||||
<View style={style.scrollView}>
|
||||
{customStatusInput}
|
||||
{this.renderRecentCustomStatuses(style)}
|
||||
{this.renderCustomStatusSuggestions(style)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(CustomStatusModal);
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
|
||||
paddingTop: 32,
|
||||
},
|
||||
inputContainer: {
|
||||
position: 'relative',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
input: {
|
||||
alignSelf: 'stretch',
|
||||
color: theme.centerChannelColor,
|
||||
width: '100%',
|
||||
fontSize: 17,
|
||||
paddingHorizontal: 52,
|
||||
textAlignVertical: 'center',
|
||||
height: 48,
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
},
|
||||
emoji: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
iconContainer: {
|
||||
position: 'absolute',
|
||||
left: 14,
|
||||
top: 12,
|
||||
},
|
||||
separator: {
|
||||
marginTop: 32,
|
||||
},
|
||||
block: {
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderBottomWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderTopWidth: 1,
|
||||
},
|
||||
title: {
|
||||
fontSize: 17,
|
||||
marginBottom: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
marginLeft: 16,
|
||||
},
|
||||
clearButton: {
|
||||
position: 'absolute',
|
||||
top: 3,
|
||||
right: 14,
|
||||
},
|
||||
};
|
||||
});
|
||||
52
app/screens/custom_status/custom_status_suggestion.test.tsx
Normal file
52
app/screens/custom_status/custom_status_suggestion.test.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {TouchableOpacity} from 'react-native';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import CustomStatusSuggestion from '@screens/custom_status/custom_status_suggestion';
|
||||
|
||||
describe('screens/custom_status_suggestion', () => {
|
||||
const baseProps = {
|
||||
handleSuggestionClick: jest.fn(),
|
||||
emoji: 'calendar',
|
||||
text: 'In a meeting',
|
||||
theme: Preferences.THEMES.default,
|
||||
separator: false,
|
||||
};
|
||||
|
||||
it('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<CustomStatusSuggestion
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot with separator and clear button', () => {
|
||||
const wrapper = shallow(
|
||||
<CustomStatusSuggestion
|
||||
{...baseProps}
|
||||
separator={true}
|
||||
handleClear={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should call handleSuggestionClick on clicking the suggestion', () => {
|
||||
const wrapper = shallow(
|
||||
<CustomStatusSuggestion
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
|
||||
wrapper.find(TouchableOpacity).simulate('press');
|
||||
expect(baseProps.handleSuggestionClick).toBeCalled();
|
||||
});
|
||||
});
|
||||
116
app/screens/custom_status/custom_status_suggestion.tsx
Normal file
116
app/screens/custom_status/custom_status_suggestion.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {View, TouchableOpacity, Text} from 'react-native';
|
||||
|
||||
import Emoji from '@components/emoji';
|
||||
import ClearButton from '@components/custom_status/clear_button';
|
||||
import CustomStatusText from '@components/custom_status/custom_status_text';
|
||||
import {Theme} from '@mm-redux/types/preferences';
|
||||
import {UserCustomStatus} from '@mm-redux/types/users';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
handleSuggestionClick: (status: UserCustomStatus) => void;
|
||||
emoji: string;
|
||||
text: string;
|
||||
handleClear?: (status: UserCustomStatus) => void;
|
||||
theme: Theme;
|
||||
separator: boolean;
|
||||
};
|
||||
|
||||
const CustomStatusSuggestion = (props: Props) => {
|
||||
const {handleSuggestionClick, emoji, text, theme, separator, handleClear} = props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const handleClick = useCallback(preventDoubleTap(() => {
|
||||
handleSuggestionClick({emoji, text});
|
||||
}), []);
|
||||
|
||||
const handleSuggestionClear = useCallback(() => {
|
||||
if (handleClear) {
|
||||
handleClear({emoji, text});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const clearButton = handleClear ?
|
||||
(
|
||||
<View style={style.clearButtonContainer}>
|
||||
<ClearButton
|
||||
handlePress={handleSuggestionClear}
|
||||
theme={theme}
|
||||
iconName='close'
|
||||
size={18}
|
||||
testID='custom_status_suggestion.clear.button'
|
||||
/>
|
||||
</View>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
testID={`custom_status_suggestion.${text}`}
|
||||
onPress={handleClick}
|
||||
>
|
||||
<View style={style.container}>
|
||||
<Text style={style.iconContainer}>
|
||||
<Emoji
|
||||
emojiName={emoji}
|
||||
size={20}
|
||||
/>
|
||||
</Text>
|
||||
<View style={style.wrapper}>
|
||||
<View style={style.textContainer}>
|
||||
<CustomStatusText
|
||||
text={text}
|
||||
theme={theme}
|
||||
textStyle={{color: theme.centerChannelColor}}
|
||||
/>
|
||||
</View>
|
||||
{clearButton}
|
||||
{separator && <View style={style.divider}/>}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomStatusSuggestion;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flexDirection: 'row',
|
||||
minHeight: 50,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 45,
|
||||
height: 46,
|
||||
left: 14,
|
||||
top: 12,
|
||||
marginRight: 6,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
},
|
||||
textContainer: {
|
||||
paddingTop: 14,
|
||||
paddingBottom: 14,
|
||||
justifyContent: 'center',
|
||||
width: '70%',
|
||||
flex: 1,
|
||||
},
|
||||
clearButtonContainer: {
|
||||
position: 'absolute',
|
||||
top: 4,
|
||||
right: 13,
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
41
app/screens/custom_status/index.ts
Normal file
41
app/screens/custom_status/index.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators, Dispatch} from 'redux';
|
||||
|
||||
import {setCustomStatus, unsetCustomStatus, removeRecentCustomStatus} from '@actions/views/custom_status';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {GenericAction} from '@mm-redux/types/actions';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import CustomStatusModal from '@screens/custom_status/custom_status_modal';
|
||||
import {getRecentCustomStatuses, makeGetCustomStatus} from '@selectors/custom_status';
|
||||
import {isLandscape} from '@selectors/device';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const getCustomStatus = makeGetCustomStatus();
|
||||
return (state: GlobalState) => {
|
||||
const customStatus = getCustomStatus(state);
|
||||
const recentCustomStatuses = getRecentCustomStatuses(state);
|
||||
const theme = getTheme(state);
|
||||
|
||||
return {
|
||||
customStatus,
|
||||
recentCustomStatuses,
|
||||
theme,
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
setCustomStatus,
|
||||
unsetCustomStatus,
|
||||
removeRecentCustomStatus,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(CustomStatusModal);
|
||||
|
|
@ -70,6 +70,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
|||
case 'CreateChannel':
|
||||
screen = require('@screens/create_channel').default;
|
||||
break;
|
||||
case 'CustomStatus':
|
||||
screen = require('@screens/custom_status').default;
|
||||
break;
|
||||
case 'DisplaySettings':
|
||||
screen = require('@screens/settings/display_settings').default;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -389,3 +389,480 @@ exports[`user_profile should match snapshot when user is from remote 1`] = `
|
|||
</ScrollView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
||||
exports[`user_profile should match snapshot with custom status 1`] = `
|
||||
<RNCSafeAreaView
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="user_profile.screen"
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<ScrollView
|
||||
contentContainerStyle={
|
||||
Object {
|
||||
"paddingBottom": 48,
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="user_profile.scroll_view"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"padding": 25,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
iconSize={104}
|
||||
size={153}
|
||||
statusBorderWidth={6}
|
||||
statusSize={36}
|
||||
testID="user_profile.profile_picture"
|
||||
userId="4hzdnk6mg7gepe7yze6m3domnc"
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
"marginTop": 15,
|
||||
}
|
||||
}
|
||||
testID="user_profile.username"
|
||||
>
|
||||
@fred
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#EBEBEC",
|
||||
"height": 1,
|
||||
"marginLeft": 16,
|
||||
"marginRight": 22,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": 25,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
testID="user_profile.custom_status"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"fontSize": 13,
|
||||
"fontWeight": "600",
|
||||
"marginBottom": 10,
|
||||
"marginTop": 25,
|
||||
"textTransform": "uppercase",
|
||||
}
|
||||
}
|
||||
>
|
||||
Status
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
testID="custom_status.emoji.calendar"
|
||||
>
|
||||
<Connect(Emoji)
|
||||
emojiName="calendar"
|
||||
size={20}
|
||||
/>
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"justifyContent": "center",
|
||||
"width": "80%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
In a meeting
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
testID="user_profile.display_block"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"fontSize": 13,
|
||||
"fontWeight": "600",
|
||||
"marginBottom": 10,
|
||||
"marginTop": 25,
|
||||
"textTransform": "uppercase",
|
||||
}
|
||||
}
|
||||
testID="user_profile.display_block.nickname.label"
|
||||
>
|
||||
Nickname
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
testID="user_profile.display_block.nickname.value"
|
||||
>
|
||||
nick
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#EBEBEC",
|
||||
"height": 1,
|
||||
"marginLeft": 16,
|
||||
"marginRight": 22,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<userProfileRow
|
||||
action={[Function]}
|
||||
defaultMessage="Send Message"
|
||||
icon="send"
|
||||
iconColor="rgba(0, 0, 0, 0.7)"
|
||||
iconSize={24}
|
||||
testID="user_profile.send_message.action"
|
||||
textColor="#000"
|
||||
textId="mobile.routes.user_profile.send_message"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</ScrollView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
||||
exports[`user_profile should match snapshot with custom status and isMyUser true 1`] = `
|
||||
<RNCSafeAreaView
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="user_profile.screen"
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<ScrollView
|
||||
contentContainerStyle={
|
||||
Object {
|
||||
"paddingBottom": 48,
|
||||
}
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="user_profile.scroll_view"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"padding": 25,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
iconSize={104}
|
||||
size={153}
|
||||
statusBorderWidth={6}
|
||||
statusSize={36}
|
||||
testID="user_profile.profile_picture"
|
||||
userId="4hzdnk6mg7gepe7yze6m3domnc"
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
"marginTop": 15,
|
||||
}
|
||||
}
|
||||
testID="user_profile.username"
|
||||
>
|
||||
@fred
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#EBEBEC",
|
||||
"height": 1,
|
||||
"marginLeft": 16,
|
||||
"marginRight": 22,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": 25,
|
||||
"marginHorizontal": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
testID="user_profile.custom_status"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"fontSize": 13,
|
||||
"fontWeight": "600",
|
||||
"marginBottom": 10,
|
||||
"marginTop": 25,
|
||||
"textTransform": "uppercase",
|
||||
}
|
||||
}
|
||||
>
|
||||
Status
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
testID="custom_status.emoji.calendar"
|
||||
>
|
||||
<Connect(Emoji)
|
||||
emojiName="calendar"
|
||||
size={20}
|
||||
/>
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"justifyContent": "center",
|
||||
"width": "80%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
In a meeting
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": -8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ClearButton
|
||||
containerSize={40}
|
||||
handlePress={[MockFunction]}
|
||||
iconName="close-circle"
|
||||
size={20}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
testID="user_profile.display_block"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": undefined,
|
||||
"fontSize": 13,
|
||||
"fontWeight": "600",
|
||||
"marginBottom": 10,
|
||||
"marginTop": 25,
|
||||
"textTransform": "uppercase",
|
||||
}
|
||||
}
|
||||
testID="user_profile.display_block.nickname.label"
|
||||
>
|
||||
Nickname
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 15,
|
||||
}
|
||||
}
|
||||
testID="user_profile.display_block.nickname.value"
|
||||
>
|
||||
nick
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#EBEBEC",
|
||||
"height": 1,
|
||||
"marginLeft": 16,
|
||||
"marginRight": 22,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<userProfileRow
|
||||
action={[Function]}
|
||||
defaultMessage="Send Message"
|
||||
icon="send"
|
||||
iconColor="rgba(0, 0, 0, 0.7)"
|
||||
iconSize={24}
|
||||
testID="user_profile.send_message.action"
|
||||
textColor="#000"
|
||||
textId="mobile.routes.user_profile.send_message"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</ScrollView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {bindActionCreators} from 'redux';
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {setChannelDisplayName} from '@actions/views/channel';
|
||||
import {unsetCustomStatus} from '@actions/views/custom_status';
|
||||
import {makeDirectChannel} from '@actions/views/more_dms';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getTeammateNameDisplaySetting, getTheme, getBool} from '@mm-redux/selectors/entities/preferences';
|
||||
|
|
@ -14,28 +15,34 @@ import {loadBot} from '@mm-redux/actions/bots';
|
|||
import {getRemoteClusterInfo} from '@mm-redux/actions/remote_cluster';
|
||||
import {getBotAccounts} from '@mm-redux/selectors/entities/bots';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {makeGetCustomStatus, isCustomStatusEnabled} from '@selectors/custom_status';
|
||||
|
||||
import UserProfile from './user_profile';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const config = getConfig(state);
|
||||
const {createChannel: createChannelRequest} = state.requests.channels;
|
||||
const isMilitaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time');
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
const user = state.entities.users.profiles[ownProps.userId];
|
||||
function makeMapStateToProps() {
|
||||
const getCustomStatus = makeGetCustomStatus();
|
||||
return (state, ownProps) => {
|
||||
const config = getConfig(state);
|
||||
const {createChannel: createChannelRequest} = state.requests.channels;
|
||||
const isMilitaryTime = getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time');
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
const user = state.entities.users.profiles[ownProps.userId];
|
||||
|
||||
return {
|
||||
config,
|
||||
createChannelRequest,
|
||||
currentDisplayName: state.views.channel.displayName,
|
||||
user,
|
||||
bot: getBotAccounts(state)[ownProps.userId],
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
enableTimezone,
|
||||
isMilitaryTime,
|
||||
theme: getTheme(state),
|
||||
isMyUser: getCurrentUserId(state) === ownProps.userId,
|
||||
remoteClusterInfo: state.entities.remoteCluster.info[user?.remote_id],
|
||||
const customStatus = isCustomStatusEnabled(state) ? getCustomStatus(state, user?.id) : undefined;
|
||||
return {
|
||||
config,
|
||||
createChannelRequest,
|
||||
currentDisplayName: state.views.channel.displayName,
|
||||
user,
|
||||
bot: getBotAccounts(state)[ownProps.userId],
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
enableTimezone,
|
||||
isMilitaryTime,
|
||||
theme: getTheme(state),
|
||||
isMyUser: getCurrentUserId(state) === ownProps.userId,
|
||||
remoteClusterInfo: state.entities.remoteCluster.info[user?.remote_id],
|
||||
customStatus,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -46,8 +53,9 @@ function mapDispatchToProps(dispatch) {
|
|||
setChannelDisplayName,
|
||||
loadBot,
|
||||
getRemoteClusterInfo,
|
||||
unsetCustomStatus,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(UserProfile);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import {
|
|||
dismissAllModalsAndPopToRoot,
|
||||
} from '@actions/navigation';
|
||||
import Config from '@assets/config';
|
||||
import Emoji from '@components/emoji';
|
||||
import ClearButton from '@components/custom_status/clear_button';
|
||||
import ChannelIcon from '@components/channel_icon';
|
||||
import FormattedTime from '@components/formatted_time';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
|
|
@ -44,6 +46,7 @@ export default class UserProfile extends PureComponent {
|
|||
setChannelDisplayName: PropTypes.func.isRequired,
|
||||
loadBot: PropTypes.func.isRequired,
|
||||
getRemoteClusterInfo: PropTypes.func.isRequired,
|
||||
unsetCustomStatus: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
componentId: PropTypes.string,
|
||||
config: PropTypes.object.isRequired,
|
||||
|
|
@ -56,6 +59,7 @@ export default class UserProfile extends PureComponent {
|
|||
enableTimezone: PropTypes.bool.isRequired,
|
||||
isMyUser: PropTypes.bool.isRequired,
|
||||
remoteClusterInfo: PropTypes.object,
|
||||
customStatus: PropTypes.object,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -226,6 +230,50 @@ export default class UserProfile extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
buildCustomStatusBlock = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {customStatus, theme, isMyUser} = this.props;
|
||||
const style = createStyleSheet(theme);
|
||||
const isStatusSet = customStatus?.emoji;
|
||||
|
||||
if (!isStatusSet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const label = formatMessage({id: 'user.settings.general.status', defaultMessage: 'Status'});
|
||||
return (
|
||||
<View
|
||||
testID='user_profile.custom_status'
|
||||
>
|
||||
<Text style={style.header}>{label}</Text>
|
||||
<View style={style.customStatus}>
|
||||
<Text
|
||||
style={style.iconContainer}
|
||||
testID={`custom_status.emoji.${customStatus.emoji}`}
|
||||
>
|
||||
<Emoji
|
||||
emojiName={customStatus.emoji}
|
||||
size={20}
|
||||
/>
|
||||
</Text>
|
||||
<View style={style.customStatusTextContainer}>
|
||||
<Text style={style.text}>
|
||||
{customStatus.text}
|
||||
</Text>
|
||||
</View>
|
||||
{isMyUser && (
|
||||
<View style={style.clearButton}>
|
||||
<ClearButton
|
||||
theme={theme}
|
||||
handlePress={this.props.actions.unsetCustomStatus}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
buildTimezoneBlock = () => {
|
||||
const {theme, user, isMilitaryTime} = this.props;
|
||||
const style = createStyleSheet(theme);
|
||||
|
|
@ -376,6 +424,7 @@ export default class UserProfile extends PureComponent {
|
|||
{this.props.config.ShowFullName === 'true' && this.buildDisplayBlock('first_name')}
|
||||
{this.props.config.ShowFullName === 'true' && this.buildDisplayBlock('last_name')}
|
||||
{this.props.config.ShowEmailAddress === 'true' && this.buildDisplayBlock('email')}
|
||||
{this.props.config.EnableCustomUserStatuses === 'true' && this.buildCustomStatusBlock()}
|
||||
{this.buildDisplayBlock('nickname')}
|
||||
{this.buildOrganizationBlock()}
|
||||
{this.buildDisplayBlock('position')}
|
||||
|
|
@ -444,6 +493,22 @@ const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
iconContainer: {
|
||||
marginRight: 5,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
customStatus: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
customStatusTextContainer: {
|
||||
justifyContent: 'center',
|
||||
width: '80%',
|
||||
},
|
||||
clearButton: {
|
||||
position: 'absolute',
|
||||
top: -8,
|
||||
right: 0,
|
||||
},
|
||||
content: {
|
||||
marginBottom: 25,
|
||||
marginHorizontal: 15,
|
||||
|
|
@ -498,4 +563,3 @@ const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ describe('user_profile', () => {
|
|||
makeDirectChannel: jest.fn(),
|
||||
loadBot: jest.fn(),
|
||||
getRemoteClusterInfo: jest.fn(),
|
||||
unsetCustomStatus: jest.fn(),
|
||||
};
|
||||
const baseProps = {
|
||||
actions,
|
||||
|
|
@ -48,6 +49,20 @@ describe('user_profile', () => {
|
|||
is_bot: false,
|
||||
};
|
||||
|
||||
const customStatus = {
|
||||
emoji: 'calendar',
|
||||
text: 'In a meeting',
|
||||
};
|
||||
|
||||
const customStatusProps = {
|
||||
...baseProps,
|
||||
customStatus,
|
||||
config: {
|
||||
EnableCustomUserStatuses: 'true',
|
||||
},
|
||||
user,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<UserProfile
|
||||
|
|
@ -58,6 +73,27 @@ describe('user_profile', () => {
|
|||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with custom status', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<UserProfile
|
||||
{...customStatusProps}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with custom status and isMyUser true', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<UserProfile
|
||||
{...customStatusProps}
|
||||
isMyUser={true}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should contain bot tag', () => {
|
||||
const botUser = {
|
||||
email: 'test@test.com',
|
||||
|
|
|
|||
|
|
@ -48,9 +48,12 @@ const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
function createTouchableComponent(children, action) {
|
||||
function createTouchableComponent(children, action, testID) {
|
||||
return (
|
||||
<TouchableWithFeedback onPress={action}>
|
||||
<TouchableWithFeedback
|
||||
onPress={action}
|
||||
testID={testID}
|
||||
>
|
||||
{children}
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
|
|
@ -100,7 +103,7 @@ function userProfileRow(props) {
|
|||
return RowComponent;
|
||||
}
|
||||
|
||||
return createTouchableComponent(RowComponent, action);
|
||||
return createTouchableComponent(RowComponent, action, testID);
|
||||
}
|
||||
|
||||
userProfileRow.propTypes = {
|
||||
|
|
@ -118,6 +121,7 @@ userProfileRow.propTypes = {
|
|||
togglable: PropTypes.bool,
|
||||
textColor: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
userProfileRow.defaultProps = {
|
||||
|
|
@ -125,6 +129,7 @@ userProfileRow.defaultProps = {
|
|||
iconSize: 15,
|
||||
textColor: '#000',
|
||||
togglable: false,
|
||||
testID: 'user_profile.row',
|
||||
};
|
||||
|
||||
export default userProfileRow;
|
||||
|
|
|
|||
43
app/selectors/custom_status.ts
Normal file
43
app/selectors/custom_status.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {Preferences} from '@mm-redux/constants';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {get} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUser, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {UserCustomStatus} from '@mm-redux/types/users';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
||||
|
||||
export function makeGetCustomStatus(): (state: GlobalState, userID?: string) => UserCustomStatus {
|
||||
return createSelector(
|
||||
(state: GlobalState, userID?: string) => (userID ? getUser(state, userID) : getCurrentUser(state)),
|
||||
(user) => {
|
||||
const userProps = user?.props || {};
|
||||
if (userProps.customStatus) {
|
||||
return JSON.parse(userProps.customStatus) || undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export const getRecentCustomStatuses = createSelector(
|
||||
(state: GlobalState) => get(state, Preferences.CATEGORY_CUSTOM_STATUS, Preferences.NAME_RECENT_CUSTOM_STATUSES),
|
||||
(value) => {
|
||||
try {
|
||||
return value ? JSON.parse(value) : [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export function isCustomStatusEnabled(state: GlobalState) {
|
||||
const config = getConfig(state);
|
||||
const serverVersion = state.entities.general.serverVersion;
|
||||
return config && config.EnableCustomUserStatuses === 'true' && isMinimumServerVersion(serverVersion, 5, 36);
|
||||
}
|
||||
|
|
@ -125,6 +125,15 @@
|
|||
"create_comment.addComment": "Add a comment...",
|
||||
"create_post.deactivated": "You are viewing an archived channel with a deactivated user.",
|
||||
"create_post.write": "Write to {channelDisplayName}",
|
||||
"custom_status.failure_message": "Failed to update status. Try again",
|
||||
"custom_status.set_status": "Set a status",
|
||||
"custom_status.suggestions.in_a_meeting": "In a meeting",
|
||||
"custom_status.suggestions.on_a_vacation": "On a vacation",
|
||||
"custom_status.suggestions.out_for_lunch": "Out for lunch",
|
||||
"custom_status.suggestions.out_sick": "Out sick",
|
||||
"custom_status.suggestions.recent_title": "RECENT",
|
||||
"custom_status.suggestions.title": "SUGGESTIONS",
|
||||
"custom_status.suggestions.working_from_home": "Working from home",
|
||||
"date_separator.today": "Today",
|
||||
"date_separator.yesterday": "Yesterday",
|
||||
"edit_post.editPost": "Edit the post...",
|
||||
|
|
@ -252,6 +261,8 @@
|
|||
"mobile.create_channel.public": "New Public Channel",
|
||||
"mobile.create_post.read_only": "This channel is read-only",
|
||||
"mobile.custom_list.no_results": "No Results",
|
||||
"mobile.custom_status.choose_emoji": "Choose an emoji",
|
||||
"mobile.custom_status.modal_confirm": "Done",
|
||||
"mobile.display_settings.sidebar": "Sidebar",
|
||||
"mobile.display_settings.theme": "Theme",
|
||||
"mobile.document_preview.failed_description": "An error occurred while opening the document. Please make sure you have a {fileType} viewer installed and try again.\n",
|
||||
|
|
@ -470,6 +481,7 @@
|
|||
"mobile.routes.channelInfo.unarchive_channel": "Unarchive Channel",
|
||||
"mobile.routes.code": "{language} Code",
|
||||
"mobile.routes.code.noLanguage": "Code",
|
||||
"mobile.routes.custom_status": "Set a Status",
|
||||
"mobile.routes.edit_profile": "Edit Profile",
|
||||
"mobile.routes.login": "Login",
|
||||
"mobile.routes.loginOptions": "Login Chooser",
|
||||
|
|
@ -665,6 +677,7 @@
|
|||
"user.settings.general.lastName": "Last Name",
|
||||
"user.settings.general.nickname": "Nickname",
|
||||
"user.settings.general.position": "Position",
|
||||
"user.settings.general.status": "Status",
|
||||
"user.settings.general.username": "Username",
|
||||
"user.settings.modal.display": "Display",
|
||||
"user.settings.modal.notifications": "Notifications",
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@
|
|||
"EnableUserCreation": true,
|
||||
"EnableOpenServer": true,
|
||||
"EnableUserDeactivation": false,
|
||||
"EnableCustomUserStatuses": false,
|
||||
"RestrictCreationToDomains": "",
|
||||
"EnableCustomBrand": false,
|
||||
"CustomBrandText": "",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class SettingsSidebar {
|
|||
editProfileAction: 'settings.sidebar.edit_profile.action',
|
||||
settingsAction: 'settings.sidebar.settings.action',
|
||||
logoutAction: 'settings.sidebar.logout.action',
|
||||
customStatusAction: 'settings.sidebar.custom_status.action',
|
||||
customStatusClearButton: 'settings.sidebar.custom_status.action.clear',
|
||||
}
|
||||
|
||||
settingsSidebar = element(by.id(this.testID.settingsSidebar));
|
||||
|
|
@ -25,6 +27,8 @@ class SettingsSidebar {
|
|||
editProfileAction = element(by.id(this.testID.editProfileAction));
|
||||
settingsAction = element(by.id(this.testID.settingsAction));
|
||||
logoutAction = element(by.id(this.testID.logoutAction));
|
||||
customStatusAction = element(by.id(this.testID.customStatusAction));
|
||||
customStatusClearButton = element(by.id(this.testID.customStatusClearButton));
|
||||
|
||||
getUserStatus(userStatus) {
|
||||
const userStatusIconTestID = `${this.testID.userStatusIconPrefix}${userStatus}`;
|
||||
|
|
@ -79,6 +83,11 @@ class SettingsSidebar {
|
|||
await this.logoutAction.tap();
|
||||
await expect(this.settingsSidebar).not.toBeVisible();
|
||||
}
|
||||
|
||||
tapCustomStatusAction = async () => {
|
||||
await this.customStatusAction.tap();
|
||||
await expect(this.settingsSidebar).not.toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
const settingsSidebar = new SettingsSidebar();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class ChannelInfoScreen {
|
|||
channelDisplayName: 'channel_info.header.display_name',
|
||||
channelHeader: 'channel_info.header.header',
|
||||
channelPurpose: 'channel_info.header.purpose',
|
||||
headerCustomStatus: 'channel_info.header.custom_status',
|
||||
favoritePreferenceAction: 'channel_info.favorite.action',
|
||||
favoriteSwitchFalse: 'channel_info.favorite.action.switch.false',
|
||||
favoriteSwitchTrue: 'channel_info.favorite.action.switch.true',
|
||||
|
|
|
|||
64
detox/e2e/support/ui/screen/custom_status.js
Normal file
64
detox/e2e/support/ui/screen/custom_status.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {SettingsSidebar} from '@support/ui/component';
|
||||
|
||||
class CustomStatusScreen {
|
||||
testID = {
|
||||
customStatusScreen: 'custom_status.screen',
|
||||
input: 'custom_status.input',
|
||||
selectedEmojiPrefix: 'custom_status.emoji.',
|
||||
inputClearButton: 'custom_status.input.clear.button',
|
||||
doneButton: 'custom_status.done.button',
|
||||
suggestionPrefix: 'custom_status_suggestion.',
|
||||
suggestionClearButton: 'custom_status_suggestion.clear.button',
|
||||
}
|
||||
|
||||
customStatusScreen = element(by.id(this.testID.customStatusScreen));
|
||||
input = element(by.id(this.testID.input));
|
||||
inputClearButton = element(by.id(this.testID.inputClearButton))
|
||||
doneButton = element(by.id(this.testID.doneButton))
|
||||
|
||||
getCustomStatusSelectedEmoji = (emoji) => {
|
||||
const emojiTestID = `${this.testID.selectedEmojiPrefix}${emoji}`;
|
||||
return element(by.id(emojiTestID));
|
||||
}
|
||||
|
||||
getCustomStatusSuggestion = (text) => {
|
||||
const suggestionID = `${this.testID.suggestionPrefix}${text}`;
|
||||
return element(by.id(suggestionID));
|
||||
}
|
||||
|
||||
getSuggestionClearButton = (text) => {
|
||||
const suggestionID = `${this.testID.suggestionPrefix}${text}`;
|
||||
return element(by.id(this.testID.suggestionClearButton).withAncestor(by.id(suggestionID)));
|
||||
}
|
||||
|
||||
toBeVisible = async () => {
|
||||
await expect(this.customStatusScreen).toBeVisible();
|
||||
|
||||
return this.customStatusScreen;
|
||||
}
|
||||
|
||||
open = async () => {
|
||||
// # Open custom status screen
|
||||
await SettingsSidebar.tapCustomStatusAction();
|
||||
|
||||
return this.toBeVisible();
|
||||
}
|
||||
|
||||
tapSuggestion = async ({emoji, text}) => {
|
||||
await this.getCustomStatusSuggestion(text).tap();
|
||||
|
||||
await expect(this.input).toHaveText(text);
|
||||
await expect(this.getCustomStatusSelectedEmoji(emoji)).toBeVisible();
|
||||
}
|
||||
|
||||
close = async () => {
|
||||
await this.doneButton.tap();
|
||||
return expect(this.customStatusScreen).not.toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
const customStatusScreen = new CustomStatusScreen();
|
||||
export default customStatusScreen;
|
||||
|
|
@ -11,6 +11,7 @@ import ChannelNotificationPreferenceScreen from './channel_notification_preferen
|
|||
import ChannelScreen from './channel';
|
||||
import ClockDisplaySettingsScreen from './clock_display_settings';
|
||||
import CreateChannelScreen from './create_channel';
|
||||
import CustomStatusScreen from './custom_status';
|
||||
import DisplaySettingsScreen from './display_settings';
|
||||
import EditChannelScreen from './edit_channel';
|
||||
import EditPostScreen from './edit_post';
|
||||
|
|
@ -47,6 +48,7 @@ export {
|
|||
ChannelScreen,
|
||||
ClockDisplaySettingsScreen,
|
||||
CreateChannelScreen,
|
||||
CustomStatusScreen,
|
||||
DisplaySettingsScreen,
|
||||
EditChannelScreen,
|
||||
EditPostScreen,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ class UserProfileScreen {
|
|||
testID = {
|
||||
userProfilePicturePrefix: 'user_profile.profile_picture.',
|
||||
userProfileScreen: 'user_profile.screen',
|
||||
customStatus: 'user_profile.custom_status',
|
||||
profilePicture: 'user_profile.profile_picture',
|
||||
userProfileScrollView: 'user_profile.scroll_view',
|
||||
closeSettingsButton: 'close.settings.button',
|
||||
editButton: 'user_profile.edit.button',
|
||||
|
|
@ -35,6 +37,8 @@ class UserProfileScreen {
|
|||
|
||||
userProfileScreen = element(by.id(this.testID.userProfileScreen));
|
||||
userProfileScrollView = element(by.id(this.testID.userProfileScrollView));
|
||||
customStatus = element(by.id(this.testID.customStatus));
|
||||
profilePicture = element(by.id(this.testID.profilePicture))
|
||||
closeSettingsButton = element(by.id(this.testID.closeSettingsButton));
|
||||
editButton = element(by.id(this.testID.editButton));
|
||||
userProfileBotTag = element(by.id(this.testID.userProfileBotTag));
|
||||
|
|
|
|||
296
detox/e2e/test/custom_statuses/custom_status.e2e.js
Normal file
296
detox/e2e/test/custom_statuses/custom_status.e2e.js
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {SettingsSidebar} from '@support/ui/component';
|
||||
import {
|
||||
AddReactionScreen,
|
||||
ChannelInfoScreen,
|
||||
ChannelScreen,
|
||||
CustomStatusScreen,
|
||||
MoreDirectMessagesScreen,
|
||||
ThreadScreen,
|
||||
UserProfileScreen,
|
||||
} from '@support/ui/screen';
|
||||
import {
|
||||
Setup,
|
||||
System,
|
||||
Post,
|
||||
Channel,
|
||||
} from '@support/server_api';
|
||||
|
||||
describe('Custom status', () => {
|
||||
const {
|
||||
closeMainSidebar,
|
||||
closeSettingsSidebar,
|
||||
openMainSidebar,
|
||||
openSettingsSidebar,
|
||||
} = ChannelScreen;
|
||||
const {
|
||||
getCustomStatusSelectedEmoji,
|
||||
getCustomStatusSuggestion,
|
||||
getSuggestionClearButton,
|
||||
tapSuggestion,
|
||||
} = CustomStatusScreen;
|
||||
const defaultCustomStatuses = ['In a meeting', 'Out for lunch', 'Out sick', 'Working from home', 'On a vacation'];
|
||||
const defaultStatus = {
|
||||
emoji: 'calendar',
|
||||
text: 'In a meeting',
|
||||
};
|
||||
const customStatus = {
|
||||
emoji: '😀',
|
||||
emojiName: 'grinning',
|
||||
text: 'Watering plants',
|
||||
};
|
||||
let testChannel;
|
||||
let testUser;
|
||||
|
||||
beforeAll(async () => {
|
||||
await System.apiUpdateConfig({TeamSettings: {EnableCustomUserStatuses: true}});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const {team, user} = await Setup.apiInit();
|
||||
const {channel} = await Channel.apiGetChannelByName(team.id, 'town-square');
|
||||
testChannel = channel;
|
||||
testUser = user;
|
||||
|
||||
// # Open channel screen
|
||||
await ChannelScreen.open(user);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await ChannelScreen.logout();
|
||||
});
|
||||
|
||||
it('MM-T3890 Setting a custom status', async () => {
|
||||
// # Open custom status screen
|
||||
await openSettingsSidebar();
|
||||
await CustomStatusScreen.open();
|
||||
|
||||
// * Check if all the default suggestions are visible
|
||||
const isSuggestionPresentPromiseArray = [];
|
||||
defaultCustomStatuses.map(async (text) => {
|
||||
isSuggestionPresentPromiseArray.push(expect(getCustomStatusSuggestion(text)).toBeVisible());
|
||||
});
|
||||
await Promise.all(isSuggestionPresentPromiseArray);
|
||||
|
||||
// * Tap a suggestion and check if it is selected
|
||||
await tapSuggestion(defaultStatus);
|
||||
|
||||
// * Tap again and check if it is selected again
|
||||
await tapSuggestion(defaultStatus);
|
||||
|
||||
// # Tap on Done button and check if the modal closes
|
||||
await CustomStatusScreen.close();
|
||||
|
||||
// * Check if the selected emoji and text are visible in the sidebar
|
||||
await openSettingsSidebar();
|
||||
await expect(element(by.text(defaultStatus.text))).toBeVisible();
|
||||
await expect(element(by.id(`custom_status.emoji.${defaultStatus.emoji}`))).toBeVisible();
|
||||
|
||||
// # Click on the Set a custom status option and check if the modal opens
|
||||
await CustomStatusScreen.open();
|
||||
await CustomStatusScreen.close();
|
||||
|
||||
// # Close settings sidebar
|
||||
await closeSettingsSidebar();
|
||||
});
|
||||
|
||||
it('MM-T3891 Setting your own custom status', async () => {
|
||||
// # Open custom status modal and close it after setting the status
|
||||
await openCustomStatusModalAndSetStatus(customStatus);
|
||||
await openSettingsSidebar();
|
||||
|
||||
// * Check if the selected emoji and text are visible in the sideba
|
||||
await expect(element(by.text(customStatus.text).withAncestor(by.id(SettingsSidebar.testID.customStatusAction)))).toBeVisible();
|
||||
await expect(element(by.id(`custom_status.emoji.${customStatus.emojiName}`))).toBeVisible();
|
||||
|
||||
// # Tap on the clear custom status button in the sidebar
|
||||
await SettingsSidebar.customStatusClearButton.tap();
|
||||
|
||||
// * Check if the custom status is cleared and open the modal
|
||||
await expect(element(by.text('Set a Status').withAncestor(by.id(SettingsSidebar.testID.customStatusAction)))).toBeVisible();
|
||||
await CustomStatusScreen.open();
|
||||
|
||||
// * Check if the previously set status is present in the Recents section
|
||||
await expect(element(by.text(customStatus.text).withAncestor(by.id('custom_status.recents')))).toBeVisible();
|
||||
|
||||
// # Tap on the status in the recents section and close the modal by clicking Done button
|
||||
await element(by.text(customStatus.text).withAncestor(by.id('custom_status.recents'))).tap();
|
||||
await CustomStatusScreen.close();
|
||||
|
||||
// * Check if the status is set and open the modal
|
||||
await openSettingsSidebar();
|
||||
await expect(element(by.text(customStatus.text).withAncestor(by.id(SettingsSidebar.testID.customStatusAction)))).toBeVisible();
|
||||
await expect(element(by.id(`custom_status.emoji.${customStatus.emojiName}`))).toBeVisible();
|
||||
await CustomStatusScreen.open();
|
||||
|
||||
// # Tap on the input clear button in the custom status modal
|
||||
await CustomStatusScreen.inputClearButton.tap();
|
||||
|
||||
// * Check if the custom status input and emoji have cleared or not and close the modal
|
||||
await expect(CustomStatusScreen.input).toHaveText('');
|
||||
await expect(getCustomStatusSelectedEmoji('default')).toBeVisible();
|
||||
await CustomStatusScreen.close();
|
||||
|
||||
// * Check if the custom status is cleared
|
||||
await openSettingsSidebar();
|
||||
await expect(element(by.text('Set a Status').withAncestor(by.id(SettingsSidebar.testID.customStatusAction)))).toBeVisible();
|
||||
await expect(element(by.id('custom_status.emoji.default'))).toBeVisible();
|
||||
|
||||
// # Close settings sidebar
|
||||
await closeSettingsSidebar();
|
||||
});
|
||||
|
||||
it('MM-T3892 Recent statuses', async () => {
|
||||
// # Open custom status modal and close it after setting the status
|
||||
await openCustomStatusModalAndSetStatus(customStatus);
|
||||
await openSettingsSidebar();
|
||||
|
||||
// * Check if the status is set in the sidebar
|
||||
await expect(element(by.text(customStatus.text).withAncestor(by.id(SettingsSidebar.testID.customStatusAction)))).toBeVisible();
|
||||
await expect(element(by.id(`custom_status.emoji.${customStatus.emojiName}`))).toBeVisible();
|
||||
|
||||
// # Tap on the clear custom status button in the sidebar
|
||||
await SettingsSidebar.customStatusClearButton.tap();
|
||||
|
||||
// * Check if the custom status is cleared and open the modal
|
||||
await expect(element(by.text('Set a Status').withAncestor(by.id(SettingsSidebar.testID.customStatusAction)))).toBeVisible();
|
||||
await CustomStatusScreen.open();
|
||||
|
||||
// * Check if the previously set status is present in the Recents section
|
||||
await expect(element(by.text(customStatus.text).withAncestor(by.id('custom_status.recents')))).toBeVisible();
|
||||
|
||||
// # Tap on the clear button corresponding to the suggestion containing the previously set status
|
||||
await getSuggestionClearButton(customStatus.text).tap();
|
||||
|
||||
// * Check if the suggestion is removed or not
|
||||
await expect(getCustomStatusSuggestion(customStatus.text)).not.toExist();
|
||||
|
||||
// # Choose a status from the suggestions and set the status
|
||||
await tapSuggestion(defaultStatus);
|
||||
await CustomStatusScreen.close();
|
||||
|
||||
// * Check if the status is set in the sidebar
|
||||
await openSettingsSidebar();
|
||||
await expect(element(by.text(defaultStatus.text))).toBeVisible();
|
||||
await expect(element(by.id(`custom_status.emoji.${defaultStatus.emoji}`))).toBeVisible();
|
||||
|
||||
// # Tap on the clear custom status button in the sidebar
|
||||
await SettingsSidebar.customStatusClearButton.tap();
|
||||
|
||||
// * Check if the custom status is cleared and open the modal
|
||||
await expect(element(by.text('Set a Status').withAncestor(by.id(SettingsSidebar.testID.customStatusAction)))).toBeVisible();
|
||||
await CustomStatusScreen.open();
|
||||
|
||||
// * The last set status should be present in Recents list and not in the Suggestions list
|
||||
await expect(element(by.text(defaultStatus.text).withAncestor(by.id('custom_status.recents')))).toBeVisible();
|
||||
await expect(element(by.text(defaultStatus.text).withAncestor(by.id('custom_status.suggestions')))).not.toExist();
|
||||
|
||||
// # Tap on the clear button of the suggestion containing the last set status
|
||||
await getSuggestionClearButton(defaultStatus.text).tap();
|
||||
|
||||
// * The status should be moved from the Recents list to the Suggestions list
|
||||
await expect(element(by.text(defaultStatus.text).withAncestor(by.id('custom_status.recents')))).not.toExist();
|
||||
await expect(element(by.text(defaultStatus.text).withAncestor(by.id('custom_status.suggestions')))).toBeVisible();
|
||||
await CustomStatusScreen.close();
|
||||
|
||||
// # Close settings sidebar
|
||||
await closeSettingsSidebar();
|
||||
});
|
||||
|
||||
it('MM-T3893 Verifying where the custom status appears', async () => {
|
||||
const message = 'Hello';
|
||||
|
||||
// # Open custom status modal and close it after setting the status
|
||||
await openCustomStatusModalAndSetStatus(customStatus);
|
||||
await openSettingsSidebar();
|
||||
|
||||
// * Check if the status is set in the sidebar and close the sidebar
|
||||
await expect(element(by.text(customStatus.text).withAncestor(by.id(SettingsSidebar.testID.customStatusAction)))).toBeVisible();
|
||||
await expect(element(by.id(`custom_status.emoji.${customStatus.emojiName}`))).toBeVisible();
|
||||
await ChannelScreen.closeSettingsSidebar();
|
||||
|
||||
// # Post a message and check if custom status emoji is present in the post header
|
||||
await ChannelScreen.postMessage(message);
|
||||
await expect(element(by.id(`custom_status_emoji.${customStatus.emojiName}`).withAncestor(by.id('post_header')))).toBeVisible();
|
||||
|
||||
// # Open the reply thread for the last post
|
||||
const {post} = await Post.apiGetLastPostInChannel(testChannel.id);
|
||||
await ChannelScreen.openReplyThreadFor(post.id, message);
|
||||
|
||||
// * Check if the custom status emoji is present in the post header and close thread
|
||||
await expect(element(by.id(`custom_status_emoji.${customStatus.emojiName}`).withAncestor(by.id('post_header')))).toBeVisible();
|
||||
await ThreadScreen.back();
|
||||
|
||||
// # Open user profile screen
|
||||
await openSettingsSidebar();
|
||||
await UserProfileScreen.open();
|
||||
await UserProfileScreen.toBeVisible();
|
||||
|
||||
// * Check if custom status is present in the user profile screen and close it
|
||||
await expect(element(by.id(`custom_status.emoji.${customStatus.emojiName}`)).atIndex(0)).toExist();
|
||||
await expect(element(by.text(customStatus.text).withAncestor(by.id(UserProfileScreen.testID.customStatus)))).toBeVisible();
|
||||
await UserProfileScreen.close();
|
||||
|
||||
// # Open the main sidebar and click on more direct messages button
|
||||
await ChannelScreen.openMainSidebar();
|
||||
await MoreDirectMessagesScreen.open();
|
||||
|
||||
// # Type the logged in user's username and tap it to open the DM
|
||||
await MoreDirectMessagesScreen.searchInput.typeText(testUser.username);
|
||||
await MoreDirectMessagesScreen.getUserAtIndex(0).tap();
|
||||
|
||||
// * Check if the custom status emoji is present in the channel title
|
||||
await expect(element(by.id(`custom_status_emoji.${customStatus.emojiName}`).withAncestor(by.id('channel.title.button')))).toBeVisible();
|
||||
|
||||
// # Open the channel info screen
|
||||
await ChannelInfoScreen.open();
|
||||
|
||||
// * Check if the custom status is present in the channel info screen and then close the screen
|
||||
await expect(element(by.id(`custom_status.emoji.${customStatus.emojiName}`)).atIndex(0)).toExist();
|
||||
await expect(element(by.text(customStatus.text).withAncestor(by.id(ChannelInfoScreen.testID.headerCustomStatus)))).toBeVisible();
|
||||
await ChannelInfoScreen.close();
|
||||
|
||||
// # Open main sidebar and check if custom status emoji is present next to the username in the Direct messages section
|
||||
await openMainSidebar();
|
||||
expect(element(by.id(`${testUser.username}.custom_status_emoji.${customStatus.emojiName}`))).toBeVisible();
|
||||
|
||||
// # Close main sidebar
|
||||
await closeMainSidebar();
|
||||
});
|
||||
});
|
||||
|
||||
async function openCustomStatusModalAndSetStatus(customStatus) {
|
||||
const {addReactionScreen} = AddReactionScreen;
|
||||
const {getCustomStatusSelectedEmoji} = CustomStatusScreen;
|
||||
|
||||
// # Open settings sidebar
|
||||
await ChannelScreen.openSettingsSidebar();
|
||||
|
||||
// # Click on the Set a custom status option and check if the modal opens
|
||||
await CustomStatusScreen.open();
|
||||
|
||||
// # Type the custom status text in the custom status input
|
||||
await CustomStatusScreen.input.typeText(customStatus.text);
|
||||
|
||||
// * Check if the speech balloon emoji appears when some text is typed in the input
|
||||
await expect(getCustomStatusSelectedEmoji('speech_balloon')).toBeVisible();
|
||||
|
||||
// # First tap to dismiss the keyboard and then tap again to open the emoji picker
|
||||
await getCustomStatusSelectedEmoji('speech_balloon').multiTap(2);
|
||||
|
||||
// * Check if the Add reaction screen appears
|
||||
await expect(addReactionScreen).toBeVisible();
|
||||
|
||||
// * Check if the emoji to be selected is present in the emojipicker and select it
|
||||
await expect(element(by.text(customStatus.emoji))).toBeVisible();
|
||||
await element(by.text(customStatus.emoji)).tap();
|
||||
|
||||
// * Check if the Add reaction screen disappears
|
||||
await expect(addReactionScreen).not.toBeVisible();
|
||||
|
||||
// * Check if the selected emoji is visible and close the modal by clicking on Done button
|
||||
await expect(getCustomStatusSelectedEmoji(customStatus.emojiName)).toBeVisible();
|
||||
await CustomStatusScreen.close();
|
||||
}
|
||||
Loading…
Reference in a new issue