reduce usage of Utils.localizeMessage (#29282)
* reduce usage of Utils.localizeMessage As we begin to transition to react-intl's `formatjs` for extraction, our custom wrappers like `Utils.localizeMessage` prevent an adoption roadblock. This is the first in a series of PRs to begin to migrate away in favour of: * `useIntl` when inside React functional component * use `injectIntl` and use access the `intl` prop from class components * use the new `getIntl`, leveraging a memoized `createIntl` and the store outside of React components I'm pausing in this effort to get a feel from peers on both the substance of these changes and best practices in supporting them. * prefer shallowWithIntl, renderWithContext, and local injectIntl wrapping * revert unintentional whitespaces * clarify getIntl, add minor unit test * avoid triggering mmjstool * update e2e tests --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
bc22267829
Коммит
7179a9d1d6
@@ -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
Обычный файл
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);
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user