diff --git a/src/components/Contacts/StarContactIconButton/StarContactIconButton.tsx b/src/components/Contacts/StarContactIconButton/StarContactIconButton.tsx index 3a30661c5..3d1d0bd54 100644 --- a/src/components/Contacts/StarContactIconButton/StarContactIconButton.tsx +++ b/src/components/Contacts/StarContactIconButton/StarContactIconButton.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { IconButton } from '@mui/material'; +import { IconButton, IconButtonProps } from '@mui/material'; import { StarredItemIcon } from '../../common/StarredItemIcon/StarredItemIcon'; import { useSetContactStarredMutation } from './SetContactStarred.generated'; @@ -7,17 +7,20 @@ interface Props { accountListId: string; contactId: string; isStarred: boolean; + size?: IconButtonProps['size']; } export const StarContactIconButton: React.FC = ({ accountListId, contactId, isStarred, + size = 'medium', }) => { const [setContactStarred] = useSetContactStarredMutation(); return ( { event.stopPropagation(); diff --git a/src/components/Shared/Header/ListHeader.test.tsx b/src/components/Shared/Header/ListHeader.test.tsx index 877525651..ad0abfe2b 100644 --- a/src/components/Shared/Header/ListHeader.test.tsx +++ b/src/components/Shared/Header/ListHeader.test.tsx @@ -14,6 +14,7 @@ import { TasksMassActionsDropdown } from '../MassActions/TasksMassActionsDropdow import { ListHeader, ListHeaderCheckBoxState, + PageEnum, TableViewModeEnum, } from './ListHeader'; @@ -96,7 +97,7 @@ describe('ListHeader', () => { { { { { { { { { { { { { { { { { { { { { = ({ - {page === PageEnum.Contact && ( + {(page === PageEnum.Contact || page === PageEnum.Appeal) && ( = ({ /> )} - {page === PageEnum.Appeal && {buttonGroup}} - {starredFilter && toggleStarredFilter && ( // This hidden doesn't remove from document diff --git a/src/components/Tool/Appeal/AppealDetails/AppealsMainPanel/AppealsMainPanelHeader.test.tsx b/src/components/Tool/Appeal/AppealDetails/AppealsMainPanel/AppealsMainPanelHeader.test.tsx new file mode 100644 index 000000000..5df5b2654 --- /dev/null +++ b/src/components/Tool/Appeal/AppealDetails/AppealsMainPanel/AppealsMainPanelHeader.test.tsx @@ -0,0 +1,194 @@ +import { ThemeProvider } from '@mui/material/styles'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon'; +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SnackbarProvider } from 'notistack'; +import TestRouter from '__tests__/util/TestRouter'; +import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; +import { AppealsWrapper } from 'pages/accountLists/[accountListId]/tools/appeals/AppealsWrapper'; +import theme from 'src/theme'; +import { + AppealsContext, + AppealsType, +} from '../../AppealsContext/AppealsContext'; +import { AppealsMainPanelHeader } from './AppealsMainPanelHeader'; + +const mockEnqueue = jest.fn(); + +jest.mock('notistack', () => ({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ...jest.requireActual('notistack'), + useSnackbar: () => { + return { + enqueueSnackbar: mockEnqueue, + }; + }, +})); + +const accountListId = 'accountListId'; +const handleViewModeChange = jest.fn(); +const toggleFilterPanel = jest.fn(); +const toggleSelectAll = jest.fn(); +const setSearchTerm = jest.fn(); +const defaultRouter = { + query: { accountListId }, + isReady: true, +}; +const defaultContactsQueryResult = { + data: { contacts: { nodes: [] } }, + loading: true, +}; +type ComponentsProps = { + router?: object; + contactsQueryResult?: object; +}; + +const Components = ({ + router = defaultRouter, + contactsQueryResult = defaultContactsQueryResult, +}: ComponentsProps) => ( + + + + + + + + + + + + + + + +); + +describe('AppealsMainPanelHeader', () => { + it('renders default view', () => { + const { getByRole } = render(); + + expect(getByRole('button', { name: 'Appeals' })).toBeInTheDocument(); + expect( + getByRole('button', { name: 'Toggle Filter Panel' }), + ).toBeInTheDocument(); + expect(getByRole('button', { name: 'List View' })).toBeInTheDocument(); + expect(getByRole('button', { name: 'Flows View' })).toBeInTheDocument(); + }); + + it('should open filters', () => { + const { getByRole } = render( + , + ); + + userEvent.click(getByRole('button', { name: 'Toggle Filter Panel' })); + + expect(toggleFilterPanel).toHaveBeenCalled(); + }); + + it('should disable select all contacts if no contacts', () => { + const { getByRole } = render( + , + ); + + expect(getByRole('checkbox')).toBeDisabled(); + }); + + it('should search contacts', () => { + const { getByRole } = render( + , + ); + + userEvent.type(getByRole('textbox'), 'search term'); + + expect(setSearchTerm).toHaveBeenCalledWith('search term'); + }); + + it('should change view', async () => { + const { getByRole } = render( + , + ); + + expect(getByRole('button', { name: 'List View' })).toBeDisabled(); + userEvent.click(getByRole('button', { name: 'Flows View' })); + expect(handleViewModeChange).toHaveBeenCalledWith( + expect.objectContaining({}), + 'flows', + ); + }); + + it('should allow select all to be checked', () => { + const { getByRole } = render( + , + ); + + expect(getByRole('checkbox')).not.toBeDisabled(); + userEvent.click(getByRole('checkbox')); + + expect(toggleSelectAll).toHaveBeenCalled(); + }); +}); diff --git a/src/components/Tool/Appeal/AppealDetails/AppealsMainPanel/AppealsMainPanelHeader.tsx b/src/components/Tool/Appeal/AppealDetails/AppealsMainPanel/AppealsMainPanelHeader.tsx index 9cdcaa9c9..1387959d0 100644 --- a/src/components/Tool/Appeal/AppealDetails/AppealsMainPanel/AppealsMainPanelHeader.tsx +++ b/src/components/Tool/Appeal/AppealDetails/AppealsMainPanel/AppealsMainPanelHeader.tsx @@ -38,6 +38,12 @@ const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }) => ({ marginLeft: theme.spacing(1), })); +const StyledBox = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + marginRight: theme.spacing(1.5), +})); + export const AppealsMainPanelHeader: React.FC = () => { const { t } = useTranslation(); @@ -90,7 +96,7 @@ export const AppealsMainPanelHeader: React.FC = () => { } buttonGroup={ - + { value={TableViewModeEnum.Flows} disabled={viewMode === TableViewModeEnum.Flows} > - + - + } /> diff --git a/src/components/Tool/Appeal/AppealsContext/AppealsContext.tsx b/src/components/Tool/Appeal/AppealsContext/AppealsContext.tsx index a993b8e58..6d31bfeac 100644 --- a/src/components/Tool/Appeal/AppealsContext/AppealsContext.tsx +++ b/src/components/Tool/Appeal/AppealsContext/AppealsContext.tsx @@ -269,22 +269,30 @@ export const AppealsProvider: React.FC = ({ debounce((searchTerm: string) => { const { searchTerm: _, ...oldQuery } = query; if (searchTerm !== '') { - replace({ - pathname, - query: { - ...oldQuery, - accountListId, - ...(searchTerm && { searchTerm }), + replace( + { + pathname, + query: { + ...oldQuery, + accountListId, + ...(searchTerm && { searchTerm }), + }, }, - }); + undefined, + { shallow: true }, + ); } else { - replace({ - pathname, - query: { - ...oldQuery, - accountListId, + replace( + { + pathname, + query: { + ...oldQuery, + accountListId, + }, }, - }); + undefined, + { shallow: true }, + ); } }, 500), [accountListId], diff --git a/src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.test.tsx b/src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.test.tsx index f808680ba..8d3901d26 100644 --- a/src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.test.tsx +++ b/src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.test.tsx @@ -1,13 +1,17 @@ import React from 'react'; import { ThemeProvider } from '@mui/material/styles'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import TestWrapper from '__tests__/util/TestWrapper'; import { ContactRowFragment } from 'src/components/Contacts/ContactRow/ContactRow.generated'; import theme from 'src/theme'; -import { AppealStatusEnum } from '../../AppealsContext/AppealsContext'; +import { + AppealStatusEnum, + AppealsContext, + AppealsType, +} from '../../AppealsContext/AppealsContext'; import { ContactFlowRow } from './ContactFlowRow'; const accountListId = 'abc'; @@ -22,17 +26,28 @@ const contact = { uncompletedTasksCount: 0, } as ContactRowFragment; const onContactSelected = jest.fn(); +const toggleSelectionById = jest.fn(); +const isChecked = jest.fn().mockImplementation(() => false); const Components = () => ( - + + + @@ -51,4 +66,13 @@ describe('ContactFlowRow', () => { expect(getByText('Test Name')).toBeInTheDocument(); expect(onContactSelected).toHaveBeenCalledWith('123', true, true); }); + + it('should call check contact', async () => { + const { getByRole } = render(); + + userEvent.click(getByRole('checkbox')); + await waitFor(() => { + expect(toggleSelectionById).toHaveBeenLastCalledWith(contact.id); + }); + }); }); diff --git a/src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.tsx b/src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.tsx index eb57712c8..4f0512a85 100644 --- a/src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.tsx +++ b/src/components/Tool/Appeal/Flow/ContactFlowRow/ContactFlowRow.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useMemo } from 'react'; -import { Box, Typography } from '@mui/material'; +import { Box, Checkbox, ListItemIcon, Typography } from '@mui/material'; +import { styled } from '@mui/material/styles'; import { useDrag } from 'react-dnd'; import { getEmptyImage } from 'react-dnd-html5-backend'; import { useTranslation } from 'react-i18next'; @@ -9,14 +10,18 @@ import { DraggedContact as ContactsDraggedContact, ContainerBox, DraggableBox, - StyledAvatar, } from 'src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow'; import { StarContactIconButton } from 'src/components/Contacts/StarContactIconButton/StarContactIconButton'; import { StatusEnum } from 'src/graphql/types.generated'; import { useLocale } from 'src/hooks/useLocale'; import { currencyFormat } from 'src/lib/intlFormat'; +import theme from 'src/theme'; import { getLocalizedContactStatus } from 'src/utils/functions/getLocalizedContactStatus'; -import { AppealStatusEnum } from '../../AppealsContext/AppealsContext'; +import { + AppealStatusEnum, + AppealsContext, + AppealsType, +} from '../../AppealsContext/AppealsContext'; // When making changes in this file, also check to see if you don't need to make changes to the below file // src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.tsx @@ -30,6 +35,22 @@ export interface DraggedContact extends Omit { status: AppealStatusEnum; } +const StyledCheckbox = styled(Checkbox)(() => ({ + '&:hover': { + backgroundColor: 'rgba(0, 0, 0, 0.04)', + }, +})); + +const StyledListItemIcon = styled(ListItemIcon)(() => ({ + minWidth: '40px', +})); + +const FlexCenterAlignedBox = styled(Box)(() => ({ + display: 'flex', + alignItems: 'center', + width: '100%', +})); + export const ContactFlowRow: React.FC = ({ accountListId, contact, @@ -38,9 +59,12 @@ export const ContactFlowRow: React.FC = ({ onContactSelected, columnWidth, }) => { - const { id, name, starred, avatar, pledgeAmount, pledgeCurrency } = contact; + const { id, name, starred, pledgeAmount, pledgeCurrency } = contact; const { t } = useTranslation(); const locale = useLocale(); + const { isRowChecked: isChecked, toggleSelectionById: onContactCheckToggle } = + React.useContext(AppealsContext) as AppealsType; + const [{ isDragging }, drag, preview] = useDrag( () => ({ type: 'contact', @@ -77,19 +101,18 @@ export const ContactFlowRow: React.FC = ({ - - - - + + + + onContactSelected(id, true, true)}> {name} @@ -97,22 +120,25 @@ export const ContactFlowRow: React.FC = ({ {getLocalizedContactStatus(t, contactStatus)} + + + + event.stopPropagation()} + onChange={() => onContactCheckToggle(contact.id)} + /> + - - - - - + + {pledgedAmount && ( {pledgedAmount} )} - + );