From 86359529c98b1f41c263bc16d2c11a3ad2333de9 Mon Sep 17 00:00:00 2001 From: Ivy Gesare <83957195+Gesare5@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:40:38 +0300 Subject: [PATCH] [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 --- .../sidebar_base_channel.test.tsx.snap | 20 +-- .../sidebar_base_channel.test.tsx | 62 ++++++++-- .../sidebar_base_channel.tsx | 117 ++++++++---------- .../sidebar_base_channel_icon.tsx | 43 +++++++ 4 files changed, 155 insertions(+), 87 deletions(-) create mode 100644 webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel_icon.tsx diff --git a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/__snapshots__/sidebar_base_channel.test.tsx.snap b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/__snapshots__/sidebar_base_channel.test.tsx.snap index 5c30f53ee5..c90ecae0f0 100644 --- a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/__snapshots__/sidebar_base_channel.test.tsx.snap +++ b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/__snapshots__/sidebar_base_channel.test.tsx.snap @@ -24,8 +24,9 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn } channelLeaveHandler={[Function]} icon={ - } label="channel_display_name" @@ -57,8 +58,9 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn } channelLeaveHandler={[Function]} icon={ - } label="channel_display_name" @@ -91,10 +93,9 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn } channelLeaveHandler={[Function]} icon={ - } label="channel_display_name" @@ -127,10 +128,9 @@ exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match sn } channelLeaveHandler={[Function]} icon={ - } label="channel_display_name" diff --git a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel.test.tsx b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel.test.tsx index 254afda990..85bc744a1e 100644 --- a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel.test.tsx +++ b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel.test.tsx @@ -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(); - 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(); + + 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(); - wrapper.instance().handleLeavePrivateChannel(callback); - expect(callback).toBeCalled(); + renderWithContext(); + + 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); + }); }); }); diff --git a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel.tsx b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel.tsx index 40637e0e6b..2bde4b43b7 100644 --- a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel.tsx +++ b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel.tsx @@ -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 { - 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 ( - - ); - } else if (channel.type === Constants.OPEN_CHANNEL) { - return ( - - ); - } else if (channel.type === Constants.PRIVATE_CHANNEL) { - return ( - - ); - } - - 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 ( - - ); + 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 = ( + + ); + + 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 ( + + ); +}; + +export default SidebarBaseChannel; diff --git a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel_icon.tsx b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel_icon.tsx new file mode 100644 index 0000000000..96d6a454f1 --- /dev/null +++ b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel_icon.tsx @@ -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 ( + + ); + } + if (channelType === Constants.OPEN_CHANNEL) { + return ( + + ); + } + if (channelType === Constants.PRIVATE_CHANNEL) { + return ( + + ); + } + return null; +}; + +export default SidebarBaseChannelIcon;