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>
Этот коммит содержится в:
Jesse Hallam
2024-12-13 09:49:28 -04:00
коммит произвёл GitHub
родитель bc22267829
Коммит 7179a9d1d6
31 изменённых файлов: 475 добавлений и 228 удалений

Просмотреть файл

@@ -181,7 +181,7 @@ describe('Verify Accessibility Support in Post', () => {
} }
// * Verify focus is on the reactions button // * Verify focus is on the reactions button
cy.get(`#CENTER_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'add reaction'); cy.get(`#CENTER_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'Add Reaction');
cy.focused().tab(); cy.focused().tab();
// * Verify focus is on the save post button // * Verify focus is on the save post button
@@ -247,7 +247,7 @@ describe('Verify Accessibility Support in Post', () => {
cy.focused().tab({shift: true}); cy.focused().tab({shift: true});
// * Verify focus is on the reactions button // * Verify focus is on the reactions button
cy.get(`#RHS_COMMENT_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'add reaction'); cy.get(`#RHS_COMMENT_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'Add Reaction');
cy.focused().tab({shift: true}); cy.focused().tab({shift: true});
// * Verify focus is on most recent action // * Verify focus is on most recent action

Просмотреть файл

@@ -24,7 +24,6 @@ import Pluggable from 'plugins/pluggable';
import {createCallContext} from 'utils/apps'; import {createCallContext} from 'utils/apps';
import {Constants, Locations, ModalIdentifiers} from 'utils/constants'; import {Constants, Locations, ModalIdentifiers} from 'utils/constants';
import * as PostUtils from 'utils/post_utils'; import * as PostUtils from 'utils/post_utils';
import * as Utils from 'utils/utils';
import type {ModalData} from 'types/actions'; import type {ModalData} from 'types/actions';
import type {HandleBindingClick, OpenAppsModal, PostEphemeralCallResponseForPost} from 'types/apps'; import type {HandleBindingClick, OpenAppsModal, PostEphemeralCallResponseForPost} from 'types/apps';
@@ -395,7 +394,7 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
key='more-actions-button' key='more-actions-button'
ref={this.buttonRef} ref={this.buttonRef}
id={`${this.props.location}_actions_button_${this.props.post.id}`} 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', { className={classNames('post-menu__item', {
'post-menu__item--active': this.props.isMenuOpen, '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}`} id={`${this.props.location}_actions_dropdown_${this.props.post.id}`}
openLeft={true} openLeft={true}
openUp={this.state.openUp} 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}`} key={`${this.props.location}_actions_dropdown_${this.props.post.id}`}
> >
{menuItems} {menuItems}

Просмотреть файл

@@ -17,7 +17,7 @@ exports[`components/MarkdownImage should match snapshot 1`] = `
`; `;
exports[`components/MarkdownImage should match snapshot for SizeAwareImage dimensions 1`] = ` exports[`components/MarkdownImage should match snapshot for SizeAwareImage dimensions 1`] = `
<SizeAwareImage <injectIntl(SizeAwareImage)
alt="test image" alt="test image"
className="markdown-inline-img markdown-inline-img--hover markdown-inline-img--no-border" className="markdown-inline-img markdown-inline-img--hover markdown-inline-img--no-border"
dimensions={ dimensions={
@@ -62,7 +62,7 @@ exports[`components/MarkdownImage should provide image src as an alt text for Ma
imageKey="https://example.com/image.png" imageKey="https://example.com/image.png"
postId="post_id" postId="post_id"
> >
<SizeAwareImage <injectIntl(SizeAwareImage)
alt="" alt=""
className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active" className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active"
dimensions={Object {}} 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`] = ` exports[`components/MarkdownImage should render an image with no preview if the source is safe and the image is a link 1`] = `
<div> <div>
<SizeAwareImage <injectIntl(SizeAwareImage)
alt="test image" alt="test image"
className="markdown-inline-img markdown-inline-img--hover markdown-inline-img--no-border" className="markdown-inline-img markdown-inline-img--hover markdown-inline-img--no-border"
dimensions={ 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`] = ` exports[`components/MarkdownImage should render an image with preview modal if the source is safe 1`] = `
<div> <div>
<SizeAwareImage <injectIntl(SizeAwareImage)
alt="test image" alt="test image"
className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active" className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active"
dimensions={ dimensions={
@@ -152,7 +152,7 @@ exports[`components/MarkdownImage should render image with MarkdownImageExpand i
imageKey="https://example.com/image.png" imageKey="https://example.com/image.png"
postId="post_id" postId="post_id"
> >
<SizeAwareImage <injectIntl(SizeAwareImage)
alt="test image" alt="test image"
className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active" className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active"
dimensions={Object {}} dimensions={Object {}}

Просмотреть файл

@@ -26,7 +26,7 @@ exports[`components/post_view/PostReaction should match snapshot 1`] = `
} }
> >
<button <button
aria-label="add reaction" aria-label="Add Reaction"
className="post-menu__item post-menu__item--reactions" className="post-menu__item post-menu__item--reactions"
data-testid="post-reaction-emoji-icon" data-testid="post-reaction-emoji-icon"
id="CENTER_reaction_post_id_1" id="CENTER_reaction_post_id_1"

Просмотреть файл

@@ -1,12 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react'; import React from 'react';
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
import {TestHelper} from 'utils/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', () => { describe('components/post_view/PostReaction', () => {
const baseProps = { const baseProps = {
@@ -23,13 +23,13 @@ describe('components/post_view/PostReaction', () => {
}; };
test('should match snapshot', () => { test('should match snapshot', () => {
const wrapper = shallow(<PostReaction {...baseProps}/>); const wrapper = shallowWithIntl(<PostReaction {...baseProps}/>);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('should call toggleReaction and toggleEmojiPicker on handleToggleEmoji', () => { test('should call toggleReaction and toggleEmojiPicker on handleToggleEmoji', () => {
const wrapper = shallow(<PostReaction {...baseProps}/>); const wrapper = shallowWithIntl(<PostReaction {...baseProps}/>);
const instance = wrapper.instance() as PostReaction; const instance = wrapper.instance() as PostReactionComponent;
instance.handleToggleEmoji(TestHelper.getCustomEmojiMock({name: 'smile'})); instance.handleToggleEmoji(TestHelper.getCustomEmojiMock({name: 'smile'}));
expect(baseProps.actions.toggleReaction).toHaveBeenCalledTimes(1); expect(baseProps.actions.toggleReaction).toHaveBeenCalledTimes(1);

Просмотреть файл

@@ -3,7 +3,8 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React from 'react'; 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'; 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 WithTooltip from 'components/with_tooltip/with_tooltip_new';
import {Locations} from 'utils/constants'; import {Locations} from 'utils/constants';
import {localizeMessage} from 'utils/utils';
const TOP_OFFSET = -7; const TOP_OFFSET = -7;
@@ -27,12 +27,12 @@ const messages = defineMessages({
}, },
}); });
export type Props = { export type Props = WrappedComponentProps & {
channelId?: string; channelId?: string;
postId: string; postId: string;
teamId: string; teamId: string;
getDotMenuRef: () => HTMLDivElement | null; getDotMenuRef: () => HTMLDivElement | null;
location: keyof typeof Locations; location?: keyof typeof Locations;
showEmojiPicker: boolean; showEmojiPicker: boolean;
toggleEmojiPicker: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void; toggleEmojiPicker: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
actions: { actions: {
@@ -45,7 +45,7 @@ type State = {
showEmojiPicker: boolean; showEmojiPicker: boolean;
} }
export default class PostReaction extends React.PureComponent<Props, State> { export class PostReaction extends React.PureComponent<Props, State> {
public static defaultProps: Partial<Props> = { public static defaultProps: Partial<Props> = {
location: Locations.CENTER as 'CENTER', location: Locations.CENTER as 'CENTER',
showEmojiPicker: false, showEmojiPicker: false,
@@ -65,6 +65,7 @@ export default class PostReaction extends React.PureComponent<Props, State> {
postId, postId,
showEmojiPicker, showEmojiPicker,
teamId, teamId,
intl,
} = this.props; } = this.props;
let spaceRequiredAbove; let spaceRequiredAbove;
@@ -96,7 +97,7 @@ export default class PostReaction extends React.PureComponent<Props, State> {
<button <button
data-testid='post-reaction-emoji-icon' data-testid='post-reaction-emoji-icon'
id={`${location}_reaction_${postId}`} 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', { className={classNames('post-menu__item', 'post-menu__item--reactions', {
'post-menu__item--active': showEmojiPicker, '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" pluggableName="LeftSidebarHeader"
/> />
</div> </div>
<Connect(SidebarList) <Connect(injectIntl(SidebarList))
handleOpenMoreDirectChannelsModal={[Function]} handleOpenMoreDirectChannelsModal={[Function]}
onDragEnd={[Function]} onDragEnd={[Function]}
onDragStart={[Function]} onDragStart={[Function]}
@@ -79,7 +79,7 @@ exports[`components/sidebar should match snapshot when direct channels modal is
pluggableName="LeftSidebarHeader" pluggableName="LeftSidebarHeader"
/> />
</div> </div>
<Connect(SidebarList) <Connect(injectIntl(SidebarList))
handleOpenMoreDirectChannelsModal={[Function]} handleOpenMoreDirectChannelsModal={[Function]}
onDragEnd={[Function]} onDragEnd={[Function]}
onDragStart={[Function]} onDragStart={[Function]}
@@ -126,7 +126,7 @@ exports[`components/sidebar should match snapshot when more channels modal is op
pluggableName="LeftSidebarHeader" pluggableName="LeftSidebarHeader"
/> />
</div> </div>
<Connect(SidebarList) <Connect(injectIntl(SidebarList))
handleOpenMoreDirectChannelsModal={[Function]} handleOpenMoreDirectChannelsModal={[Function]}
onDragEnd={[Function]} onDragEnd={[Function]}
onDragStart={[Function]} onDragStart={[Function]}

Просмотреть файл

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot 1`] = ` exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot 1`] = `
<Connect(SidebarChannelLink) <Connect(injectIntl(SidebarChannelLink))
ariaLabelPrefix="public channel" ariaLabelPrefix="public channel"
channel={ channel={
Object { 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`] = ` exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot when private channel 1`] = `
<Connect(SidebarChannelLink) <Connect(injectIntl(SidebarChannelLink))
ariaLabelPrefix="private channel" ariaLabelPrefix="private channel"
channel={ channel={
Object { 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`] = ` exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot when shared channel 1`] = `
<Connect(SidebarChannelLink) <Connect(injectIntl(SidebarChannelLink))
ariaLabelPrefix="public channel" ariaLabelPrefix="public channel"
channel={ channel={
Object { 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`] = ` exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot when shared private channel 1`] = `
<Connect(SidebarChannelLink) <Connect(injectIntl(SidebarChannelLink))
ariaLabelPrefix="private channel" ariaLabelPrefix="private channel"
channel={ channel={
Object { Object {

Просмотреть файл

@@ -1,13 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react'; import React from 'react';
import type {ChannelType} from '@mattermost/types/channels'; 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 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', () => { describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
const baseProps = { const baseProps = {
channel: { channel: {
@@ -49,7 +51,7 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
}; };
test('should match snapshot', () => { test('should match snapshot', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<SidebarChannelLink {...baseProps}/>, <SidebarChannelLink {...baseProps}/>,
); );
@@ -60,7 +62,7 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
const userAgentMock = jest.requireMock('utils/user_agent'); const userAgentMock = jest.requireMock('utils/user_agent');
userAgentMock.isDesktopApp.mockImplementation(() => false); userAgentMock.isDesktopApp.mockImplementation(() => false);
const wrapper = shallow( const wrapper = shallowWithIntl(
<SidebarChannelLink {...baseProps}/>, <SidebarChannelLink {...baseProps}/>,
); );
@@ -68,7 +70,7 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
}); });
test('should match snapshot when tooltip is enabled', () => { test('should match snapshot when tooltip is enabled', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<SidebarChannelLink {...baseProps}/>, <SidebarChannelLink {...baseProps}/>,
); );
@@ -84,7 +86,7 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
ariaLabelPrefix: 'aria_label_prefix_', ariaLabelPrefix: 'aria_label_prefix_',
}; };
const wrapper = shallow( const wrapper = shallowWithIntl(
<SidebarChannelLink {...props}/>, <SidebarChannelLink {...props}/>,
); );
@@ -92,10 +94,10 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
}); });
test('should enable tooltip when needed', () => { test('should enable tooltip when needed', () => {
const wrapper = shallow<SidebarChannelLink>( const wrapper = shallowWithIntl(
<SidebarChannelLink {...baseProps}/>, <SidebarChannelLink {...baseProps}/>,
); );
const instance = wrapper.instance(); const instance = wrapper.instance() as SidebarChannelLinkComponent;
instance.labelRef = { instance.labelRef = {
current: { current: {

Просмотреть файл

@@ -3,6 +3,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React from 'react'; import React from 'react';
import {type WrappedComponentProps, injectIntl} from 'react-intl';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import type {Channel} from '@mattermost/types/channels'; import type {Channel} from '@mattermost/types/channels';
@@ -19,7 +20,6 @@ import Constants, {RHSStates} from 'utils/constants';
import {wrapEmojis} from 'utils/emoji_utils'; import {wrapEmojis} from 'utils/emoji_utils';
import {cmdOrCtrlPressed} from 'utils/keyboard'; import {cmdOrCtrlPressed} from 'utils/keyboard';
import {Mark} from 'utils/performance_telemetry'; import {Mark} from 'utils/performance_telemetry';
import {localizeMessage} from 'utils/utils';
import type {RhsState} from 'types/store/rhs'; import type {RhsState} from 'types/store/rhs';
@@ -28,7 +28,7 @@ import ChannelPencilIcon from '../channel_pencil_icon';
import SidebarChannelIcon from '../sidebar_channel_icon'; import SidebarChannelIcon from '../sidebar_channel_icon';
import SidebarChannelMenu from '../sidebar_channel_menu'; import SidebarChannelMenu from '../sidebar_channel_menu';
type Props = { type Props = WrappedComponentProps & {
channel: Channel; channel: Channel;
link: string; link: string;
label: string; label: string;
@@ -79,7 +79,7 @@ type State = {
showTooltip: boolean; showTooltip: boolean;
}; };
export default class SidebarChannelLink extends React.PureComponent<Props, State> { export class SidebarChannelLink extends React.PureComponent<Props, State> {
labelRef: React.RefObject<HTMLDivElement>; labelRef: React.RefObject<HTMLDivElement>;
constructor(props: Props) { constructor(props: Props) {
@@ -110,7 +110,7 @@ export default class SidebarChannelLink extends React.PureComponent<Props, State
}; };
getAriaLabel = (): string => { getAriaLabel = (): string => {
const {label, ariaLabelPrefix, unreadMentions} = this.props; const {label, ariaLabelPrefix, unreadMentions, intl} = this.props;
let ariaLabel = label; let ariaLabel = label;
@@ -119,13 +119,13 @@ export default class SidebarChannelLink extends React.PureComponent<Props, State
} }
if (unreadMentions === 1) { 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) { } 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) { 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(); 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 // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match snapshot 1`] = ` exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match snapshot 1`] = `
<Connect(SidebarChannelLink) <Connect(injectIntl(SidebarChannelLink))
channel={ channel={
Object { Object {
"create_at": 0, "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`] = ` 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={ channel={
Object { Object {
"create_at": 0, "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`] = ` exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match snapshot if DM is with current user 1`] = `
<Connect(SidebarChannelLink) <Connect(injectIntl(SidebarChannelLink))
channel={ channel={
Object { Object {
"create_at": 0, "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`] = ` exports[`components/sidebar/sidebar_channel/sidebar_direct_channel should match snapshot if DM is with deleted user 1`] = `
<Connect(SidebarChannelLink) <Connect(injectIntl(SidebarChannelLink))
channel={ channel={
Object { Object {
"create_at": 0, "create_at": 0,

Просмотреть файл

@@ -11,10 +11,11 @@ import type {TeamType} from '@mattermost/types/teams';
import {CategoryTypes} from 'mattermost-redux/constants/channel_categories'; import {CategoryTypes} from 'mattermost-redux/constants/channel_categories';
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
import {DraggingStates, DraggingStateTypes} from 'utils/constants'; import {DraggingStates, DraggingStateTypes} from 'utils/constants';
import {TestHelper} from 'utils/test_helper'; import {TestHelper} from 'utils/test_helper';
import SidebarList from './sidebar_list'; import SidebarList, {type SidebarList as SidebarListComponent} from './sidebar_list';
describe('SidebarList', () => { describe('SidebarList', () => {
const currentChannel = TestHelper.getChannelMock({ const currentChannel = TestHelper.getChannelMock({
@@ -115,7 +116,7 @@ describe('SidebarList', () => {
}; };
test('should match snapshot', () => { test('should match snapshot', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>, <SidebarList {...baseProps}/>,
); );
@@ -130,7 +131,7 @@ describe('SidebarList', () => {
}); });
test('should close sidebar on mobile when channel is selected (ie. changed)', () => { test('should close sidebar on mobile when channel is selected (ie. changed)', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>, <SidebarList {...baseProps}/>,
); );
@@ -139,11 +140,12 @@ describe('SidebarList', () => {
}); });
test('should scroll to top when team changes', () => { test('should scroll to top when team changes', () => {
const wrapper = shallow<SidebarList>( const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>, <SidebarList {...baseProps}/>,
); );
const instance = wrapper.instance() as SidebarListComponent;
wrapper.instance().scrollbar = { instance.scrollbar = {
current: { current: {
scrollToTop: jest.fn(), scrollToTop: jest.fn(),
} as any, } as any,
@@ -155,14 +157,14 @@ describe('SidebarList', () => {
}; };
wrapper.setProps({currentTeam: newCurrentTeam}); 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', () => { test('should display unread scroll indicator when channels appear outside visible area', () => {
const wrapper = shallow<SidebarList>( const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>, <SidebarList {...baseProps}/>,
); );
const instance = wrapper.instance(); const instance = wrapper.instance() as SidebarListComponent;
instance.scrollbar = { instance.scrollbar = {
current: { current: {
@@ -189,10 +191,10 @@ describe('SidebarList', () => {
}); });
test('should scroll to correct position when scrolling to channel', () => { test('should scroll to correct position when scrolling to channel', () => {
const wrapper = shallow<SidebarList>( const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>, <SidebarList {...baseProps}/>,
); );
const instance = wrapper.instance(); const instance = wrapper.instance() as SidebarListComponent;
instance.scrollToPosition = jest.fn(); instance.scrollToPosition = jest.fn();
@@ -218,7 +220,7 @@ describe('SidebarList', () => {
style: {}, style: {},
}]); }]);
const wrapper = shallow<SidebarList>( const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>, <SidebarList {...baseProps}/>,
); );
@@ -232,7 +234,9 @@ describe('SidebarList', () => {
type: DraggingStateTypes.CATEGORY, type: DraggingStateTypes.CATEGORY,
}; };
wrapper.instance().onBeforeCapture(categoryBefore); const instance = wrapper.instance() as SidebarListComponent;
instance.onBeforeCapture(categoryBefore);
expect(baseProps.actions.setDraggingState).toHaveBeenCalledWith(expectedCategoryBefore); expect(baseProps.actions.setDraggingState).toHaveBeenCalledWith(expectedCategoryBefore);
const channelBefore = { const channelBefore = {
@@ -245,14 +249,15 @@ describe('SidebarList', () => {
type: DraggingStateTypes.CHANNEL, type: DraggingStateTypes.CHANNEL,
}; };
wrapper.instance().onBeforeCapture(channelBefore); instance.onBeforeCapture(channelBefore);
expect(baseProps.actions.setDraggingState).toHaveBeenCalledWith(expectedChannelBefore); expect(baseProps.actions.setDraggingState).toHaveBeenCalledWith(expectedChannelBefore);
}); });
test('should call correct action on dropping item', () => { test('should call correct action on dropping item', () => {
const wrapper = shallow<SidebarList>( const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>, <SidebarList {...baseProps}/>,
); );
const instance = wrapper.instance() as SidebarListComponent;
const categoryResult: DropResult = { const categoryResult: DropResult = {
reason: 'DROP', reason: 'DROP',
@@ -269,7 +274,7 @@ describe('SidebarList', () => {
mode: 'SNAP' as MovementMode, mode: 'SNAP' as MovementMode,
}; };
wrapper.instance().onDragEnd(categoryResult); instance.onDragEnd(categoryResult);
expect(baseProps.actions.moveCategory).toHaveBeenCalledWith(baseProps.currentTeam.id, categoryResult.draggableId, categoryResult.destination!.index); expect(baseProps.actions.moveCategory).toHaveBeenCalledWith(baseProps.currentTeam.id, categoryResult.draggableId, categoryResult.destination!.index);
const channelResult: DropResult = { const channelResult: DropResult = {
@@ -287,7 +292,7 @@ describe('SidebarList', () => {
mode: 'SNAP' as MovementMode, mode: 'SNAP' as MovementMode,
}; };
wrapper.instance().onDragEnd(channelResult); instance.onDragEnd(channelResult);
expect(baseProps.actions.moveChannelsInSidebar).toHaveBeenCalledWith(channelResult.destination!.droppableId, channelResult.destination!.index, channelResult.draggableId); 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 {DragDropContext, Droppable} from 'react-beautiful-dnd';
import type {DropResult, DragStart, BeforeCapture} from 'react-beautiful-dnd'; import type {DropResult, DragStart, BeforeCapture} from 'react-beautiful-dnd';
import Scrollbars from 'react-custom-scrollbars'; import Scrollbars from 'react-custom-scrollbars';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage, injectIntl, type WrappedComponentProps} from 'react-intl';
import {SpringSystem} from 'rebound'; import {SpringSystem} from 'rebound';
import type {Spring} 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 {findNextUnreadChannelId} from 'utils/channel_utils';
import {Constants, DraggingStates, DraggingStateTypes} from 'utils/constants'; import {Constants, DraggingStates, DraggingStateTypes} from 'utils/constants';
import {isKeyPressed, cmdOrCtrlPressed} from 'utils/keyboard'; 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 {DraggingState} from 'types/store';
import type {StaticPage} from 'types/store/lhs'; import type {StaticPage} from 'types/store/lhs';
@@ -74,7 +74,7 @@ export function renderThumbVertical(props: React.HTMLProps<HTMLDivElement>) {
const scrollbarStyles: CSSProperties = {position: 'absolute'}; const scrollbarStyles: CSSProperties = {position: 'absolute'};
type Props = { type Props = WrappedComponentProps & {
currentTeam?: Team; currentTeam?: Team;
currentChannelId: string; currentChannelId: string;
categories: ChannelCategory[]; categories: ChannelCategory[];
@@ -122,7 +122,7 @@ const categoryHeaderHeight = 32;
// that the channel is not under the unread indicator. // that the channel is not under the unread indicator.
const scrollMarginWithUnread = 55; const scrollMarginWithUnread = 55;
export default class SidebarList extends React.PureComponent<Props, State> { export class SidebarList extends React.PureComponent<Props, State> {
channelRefs: Map<string, HTMLLIElement>; channelRefs: Map<string, HTMLLIElement>;
scrollbar: React.RefObject<Scrollbars>; scrollbar: React.RefObject<Scrollbars>;
animate: SpringSystem; 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 ( 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 <div
className="image-permalink" className="image-permalink"
> >
<SizeAwareImage <injectIntl(SizeAwareImage)
className="image-permalink" className="image-permalink"
dimensions={ dimensions={
Object { Object {
@@ -108,7 +108,7 @@ exports[`components/SingleImageView should match snapshot 1`] = `
<div <div
className="" className=""
> >
<SizeAwareImage <injectIntl(SizeAwareImage)
className="" className=""
dimensions={ dimensions={
Object { Object {
@@ -183,7 +183,7 @@ exports[`components/SingleImageView should match snapshot 2`] = `
<div <div
className="" className=""
> >
<SizeAwareImage <injectIntl(SizeAwareImage)
className="" className=""
dimensions={ dimensions={
Object { Object {
@@ -262,7 +262,7 @@ exports[`components/SingleImageView should match snapshot, SVG image 1`] = `
<div <div
className="" className=""
> >
<SizeAwareImage <injectIntl(SizeAwareImage)
className="" className=""
dimensions={ dimensions={
Object { Object {
@@ -341,7 +341,7 @@ exports[`components/SingleImageView should match snapshot, SVG image 2`] = `
<div <div
className="" className=""
> >
<SizeAwareImage <injectIntl(SizeAwareImage)
className="" className=""
dimensions={ dimensions={
Object { Object {
@@ -416,7 +416,7 @@ exports[`components/SingleImageView should set loaded state on callback of onIma
<div <div
className="" className=""
> >
<SizeAwareImage <injectIntl(SizeAwareImage)
className="" className=""
dimensions={ dimensions={
Object { Object {

Просмотреть файл

@@ -57,7 +57,7 @@ describe('components/SingleImageView', () => {
<SingleImageView {...baseProps}/>, <SingleImageView {...baseProps}/>,
); );
wrapper.find('SizeAwareImage').at(0).simulate('click', {preventDefault: () => {}}); wrapper.find(SizeAwareImage).at(0).simulate('click', {preventDefault: () => { }});
expect(baseProps.actions.openModal).toHaveBeenCalledTimes(1); expect(baseProps.actions.openModal).toHaveBeenCalledTimes(1);
}); });

Просмотреть файл

@@ -1,19 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {mount, shallow} from 'enzyme';
import React from 'react'; import React from 'react';
import {Provider} from 'react-redux'; import {Provider} from 'react-redux';
import LoadingImagePreview from 'components/loading_image_preview'; import LoadingImagePreview from 'components/loading_image_preview';
import SizeAwareImage from 'components/size_aware_image'; import SizeAwareImage, {SizeAwareImage as SizeAwareImageComponent} from 'components/size_aware_image';
import type {Props} from 'components/size_aware_image';
import {shallowWithIntl, mountWithIntl} from 'tests/helpers/intl-test-helper';
import mockStore from 'tests/test_store'; import mockStore from 'tests/test_store';
import {TestHelper} from 'utils/test_helper'; import {TestHelper} from 'utils/test_helper';
describe('components/SizeAwareImage', () => { describe('components/SizeAwareImage', () => {
const baseProps: Props = { const baseProps = {
dimensions: { dimensions: {
height: 200, height: 200,
width: 300, 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', () => { 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 // 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'); expect(viewBox).toEqual('0 0 300 200');
const style = wrapper.find('.file-preview__button').prop('style'); const style = wrapper.find('.file-preview__button').prop('style');
expect(style).toHaveProperty('display', 'none'); expect(style).toHaveProperty('display', 'none');
}); });
test('img should have inherited class name from prop', () => { 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'); const className = wrapper.find('img').prop('className');
expect(className).toEqual('imgClass'); expect(className).toEqual('imgClass');
@@ -63,7 +62,7 @@ describe('components/SizeAwareImage', () => {
showLoader: true, showLoader: true,
}; };
const wrapper = shallow(<SizeAwareImage {...props}/>); const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
expect(wrapper.find(LoadingImagePreview).exists()).toEqual(true); expect(wrapper.find(LoadingImagePreview).exists()).toEqual(true);
expect(wrapper).toMatchSnapshot(); 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'); const src = wrapper.find('.image-loading__container img').prop('src');
expect(src).toEqual('data:mime_type;base64,mini_preview'); expect(src).toEqual('data:mime_type;base64,mini_preview');
}); });
test('should have display set to initial in loaded state', () => { test('should have display set to initial in loaded state', () => {
const wrapper = mount(<Provider store={store}><SizeAwareImage {...baseProps}/></Provider>); const wrapper = mountWithIntl(<Provider store={store}><SizeAwareImage {...baseProps}/></Provider>);
wrapper.find(SizeAwareImage).setState({loaded: true, error: false}); wrapper.find(SizeAwareImageComponent).setState({loaded: true, error: false});
const style = wrapper.find('.file-preview__button').prop('style'); const style = wrapper.find('.file-preview__button').prop('style');
expect(style).toHaveProperty('display', 'inline-block'); expect(style).toHaveProperty('display', 'inline-block');
@@ -98,9 +97,9 @@ describe('components/SizeAwareImage', () => {
const props = {...baseProps}; const props = {...baseProps};
Reflect.deleteProperty(props, 'dimensions'); 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'); const src = wrapper.find('img').prop('src');
expect(src).toEqual(baseProps.src); expect(src).toEqual(baseProps.src);
@@ -110,7 +109,7 @@ describe('components/SizeAwareImage', () => {
const height = 123; const height = 123;
const width = 1234; 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>); wrapper.find('img')?.prop('onLoad')?.({target: {naturalHeight: height, naturalWidth: width}} as unknown as React.SyntheticEvent<HTMLImageElement>);
expect(wrapper.state('loaded')).toBe(true); 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', () => { 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 = { const errorEvent = {
target: {}, target: {},
currentTarget: {}, currentTarget: {},
preventDefault: () => { }, preventDefault: () => { },
stopPropagation: () => { }, stopPropagation: () => { },
} as React.SyntheticEvent<HTMLImageElement>; } 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(SizeAwareImageComponent).state('error')).toBe(true);
expect(wrapper.find(SizeAwareImage).find('svg').exists()).toEqual(true); expect(wrapper.find(SizeAwareImageComponent).find('svg').exists()).toEqual(true);
expect(wrapper.find(SizeAwareImage).find(LoadingImagePreview).exists()).toEqual(false); expect(wrapper.find(SizeAwareImageComponent).find(LoadingImagePreview).exists()).toEqual(false);
}); });
test('should match snapshot when handleSmallImageContainer prop is passed', () => { test('should match snapshot when handleSmallImageContainer prop is passed', () => {
@@ -138,7 +137,7 @@ describe('components/SizeAwareImage', () => {
handleSmallImageContainer: true, handleSmallImageContainer: true,
}; };
const wrapper = shallow(<SizeAwareImage {...props}/>); const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
@@ -148,7 +147,7 @@ describe('components/SizeAwareImage', () => {
handleSmallImageContainer: true, handleSmallImageContainer: true,
}; };
const wrapper = shallow(<SizeAwareImage {...props}/>); const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
wrapper.instance().setState({isSmallImage: true}); wrapper.instance().setState({isSmallImage: true});
@@ -163,7 +162,7 @@ describe('components/SizeAwareImage', () => {
handleSmallImageContainer: true, handleSmallImageContainer: true,
}; };
const wrapper = shallow(<SizeAwareImage {...props}/>); const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
wrapper.instance().setState({isSmallImage: true, imageWidth: 220}); wrapper.instance().setState({isSmallImage: true, imageWidth: 220});
expect(wrapper.find('div.small-image__container').prop('style')). expect(wrapper.find('div.small-image__container').prop('style')).
@@ -182,7 +181,7 @@ describe('components/SizeAwareImage', () => {
handleSmallImageContainer: true, handleSmallImageContainer: true,
}; };
const wrapper = shallow(<SizeAwareImage {...props}/>); const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
wrapper.instance().setState({isSmallImage: true, imageWidth: 24}); wrapper.instance().setState({isSmallImage: true, imageWidth: 24});
@@ -195,7 +194,7 @@ describe('components/SizeAwareImage', () => {
...baseProps, ...baseProps,
fileURL, fileURL,
}; };
const wrapper = shallow(<SizeAwareImage {...props}/>); const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
@@ -205,7 +204,7 @@ describe('components/SizeAwareImage', () => {
...baseProps, ...baseProps,
fileURL, fileURL,
}; };
const wrapper = shallow(<SizeAwareImage {...props}/>); const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
expect(wrapper.find('.size-aware-image__download').prop('href')).toBe(fileURL); expect(wrapper.find('.size-aware-image__download').prop('href')).toBe(fileURL);
}); });
@@ -216,7 +215,7 @@ describe('components/SizeAwareImage', () => {
fileURL, fileURL,
}; };
const wrapper = shallow(<SizeAwareImage {...props}/>); const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
expect(wrapper.state('linkCopyInProgress')).toBe(false); expect(wrapper.state('linkCopyInProgress')).toBe(false);
wrapper.find('.size-aware-image__copy_link').first().simulate('click'); wrapper.find('.size-aware-image__copy_link').first().simulate('click');
expect(wrapper.state('linkCopyInProgress')).toBe(true); expect(wrapper.state('linkCopyInProgress')).toBe(true);
@@ -228,7 +227,7 @@ describe('components/SizeAwareImage', () => {
enablePublicLink: false, enablePublicLink: false,
}; };
const wrapper = shallow(<SizeAwareImage {...props}/>); const wrapper = shallowWithIntl(<SizeAwareImage {...props}/>);
expect(wrapper.find('button.size-aware-image__copy_link').exists()).toEqual(false); expect(wrapper.find('button.size-aware-image__copy_link').exists()).toEqual(false);
}); });
}); });

Просмотреть файл

@@ -6,7 +6,8 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React from 'react'; import React from 'react';
import type {KeyboardEvent, MouseEvent, SyntheticEvent} 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 {DownloadOutlineIcon, LinkVariantIcon, CheckIcon} from '@mattermost/compass-icons/components';
import type {FileInfo} from '@mattermost/types/files'; 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 WithTooltip from 'components/with_tooltip';
import {FileTypes} from 'utils/constants'; 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 = 48;
const MIN_IMAGE_SIZE_FOR_INTERNAL_BUTTONS = 100; const MIN_IMAGE_SIZE_FOR_INTERNAL_BUTTONS = 100;
const MAX_IMAGE_HEIGHT = 350; const MAX_IMAGE_HEIGHT = 350;
export type Props = { export type Props = WrappedComponentProps & {
/* /*
* The source URL of the image * The source URL of the image
@@ -105,7 +106,7 @@ type State = {
// SizeAwareImage is a component used for rendering images where the dimensions of the image are important for // 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. // 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 heightTimeout = 0;
public mounted = false; public mounted = false;
public timeout: NodeJS.Timeout | null = null; public timeout: NodeJS.Timeout | null = null;
@@ -199,6 +200,7 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
src, src,
fileURL, fileURL,
enablePublicLink, enablePublicLink,
intl,
...props ...props
} = this.props; } = this.props;
Reflect.deleteProperty(props, 'showLoader'); Reflect.deleteProperty(props, 'showLoader');
@@ -210,8 +212,9 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
Reflect.deleteProperty(props, 'onClick'); Reflect.deleteProperty(props, 'onClick');
Reflect.deleteProperty(props, 'hideUtilities'); Reflect.deleteProperty(props, 'hideUtilities');
Reflect.deleteProperty(props, 'getFilePublicLink'); 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) { if (fileInfo) {
ariaLabelImage += ` ${fileInfo.name}`.toLowerCase(); 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', { className={classNames('style--none', 'size-aware-image__copy_link', {
'size-aware-image__copy_link--recently_copied': this.state.linkCopiedRecently, '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} onClick={this.copyLinkToAsset}
> >
{this.state.linkCopiedRecently ? ( {this.state.linkCopiedRecently ? (
@@ -306,7 +309,7 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
className='style--none size-aware-image__download' className='style--none size-aware-image__download'
download={true} download={true}
role={this.isInternalImage ? 'button' : undefined} 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 <DownloadOutlineIcon
className={'style--none'} className={'style--none'}
@@ -396,7 +399,7 @@ export default class SizeAwareImage extends React.PureComponent<Props, State> {
fileInfo, fileInfo,
} = this.props; } = 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) { if (fileInfo) {
ariaLabelImage += ` ${fileInfo.name}`.toLowerCase(); 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} show={true}
text="Away" text="Away"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[Function]} action={[Function]}
ariaLabel="Do not disturb. Disables all notifications" ariaLabel="Do not disturb. Disables all notifications"
direction="left" direction="left"
@@ -125,9 +125,7 @@ exports[`components/StatusDropdown should match snapshot in default state 1`] =
} }
id="status-menu-dnd" id="status-menu-dnd"
openUp={false} openUp={false}
renderSelected={true}
rightDecorator={false} rightDecorator={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -190,7 +188,6 @@ exports[`components/StatusDropdown should match snapshot in default state 1`] =
}, },
] ]
} }
subMenuClass="pl-4"
text="Do not disturb" text="Do not disturb"
/> />
<MenuItemAction <MenuItemAction
@@ -439,7 +436,7 @@ exports[`components/StatusDropdown should match snapshot with custom status and
show={true} show={true}
text="Away" text="Away"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[Function]} action={[Function]}
ariaLabel="Do not disturb. Disables all notifications" ariaLabel="Do not disturb. Disables all notifications"
direction="left" direction="left"
@@ -452,9 +449,7 @@ exports[`components/StatusDropdown should match snapshot with custom status and
} }
id="status-menu-dnd" id="status-menu-dnd"
openUp={false} openUp={false}
renderSelected={true}
rightDecorator={false} rightDecorator={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -517,7 +512,6 @@ exports[`components/StatusDropdown should match snapshot with custom status and
}, },
] ]
} }
subMenuClass="pl-4"
text="Do not disturb" text="Do not disturb"
/> />
<MenuItemAction <MenuItemAction
@@ -729,7 +723,7 @@ exports[`components/StatusDropdown should match snapshot with custom status enab
show={true} show={true}
text="Away" text="Away"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[Function]} action={[Function]}
ariaLabel="Do not disturb. Disables all notifications" ariaLabel="Do not disturb. Disables all notifications"
direction="left" direction="left"
@@ -742,9 +736,7 @@ exports[`components/StatusDropdown should match snapshot with custom status enab
} }
id="status-menu-dnd" id="status-menu-dnd"
openUp={false} openUp={false}
renderSelected={true}
rightDecorator={false} rightDecorator={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -807,7 +799,6 @@ exports[`components/StatusDropdown should match snapshot with custom status enab
}, },
] ]
} }
subMenuClass="pl-4"
text="Do not disturb" text="Do not disturb"
/> />
<MenuItemAction <MenuItemAction
@@ -1019,7 +1010,7 @@ exports[`components/StatusDropdown should match snapshot with custom status expi
show={true} show={true}
text="Away" text="Away"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[Function]} action={[Function]}
ariaLabel="Do not disturb. Disables all notifications" ariaLabel="Do not disturb. Disables all notifications"
direction="left" direction="left"
@@ -1032,9 +1023,7 @@ exports[`components/StatusDropdown should match snapshot with custom status expi
} }
id="status-menu-dnd" id="status-menu-dnd"
openUp={false} openUp={false}
renderSelected={true}
rightDecorator={false} rightDecorator={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -1097,7 +1086,6 @@ exports[`components/StatusDropdown should match snapshot with custom status expi
}, },
] ]
} }
subMenuClass="pl-4"
text="Do not disturb" text="Do not disturb"
/> />
<MenuItemAction <MenuItemAction
@@ -1310,7 +1298,7 @@ exports[`components/StatusDropdown should match snapshot with custom status puls
show={true} show={true}
text="Away" text="Away"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[Function]} action={[Function]}
ariaLabel="Do not disturb. Disables all notifications" ariaLabel="Do not disturb. Disables all notifications"
direction="left" direction="left"
@@ -1323,9 +1311,7 @@ exports[`components/StatusDropdown should match snapshot with custom status puls
} }
id="status-menu-dnd" id="status-menu-dnd"
openUp={false} openUp={false}
renderSelected={true}
rightDecorator={false} rightDecorator={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -1388,7 +1374,6 @@ exports[`components/StatusDropdown should match snapshot with custom status puls
}, },
] ]
} }
subMenuClass="pl-4"
text="Do not disturb" text="Do not disturb"
/> />
<MenuItemAction <MenuItemAction
@@ -1576,7 +1561,7 @@ exports[`components/StatusDropdown should match snapshot with profile picture UR
show={true} show={true}
text="Away" text="Away"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[Function]} action={[Function]}
ariaLabel="Do not disturb. Disables all notifications" ariaLabel="Do not disturb. Disables all notifications"
direction="left" direction="left"
@@ -1589,9 +1574,7 @@ exports[`components/StatusDropdown should match snapshot with profile picture UR
} }
id="status-menu-dnd" id="status-menu-dnd"
openUp={false} openUp={false}
renderSelected={true}
rightDecorator={false} rightDecorator={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -1654,7 +1637,6 @@ exports[`components/StatusDropdown should match snapshot with profile picture UR
}, },
] ]
} }
subMenuClass="pl-4"
text="Do not disturb" text="Do not disturb"
/> />
<MenuItemAction <MenuItemAction
@@ -1834,7 +1816,7 @@ exports[`components/StatusDropdown should match snapshot with status dropdown op
show={true} show={true}
text="Away" text="Away"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[Function]} action={[Function]}
ariaLabel="Do not disturb. Disables all notifications" ariaLabel="Do not disturb. Disables all notifications"
direction="left" direction="left"
@@ -1847,9 +1829,7 @@ exports[`components/StatusDropdown should match snapshot with status dropdown op
} }
id="status-menu-dnd" id="status-menu-dnd"
openUp={false} openUp={false}
renderSelected={true}
rightDecorator={false} rightDecorator={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -1912,7 +1892,6 @@ exports[`components/StatusDropdown should match snapshot with status dropdown op
}, },
] ]
} }
subMenuClass="pl-4"
text="Do not disturb" text="Do not disturb"
/> />
<MenuItemAction <MenuItemAction
@@ -2124,7 +2103,7 @@ exports[`components/StatusDropdown should not show clear status button when cust
show={true} show={true}
text="Away" text="Away"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[Function]} action={[Function]}
ariaLabel="Do not disturb. Disables all notifications" ariaLabel="Do not disturb. Disables all notifications"
direction="left" direction="left"
@@ -2137,9 +2116,7 @@ exports[`components/StatusDropdown should not show clear status button when cust
} }
id="status-menu-dnd" id="status-menu-dnd"
openUp={false} openUp={false}
renderSelected={true}
rightDecorator={false} rightDecorator={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -2202,7 +2179,6 @@ exports[`components/StatusDropdown should not show clear status button when cust
}, },
] ]
} }
subMenuClass="pl-4"
text="Do not disturb" text="Do not disturb"
/> />
<MenuItemAction <MenuItemAction
@@ -2451,7 +2427,7 @@ exports[`components/StatusDropdown should show clear status button when custom s
show={true} show={true}
text="Away" text="Away"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[Function]} action={[Function]}
ariaLabel="Do not disturb. Disables all notifications" ariaLabel="Do not disturb. Disables all notifications"
direction="left" direction="left"
@@ -2464,9 +2440,7 @@ exports[`components/StatusDropdown should show clear status button when custom s
} }
id="status-menu-dnd" id="status-menu-dnd"
openUp={false} openUp={false}
renderSelected={true}
rightDecorator={false} rightDecorator={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -2529,7 +2503,6 @@ exports[`components/StatusDropdown should show clear status button when custom s
}, },
] ]
} }
subMenuClass="pl-4"
text="Do not disturb" text="Do not disturb"
/> />
<MenuItemAction <MenuItemAction

Просмотреть файл

@@ -3,7 +3,8 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React from 'react'; 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 {connect, useSelector} from 'react-redux';
import type {Channel, ChannelMembership} from '@mattermost/types/channels'; 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 GuestTag from 'components/widgets/tag/guest_tag';
import {Constants, StoragePrefixes} from 'utils/constants'; import {Constants, StoragePrefixes} from 'utils/constants';
import {getIntl} from 'utils/i18n';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import type {GlobalState} from 'types/store'; import type {GlobalState} from 'types/store';
@@ -111,7 +113,7 @@ interface WrappedChannel {
unread_mentions?: number; unread_mentions?: number;
} }
type Props = SuggestionProps<WrappedChannel> & { type Props = SuggestionProps<WrappedChannel> & WrappedComponentProps & {
channelMember: ChannelMembership; channelMember: ChannelMembership;
collapsedThreads: boolean; collapsedThreads: boolean;
dmChannelTeammate?: UserProfile; dmChannelTeammate?: UserProfile;
@@ -220,7 +222,7 @@ const SwitchChannelSuggestion = React.forwardRef<HTMLDivElement, Props>((props,
let deactivated = ''; let deactivated = '';
if (teammate.delete_at) { 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)) { if (channel.display_name && !(teammate && teammate.is_bot)) {
@@ -228,7 +230,7 @@ const SwitchChannelSuggestion = React.forwardRef<HTMLDivElement, Props>((props,
} else { } else {
name = teammate.username; name = teammate.username;
if (teammate.id === currentUserId) { 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; 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 = ''; let prefix = '';
@@ -528,6 +530,8 @@ export default class SwitchChannelProvider extends Provider {
} }
userWrappedChannel(user: UserProfile, channel?: ChannelItem): WrappedChannel { userWrappedChannel(user: UserProfile, channel?: ChannelItem): WrappedChannel {
const intl = getIntl();
let displayName = ''; let displayName = '';
const currentUserId = getCurrentUserId(this.store.getState()); const currentUserId = getCurrentUserId(this.store.getState());
@@ -545,7 +549,7 @@ export default class SwitchChannelProvider extends Provider {
} }
if (user.id === currentUserId && displayName) { if (user.id === currentUserId && displayName) {
displayName += (' ' + Utils.localizeMessage({id: 'suggestion.user.isCurrent', defaultMessage: '(you)'})); displayName += (' ' + intl.formatMessage({id: 'suggestion.user.isCurrent', defaultMessage: '(you)'}));
} }
return { return {

Просмотреть файл

@@ -30,12 +30,22 @@ exports[`components/team_members_dropdown should match snapshot for a bot with g
id="removeFromTeam" id="removeFromTeam"
onClick={[Function]} onClick={[Function]}
show={true} show={true}
text="Remove From Team" text={
<Memo(MemoizedFormattedMessage)
defaultMessage="Remove from Team"
id="team_members_dropdown.leave_team"
/>
}
/> />
<MenuItemAction <MenuItemAction
onClick={[Function]} onClick={[Function]}
show={true} show={true}
text="Make Member" text={
<Memo(MemoizedFormattedMessage)
defaultMessage="Make Team Member"
id="team_members_dropdown.makeMember"
/>
}
/> />
</Menu> </Menu>
</div> </div>
@@ -72,12 +82,22 @@ exports[`components/team_members_dropdown should match snapshot for team_members
id="removeFromTeam" id="removeFromTeam"
onClick={[Function]} onClick={[Function]}
show={true} show={true}
text="Remove From Team" text={
<Memo(MemoizedFormattedMessage)
defaultMessage="Remove from Team"
id="team_members_dropdown.leave_team"
/>
}
/> />
<MenuItemAction <MenuItemAction
onClick={[Function]} onClick={[Function]}
show={true} show={true}
text="Make Member" text={
<Memo(MemoizedFormattedMessage)
defaultMessage="Make Team Member"
id="team_members_dropdown.makeMember"
/>
}
/> />
</Menu> </Menu>
</div> </div>
@@ -114,12 +134,22 @@ exports[`components/team_members_dropdown should match snapshot opening dropdown
id="removeFromTeam" id="removeFromTeam"
onClick={[Function]} onClick={[Function]}
show={true} show={true}
text="Remove From Team" text={
<Memo(MemoizedFormattedMessage)
defaultMessage="Remove from Team"
id="team_members_dropdown.leave_team"
/>
}
/> />
<MenuItemAction <MenuItemAction
onClick={[Function]} onClick={[Function]}
show={true} show={true}
text="Make Member" text={
<Memo(MemoizedFormattedMessage)
defaultMessage="Make Team Member"
id="team_members_dropdown.makeMember"
/>
}
/> />
</Menu> </Menu>
</div> </div>
@@ -155,7 +185,12 @@ exports[`components/team_members_dropdown should match snapshot with group-const
<MenuItemAction <MenuItemAction
onClick={[Function]} onClick={[Function]}
show={true} show={true}
text="Make Member" text={
<Memo(MemoizedFormattedMessage)
defaultMessage="Make Team Member"
id="team_members_dropdown.makeMember"
/>
}
/> />
</Menu> </Menu>
</div> </div>

Просмотреть файл

@@ -1,11 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react'; import React from 'react';
import TeamMembersDropdown from 'components/team_members_dropdown/team_members_dropdown'; import TeamMembersDropdown from 'components/team_members_dropdown/team_members_dropdown';
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
import {TestHelper} from 'utils/test_helper'; import {TestHelper} from 'utils/test_helper';
describe('components/team_members_dropdown', () => { describe('components/team_members_dropdown', () => {
@@ -40,14 +40,14 @@ describe('components/team_members_dropdown', () => {
}; };
test('should match snapshot for team_members_dropdown', () => { test('should match snapshot for team_members_dropdown', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<TeamMembersDropdown {...baseProps}/>, <TeamMembersDropdown {...baseProps}/>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('should match snapshot opening dropdown upwards', () => { test('should match snapshot opening dropdown upwards', () => {
const wrapper = shallow( const wrapper = shallowWithIntl(
<TeamMembersDropdown <TeamMembersDropdown
{...baseProps} {...baseProps}
index={4} index={4}
@@ -59,7 +59,7 @@ describe('components/team_members_dropdown', () => {
test('should match snapshot with group-constrained team', () => { test('should match snapshot with group-constrained team', () => {
baseProps.currentTeam.group_constrained = true; baseProps.currentTeam.group_constrained = true;
const wrapper = shallow( const wrapper = shallowWithIntl(
<TeamMembersDropdown {...baseProps}/>, <TeamMembersDropdown {...baseProps}/>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
@@ -68,7 +68,7 @@ describe('components/team_members_dropdown', () => {
test('should match snapshot for a bot with group-constrained team', () => { test('should match snapshot for a bot with group-constrained team', () => {
baseProps.currentTeam.group_constrained = true; baseProps.currentTeam.group_constrained = true;
baseProps.user = bot; baseProps.user = bot;
const wrapper = shallow( const wrapper = shallowWithIntl(
<TeamMembersDropdown {...baseProps}/>, <TeamMembersDropdown {...baseProps}/>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();

Просмотреть файл

@@ -2,7 +2,8 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; 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 {Team, TeamMembership} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users'; 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 MenuWrapper from 'components/widgets/menu/menu_wrapper';
import {getHistory} from 'utils/browser_history'; import {getHistory} from 'utils/browser_history';
import * as Utils from 'utils/utils';
const ROWS_FROM_BOTTOM_TO_OPEN_UP = 3; const ROWS_FROM_BOTTOM_TO_OPEN_UP = 3;
type Props = { type Props = WrappedComponentProps & {
user: UserProfile; user: UserProfile;
currentUser: UserProfile; currentUser: UserProfile;
teamMember: TeamMembership; teamMember: TeamMembership;
@@ -50,7 +50,7 @@ type State = {
role: string|null; role: string|null;
} }
export default class TeamMembersDropdown extends React.PureComponent<Props, State> { class TeamMembersDropdown extends React.PureComponent<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(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; let currentRoles = null;
@@ -253,19 +253,34 @@ export default class TeamMembersDropdown extends React.PureComponent<Props, Stat
<Menu.ItemAction <Menu.ItemAction
id='removeFromTeam' id='removeFromTeam'
onClick={this.handleRemoveFromTeam} 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 = ( const menuMakeAdmin = (
<Menu.ItemAction <Menu.ItemAction
onClick={this.handleMakeAdmin} 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 = ( const menuMakeMember = (
<Menu.ItemAction <Menu.ItemAction
onClick={this.handleMakeMember} 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 ( return (
@@ -283,7 +298,7 @@ export default class TeamMembersDropdown extends React.PureComponent<Props, Stat
<Menu <Menu
openLeft={true} openLeft={true}
openUp={openUp} 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} {canRemoveFromTeam ? menuRemove : null}
{showMakeAdmin ? menuMakeAdmin : 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 {DragDropContext, Droppable} from 'react-beautiful-dnd';
import type {DroppableProvided, DropResult} from 'react-beautiful-dnd'; import type {DroppableProvided, DropResult} from 'react-beautiful-dnd';
import Scrollbars from 'react-custom-scrollbars'; 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 {RouteComponentProps} from 'react-router-dom';
import type {Team} from '@mattermost/types/teams'; import type {Team} from '@mattermost/types/teams';
@@ -26,7 +27,7 @@ import * as Utils from 'utils/utils';
import type {PropsFromRedux} from './index'; import type {PropsFromRedux} from './index';
export interface Props extends PropsFromRedux { export interface Props extends PropsFromRedux, WrappedComponentProps {
location: RouteComponentProps['location']; 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) { constructor(props: Props) {
super(props); super(props);
@@ -202,6 +203,8 @@ export default class TeamSidebar extends React.PureComponent<Props, State> {
}; };
render() { render() {
const {intl} = this.props;
const root: Element | null = document.querySelector('#root'); const root: Element | null = document.querySelector('#root');
if (this.props.myTeams.length <= 1) { if (this.props.myTeams.length <= 1) {
root!.classList.remove('multi-teams'); root!.classList.remove('multi-teams');
@@ -246,7 +249,7 @@ export default class TeamSidebar extends React.PureComponent<Props, State> {
<i <i
className='icon icon-plus' className='icon icon-plus'
role={'img'} 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. // See LICENSE.txt for license information.
import React, {useCallback, useEffect, useRef, useState} from 'react'; 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 type {ListChildComponentProps} from 'react-window';
import {VariableSizeList} from 'react-window'; import {VariableSizeList} from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader'; 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 MenuWrapper from 'components/widgets/menu/menu_wrapper';
import {ModalIdentifiers} from 'utils/constants'; import {ModalIdentifiers} from 'utils/constants';
import * as Utils from 'utils/utils';
import type {ModalData} from 'types/actions'; import type {ModalData} from 'types/actions';
@@ -58,6 +57,7 @@ const UserGroupsList = (props: Props) => {
const variableSizeListRef = useRef<VariableSizeList | null>(null); const variableSizeListRef = useRef<VariableSizeList | null>(null);
const [hasMounted, setHasMounted] = useState(false); const [hasMounted, setHasMounted] = useState(false);
const [overflowState, setOverflowState] = useState('overlay'); const [overflowState, setOverflowState] = useState('overlay');
const {formatMessage} = useIntl();
useEffect(() => { useEffect(() => {
if (groups.length === 1) { if (groups.length === 1) {
@@ -171,7 +171,7 @@ const UserGroupsList = (props: Props) => {
openLeft={true} openLeft={true}
openUp={groupListOpenUp(index)} openUp={groupListOpenUp(index)}
className={'group-actions-menu'} 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.Group>
<Menu.ItemAction <Menu.ItemAction
@@ -179,7 +179,7 @@ const UserGroupsList = (props: Props) => {
goToViewGroupModal(group); goToViewGroupModal(group);
}} }}
icon={<i className='icon-account-multiple-outline'/>} 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} disabled={false}
/> />
</Menu.Group> </Menu.Group>
@@ -190,7 +190,7 @@ const UserGroupsList = (props: Props) => {
archiveGroup(group.id); archiveGroup(group.id);
}} }}
icon={<i className='icon-archive-outline'/>} 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} disabled={false}
isDangerous={true} isDangerous={true}
/> />
@@ -200,7 +200,7 @@ const UserGroupsList = (props: Props) => {
restoreGroup(group.id); restoreGroup(group.id);
}} }}
icon={<i className='icon-restore'/>} 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} disabled={false}
/> />
</Menu.Group> </Menu.Group>

Просмотреть файл

@@ -5,6 +5,44 @@ exports[`components/widgets/menu/menu_items/submenu_item empty subMenu should ma
action={[MockFunction]} action={[MockFunction]}
direction="left" direction="left"
id="1" 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} renderSelected={true}
root={true} root={true}
show={true} show={true}
@@ -59,6 +97,44 @@ exports[`components/widgets/menu/menu_items/submenu_item present subMenu should
action={[MockFunction]} action={[MockFunction]}
direction="left" direction="left"
id="1" 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} renderSelected={true}
root={true} root={true}
show={true} show={true}
@@ -124,6 +200,44 @@ exports[`components/widgets/menu/menu_items/submenu_item present subMenu should
<SubMenuItem <SubMenuItem
direction="left" direction="left"
id="A" 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} renderSelected={true}
root={false} root={false}
show={true} show={true}
@@ -180,6 +294,44 @@ exports[`components/widgets/menu/menu_items/submenu_item present subMenu should
<SubMenuItem <SubMenuItem
direction="left" direction="left"
id="B" 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} renderSelected={true}
root={false} root={false}
show={true} show={true}

Просмотреть файл

@@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import {mount} from 'enzyme';
import React from 'react'; 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 Constants from 'utils/constants';
import SubMenuItem from './submenu_item'; import SubMenuItem, {SubMenuItem as SubMenuItemClass} from './submenu_item';
jest.mock('../is_mobile_view_hack', () => ({ jest.mock('../is_mobile_view_hack', () => ({
isMobile: jest.fn(() => false), isMobile: jest.fn(() => false),
@@ -15,7 +15,7 @@ jest.mock('../is_mobile_view_hack', () => ({
describe('components/widgets/menu/menu_items/submenu_item', () => { describe('components/widgets/menu/menu_items/submenu_item', () => {
test('empty subMenu should match snapshot', () => { test('empty subMenu should match snapshot', () => {
const wrapper = mount( const wrapper = mountWithIntl(
<SubMenuItem <SubMenuItem
key={'_pluginmenuitem'} key={'_pluginmenuitem'}
id={'1'} id={'1'}
@@ -30,7 +30,7 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
}); });
test('present subMenu should match snapshot with submenu', () => { test('present subMenu should match snapshot with submenu', () => {
const wrapper = mount( const wrapper = mountWithIntl(
<SubMenuItem <SubMenuItem
key={'_pluginmenuitem'} key={'_pluginmenuitem'}
id={'1'} id={'1'}
@@ -60,7 +60,7 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
const action2 = jest.fn(); const action2 = jest.fn();
const action3 = jest.fn(); const action3 = jest.fn();
render( renderWithContext(
<SubMenuItem <SubMenuItem
key={'_pluginmenuitem'} key={'_pluginmenuitem'}
id={'Z'} id={'Z'}
@@ -98,7 +98,7 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
}); });
test('should show/hide submenu based on keyboard commands', () => { test('should show/hide submenu based on keyboard commands', () => {
const wrapper = mount<SubMenuItem>( const wrapper = mountWithIntl(
<SubMenuItem <SubMenuItem
key={'_pluginmenuitem'} key={'_pluginmenuitem'}
id={'1'} id={'1'}
@@ -109,16 +109,18 @@ describe('components/widgets/menu/menu_items/submenu_item', () => {
/>, />,
); );
wrapper.instance().show = jest.fn(); const instance = wrapper.find(SubMenuItemClass).instance() as SubMenuItemClass;
wrapper.instance().hide = jest.fn();
wrapper.instance().handleKeyDown({keyCode: Constants.KeyCodes.ENTER[1]} as any); instance.show = jest.fn();
expect(wrapper.instance().show).toHaveBeenCalled(); instance.hide = jest.fn();
wrapper.instance().handleKeyDown({keyCode: Constants.KeyCodes.LEFT[1]} as any); instance.handleKeyDown({keyCode: Constants.KeyCodes.ENTER[1]} as any);
expect(wrapper.instance().hide).toHaveBeenCalled(); expect(instance.show).toHaveBeenCalled();
wrapper.instance().handleKeyDown({keyCode: Constants.KeyCodes.RIGHT[1]} as any); instance.handleKeyDown({keyCode: Constants.KeyCodes.LEFT[1]} as any);
expect(wrapper.instance().show).toHaveBeenCalled(); 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 classNames from 'classnames';
import React from 'react'; import React from 'react';
import type {CSSProperties} 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 {showMobileSubMenuModal} from 'actions/global_actions';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import * as Keyboard from 'utils/keyboard'; import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils';
import type {Menu} from 'types/store/plugins'; import type {Menu} from 'types/store/plugins';
@@ -38,7 +39,7 @@ import './menu_item.scss';
// } // }
// Submenus can contain Submenus as well // Submenus can contain Submenus as well
export type Props = { export type Props = WrappedComponentProps & {
id?: string; id?: string;
postId?: string; postId?: string;
text: React.ReactNode; text: React.ReactNode;
@@ -68,12 +69,12 @@ type State = {
/** /**
* @deprecated Use the "webapp/channels/src/components/menu" instead. * @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>; private node: React.RefObject<HTMLLIElement>;
public static defaultProps = { public static defaultProps = {
show: true, show: true,
direction: 'left', direction: 'left' as const,
subMenuClass: 'pl-4', subMenuClass: 'pl-4',
renderSelected: true, renderSelected: true,
}; };
@@ -149,7 +150,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
}; };
public render() { 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(); const isMobile = isMobileViewHack();
if (filter && !filter(id)) { if (filter && !filter(id)) {
@@ -191,7 +192,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
const hasDivider = s.id === 'ChannelMenu-moveToDivider'; const hasDivider = s.id === 'ChannelMenu-moveToDivider';
let aria = ariaLabel; let aria = ariaLabel;
if (s.action) { 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 ( return (
<span <span
@@ -213,6 +214,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
direction={s.direction} direction={s.direction}
isHeader={s.isHeader} isHeader={s.isHeader}
tabIndex={1} tabIndex={1}
intl={this.props.intl}
/> />
{s.text === selectedValueText && <span className='sorting-menu-checkbox'> {s.text === selectedValueText && <span className='sorting-menu-checkbox'>
<i className='icon-check'/> <i className='icon-check'/>
@@ -248,7 +250,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
<span <span
id={'channelHeaderDropdownIconRight_' + id} id={'channelHeaderDropdownIconRight_' + id}
className={classNames([`fa fa-angle-right SubMenu__icon-right${hasSubmenu ? '' : '-empty'}`, {mobile: isMobile}])} 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> </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" ariaLabel="mobile submenu"
openLeft={true} openLeft={true}
> >
<SubMenuItem <injectIntl(SubMenuItem)
action={[MockFunction]} action={[MockFunction]}
direction="left"
id="A" id="A"
key="A" key="A"
renderSelected={true}
root={false} root={false}
show={true}
subMenuClass="pl-4"
text="Text A" text="Text A"
/> />
<SubMenuItem <injectIntl(SubMenuItem)
action={[MockFunction]} action={[MockFunction]}
direction="left"
id="B" id="B"
key="B" key="B"
renderSelected={true}
root={false} root={false}
show={true}
subMenu={ subMenu={
Array [ Array [
Object { Object {
@@ -73,7 +66,6 @@ exports[`components/submenu_modal should match snapshot 1`] = `
}, },
] ]
} }
subMenuClass="pl-4"
text="Text B" text="Text B"
/> />
</Menu> </Menu>

Просмотреть файл

@@ -6,8 +6,7 @@ import {shallow} from 'enzyme';
import React from 'react'; import React from 'react';
import {Modal} from 'react-bootstrap'; import {Modal} from 'react-bootstrap';
import {withIntl} from 'tests/helpers/intl-test-helper'; import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
import {render, screen, userEvent} from 'tests/react_testing_utils';
import SubMenuModal from './submenu_modal'; import SubMenuModal from './submenu_modal';
@@ -58,9 +57,9 @@ describe('components/submenu_modal', () => {
}); });
test('should hide on modal body click', async () => { test('should hide on modal body click', async () => {
const view = render(withIntl( const view = renderWithContext(
<SubMenuModal {...baseProps}/>, <SubMenuModal {...baseProps}/>,
)); );
screen.getByText('Text A'); screen.getByText('Text A');
screen.getByText('Text B'); screen.getByText('Text B');
@@ -78,7 +77,7 @@ describe('components/submenu_modal', () => {
...baseProps, ...baseProps,
}; };
render( renderWithContext(
<SubMenuModal {...props}/>, <SubMenuModal {...props}/>,
); );

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. // See LICENSE.txt for license information.
import React from 'react'; 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 { export function isMessageDescriptor(descriptor: unknown): descriptor is MessageDescriptor {
return Boolean(descriptor && (descriptor as MessageDescriptor).id); return Boolean(descriptor && (descriptor as MessageDescriptor).id);