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

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

@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {screen, waitFor} from '@testing-library/react';
import {shallow} from 'enzyme';
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 {renderWithContext, userEvent} from 'tests/react_testing_utils';
describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
const baseProps = {
channel: {
@@ -91,25 +94,64 @@ describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
expect(wrapper).toMatchSnapshot();
});
test('expect callback to be called when leave public channel ', () => {
const callback = jest.fn();
const wrapper = shallow<SidebarBaseChannel>(<SidebarBaseChannel {...baseProps}/>);
wrapper.instance().handleLeavePublicChannel(callback);
expect(callback).toBeCalled();
test('expect leaveChannel to be called when leave public channel ', async () => {
const mockfn = jest.fn();
const props = {
...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 ', () => {
const callback = jest.fn();
test('expect openModal to be called when leave private channel ', async () => {
const mockfn = jest.fn();
const props = {
...baseProps,
channel: {
...baseProps.channel,
type: 'P' as ChannelType,
name: 'l',
},
actions: {
leaveChannel: jest.fn(),
openModal: mockfn,
},
};
const wrapper = shallow<SidebarBaseChannel>(<SidebarBaseChannel {...props}/>);
wrapper.instance().handleLeavePrivateChannel(callback);
expect(callback).toBeCalled();
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);
});
});
});

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

@@ -1,18 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// 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 {trackEvent} from 'actions/telemetry_actions';
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 Constants, {ModalIdentifiers} from 'utils/constants';
import {localizeMessage} from 'utils/utils';
import SidebarBaseChannelIcon from './sidebar_base_channel_icon';
import type {PropsFromRedux} from './index';
@@ -21,74 +22,56 @@ export interface Props extends PropsFromRedux {
currentTeamName: string;
}
export default class SidebarBaseChannel extends React.PureComponent<Props> {
handleLeavePublicChannel = (callback: () => void) => {
this.props.actions.leaveChannel(this.props.channel.id);
const SidebarBaseChannel = ({
channel,
currentTeamName,
actions,
}: Props) => {
const intl = useIntl();
const handleLeavePublicChannel = useCallback((callback: () => void) => {
actions.leaveChannel(channel.id);
trackEvent('ui', 'ui_public_channel_x_button_clicked');
callback();
};
}, [channel.id, actions.leaveChannel]);
handleLeavePrivateChannel = (callback: () => void) => {
this.props.actions.openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel: this.props.channel}});
const handleLeavePrivateChannel = useCallback((callback: () => void) => {
actions.openModal({modalId: ModalIdentifiers.LEAVE_PRIVATE_CHANNEL_MODAL, dialogType: LeaveChannelModal, dialogProps: {channel}});
trackEvent('ui', 'ui_private_channel_x_button_clicked');
callback();
};
}, [channel, actions.openModal]);
getChannelLeaveHandler = () => {
const {channel} = this.props;
if (channel.type === Constants.OPEN_CHANNEL && channel.name !== Constants.DEFAULT_CHANNEL) {
return this.handleLeavePublicChannel;
} else if (channel.type === Constants.PRIVATE_CHANNEL) {
return this.handleLeavePrivateChannel;
}
return null;
};
getIcon = () => {
const {channel} = this.props;
if (channel.shared) {
return (
<SharedChannelIndicator
className='icon'
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;
if (channel.type === Constants.OPEN_CHANNEL) {
ariaLabelPrefix = localizeMessage('accessibility.sidebar.types.public', 'public channel');
} else if (channel.type === Constants.PRIVATE_CHANNEL) {
ariaLabelPrefix = localizeMessage('accessibility.sidebar.types.private', 'private channel');
}
return (
<SidebarChannelLink
channel={channel}
link={`/${currentTeamName}/channels/${channel.name}`}
label={channel.display_name}
ariaLabelPrefix={ariaLabelPrefix}
channelLeaveHandler={this.getChannelLeaveHandler()!}
icon={this.getIcon()!}
/>
);
let channelLeaveHandler = null;
if (channel.type === Constants.OPEN_CHANNEL && channel.name !== Constants.DEFAULT_CHANNEL) {
channelLeaveHandler = handleLeavePublicChannel;
} else if (channel.type === Constants.PRIVATE_CHANNEL) {
channelLeaveHandler = handleLeavePrivateChannel;
}
}
const channelIcon = (
<SidebarBaseChannelIcon
isSharedChannel={Boolean(channel.shared)}
channelType={channel.type}
/>
);
let ariaLabelPrefix;
if (channel.type === Constants.OPEN_CHANNEL) {
ariaLabelPrefix = intl.formatMessage({id: 'accessibility.sidebar.types.public', defaultMessage: 'public channel'});
} else if (channel.type === Constants.PRIVATE_CHANNEL) {
ariaLabelPrefix = intl.formatMessage({id: 'accessibility.sidebar.types.private', defaultMessage: 'private channel'});
}
return (
<SidebarChannelLink
channel={channel}
link={`/${currentTeamName}/channels/${channel.name}`}
label={channel.display_name}
ariaLabelPrefix={ariaLabelPrefix}
channelLeaveHandler={channelLeaveHandler!}
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;