[MM-60076] Convert ./components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel.tsx from Class Component to Function Component (#27995)

* Change: Convert sidebar_base_channel to functional component
Change: Update sidebar_base_channel test

* Change: Add waitFor method to fix failing tests

* Add: Sidebar_base_channel_icon component
Change: Use useCallback to cache the handleLeave functions
Change: Update test snapshots

* Change: getIcon to channelIcon
Change: Add actions.leaveChannel and actions.openModal as useCallback dependencies
Change: Remove else statements
Этот коммит содержится в:
Ivy Gesare
2024-08-23 14:40:38 +03:00
коммит произвёл GitHub
родитель 56961b3b49
Коммит 86359529c9
4 изменённых файлов: 155 добавлений и 87 удалений

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

@@ -24,8 +24,9 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn
} }
channelLeaveHandler={[Function]} channelLeaveHandler={[Function]}
icon={ icon={
<i <SidebarBaseChannelIcon
className="icon icon-globe" channelType="O"
isSharedChannel={false}
/> />
} }
label="channel_display_name" label="channel_display_name"
@@ -57,8 +58,9 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn
} }
channelLeaveHandler={[Function]} channelLeaveHandler={[Function]}
icon={ icon={
<i <SidebarBaseChannelIcon
className="icon icon-lock-outline" channelType="P"
isSharedChannel={false}
/> />
} }
label="channel_display_name" label="channel_display_name"
@@ -91,10 +93,9 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn
} }
channelLeaveHandler={[Function]} channelLeaveHandler={[Function]}
icon={ icon={
<SharedChannelIndicator <SidebarBaseChannelIcon
channelType="O" channelType="O"
className="icon" isSharedChannel={true}
withTooltip={true}
/> />
} }
label="channel_display_name" label="channel_display_name"
@@ -127,10 +128,9 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn
} }
channelLeaveHandler={[Function]} channelLeaveHandler={[Function]}
icon={ icon={
<SharedChannelIndicator <SidebarBaseChannelIcon
channelType="P" channelType="P"
className="icon" isSharedChannel={true}
withTooltip={true}
/> />
} }
label="channel_display_name" label="channel_display_name"

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

@@ -1,6 +1,7 @@
// 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 {screen, waitFor} from '@testing-library/react';
import {shallow} from 'enzyme'; import {shallow} from 'enzyme';
import React from 'react'; import React from 'react';
@@ -8,6 +9,8 @@ import type {ChannelType} from '@mattermost/types/channels';
import SidebarBaseChannel from 'components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel'; import SidebarBaseChannel from 'components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel';
import {renderWithContext, userEvent} from 'tests/react_testing_utils';
describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => { describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
const baseProps = { const baseProps = {
channel: { channel: {
@@ -91,25 +94,64 @@ describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('expect callback to be called when leave public channel ', () => { test('expect leaveChannel to be called when leave public channel ', async () => {
const callback = jest.fn(); const mockfn = jest.fn();
const wrapper = shallow<SidebarBaseChannel>(<SidebarBaseChannel {...baseProps}/>);
wrapper.instance().handleLeavePublicChannel(callback); const props = {
expect(callback).toBeCalled(); ...baseProps,
channel: {
...baseProps.channel,
type: 'O' as ChannelType,
shared: true,
name: 'l',
},
actions: {
leaveChannel: mockfn,
openModal: jest.fn(),
},
};
renderWithContext(<SidebarBaseChannel {...props}/>);
const optionsBtn = screen.getByRole('button');
expect(optionsBtn.classList).toContain('SidebarMenu_menuButton');
await userEvent.click(optionsBtn); // open options
const leaveOption: HTMLElement = screen.getByText('Leave Channel').parentElement!;
await userEvent.click(leaveOption);
await waitFor(() => {
expect(mockfn).toHaveBeenCalledTimes(1);
});
}); });
test('expect callback to be called when leave private channel ', () => { test('expect openModal to be called when leave private channel ', async () => {
const callback = jest.fn(); const mockfn = jest.fn();
const props = { const props = {
...baseProps, ...baseProps,
channel: { channel: {
...baseProps.channel, ...baseProps.channel,
type: 'P' as ChannelType, type: 'P' as ChannelType,
name: 'l',
},
actions: {
leaveChannel: jest.fn(),
openModal: mockfn,
}, },
}; };
const wrapper = shallow<SidebarBaseChannel>(<SidebarBaseChannel {...props}/>); renderWithContext(<SidebarBaseChannel {...props}/>);
wrapper.instance().handleLeavePrivateChannel(callback);
expect(callback).toBeCalled(); const optionsBtn = screen.getByRole('button');
expect(optionsBtn.classList).toContain('SidebarMenu_menuButton');
await userEvent.click(optionsBtn); // open options
const leaveOption: HTMLElement = screen.getByText('Leave Channel').parentElement!;
await userEvent.click(leaveOption);
await waitFor(() => {
expect(mockfn).toHaveBeenCalledTimes(1);
});
}); });
}); });

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

@@ -1,18 +1,19 @@
// 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 React from 'react'; import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
import type {Channel} from '@mattermost/types/channels'; import type {Channel} from '@mattermost/types/channels';
import {trackEvent} from 'actions/telemetry_actions'; import {trackEvent} from 'actions/telemetry_actions';
import LeaveChannelModal from 'components/leave_channel_modal'; import LeaveChannelModal from 'components/leave_channel_modal';
import SharedChannelIndicator from 'components/shared_channel_indicator';
import SidebarChannelLink from 'components/sidebar/sidebar_channel/sidebar_channel_link'; import SidebarChannelLink from 'components/sidebar/sidebar_channel/sidebar_channel_link';
import Constants, {ModalIdentifiers} from 'utils/constants'; import Constants, {ModalIdentifiers} from 'utils/constants';
import {localizeMessage} from 'utils/utils';
import SidebarBaseChannelIcon from './sidebar_base_channel_icon';
import type {PropsFromRedux} from './index'; import type {PropsFromRedux} from './index';
@@ -21,63 +22,44 @@ export interface Props extends PropsFromRedux {
currentTeamName: string; currentTeamName: string;
} }
export default class SidebarBaseChannel extends React.PureComponent<Props> { const SidebarBaseChannel = ({
handleLeavePublicChannel = (callback: () => void) => { channel,
this.props.actions.leaveChannel(this.props.channel.id); currentTeamName,
actions,
}: Props) => {
const intl = useIntl();
const handleLeavePublicChannel = useCallback((callback: () => void) => {
actions.leaveChannel(channel.id);
trackEvent('ui', 'ui_public_channel_x_button_clicked'); trackEvent('ui', 'ui_public_channel_x_button_clicked');
callback(); callback();
}; }, [channel.id, actions.leaveChannel]);
handleLeavePrivateChannel = (callback: () => void) => { const handleLeavePrivateChannel = useCallback((callback: () => void) => {
this.props.actions.openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel: this.props.channel}}); actions.openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel}});
trackEvent('ui', 'ui_private_channel_x_button_clicked'); trackEvent('ui', 'ui_private_channel_x_button_clicked');
callback(); callback();
}; }, [channel, actions.openModal]);
getChannelLeaveHandler = () => {
const {channel} = this.props;
let channelLeaveHandler = null;
if (channel.type === Constants.OPEN_CHANNEL && channel.name !== Constants.DEFAULT_CHANNEL) { if (channel.type === Constants.OPEN_CHANNEL && channel.name !== Constants.DEFAULT_CHANNEL) {
return this.handleLeavePublicChannel; channelLeaveHandler = handleLeavePublicChannel;
} else if (channel.type === Constants.PRIVATE_CHANNEL) { } else if (channel.type === Constants.PRIVATE_CHANNEL) {
return this.handleLeavePrivateChannel; channelLeaveHandler = handleLeavePrivateChannel;
} }
return null; const channelIcon = (
}; <SidebarBaseChannelIcon
isSharedChannel={Boolean(channel.shared)}
getIcon = () => {
const {channel} = this.props;
if (channel.shared) {
return (
<SharedChannelIndicator
className='icon'
channelType={channel.type} channelType={channel.type}
withTooltip={true}
/> />
); );
} else if (channel.type === Constants.OPEN_CHANNEL) {
return (
<i className='icon icon-globe'/>
);
} else if (channel.type === Constants.PRIVATE_CHANNEL) {
return (
<i className='icon icon-lock-outline'/>
);
}
return null;
};
render() {
const {channel, currentTeamName} = this.props;
let ariaLabelPrefix; let ariaLabelPrefix;
if (channel.type === Constants.OPEN_CHANNEL) { if (channel.type === Constants.OPEN_CHANNEL) {
ariaLabelPrefix = localizeMessage('accessibility.sidebar.types.public', 'public channel'); ariaLabelPrefix = intl.formatMessage({id: 'accessibility.sidebar.types.public', defaultMessage: 'public channel'});
} else if (channel.type === Constants.PRIVATE_CHANNEL) { } else if (channel.type === Constants.PRIVATE_CHANNEL) {
ariaLabelPrefix = localizeMessage('accessibility.sidebar.types.private', 'private channel'); ariaLabelPrefix = intl.formatMessage({id: 'accessibility.sidebar.types.private', defaultMessage: 'private channel'});
} }
return ( return (
@@ -86,9 +68,10 @@ export default class SidebarBaseChannel extends React.PureComponent<Props> {
link={`/${currentTeamName}/channels/${channel.name}`} link={`/${currentTeamName}/channels/${channel.name}`}
label={channel.display_name} label={channel.display_name}
ariaLabelPrefix={ariaLabelPrefix} ariaLabelPrefix={ariaLabelPrefix}
channelLeaveHandler={this.getChannelLeaveHandler()!} channelLeaveHandler={channelLeaveHandler!}
icon={this.getIcon()!} icon={channelIcon}
/> />
); );
} };
}
export default SidebarBaseChannel;

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

@@ -0,0 +1,43 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import type {ChannelType} from '@mattermost/types/channels';
import SharedChannelIndicator from 'components/shared_channel_indicator';
import Constants from 'utils/constants';
type Props = {
isSharedChannel: boolean;
channelType: ChannelType;
}
const SidebarBaseChannelIcon = ({
isSharedChannel,
channelType,
}: Props) => {
if (isSharedChannel) {
return (
<SharedChannelIndicator
className='icon'
channelType={channelType}
withTooltip={true}
/>
);
}
if (channelType === Constants.OPEN_CHANNEL) {
return (
<i className='icon icon-globe'/>
);
}
if (channelType === Constants.PRIVATE_CHANNEL) {
return (
<i className='icon icon-lock-outline'/>
);
}
return null;
};
export default SidebarBaseChannelIcon;