reduce usage of Utils.localizeMessage (#29282)
* reduce usage of Utils.localizeMessage As we begin to transition to react-intl's `formatjs` for extraction, our custom wrappers like `Utils.localizeMessage` prevent an adoption roadblock. This is the first in a series of PRs to begin to migrate away in favour of: * `useIntl` when inside React functional component * use `injectIntl` and use access the `intl` prop from class components * use the new `getIntl`, leveraging a memoized `createIntl` and the store outside of React components I'm pausing in this effort to get a feel from peers on both the substance of these changes and best practices in supporting them. * prefer shallowWithIntl, renderWithContext, and local injectIntl wrapping * revert unintentional whitespaces * clarify getIntl, add minor unit test * avoid triggering mmjstool * update e2e tests --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
bc22267829
Коммит
7179a9d1d6
@@ -24,7 +24,6 @@ import Pluggable from 'plugins/pluggable';
|
||||
import {createCallContext} from 'utils/apps';
|
||||
import {Constants, Locations, ModalIdentifiers} from 'utils/constants';
|
||||
import * as PostUtils from 'utils/post_utils';
|
||||
import * as Utils from 'utils/utils';
|
||||
|
||||
import type {ModalData} from 'types/actions';
|
||||
import type {HandleBindingClick, OpenAppsModal, PostEphemeralCallResponseForPost} from 'types/apps';
|
||||
@@ -395,7 +394,7 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
|
||||
key='more-actions-button'
|
||||
ref={this.buttonRef}
|
||||
id={`${this.props.location}_actions_button_${this.props.post.id}`}
|
||||
aria-label={Utils.localizeMessage({id: 'post_info.actions.tooltip.actions', defaultMessage: 'Actions'}).toLowerCase()}
|
||||
aria-label={formatMessage({id: 'post_info.actions.tooltip.actions', defaultMessage: 'Actions'}).toLowerCase()}
|
||||
className={classNames('post-menu__item', {
|
||||
'post-menu__item--active': this.props.isMenuOpen,
|
||||
})}
|
||||
@@ -409,7 +408,7 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
|
||||
id={`${this.props.location}_actions_dropdown_${this.props.post.id}`}
|
||||
openLeft={true}
|
||||
openUp={this.state.openUp}
|
||||
ariaLabel={Utils.localizeMessage({id: 'post_info.menuAriaLabel', defaultMessage: 'Post extra options'})}
|
||||
ariaLabel={formatMessage({id: 'post_info.menuAriaLabel', defaultMessage: 'Post extra options'})}
|
||||
key={`${this.props.location}_actions_dropdown_${this.props.post.id}`}
|
||||
>
|
||||
{menuItems}
|
||||
|
||||
@@ -17,7 +17,7 @@ exports[`components/MarkdownImage should match snapshot 1`] = `
|
||||
`;
|
||||
|
||||
exports[`components/MarkdownImage should match snapshot for SizeAwareImage dimensions 1`] = `
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
alt="test image"
|
||||
className="markdown-inline-img markdown-inline-img--hover markdown-inline-img--no-border"
|
||||
dimensions={
|
||||
@@ -62,7 +62,7 @@ exports[`components/MarkdownImage should provide image src as an alt text for Ma
|
||||
imageKey="https://example.com/image.png"
|
||||
postId="post_id"
|
||||
>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
alt=""
|
||||
className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active"
|
||||
dimensions={Object {}}
|
||||
@@ -97,7 +97,7 @@ exports[`components/MarkdownImage should render a link if the source is unsafe 1
|
||||
|
||||
exports[`components/MarkdownImage should render an image with no preview if the source is safe and the image is a link 1`] = `
|
||||
<div>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
alt="test image"
|
||||
className="markdown-inline-img markdown-inline-img--hover markdown-inline-img--no-border"
|
||||
dimensions={
|
||||
@@ -122,7 +122,7 @@ exports[`components/MarkdownImage should render an image with no preview if the
|
||||
|
||||
exports[`components/MarkdownImage should render an image with preview modal if the source is safe 1`] = `
|
||||
<div>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
alt="test image"
|
||||
className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active"
|
||||
dimensions={
|
||||
@@ -152,7 +152,7 @@ exports[`components/MarkdownImage should render image with MarkdownImageExpand i
|
||||
imageKey="https://example.com/image.png"
|
||||
postId="post_id"
|
||||
>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
alt="test image"
|
||||
className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active"
|
||||
dimensions={Object {}}
|
||||
|
||||
@@ -26,7 +26,7 @@ exports[`components/post_view/PostReaction should match snapshot 1`] = `
|
||||
}
|
||||
>
|
||||
<button
|
||||
aria-label="add reaction"
|
||||
aria-label="Add Reaction"
|
||||
className="post-menu__item post-menu__item--reactions"
|
||||
data-testid="post-reaction-emoji-icon"
|
||||
id="CENTER_reaction_post_id_1"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import PostReaction from './post_reaction';
|
||||
import PostReaction, {type PostReaction as PostReactionComponent} from './post_reaction';
|
||||
|
||||
describe('components/post_view/PostReaction', () => {
|
||||
const baseProps = {
|
||||
@@ -23,13 +23,13 @@ describe('components/post_view/PostReaction', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(<PostReaction {...baseProps}/>);
|
||||
const wrapper = shallowWithIntl(<PostReaction {...baseProps}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call toggleReaction and toggleEmojiPicker on handleToggleEmoji', () => {
|
||||
const wrapper = shallow(<PostReaction {...baseProps}/>);
|
||||
const instance = wrapper.instance() as PostReaction;
|
||||
const wrapper = shallowWithIntl(<PostReaction {...baseProps}/>);
|
||||
const instance = wrapper.instance() as PostReactionComponent;
|
||||
|
||||
instance.handleToggleEmoji(TestHelper.getCustomEmojiMock({name: 'smile'}));
|
||||
expect(baseProps.actions.toggleReaction).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import type {WrappedComponentProps} from 'react-intl';
|
||||
import {defineMessages, injectIntl} from 'react-intl';
|
||||
|
||||
import type {Emoji} from '@mattermost/types/emojis';
|
||||
|
||||
@@ -16,7 +17,6 @@ import EmojiIcon from 'components/widgets/icons/emoji_icon';
|
||||
import WithTooltip from 'components/with_tooltip/with_tooltip_new';
|
||||
|
||||
import {Locations} from 'utils/constants';
|
||||
import {localizeMessage} from 'utils/utils';
|
||||
|
||||
const TOP_OFFSET = -7;
|
||||
|
||||
@@ -27,12 +27,12 @@ const messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export type Props = {
|
||||
export type Props = WrappedComponentProps & {
|
||||
channelId?: string;
|
||||
postId: string;
|
||||
teamId: string;
|
||||
getDotMenuRef: () => HTMLDivElement | null;
|
||||
location: keyof typeof Locations;
|
||||
location?: keyof typeof Locations;
|
||||
showEmojiPicker: boolean;
|
||||
toggleEmojiPicker: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
actions: {
|
||||
@@ -45,7 +45,7 @@ type State = {
|
||||
showEmojiPicker: boolean;
|
||||
}
|
||||
|
||||
export default class PostReaction extends React.PureComponent<Props, State> {
|
||||
export class PostReaction extends React.PureComponent<Props, State> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
location: Locations.CENTER as 'CENTER',
|
||||
showEmojiPicker: false,
|
||||
@@ -65,6 +65,7 @@ export default class PostReaction extends React.PureComponent<Props, State> {
|
||||
postId,
|
||||
showEmojiPicker,
|
||||
teamId,
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
let spaceRequiredAbove;
|
||||
@@ -96,7 +97,7 @@ export default class PostReaction extends React.PureComponent<Props, State> {
|
||||
<button
|
||||
data-testid='post-reaction-emoji-icon'
|
||||
id={`${location}_reaction_${postId}`}
|
||||
aria-label={localizeMessage({id: 'post_info.tooltip.add_reactions', defaultMessage: 'Add Reaction'}).toLowerCase()}
|
||||
aria-label={intl.formatMessage({id: 'post_info.tooltip.add_reactions', defaultMessage: 'Add Reaction'})}
|
||||
className={classNames('post-menu__item', 'post-menu__item--reactions', {
|
||||
'post-menu__item--active': showEmojiPicker,
|
||||
})}
|
||||
@@ -110,3 +111,5 @@ export default class PostReaction extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(PostReaction);
|
||||
|
||||
@@ -36,7 +36,7 @@ exports[`components/sidebar should match snapshot 1`] = `
|
||||
pluggableName="LeftSidebarHeader"
|
||||
/>
|
||||
</div>
|
||||
<Connect(SidebarList)
|
||||
<Connect(injectIntl(SidebarList))
|
||||
handleOpenMoreDirectChannelsModal={[Function]}
|
||||
onDragEnd={[Function]}
|
||||
onDragStart={[Function]}
|
||||
@@ -79,7 +79,7 @@ exports[`components/sidebar should match snapshot when direct channels modal is
|
||||
pluggableName="LeftSidebarHeader"
|
||||
/>
|
||||
</div>
|
||||
<Connect(SidebarList)
|
||||
<Connect(injectIntl(SidebarList))
|
||||
handleOpenMoreDirectChannelsModal={[Function]}
|
||||
onDragEnd={[Function]}
|
||||
onDragStart={[Function]}
|
||||
@@ -126,7 +126,7 @@ exports[`components/sidebar should match snapshot when more channels modal is op
|
||||
pluggableName="LeftSidebarHeader"
|
||||
/>
|
||||
</div>
|
||||
<Connect(SidebarList)
|
||||
<Connect(injectIntl(SidebarList))
|
||||
handleOpenMoreDirectChannelsModal={[Function]}
|
||||
onDragEnd={[Function]}
|
||||
onDragStart={[Function]}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot 1`] = `
|
||||
<Connect(SidebarChannelLink)
|
||||
<Connect(injectIntl(SidebarChannelLink))
|
||||
ariaLabelPrefix="public channel"
|
||||
channel={
|
||||
Object {
|
||||
@@ -34,7 +34,7 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn
|
||||
`;
|
||||
|
||||
exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot when private channel 1`] = `
|
||||
<Connect(SidebarChannelLink)
|
||||
<Connect(injectIntl(SidebarChannelLink))
|
||||
ariaLabelPrefix="private channel"
|
||||
channel={
|
||||
Object {
|
||||
@@ -67,7 +67,7 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn
|
||||
`;
|
||||
|
||||
exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot when shared channel 1`] = `
|
||||
<Connect(SidebarChannelLink)
|
||||
<Connect(injectIntl(SidebarChannelLink))
|
||||
ariaLabelPrefix="public channel"
|
||||
channel={
|
||||
Object {
|
||||
@@ -102,7 +102,7 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn
|
||||
`;
|
||||
|
||||
exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot when shared private channel 1`] = `
|
||||
<Connect(SidebarChannelLink)
|
||||
<Connect(injectIntl(SidebarChannelLink))
|
||||
ariaLabelPrefix="private channel"
|
||||
channel={
|
||||
Object {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import type {ChannelType} from '@mattermost/types/channels';
|
||||
|
||||
import type {SidebarChannelLink as SidebarChannelLinkComponent} from 'components/sidebar/sidebar_channel/sidebar_channel_link/sidebar_channel_link';
|
||||
import SidebarChannelLink from 'components/sidebar/sidebar_channel/sidebar_channel_link/sidebar_channel_link';
|
||||
|
||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
|
||||
describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
|
||||
const baseProps = {
|
||||
channel: {
|
||||
@@ -49,7 +51,7 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarChannelLink {...baseProps}/>,
|
||||
);
|
||||
|
||||
@@ -60,7 +62,7 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
|
||||
const userAgentMock = jest.requireMock('utils/user_agent');
|
||||
userAgentMock.isDesktopApp.mockImplementation(() => false);
|
||||
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarChannelLink {...baseProps}/>,
|
||||
);
|
||||
|
||||
@@ -68,7 +70,7 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
|
||||
});
|
||||
|
||||
test('should match snapshot when tooltip is enabled', () => {
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarChannelLink {...baseProps}/>,
|
||||
);
|
||||
|
||||
@@ -84,7 +86,7 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
|
||||
ariaLabelPrefix: 'aria_label_prefix_',
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarChannelLink {...props}/>,
|
||||
);
|
||||
|
||||
@@ -92,10 +94,10 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
|
||||
});
|
||||
|
||||
test('should enable tooltip when needed', () => {
|
||||
const wrapper = shallow<SidebarChannelLink>(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarChannelLink {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
const instance = wrapper.instance() as SidebarChannelLinkComponent;
|
||||
|
||||
instance.labelRef = {
|
||||
current: {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import {type WrappedComponentProps, injectIntl} from 'react-intl';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
@@ -19,7 +20,6 @@ import Constants, {RHSStates} from 'utils/constants';
|
||||
import {wrapEmojis} from 'utils/emoji_utils';
|
||||
import {cmdOrCtrlPressed} from 'utils/keyboard';
|
||||
import {Mark} from 'utils/performance_telemetry';
|
||||
import {localizeMessage} from 'utils/utils';
|
||||
|
||||
import type {RhsState} from 'types/store/rhs';
|
||||
|
||||
@@ -28,7 +28,7 @@ import ChannelPencilIcon from '../channel_pencil_icon';
|
||||
import SidebarChannelIcon from '../sidebar_channel_icon';
|
||||
import SidebarChannelMenu from '../sidebar_channel_menu';
|
||||
|
||||
type Props = {
|
||||
type Props = WrappedComponentProps & {
|
||||
channel: Channel;
|
||||
link: string;
|
||||
label: string;
|
||||
@@ -79,7 +79,7 @@ type State = {
|
||||
showTooltip: boolean;
|
||||
};
|
||||
|
||||
export default class SidebarChannelLink extends React.PureComponent<Props, State> {
|
||||
export class SidebarChannelLink extends React.PureComponent<Props, State> {
|
||||
labelRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: Props) {
|
||||
@@ -110,7 +110,7 @@ export default class SidebarChannelLink extends React.PureComponent<Props, State
|
||||
};
|
||||
|
||||
getAriaLabel = (): string => {
|
||||
const {label, ariaLabelPrefix, unreadMentions} = this.props;
|
||||
const {label, ariaLabelPrefix, unreadMentions, intl} = this.props;
|
||||
|
||||
let ariaLabel = label;
|
||||
|
||||
@@ -119,13 +119,13 @@ export default class SidebarChannelLink extends React.PureComponent<Props, State
|
||||
}
|
||||
|
||||
if (unreadMentions === 1) {
|
||||
ariaLabel += ` ${unreadMentions} ${localizeMessage({id: 'accessibility.sidebar.types.mention', defaultMessage: 'mention'})}`;
|
||||
ariaLabel += ` ${unreadMentions} ${intl.formatMessage({id: 'accessibility.sidebar.types.mention', defaultMessage: 'mention'})}`;
|
||||
} else if (unreadMentions > 1) {
|
||||
ariaLabel += ` ${unreadMentions} ${localizeMessage({id: 'accessibility.sidebar.types.mentions', defaultMessage: 'mentions'})}`;
|
||||
ariaLabel += ` ${unreadMentions} ${intl.formatMessage({id: 'accessibility.sidebar.types.mentions', defaultMessage: 'mentions'})}`;
|
||||
}
|
||||
|
||||
if (this.props.isUnread && unreadMentions === 0) {
|
||||
ariaLabel += ` ${localizeMessage({id: 'accessibility.sidebar.types.unread', defaultMessage: 'unread'})}`;
|
||||
ariaLabel += ` ${intl.formatMessage({id: 'accessibility.sidebar.types.unread', defaultMessage: 'unread'})}`;
|
||||
}
|
||||
|
||||
return ariaLabel.toLowerCase();
|
||||
@@ -298,3 +298,5 @@ export default class SidebarChannelLink extends React.PureComponent<Props, State
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(SidebarChannelLink);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match snapshot 1`] = `
|
||||
<Connect(SidebarChannelLink)
|
||||
<Connect(injectIntl(SidebarChannelLink))
|
||||
channel={
|
||||
Object {
|
||||
"create_at": 0,
|
||||
@@ -39,7 +39,7 @@ exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match
|
||||
`;
|
||||
|
||||
exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match snapshot if DM is with bot with custom icon 1`] = `
|
||||
<Connect(SidebarChannelLink)
|
||||
<Connect(injectIntl(SidebarChannelLink))
|
||||
channel={
|
||||
Object {
|
||||
"create_at": 0,
|
||||
@@ -78,7 +78,7 @@ exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match
|
||||
`;
|
||||
|
||||
exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match snapshot if DM is with current user 1`] = `
|
||||
<Connect(SidebarChannelLink)
|
||||
<Connect(injectIntl(SidebarChannelLink))
|
||||
channel={
|
||||
Object {
|
||||
"create_at": 0,
|
||||
@@ -116,7 +116,7 @@ exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match
|
||||
`;
|
||||
|
||||
exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match snapshot if DM is with deleted user 1`] = `
|
||||
<Connect(SidebarChannelLink)
|
||||
<Connect(injectIntl(SidebarChannelLink))
|
||||
channel={
|
||||
Object {
|
||||
"create_at": 0,
|
||||
|
||||
@@ -11,10 +11,11 @@ import type {TeamType} from '@mattermost/types/teams';
|
||||
|
||||
import {CategoryTypes} from 'mattermost-redux/constants/channel_categories';
|
||||
|
||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {DraggingStates, DraggingStateTypes} from 'utils/constants';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import SidebarList from './sidebar_list';
|
||||
import SidebarList, {type SidebarList as SidebarListComponent} from './sidebar_list';
|
||||
|
||||
describe('SidebarList', () => {
|
||||
const currentChannel = TestHelper.getChannelMock({
|
||||
@@ -115,7 +116,7 @@ describe('SidebarList', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarList {...baseProps}/>,
|
||||
);
|
||||
|
||||
@@ -130,7 +131,7 @@ describe('SidebarList', () => {
|
||||
});
|
||||
|
||||
test('should close sidebar on mobile when channel is selected (ie. changed)', () => {
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarList {...baseProps}/>,
|
||||
);
|
||||
|
||||
@@ -139,11 +140,12 @@ describe('SidebarList', () => {
|
||||
});
|
||||
|
||||
test('should scroll to top when team changes', () => {
|
||||
const wrapper = shallow<SidebarList>(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarList {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance() as SidebarListComponent;
|
||||
|
||||
wrapper.instance().scrollbar = {
|
||||
instance.scrollbar = {
|
||||
current: {
|
||||
scrollToTop: jest.fn(),
|
||||
} as any,
|
||||
@@ -155,14 +157,14 @@ describe('SidebarList', () => {
|
||||
};
|
||||
|
||||
wrapper.setProps({currentTeam: newCurrentTeam});
|
||||
expect(wrapper.instance().scrollbar.current!.scrollToTop).toHaveBeenCalled();
|
||||
expect(instance.scrollbar.current!.scrollToTop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should display unread scroll indicator when channels appear outside visible area', () => {
|
||||
const wrapper = shallow<SidebarList>(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarList {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
const instance = wrapper.instance() as SidebarListComponent;
|
||||
|
||||
instance.scrollbar = {
|
||||
current: {
|
||||
@@ -189,10 +191,10 @@ describe('SidebarList', () => {
|
||||
});
|
||||
|
||||
test('should scroll to correct position when scrolling to channel', () => {
|
||||
const wrapper = shallow<SidebarList>(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarList {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
const instance = wrapper.instance() as SidebarListComponent;
|
||||
|
||||
instance.scrollToPosition = jest.fn();
|
||||
|
||||
@@ -218,7 +220,7 @@ describe('SidebarList', () => {
|
||||
style: {},
|
||||
}]);
|
||||
|
||||
const wrapper = shallow<SidebarList>(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarList {...baseProps}/>,
|
||||
);
|
||||
|
||||
@@ -232,7 +234,9 @@ describe('SidebarList', () => {
|
||||
type: DraggingStateTypes.CATEGORY,
|
||||
};
|
||||
|
||||
wrapper.instance().onBeforeCapture(categoryBefore);
|
||||
const instance = wrapper.instance() as SidebarListComponent;
|
||||
|
||||
instance.onBeforeCapture(categoryBefore);
|
||||
expect(baseProps.actions.setDraggingState).toHaveBeenCalledWith(expectedCategoryBefore);
|
||||
|
||||
const channelBefore = {
|
||||
@@ -245,14 +249,15 @@ describe('SidebarList', () => {
|
||||
type: DraggingStateTypes.CHANNEL,
|
||||
};
|
||||
|
||||
wrapper.instance().onBeforeCapture(channelBefore);
|
||||
instance.onBeforeCapture(channelBefore);
|
||||
expect(baseProps.actions.setDraggingState).toHaveBeenCalledWith(expectedChannelBefore);
|
||||
});
|
||||
|
||||
test('should call correct action on dropping item', () => {
|
||||
const wrapper = shallow<SidebarList>(
|
||||
const wrapper = shallowWithIntl(
|
||||
<SidebarList {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance() as SidebarListComponent;
|
||||
|
||||
const categoryResult: DropResult = {
|
||||
reason: 'DROP',
|
||||
@@ -269,7 +274,7 @@ describe('SidebarList', () => {
|
||||
mode: 'SNAP' as MovementMode,
|
||||
};
|
||||
|
||||
wrapper.instance().onDragEnd(categoryResult);
|
||||
instance.onDragEnd(categoryResult);
|
||||
expect(baseProps.actions.moveCategory).toHaveBeenCalledWith(baseProps.currentTeam.id, categoryResult.draggableId, categoryResult.destination!.index);
|
||||
|
||||
const channelResult: DropResult = {
|
||||
@@ -287,7 +292,7 @@ describe('SidebarList', () => {
|
||||
mode: 'SNAP' as MovementMode,
|
||||
};
|
||||
|
||||
wrapper.instance().onDragEnd(channelResult);
|
||||
instance.onDragEnd(channelResult);
|
||||
expect(baseProps.actions.moveChannelsInSidebar).toHaveBeenCalledWith(channelResult.destination!.droppableId, channelResult.destination!.index, channelResult.draggableId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {CSSProperties} from 'react';
|
||||
import {DragDropContext, Droppable} from 'react-beautiful-dnd';
|
||||
import type {DropResult, DragStart, BeforeCapture} from 'react-beautiful-dnd';
|
||||
import Scrollbars from 'react-custom-scrollbars';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage, injectIntl, type WrappedComponentProps} from 'react-intl';
|
||||
import {SpringSystem} from 'rebound';
|
||||
import type {Spring} from 'rebound';
|
||||
|
||||
@@ -26,7 +26,7 @@ import SidebarCategory from 'components/sidebar/sidebar_category';
|
||||
import {findNextUnreadChannelId} from 'utils/channel_utils';
|
||||
import {Constants, DraggingStates, DraggingStateTypes} from 'utils/constants';
|
||||
import {isKeyPressed, cmdOrCtrlPressed} from 'utils/keyboard';
|
||||
import {localizeMessage, mod} from 'utils/utils';
|
||||
import {mod} from 'utils/utils';
|
||||
|
||||
import type {DraggingState} from 'types/store';
|
||||
import type {StaticPage} from 'types/store/lhs';
|
||||
@@ -74,7 +74,7 @@ export function renderThumbVertical(props: React.HTMLProps<HTMLDivElement>) {
|
||||
|
||||
const scrollbarStyles: CSSProperties = {position: 'absolute'};
|
||||
|
||||
type Props = {
|
||||
type Props = WrappedComponentProps & {
|
||||
currentTeam?: Team;
|
||||
currentChannelId: string;
|
||||
categories: ChannelCategory[];
|
||||
@@ -122,7 +122,7 @@ const categoryHeaderHeight = 32;
|
||||
// that the channel is not under the unread indicator.
|
||||
const scrollMarginWithUnread = 55;
|
||||
|
||||
export default class SidebarList extends React.PureComponent<Props, State> {
|
||||
export class SidebarList extends React.PureComponent<Props, State> {
|
||||
channelRefs: Map<string, HTMLLIElement>;
|
||||
scrollbar: React.RefObject<Scrollbars>;
|
||||
animate: SpringSystem;
|
||||
@@ -547,7 +547,7 @@ export default class SidebarList extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
);
|
||||
|
||||
const ariaLabel = localizeMessage({id: 'accessibility.sections.lhsList', defaultMessage: 'channel sidebar region'});
|
||||
const ariaLabel = this.props.intl.formatMessage({id: 'accessibility.sections.lhsList', defaultMessage: 'channel sidebar region'});
|
||||
|
||||
return (
|
||||
|
||||
@@ -602,3 +602,5 @@ export default class SidebarList extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(SidebarList);
|
||||
|
||||
@@ -33,7 +33,7 @@ exports[`components/SingleImageView permalink preview should render with permali
|
||||
<div
|
||||
className="image-permalink"
|
||||
>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
className="image-permalink"
|
||||
dimensions={
|
||||
Object {
|
||||
@@ -108,7 +108,7 @@ exports[`components/SingleImageView should match snapshot 1`] = `
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
className=""
|
||||
dimensions={
|
||||
Object {
|
||||
@@ -183,7 +183,7 @@ exports[`components/SingleImageView should match snapshot 2`] = `
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
className=""
|
||||
dimensions={
|
||||
Object {
|
||||
@@ -262,7 +262,7 @@ exports[`components/SingleImageView should match snapshot, SVG image 1`] = `
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
className=""
|
||||
dimensions={
|
||||
Object {
|
||||
@@ -341,7 +341,7 @@ exports[`components/SingleImageView should match snapshot, SVG image 2`] = `
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
className=""
|
||||
dimensions={
|
||||
Object {
|
||||
@@ -416,7 +416,7 @@ exports[`components/SingleImageView should set loaded state on callback of onIma
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<SizeAwareImage
|
||||
<injectIntl(SizeAwareImage)
|
||||
className=""
|
||||
dimensions={
|
||||
Object {
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('components/SingleImageView', () => {
|
||||
<SingleImageView {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.find('SizeAwareImage').at(0).simulate('click', {preventDefault: () => {}});
|
||||
wrapper.find(SizeAwareImage).at(0).simulate('click', {preventDefault: () => { }});
|
||||
expect(baseProps.actions.openModal).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {mount, shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import LoadingImagePreview from 'components/loading_image_preview';
|
||||
import SizeAwareImage from 'components/size_aware_image';
|
||||
import type {Props} from 'components/size_aware_image';
|
||||
import SizeAwareImage, {SizeAwareImage as SizeAwareImageComponent} from 'components/size_aware_image';
|
||||
|
||||
import {shallowWithIntl, mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import mockStore from 'tests/test_store';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
describe('components/SizeAwareImage', () => {
|
||||
const baseProps: Props = {
|
||||
const baseProps = {
|
||||
dimensions: {
|
||||
height: 200,
|
||||
width: 300,
|
||||
@@ -41,17 +40,17 @@ describe('components/SizeAwareImage', () => {
|
||||
});
|
||||
|
||||
test('should render an svg when first mounted with dimensions and img display set to none', () => {
|
||||
const wrapper = mount(<Provider store={store}><SizeAwareImage {...baseProps}/></Provider>);
|
||||
const wrapper = mountWithIntl(<Provider store={store}><SizeAwareImage {...baseProps}/></Provider>);
|
||||
|
||||
// since download and copy icons use svgs now, attachment svg should be searched as a direct child of image-loading__container
|
||||
const viewBox = wrapper.find(SizeAwareImage).find('.image-loading__container').children().filter('svg').prop('viewBox');
|
||||
const viewBox = wrapper.find(SizeAwareImageComponent).find('.image-loading__container').children().filter('svg').prop('viewBox');
|
||||
expect(viewBox).toEqual('0 0 300 200');
|
||||
const style = wrapper.find('.file-preview__button').prop('style');
|
||||
expect(style).toHaveProperty('display', 'none');
|
||||
});
|
||||
|
||||
test('img should have inherited class name from prop', () => {
|
||||
const wrapper = mount(<Provider store={store}><SizeAwareImage {...{...baseProps, className: 'imgClass'}}/></Provider>);
|
||||
const wrapper = mountWithIntl(<Provider store={store}><SizeAwareImage {...{...baseProps, className: 'imgClass'}}/></Provider>);
|
||||
|
||||
const className = wrapper.find('img').prop('className');
|
||||
expect(className).toEqual('imgClass');
|
||||
@@ -63,7 +62,7 @@ describe('components/SizeAwareImage', () => {
|
||||
showLoader: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<SizeAwareImage {...props}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
|
||||
expect(wrapper.find(LoadingImagePreview).exists()).toEqual(true);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -78,17 +77,17 @@ describe('components/SizeAwareImage', () => {
|
||||
}),
|
||||
};
|
||||
|
||||
const wrapper = mount(<Provider store={store}><SizeAwareImage {...props}/></Provider>);
|
||||
const wrapper = mountWithIntl(<Provider store={store}><SizeAwareImage {...props}/></Provider>);
|
||||
|
||||
wrapper.find(SizeAwareImage).setState({loaded: false, error: false});
|
||||
wrapper.find(SizeAwareImageComponent).setState({loaded: false, error: false});
|
||||
|
||||
const src = wrapper.find('.image-loading__container img').prop('src');
|
||||
expect(src).toEqual('data:mime_type;base64,mini_preview');
|
||||
});
|
||||
|
||||
test('should have display set to initial in loaded state', () => {
|
||||
const wrapper = mount(<Provider store={store}><SizeAwareImage {...baseProps}/></Provider>);
|
||||
wrapper.find(SizeAwareImage).setState({loaded: true, error: false});
|
||||
const wrapper = mountWithIntl(<Provider store={store}><SizeAwareImage {...baseProps}/></Provider>);
|
||||
wrapper.find(SizeAwareImageComponent).setState({loaded: true, error: false});
|
||||
|
||||
const style = wrapper.find('.file-preview__button').prop('style');
|
||||
expect(style).toHaveProperty('display', 'inline-block');
|
||||
@@ -98,9 +97,9 @@ describe('components/SizeAwareImage', () => {
|
||||
const props = {...baseProps};
|
||||
Reflect.deleteProperty(props, 'dimensions');
|
||||
|
||||
const wrapper = mount(<Provider store={store}><SizeAwareImage {...props}/></Provider>);
|
||||
const wrapper = mountWithIntl(<Provider store={store}><SizeAwareImage {...props}/></Provider>);
|
||||
|
||||
wrapper.find(SizeAwareImage).setState({error: false});
|
||||
wrapper.find(SizeAwareImageComponent).setState({error: false});
|
||||
|
||||
const src = wrapper.find('img').prop('src');
|
||||
expect(src).toEqual(baseProps.src);
|
||||
@@ -110,7 +109,7 @@ describe('components/SizeAwareImage', () => {
|
||||
const height = 123;
|
||||
const width = 1234;
|
||||
|
||||
const wrapper = shallow<SizeAwareImage>(<SizeAwareImage {...baseProps}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...baseProps}/>);
|
||||
|
||||
wrapper.find('img')?.prop('onLoad')?.({target: {naturalHeight: height, naturalWidth: width}} as unknown as React.SyntheticEvent<HTMLImageElement>);
|
||||
expect(wrapper.state('loaded')).toBe(true);
|
||||
@@ -118,18 +117,18 @@ describe('components/SizeAwareImage', () => {
|
||||
});
|
||||
|
||||
test('should call onImageLoadFail when image load fails and should have svg', () => {
|
||||
const wrapper = mount(<Provider store={store}><SizeAwareImage {...baseProps}/></Provider>);
|
||||
const wrapper = mountWithIntl(<Provider store={store}><SizeAwareImage {...baseProps}/></Provider>);
|
||||
const errorEvent = {
|
||||
target: {},
|
||||
currentTarget: {},
|
||||
preventDefault: () => {},
|
||||
stopPropagation: () => {},
|
||||
preventDefault: () => { },
|
||||
stopPropagation: () => { },
|
||||
} as React.SyntheticEvent<HTMLImageElement>;
|
||||
wrapper.find(SizeAwareImage).find('img').prop('onError')?.(errorEvent);
|
||||
wrapper.find(SizeAwareImageComponent).find('img').prop('onError')?.(errorEvent);
|
||||
|
||||
expect(wrapper.find(SizeAwareImage).state('error')).toBe(true);
|
||||
expect(wrapper.find(SizeAwareImage).find('svg').exists()).toEqual(true);
|
||||
expect(wrapper.find(SizeAwareImage).find(LoadingImagePreview).exists()).toEqual(false);
|
||||
expect(wrapper.find(SizeAwareImageComponent).state('error')).toBe(true);
|
||||
expect(wrapper.find(SizeAwareImageComponent).find('svg').exists()).toEqual(true);
|
||||
expect(wrapper.find(SizeAwareImageComponent).find(LoadingImagePreview).exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('should match snapshot when handleSmallImageContainer prop is passed', () => {
|
||||
@@ -138,7 +137,7 @@ describe('components/SizeAwareImage', () => {
|
||||
handleSmallImageContainer: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<SizeAwareImage {...props}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -148,7 +147,7 @@ describe('components/SizeAwareImage', () => {
|
||||
handleSmallImageContainer: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<SizeAwareImage {...props}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
|
||||
|
||||
wrapper.instance().setState({isSmallImage: true});
|
||||
|
||||
@@ -163,7 +162,7 @@ describe('components/SizeAwareImage', () => {
|
||||
handleSmallImageContainer: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<SizeAwareImage {...props}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
|
||||
|
||||
wrapper.instance().setState({isSmallImage: true, imageWidth: 220});
|
||||
expect(wrapper.find('div.small-image__container').prop('style')).
|
||||
@@ -182,7 +181,7 @@ describe('components/SizeAwareImage', () => {
|
||||
handleSmallImageContainer: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<SizeAwareImage {...props}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
|
||||
|
||||
wrapper.instance().setState({isSmallImage: true, imageWidth: 24});
|
||||
|
||||
@@ -195,7 +194,7 @@ describe('components/SizeAwareImage', () => {
|
||||
...baseProps,
|
||||
fileURL,
|
||||
};
|
||||
const wrapper = shallow(<SizeAwareImage {...props}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -205,7 +204,7 @@ describe('components/SizeAwareImage', () => {
|
||||
...baseProps,
|
||||
fileURL,
|
||||
};
|
||||
const wrapper = shallow(<SizeAwareImage {...props}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
|
||||
expect(wrapper.find('.size-aware-image__download').prop('href')).toBe(fileURL);
|
||||
});
|
||||
|
||||
@@ -216,7 +215,7 @@ describe('components/SizeAwareImage', () => {
|
||||
fileURL,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<SizeAwareImage {...props}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
|
||||
expect(wrapper.state('linkCopyInProgress')).toBe(false);
|
||||
wrapper.find('.size-aware-image__copy_link').first().simulate('click');
|
||||
expect(wrapper.state('linkCopyInProgress')).toBe(true);
|
||||
@@ -228,7 +227,7 @@ describe('components/SizeAwareImage', () => {
|
||||
enablePublicLink: false,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<SizeAwareImage {...props}/>);
|
||||
const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
|
||||
expect(wrapper.find('button.size-aware-image__copy_link').exists()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import type {KeyboardEvent, MouseEvent, SyntheticEvent} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage, injectIntl} from 'react-intl';
|
||||
import type {WrappedComponentProps} from 'react-intl';
|
||||
|
||||
import {DownloadOutlineIcon, LinkVariantIcon, CheckIcon} from '@mattermost/compass-icons/components';
|
||||
import type {FileInfo} from '@mattermost/types/files';
|
||||
@@ -19,13 +20,13 @@ import LoadingImagePreview from 'components/loading_image_preview';
|
||||
import WithTooltip from 'components/with_tooltip';
|
||||
|
||||
import {FileTypes} from 'utils/constants';
|
||||
import {localizeMessage, copyToClipboard, getFileType} from 'utils/utils';
|
||||
import {copyToClipboard, getFileType} from 'utils/utils';
|
||||
|
||||
const MIN_IMAGE_SIZE = 48;
|
||||
const MIN_IMAGE_SIZE_FOR_INTERNAL_BUTTONS = 100;
|
||||
const MAX_IMAGE_HEIGHT = 350;
|
||||
|
||||
export type Props = {
|
||||
export type Props = WrappedComponentProps & {
|
||||
|
||||
/*
|
||||
* The source URL of the image
|
||||
@@ -86,7 +87,7 @@ export type Props = {
|
||||
/**
|
||||
* Action to fetch public link of an image from server.
|
||||
*/
|
||||
getFilePublicLink?: () => Promise<ActionResult<{link: string}>>;
|
||||
getFilePublicLink?: () => Promise<ActionResult<{ link: string }>>;
|
||||
|
||||
/*
|
||||
* Prevents display of utility buttons when image in a location that makes them inappropriate
|
||||
@@ -105,10 +106,10 @@ type State = {
|
||||
|
||||
// SizeAwareImage is a component used for rendering images where the dimensions of the image are important for
|
||||
// ensuring that the page is laid out correctly.
|
||||
export default class SizeAwareImage extends React.PureComponent<Props, State> {
|
||||
export class SizeAwareImage extends React.PureComponent<Props, State> {
|
||||
public heightTimeout = 0;
|
||||
public mounted = false;
|
||||
public timeout: NodeJS.Timeout|null = null;
|
||||
public timeout: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@@ -199,6 +200,7 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
|
||||
src,
|
||||
fileURL,
|
||||
enablePublicLink,
|
||||
intl,
|
||||
...props
|
||||
} = this.props;
|
||||
Reflect.deleteProperty(props, 'showLoader');
|
||||
@@ -210,8 +212,9 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
|
||||
Reflect.deleteProperty(props, 'onClick');
|
||||
Reflect.deleteProperty(props, 'hideUtilities');
|
||||
Reflect.deleteProperty(props, 'getFilePublicLink');
|
||||
Reflect.deleteProperty(props, 'intl');
|
||||
|
||||
let ariaLabelImage = localizeMessage({id: 'file_attachment.thumbnail', defaultMessage: 'file thumbnail'});
|
||||
let ariaLabelImage = intl.formatMessage({id: 'file_attachment.thumbnail', defaultMessage: 'file thumbnail'});
|
||||
if (fileInfo) {
|
||||
ariaLabelImage += ` ${fileInfo.name}`.toLowerCase();
|
||||
}
|
||||
@@ -269,7 +272,7 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
|
||||
className={classNames('style--none', 'size-aware-image__copy_link', {
|
||||
'size-aware-image__copy_link--recently_copied': this.state.linkCopiedRecently,
|
||||
})}
|
||||
aria-label={localizeMessage({id: 'single_image_view.copy_link_tooltip', defaultMessage: 'Copy link'})}
|
||||
aria-label={intl.formatMessage({id: 'single_image_view.copy_link_tooltip', defaultMessage: 'Copy link'})}
|
||||
onClick={this.copyLinkToAsset}
|
||||
>
|
||||
{this.state.linkCopiedRecently ? (
|
||||
@@ -306,7 +309,7 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
|
||||
className='style--none size-aware-image__download'
|
||||
download={true}
|
||||
role={this.isInternalImage ? 'button' : undefined}
|
||||
aria-label={localizeMessage({id: 'single_image_view.download_tooltip', defaultMessage: 'Download'})}
|
||||
aria-label={intl.formatMessage({id: 'single_image_view.download_tooltip', defaultMessage: 'Download'})}
|
||||
>
|
||||
<DownloadOutlineIcon
|
||||
className={'style--none'}
|
||||
@@ -396,7 +399,7 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
|
||||
fileInfo,
|
||||
} = this.props;
|
||||
|
||||
let ariaLabelImage = localizeMessage({id: 'file_attachment.thumbnail', defaultMessage: 'file thumbnail'});
|
||||
let ariaLabelImage = this.props.intl.formatMessage({id: 'file_attachment.thumbnail', defaultMessage: 'file thumbnail'});
|
||||
if (fileInfo) {
|
||||
ariaLabelImage += ` ${fileInfo.name}`.toLowerCase();
|
||||
}
|
||||
@@ -500,3 +503,5 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(SizeAwareImage);
|
||||
|
||||
@@ -112,7 +112,7 @@ exports[`components/StatusDropdown should match snapshot in default state 1`] =
|
||||
show={true}
|
||||
text="Away"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[Function]}
|
||||
ariaLabel="Do not disturb. Disables all notifications"
|
||||
direction="left"
|
||||
@@ -125,9 +125,7 @@ exports[`components/StatusDropdown should match snapshot in default state 1`] =
|
||||
}
|
||||
id="status-menu-dnd"
|
||||
openUp={false}
|
||||
renderSelected={true}
|
||||
rightDecorator={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -190,7 +188,6 @@ exports[`components/StatusDropdown should match snapshot in default state 1`] =
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Do not disturb"
|
||||
/>
|
||||
<MenuItemAction
|
||||
@@ -439,7 +436,7 @@ exports[`components/StatusDropdown should match snapshot with custom status and
|
||||
show={true}
|
||||
text="Away"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[Function]}
|
||||
ariaLabel="Do not disturb. Disables all notifications"
|
||||
direction="left"
|
||||
@@ -452,9 +449,7 @@ exports[`components/StatusDropdown should match snapshot with custom status and
|
||||
}
|
||||
id="status-menu-dnd"
|
||||
openUp={false}
|
||||
renderSelected={true}
|
||||
rightDecorator={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -517,7 +512,6 @@ exports[`components/StatusDropdown should match snapshot with custom status and
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Do not disturb"
|
||||
/>
|
||||
<MenuItemAction
|
||||
@@ -729,7 +723,7 @@ exports[`components/StatusDropdown should match snapshot with custom status enab
|
||||
show={true}
|
||||
text="Away"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[Function]}
|
||||
ariaLabel="Do not disturb. Disables all notifications"
|
||||
direction="left"
|
||||
@@ -742,9 +736,7 @@ exports[`components/StatusDropdown should match snapshot with custom status enab
|
||||
}
|
||||
id="status-menu-dnd"
|
||||
openUp={false}
|
||||
renderSelected={true}
|
||||
rightDecorator={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -807,7 +799,6 @@ exports[`components/StatusDropdown should match snapshot with custom status enab
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Do not disturb"
|
||||
/>
|
||||
<MenuItemAction
|
||||
@@ -1019,7 +1010,7 @@ exports[`components/StatusDropdown should match snapshot with custom status expi
|
||||
show={true}
|
||||
text="Away"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[Function]}
|
||||
ariaLabel="Do not disturb. Disables all notifications"
|
||||
direction="left"
|
||||
@@ -1032,9 +1023,7 @@ exports[`components/StatusDropdown should match snapshot with custom status expi
|
||||
}
|
||||
id="status-menu-dnd"
|
||||
openUp={false}
|
||||
renderSelected={true}
|
||||
rightDecorator={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -1097,7 +1086,6 @@ exports[`components/StatusDropdown should match snapshot with custom status expi
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Do not disturb"
|
||||
/>
|
||||
<MenuItemAction
|
||||
@@ -1310,7 +1298,7 @@ exports[`components/StatusDropdown should match snapshot with custom status puls
|
||||
show={true}
|
||||
text="Away"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[Function]}
|
||||
ariaLabel="Do not disturb. Disables all notifications"
|
||||
direction="left"
|
||||
@@ -1323,9 +1311,7 @@ exports[`components/StatusDropdown should match snapshot with custom status puls
|
||||
}
|
||||
id="status-menu-dnd"
|
||||
openUp={false}
|
||||
renderSelected={true}
|
||||
rightDecorator={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -1388,7 +1374,6 @@ exports[`components/StatusDropdown should match snapshot with custom status puls
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Do not disturb"
|
||||
/>
|
||||
<MenuItemAction
|
||||
@@ -1576,7 +1561,7 @@ exports[`components/StatusDropdown should match snapshot with profile picture UR
|
||||
show={true}
|
||||
text="Away"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[Function]}
|
||||
ariaLabel="Do not disturb. Disables all notifications"
|
||||
direction="left"
|
||||
@@ -1589,9 +1574,7 @@ exports[`components/StatusDropdown should match snapshot with profile picture UR
|
||||
}
|
||||
id="status-menu-dnd"
|
||||
openUp={false}
|
||||
renderSelected={true}
|
||||
rightDecorator={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -1654,7 +1637,6 @@ exports[`components/StatusDropdown should match snapshot with profile picture UR
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Do not disturb"
|
||||
/>
|
||||
<MenuItemAction
|
||||
@@ -1834,7 +1816,7 @@ exports[`components/StatusDropdown should match snapshot with status dropdown op
|
||||
show={true}
|
||||
text="Away"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[Function]}
|
||||
ariaLabel="Do not disturb. Disables all notifications"
|
||||
direction="left"
|
||||
@@ -1847,9 +1829,7 @@ exports[`components/StatusDropdown should match snapshot with status dropdown op
|
||||
}
|
||||
id="status-menu-dnd"
|
||||
openUp={false}
|
||||
renderSelected={true}
|
||||
rightDecorator={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -1912,7 +1892,6 @@ exports[`components/StatusDropdown should match snapshot with status dropdown op
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Do not disturb"
|
||||
/>
|
||||
<MenuItemAction
|
||||
@@ -2124,7 +2103,7 @@ exports[`components/StatusDropdown should not show clear status button when cust
|
||||
show={true}
|
||||
text="Away"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[Function]}
|
||||
ariaLabel="Do not disturb. Disables all notifications"
|
||||
direction="left"
|
||||
@@ -2137,9 +2116,7 @@ exports[`components/StatusDropdown should not show clear status button when cust
|
||||
}
|
||||
id="status-menu-dnd"
|
||||
openUp={false}
|
||||
renderSelected={true}
|
||||
rightDecorator={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -2202,7 +2179,6 @@ exports[`components/StatusDropdown should not show clear status button when cust
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Do not disturb"
|
||||
/>
|
||||
<MenuItemAction
|
||||
@@ -2451,7 +2427,7 @@ exports[`components/StatusDropdown should show clear status button when custom s
|
||||
show={true}
|
||||
text="Away"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[Function]}
|
||||
ariaLabel="Do not disturb. Disables all notifications"
|
||||
direction="left"
|
||||
@@ -2464,9 +2440,7 @@ exports[`components/StatusDropdown should show clear status button when custom s
|
||||
}
|
||||
id="status-menu-dnd"
|
||||
openUp={false}
|
||||
renderSelected={true}
|
||||
rightDecorator={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -2529,7 +2503,6 @@ exports[`components/StatusDropdown should show clear status button when custom s
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Do not disturb"
|
||||
/>
|
||||
<MenuItemAction
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {defineMessages, injectIntl} from 'react-intl';
|
||||
import type {WrappedComponentProps} from 'react-intl';
|
||||
import {connect, useSelector} from 'react-redux';
|
||||
|
||||
import type {Channel, ChannelMembership} from '@mattermost/types/channels';
|
||||
@@ -59,6 +60,7 @@ import BotTag from 'components/widgets/tag/bot_tag';
|
||||
import GuestTag from 'components/widgets/tag/guest_tag';
|
||||
|
||||
import {Constants, StoragePrefixes} from 'utils/constants';
|
||||
import {getIntl} from 'utils/i18n';
|
||||
import * as Utils from 'utils/utils';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
@@ -111,7 +113,7 @@ interface WrappedChannel {
|
||||
unread_mentions?: number;
|
||||
}
|
||||
|
||||
type Props = SuggestionProps<WrappedChannel> & {
|
||||
type Props = SuggestionProps<WrappedChannel> & WrappedComponentProps & {
|
||||
channelMember: ChannelMembership;
|
||||
collapsedThreads: boolean;
|
||||
dmChannelTeammate?: UserProfile;
|
||||
@@ -220,7 +222,7 @@ const SwitchChannelSuggestion = React.forwardRef<HTMLDivElement, Props>((props,
|
||||
|
||||
let deactivated = '';
|
||||
if (teammate.delete_at) {
|
||||
deactivated = (' - ' + Utils.localizeMessage({id: 'channel_switch_modal.deactivated', defaultMessage: 'Deactivated'}));
|
||||
deactivated = (' - ' + props.intl.formatMessage({id: 'channel_switch_modal.deactivated', defaultMessage: 'Deactivated'}));
|
||||
}
|
||||
|
||||
if (channel.display_name && !(teammate && teammate.is_bot)) {
|
||||
@@ -228,7 +230,7 @@ const SwitchChannelSuggestion = React.forwardRef<HTMLDivElement, Props>((props,
|
||||
} else {
|
||||
name = teammate.username;
|
||||
if (teammate.id === currentUserId) {
|
||||
name += (' ' + Utils.localizeMessage({id: 'suggestion.user.isCurrent', defaultMessage: '(you)'}));
|
||||
name += (' ' + props.intl.formatMessage({id: 'suggestion.user.isCurrent', defaultMessage: '(you)'}));
|
||||
}
|
||||
description = deactivated;
|
||||
}
|
||||
@@ -317,7 +319,7 @@ function mapStateToPropsForSwitchChannelSuggestion(state: GlobalState, ownProps:
|
||||
};
|
||||
}
|
||||
|
||||
const ConnectedSwitchChannelSuggestion = connect(mapStateToPropsForSwitchChannelSuggestion, null, null, {forwardRef: true})(SwitchChannelSuggestion);
|
||||
const ConnectedSwitchChannelSuggestion = connect(mapStateToPropsForSwitchChannelSuggestion, null, null, {forwardRef: true})(injectIntl(SwitchChannelSuggestion));
|
||||
|
||||
let prefix = '';
|
||||
|
||||
@@ -528,6 +530,8 @@ export default class SwitchChannelProvider extends Provider {
|
||||
}
|
||||
|
||||
userWrappedChannel(user: UserProfile, channel?: ChannelItem): WrappedChannel {
|
||||
const intl = getIntl();
|
||||
|
||||
let displayName = '';
|
||||
const currentUserId = getCurrentUserId(this.store.getState());
|
||||
|
||||
@@ -545,7 +549,7 @@ export default class SwitchChannelProvider extends Provider {
|
||||
}
|
||||
|
||||
if (user.id === currentUserId && displayName) {
|
||||
displayName += (' ' + Utils.localizeMessage({id: 'suggestion.user.isCurrent', defaultMessage: '(you)'}));
|
||||
displayName += (' ' + intl.formatMessage({id: 'suggestion.user.isCurrent', defaultMessage: '(you)'}));
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -30,12 +30,22 @@ exports[`components/team_members_dropdown should match snapshot for a bot with g
|
||||
id="removeFromTeam"
|
||||
onClick={[Function]}
|
||||
show={true}
|
||||
text="Remove From Team"
|
||||
text={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Remove from Team"
|
||||
id="team_members_dropdown.leave_team"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<MenuItemAction
|
||||
onClick={[Function]}
|
||||
show={true}
|
||||
text="Make Member"
|
||||
text={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Make Team Member"
|
||||
id="team_members_dropdown.makeMember"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
</div>
|
||||
@@ -72,12 +82,22 @@ exports[`components/team_members_dropdown should match snapshot for team_members
|
||||
id="removeFromTeam"
|
||||
onClick={[Function]}
|
||||
show={true}
|
||||
text="Remove From Team"
|
||||
text={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Remove from Team"
|
||||
id="team_members_dropdown.leave_team"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<MenuItemAction
|
||||
onClick={[Function]}
|
||||
show={true}
|
||||
text="Make Member"
|
||||
text={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Make Team Member"
|
||||
id="team_members_dropdown.makeMember"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
</div>
|
||||
@@ -114,12 +134,22 @@ exports[`components/team_members_dropdown should match snapshot opening dropdown
|
||||
id="removeFromTeam"
|
||||
onClick={[Function]}
|
||||
show={true}
|
||||
text="Remove From Team"
|
||||
text={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Remove from Team"
|
||||
id="team_members_dropdown.leave_team"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<MenuItemAction
|
||||
onClick={[Function]}
|
||||
show={true}
|
||||
text="Make Member"
|
||||
text={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Make Team Member"
|
||||
id="team_members_dropdown.makeMember"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
</div>
|
||||
@@ -155,7 +185,12 @@ exports[`components/team_members_dropdown should match snapshot with group-const
|
||||
<MenuItemAction
|
||||
onClick={[Function]}
|
||||
show={true}
|
||||
text="Make Member"
|
||||
text={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Make Team Member"
|
||||
id="team_members_dropdown.makeMember"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import TeamMembersDropdown from 'components/team_members_dropdown/team_members_dropdown';
|
||||
|
||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
describe('components/team_members_dropdown', () => {
|
||||
@@ -40,14 +40,14 @@ describe('components/team_members_dropdown', () => {
|
||||
};
|
||||
|
||||
test('should match snapshot for team_members_dropdown', () => {
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<TeamMembersDropdown {...baseProps}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot opening dropdown upwards', () => {
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<TeamMembersDropdown
|
||||
{...baseProps}
|
||||
index={4}
|
||||
@@ -59,7 +59,7 @@ describe('components/team_members_dropdown', () => {
|
||||
|
||||
test('should match snapshot with group-constrained team', () => {
|
||||
baseProps.currentTeam.group_constrained = true;
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<TeamMembersDropdown {...baseProps}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
@@ -68,7 +68,7 @@ describe('components/team_members_dropdown', () => {
|
||||
test('should match snapshot for a bot with group-constrained team', () => {
|
||||
baseProps.currentTeam.group_constrained = true;
|
||||
baseProps.user = bot;
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<TeamMembersDropdown {...baseProps}/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage, injectIntl} from 'react-intl';
|
||||
import type {WrappedComponentProps} from 'react-intl';
|
||||
|
||||
import type {Team, TeamMembership} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
@@ -17,11 +18,10 @@ import Menu from 'components/widgets/menu/menu';
|
||||
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
|
||||
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
import * as Utils from 'utils/utils';
|
||||
|
||||
const ROWS_FROM_BOTTOM_TO_OPEN_UP = 3;
|
||||
|
||||
type Props = {
|
||||
type Props = WrappedComponentProps & {
|
||||
user: UserProfile;
|
||||
currentUser: UserProfile;
|
||||
teamMember: TeamMembership;
|
||||
@@ -50,7 +50,7 @@ type State = {
|
||||
role: string|null;
|
||||
}
|
||||
|
||||
export default class TeamMembersDropdown extends React.PureComponent<Props, State> {
|
||||
class TeamMembersDropdown extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
@@ -141,7 +141,7 @@ export default class TeamMembersDropdown extends React.PureComponent<Props, Stat
|
||||
);
|
||||
}
|
||||
|
||||
const {currentTeam, teamMember, user} = this.props;
|
||||
const {currentTeam, teamMember, user, intl} = this.props;
|
||||
|
||||
let currentRoles = null;
|
||||
|
||||
@@ -253,19 +253,34 @@ export default class TeamMembersDropdown extends React.PureComponent<Props, Stat
|
||||
<Menu.ItemAction
|
||||
id='removeFromTeam'
|
||||
onClick={this.handleRemoveFromTeam}
|
||||
text={Utils.localizeMessage({id: 'team_members_dropdown.leave_team', defaultMessage: 'Remove From Team'})}
|
||||
text={
|
||||
<FormattedMessage
|
||||
id='team_members_dropdown.leave_team'
|
||||
defaultMessage='Remove from Team'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
const menuMakeAdmin = (
|
||||
<Menu.ItemAction
|
||||
onClick={this.handleMakeAdmin}
|
||||
text={Utils.localizeMessage({id: 'team_members_dropdown.makeAdmin', defaultMessage: 'Make Team Admin'})}
|
||||
text={
|
||||
<FormattedMessage
|
||||
id='team_members_dropdown.makeAdmin'
|
||||
defaultMessage='Make Team Admin'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
const menuMakeMember = (
|
||||
<Menu.ItemAction
|
||||
onClick={this.handleMakeMember}
|
||||
text={Utils.localizeMessage({id: 'team_members_dropdown.makeMember', defaultMessage: 'Make Member'})}
|
||||
text={
|
||||
<FormattedMessage
|
||||
id='team_members_dropdown.makeMember'
|
||||
defaultMessage='Make Team Member'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
@@ -283,7 +298,7 @@ export default class TeamMembersDropdown extends React.PureComponent<Props, Stat
|
||||
<Menu
|
||||
openLeft={true}
|
||||
openUp={openUp}
|
||||
ariaLabel={Utils.localizeMessage({id: 'team_members_dropdown.menuAriaLabel', defaultMessage: 'Change the role of a team member'})}
|
||||
ariaLabel={intl.formatMessage({id: 'team_members_dropdown.menuAriaLabel', defaultMessage: 'Change the role of a team member'})}
|
||||
>
|
||||
{canRemoveFromTeam ? menuRemove : null}
|
||||
{showMakeAdmin ? menuMakeAdmin : null}
|
||||
@@ -296,3 +311,5 @@ export default class TeamMembersDropdown extends React.PureComponent<Props, Stat
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(TeamMembersDropdown);
|
||||
|
||||
@@ -6,7 +6,8 @@ import React from 'react';
|
||||
import {DragDropContext, Droppable} from 'react-beautiful-dnd';
|
||||
import type {DroppableProvided, DropResult} from 'react-beautiful-dnd';
|
||||
import Scrollbars from 'react-custom-scrollbars';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {injectIntl, FormattedMessage} from 'react-intl';
|
||||
import type {WrappedComponentProps} from 'react-intl';
|
||||
import type {RouteComponentProps} from 'react-router-dom';
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
@@ -26,7 +27,7 @@ import * as Utils from 'utils/utils';
|
||||
|
||||
import type {PropsFromRedux} from './index';
|
||||
|
||||
export interface Props extends PropsFromRedux {
|
||||
export interface Props extends PropsFromRedux, WrappedComponentProps {
|
||||
location: RouteComponentProps['location'];
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ export function renderThumbVertical(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
export default class TeamSidebar extends React.PureComponent<Props, State> {
|
||||
export class TeamSidebar extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
@@ -202,6 +203,8 @@ export default class TeamSidebar extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {intl} = this.props;
|
||||
|
||||
const root: Element | null = document.querySelector('#root');
|
||||
if (this.props.myTeams.length <= 1) {
|
||||
root!.classList.remove('multi-teams');
|
||||
@@ -246,7 +249,7 @@ export default class TeamSidebar extends React.PureComponent<Props, State> {
|
||||
<i
|
||||
className='icon icon-plus'
|
||||
role={'img'}
|
||||
aria-label={Utils.localizeMessage({id: 'sidebar.team_menu.button.plusIcon', defaultMessage: 'Plus Icon'})}
|
||||
aria-label={intl.formatMessage({id: 'sidebar.team_menu.button.plusIcon', defaultMessage: 'Plus Icon'})}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -347,3 +350,5 @@ export default class TeamSidebar extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(TeamSidebar);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import type {ListChildComponentProps} from 'react-window';
|
||||
import {VariableSizeList} from 'react-window';
|
||||
import InfiniteLoader from 'react-window-infinite-loader';
|
||||
@@ -19,7 +19,6 @@ import Menu from 'components/widgets/menu/menu';
|
||||
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
|
||||
|
||||
import {ModalIdentifiers} from 'utils/constants';
|
||||
import * as Utils from 'utils/utils';
|
||||
|
||||
import type {ModalData} from 'types/actions';
|
||||
|
||||
@@ -58,6 +57,7 @@ const UserGroupsList = (props: Props) => {
|
||||
const variableSizeListRef = useRef<VariableSizeList | null>(null);
|
||||
const [hasMounted, setHasMounted] = useState(false);
|
||||
const [overflowState, setOverflowState] = useState('overlay');
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
if (groups.length === 1) {
|
||||
@@ -171,7 +171,7 @@ const UserGroupsList = (props: Props) => {
|
||||
openLeft={true}
|
||||
openUp={groupListOpenUp(index)}
|
||||
className={'group-actions-menu'}
|
||||
ariaLabel={Utils.localizeMessage({id: 'admin.user_item.menuAriaLabel', defaultMessage: 'User Actions Menu'})}
|
||||
ariaLabel={formatMessage({id: 'admin.user_item.menuAriaLabel', defaultMessage: 'User Actions Menu'})}
|
||||
>
|
||||
<Menu.Group>
|
||||
<Menu.ItemAction
|
||||
@@ -179,7 +179,7 @@ const UserGroupsList = (props: Props) => {
|
||||
goToViewGroupModal(group);
|
||||
}}
|
||||
icon={<i className='icon-account-multiple-outline'/>}
|
||||
text={Utils.localizeMessage({id: 'user_groups_modal.viewGroup', defaultMessage: 'View Group'})}
|
||||
text={formatMessage({id: 'user_groups_modal.viewGroup', defaultMessage: 'View Group'})}
|
||||
disabled={false}
|
||||
/>
|
||||
</Menu.Group>
|
||||
@@ -190,7 +190,7 @@ const UserGroupsList = (props: Props) => {
|
||||
archiveGroup(group.id);
|
||||
}}
|
||||
icon={<i className='icon-archive-outline'/>}
|
||||
text={Utils.localizeMessage({id: 'user_groups_modal.archiveGroup', defaultMessage: 'Archive Group'})}
|
||||
text={formatMessage({id: 'user_groups_modal.archiveGroup', defaultMessage: 'Archive Group'})}
|
||||
disabled={false}
|
||||
isDangerous={true}
|
||||
/>
|
||||
@@ -200,7 +200,7 @@ const UserGroupsList = (props: Props) => {
|
||||
restoreGroup(group.id);
|
||||
}}
|
||||
icon={<i className='icon-restore'/>}
|
||||
text={Utils.localizeMessage({id: 'user_groups_modal.restoreGroup', defaultMessage: 'Restore Group'})}
|
||||
text={formatMessage({id: 'user_groups_modal.restoreGroup', defaultMessage: 'Restore Group'})}
|
||||
disabled={false}
|
||||
/>
|
||||
</Menu.Group>
|
||||
|
||||
@@ -5,6 +5,44 @@ exports[`components/widgets/menu/menu_items/submenu_item empty subMenu should ma
|
||||
action={[MockFunction]}
|
||||
direction="left"
|
||||
id="1"
|
||||
intl={
|
||||
Object {
|
||||
"$t": [Function],
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"defaultRichTextElements": undefined,
|
||||
"fallbackOnEmptyString": true,
|
||||
"formatDate": [Function],
|
||||
"formatDateTimeRange": [Function],
|
||||
"formatDateToParts": [Function],
|
||||
"formatDisplayName": [Function],
|
||||
"formatList": [Function],
|
||||
"formatListToParts": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatNumberToParts": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelativeTime": [Function],
|
||||
"formatTime": [Function],
|
||||
"formatTimeToParts": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getDisplayNames": [Function],
|
||||
"getListFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralRules": [Function],
|
||||
"getRelativeTimeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"onError": [Function],
|
||||
"onWarn": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": "Etc/UTC",
|
||||
}
|
||||
}
|
||||
renderSelected={true}
|
||||
root={true}
|
||||
show={true}
|
||||
@@ -59,6 +97,44 @@ exports[`components/widgets/menu/menu_items/submenu_item present subMenu should
|
||||
action={[MockFunction]}
|
||||
direction="left"
|
||||
id="1"
|
||||
intl={
|
||||
Object {
|
||||
"$t": [Function],
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"defaultRichTextElements": undefined,
|
||||
"fallbackOnEmptyString": true,
|
||||
"formatDate": [Function],
|
||||
"formatDateTimeRange": [Function],
|
||||
"formatDateToParts": [Function],
|
||||
"formatDisplayName": [Function],
|
||||
"formatList": [Function],
|
||||
"formatListToParts": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatNumberToParts": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelativeTime": [Function],
|
||||
"formatTime": [Function],
|
||||
"formatTimeToParts": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getDisplayNames": [Function],
|
||||
"getListFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralRules": [Function],
|
||||
"getRelativeTimeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"onError": [Function],
|
||||
"onWarn": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": "Etc/UTC",
|
||||
}
|
||||
}
|
||||
renderSelected={true}
|
||||
root={true}
|
||||
show={true}
|
||||
@@ -124,6 +200,44 @@ exports[`components/widgets/menu/menu_items/submenu_item present subMenu should
|
||||
<SubMenuItem
|
||||
direction="left"
|
||||
id="A"
|
||||
intl={
|
||||
Object {
|
||||
"$t": [Function],
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"defaultRichTextElements": undefined,
|
||||
"fallbackOnEmptyString": true,
|
||||
"formatDate": [Function],
|
||||
"formatDateTimeRange": [Function],
|
||||
"formatDateToParts": [Function],
|
||||
"formatDisplayName": [Function],
|
||||
"formatList": [Function],
|
||||
"formatListToParts": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatNumberToParts": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelativeTime": [Function],
|
||||
"formatTime": [Function],
|
||||
"formatTimeToParts": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getDisplayNames": [Function],
|
||||
"getListFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralRules": [Function],
|
||||
"getRelativeTimeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"onError": [Function],
|
||||
"onWarn": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": "Etc/UTC",
|
||||
}
|
||||
}
|
||||
renderSelected={true}
|
||||
root={false}
|
||||
show={true}
|
||||
@@ -180,6 +294,44 @@ exports[`components/widgets/menu/menu_items/submenu_item present subMenu should
|
||||
<SubMenuItem
|
||||
direction="left"
|
||||
id="B"
|
||||
intl={
|
||||
Object {
|
||||
"$t": [Function],
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"defaultRichTextElements": undefined,
|
||||
"fallbackOnEmptyString": true,
|
||||
"formatDate": [Function],
|
||||
"formatDateTimeRange": [Function],
|
||||
"formatDateToParts": [Function],
|
||||
"formatDisplayName": [Function],
|
||||
"formatList": [Function],
|
||||
"formatListToParts": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatNumberToParts": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelativeTime": [Function],
|
||||
"formatTime": [Function],
|
||||
"formatTimeToParts": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getDisplayNames": [Function],
|
||||
"getListFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralRules": [Function],
|
||||
"getRelativeTimeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"onError": [Function],
|
||||
"onWarn": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": "Etc/UTC",
|
||||
}
|
||||
}
|
||||
renderSelected={true}
|
||||
root={false}
|
||||
show={true}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {mount} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {render, screen, userEvent} from 'tests/react_testing_utils';
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {screen, userEvent, renderWithContext} from 'tests/react_testing_utils';
|
||||
import Constants from 'utils/constants';
|
||||
|
||||
import SubMenuItem from './submenu_item';
|
||||
import SubMenuItem, {SubMenuItem as SubMenuItemClass} from './submenu_item';
|
||||
|
||||
jest.mock('../is_mobile_view_hack', () => ({
|
||||
isMobile: jest.fn(() => false),
|
||||
@@ -15,7 +15,7 @@ jest.mock('../is_mobile_view_hack', () => ({
|
||||
|
||||
describe('components/widgets/menu/menu_items/submenu_item', () => {
|
||||
test('empty subMenu should match snapshot', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = mountWithIntl(
|
||||
<SubMenuItem
|
||||
key={'_pluginmenuitem'}
|
||||
id={'1'}
|
||||
@@ -30,7 +30,7 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
|
||||
});
|
||||
|
||||
test('present subMenu should match snapshot with submenu', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = mountWithIntl(
|
||||
<SubMenuItem
|
||||
key={'_pluginmenuitem'}
|
||||
id={'1'}
|
||||
@@ -60,7 +60,7 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
|
||||
const action2 = jest.fn();
|
||||
const action3 = jest.fn();
|
||||
|
||||
render(
|
||||
renderWithContext(
|
||||
<SubMenuItem
|
||||
key={'_pluginmenuitem'}
|
||||
id={'Z'}
|
||||
@@ -98,7 +98,7 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
|
||||
});
|
||||
|
||||
test('should show/hide submenu based on keyboard commands', () => {
|
||||
const wrapper = mount<SubMenuItem>(
|
||||
const wrapper = mountWithIntl(
|
||||
<SubMenuItem
|
||||
key={'_pluginmenuitem'}
|
||||
id={'1'}
|
||||
@@ -109,16 +109,18 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
wrapper.instance().show = jest.fn();
|
||||
wrapper.instance().hide = jest.fn();
|
||||
const instance = wrapper.find(SubMenuItemClass).instance() as SubMenuItemClass;
|
||||
|
||||
wrapper.instance().handleKeyDown({keyCode: Constants.KeyCodes.ENTER[1]} as any);
|
||||
expect(wrapper.instance().show).toHaveBeenCalled();
|
||||
instance.show = jest.fn();
|
||||
instance.hide = jest.fn();
|
||||
|
||||
wrapper.instance().handleKeyDown({keyCode: Constants.KeyCodes.LEFT[1]} as any);
|
||||
expect(wrapper.instance().hide).toHaveBeenCalled();
|
||||
instance.handleKeyDown({keyCode: Constants.KeyCodes.ENTER[1]} as any);
|
||||
expect(instance.show).toHaveBeenCalled();
|
||||
|
||||
wrapper.instance().handleKeyDown({keyCode: Constants.KeyCodes.RIGHT[1]} as any);
|
||||
expect(wrapper.instance().show).toHaveBeenCalled();
|
||||
instance.handleKeyDown({keyCode: Constants.KeyCodes.LEFT[1]} as any);
|
||||
expect(instance.hide).toHaveBeenCalled();
|
||||
|
||||
instance.handleKeyDown({keyCode: Constants.KeyCodes.RIGHT[1]} as any);
|
||||
expect(instance.show).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import type {CSSProperties} from 'react';
|
||||
import {injectIntl} from 'react-intl';
|
||||
import type {WrappedComponentProps} from 'react-intl';
|
||||
|
||||
import {showMobileSubMenuModal} from 'actions/global_actions';
|
||||
|
||||
import Constants from 'utils/constants';
|
||||
import * as Keyboard from 'utils/keyboard';
|
||||
import * as Utils from 'utils/utils';
|
||||
|
||||
import type {Menu} from 'types/store/plugins';
|
||||
|
||||
@@ -38,7 +39,7 @@ import './menu_item.scss';
|
||||
// }
|
||||
// Submenus can contain Submenus as well
|
||||
|
||||
export type Props = {
|
||||
export type Props = WrappedComponentProps & {
|
||||
id?: string;
|
||||
postId?: string;
|
||||
text: React.ReactNode;
|
||||
@@ -68,12 +69,12 @@ type State = {
|
||||
/**
|
||||
* @deprecated Use the "webapp/channels/src/components/menu" instead.
|
||||
*/
|
||||
export default class SubMenuItem extends React.PureComponent<Props, State> {
|
||||
export class SubMenuItem extends React.PureComponent<Props, State> {
|
||||
private node: React.RefObject<HTMLLIElement>;
|
||||
|
||||
public static defaultProps = {
|
||||
show: true,
|
||||
direction: 'left',
|
||||
direction: 'left' as const,
|
||||
subMenuClass: 'pl-4',
|
||||
renderSelected: true,
|
||||
};
|
||||
@@ -149,7 +150,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
public render() {
|
||||
const {id, postId, text, selectedValueText, subMenu, icon, filter, ariaLabel, direction, styleSelectableItem, extraText, renderSelected, rightDecorator, tabIndex} = this.props;
|
||||
const {id, postId, text, selectedValueText, subMenu, icon, filter, ariaLabel, direction, styleSelectableItem, extraText, renderSelected, rightDecorator, tabIndex, intl} = this.props;
|
||||
const isMobile = isMobileViewHack();
|
||||
|
||||
if (filter && !filter(id)) {
|
||||
@@ -191,7 +192,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
|
||||
const hasDivider = s.id === 'ChannelMenu-moveToDivider';
|
||||
let aria = ariaLabel;
|
||||
if (s.action) {
|
||||
aria = s.text === selectedValueText ? s.text + ' ' + Utils.localizeMessage({id: 'sidebar.menu.item.selected', defaultMessage: 'selected'}) : s.text + ' ' + Utils.localizeMessage({id: 'sidebar.menu.item.notSelected', defaultMessage: 'not selected'});
|
||||
aria = s.text === selectedValueText ? s.text + ' ' + intl.formatMessage({id: 'sidebar.menu.item.selected', defaultMessage: 'selected'}) : s.text + ' ' + intl.formatMessage({id: 'sidebar.menu.item.notSelected', defaultMessage: 'not selected'});
|
||||
}
|
||||
return (
|
||||
<span
|
||||
@@ -213,6 +214,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
|
||||
direction={s.direction}
|
||||
isHeader={s.isHeader}
|
||||
tabIndex={1}
|
||||
intl={this.props.intl}
|
||||
/>
|
||||
{s.text === selectedValueText && <span className='sorting-menu-checkbox'>
|
||||
<i className='icon-check'/>
|
||||
@@ -248,7 +250,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
|
||||
<span
|
||||
id={'channelHeaderDropdownIconRight_' + id}
|
||||
className={classNames([`fa fa-angle-right SubMenu__icon-right${hasSubmenu ? '' : '-empty'}`, {mobile: isMobile}])}
|
||||
aria-label={Utils.localizeMessage({id: 'post_info.submenu.icon', defaultMessage: 'submenu icon'}).toLowerCase()}
|
||||
aria-label={intl.formatMessage({id: 'post_info.submenu.icon', defaultMessage: 'submenu icon'}).toLowerCase()}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
@@ -259,3 +261,5 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(SubMenuItem);
|
||||
|
||||
@@ -44,25 +44,18 @@ exports[`components/submenu_modal should match snapshot 1`] = `
|
||||
ariaLabel="mobile submenu"
|
||||
openLeft={true}
|
||||
>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[MockFunction]}
|
||||
direction="left"
|
||||
id="A"
|
||||
key="A"
|
||||
renderSelected={true}
|
||||
root={false}
|
||||
show={true}
|
||||
subMenuClass="pl-4"
|
||||
text="Text A"
|
||||
/>
|
||||
<SubMenuItem
|
||||
<injectIntl(SubMenuItem)
|
||||
action={[MockFunction]}
|
||||
direction="left"
|
||||
id="B"
|
||||
key="B"
|
||||
renderSelected={true}
|
||||
root={false}
|
||||
show={true}
|
||||
subMenu={
|
||||
Array [
|
||||
Object {
|
||||
@@ -73,7 +66,6 @@ exports[`components/submenu_modal should match snapshot 1`] = `
|
||||
},
|
||||
]
|
||||
}
|
||||
subMenuClass="pl-4"
|
||||
text="Text B"
|
||||
/>
|
||||
</Menu>
|
||||
|
||||
@@ -6,8 +6,7 @@ import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Modal} from 'react-bootstrap';
|
||||
|
||||
import {withIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {render, screen, userEvent} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
|
||||
|
||||
import SubMenuModal from './submenu_modal';
|
||||
|
||||
@@ -16,8 +15,8 @@ jest.mock('../../is_mobile_view_hack', () => ({
|
||||
}));
|
||||
|
||||
(global as any).MutationObserver = class {
|
||||
public disconnect() {}
|
||||
public observe() {}
|
||||
public disconnect() { }
|
||||
public observe() { }
|
||||
};
|
||||
|
||||
describe('components/submenu_modal', () => {
|
||||
@@ -58,9 +57,9 @@ describe('components/submenu_modal', () => {
|
||||
});
|
||||
|
||||
test('should hide on modal body click', async () => {
|
||||
const view = render(withIntl(
|
||||
const view = renderWithContext(
|
||||
<SubMenuModal {...baseProps}/>,
|
||||
));
|
||||
);
|
||||
|
||||
screen.getByText('Text A');
|
||||
screen.getByText('Text B');
|
||||
@@ -78,7 +77,7 @@ describe('components/submenu_modal', () => {
|
||||
...baseProps,
|
||||
};
|
||||
|
||||
render(
|
||||
renderWithContext(
|
||||
<SubMenuModal {...props}/>,
|
||||
);
|
||||
|
||||
|
||||
29
webapp/channels/src/utils/i18n.test.tsx
Обычный файл
29
webapp/channels/src/utils/i18n.test.tsx
Обычный файл
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as i18nSelectors from 'selectors/i18n';
|
||||
|
||||
import {getIntl} from './i18n';
|
||||
|
||||
describe('i18n', () => {
|
||||
jest.spyOn(i18nSelectors, 'getTranslations').mockReturnValue({
|
||||
test_key: 'expected value',
|
||||
});
|
||||
jest.spyOn(i18nSelectors, 'getCurrentLocale').mockReturnValue('en');
|
||||
|
||||
it('getIntl.formatMessage should resolve translated string', () => {
|
||||
const intl = getIntl();
|
||||
const fm = intl.formatMessage; // avoid triggering mmjstool
|
||||
|
||||
const actual = fm({id: 'test_key', defaultMessage: 'not found'});
|
||||
expect(actual).toBe('expected value');
|
||||
});
|
||||
|
||||
it('getIntl.formatMessage should resolve unknown string to default message', () => {
|
||||
const intl = getIntl();
|
||||
const fm = intl.formatMessage; // avoid triggering mmjstool
|
||||
|
||||
const actual = fm({id: 'unknown_key', defaultMessage: 'not found'});
|
||||
expect(actual).toBe('not found');
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,25 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage, type IntlShape, type MessageDescriptor} from 'react-intl';
|
||||
import {FormattedMessage, createIntl, createIntlCache, type IntlShape, type MessageDescriptor} from 'react-intl';
|
||||
|
||||
import {getCurrentLocale, getTranslations} from 'selectors/i18n';
|
||||
import store from 'stores/redux_store';
|
||||
|
||||
const cache = createIntlCache();
|
||||
|
||||
// getIntl returns an instance of IntlShape for the current locale.
|
||||
// Prefer `useIntl` and `FormattedMessage` and only use this selectively when
|
||||
// outside of React components.
|
||||
export function getIntl(): IntlShape {
|
||||
const state = store.getState();
|
||||
const locale = getCurrentLocale(state);
|
||||
|
||||
return createIntl({
|
||||
locale,
|
||||
messages: getTranslations(state, locale),
|
||||
}, cache);
|
||||
}
|
||||
|
||||
export function isMessageDescriptor(descriptor: unknown): descriptor is MessageDescriptor {
|
||||
return Boolean(descriptor && (descriptor as MessageDescriptor).id);
|
||||
|
||||
Ссылка в новой задаче
Block a user