diff --git a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx index 0ef12d797..b9823f016 100644 --- a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx +++ b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx @@ -5,7 +5,6 @@ import { useSnackbar } from 'notistack'; import { useTranslation } from 'react-i18next'; import * as yup from 'yup'; import { AddIcon } from 'src/components/Contacts/ContactDetails/ContactDetailsTab/StyledComponents'; -import { useAccountListId } from 'src/hooks/useAccountListId'; import i18n from 'src/lib/i18n'; import { useEmailAddressesMutation } from './AddEmailAddress.generated'; import { RowWrapper } from './FixEmailAddressPerson/FixEmailAddressPerson'; @@ -31,6 +30,7 @@ interface EmailValidationFormEmail { interface EmailValidationFormProps { personId: string; + accountListId: string; } const validationSchema = yup.object({ @@ -45,9 +45,11 @@ const validationSchema = yup.object({ isValid: yup.bool().default(false), }); -const EmailValidationForm = ({ personId }: EmailValidationFormProps) => { +const EmailValidationForm = ({ + personId, + accountListId, +}: EmailValidationFormProps) => { const { t } = useTranslation(); - const accountListId = useAccountListId(); const [emailAddressesMutation] = useEmailAddressesMutation(); const { enqueueSnackbar } = useSnackbar(); diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx index 74316430c..7b6634135 100644 --- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx +++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx @@ -6,11 +6,7 @@ import { DateTime } from 'luxon'; import { SnackbarProvider } from 'notistack'; import TestWrapper from '__tests__/util/TestWrapper'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; -import { - render, - screen, - waitFor, -} from '__tests__/util/testingLibraryReactMock'; +import { render, waitFor } from '__tests__/util/testingLibraryReactMock'; import theme from 'src/theme'; import { EmailAddressesMutation } from '../AddEmailAddress.generated'; import { EmailAddressData, PersonEmailAddresses } from '../FixEmailAddresses'; @@ -49,6 +45,7 @@ const person: PersonInvalidEmailFragment = { const setContactFocus = jest.fn(); const mutationSpy = jest.fn(); +const handleSingleConfirm = jest.fn(); const mockEnqueue = jest.fn(); jest.mock('notistack', () => ({ @@ -62,14 +59,23 @@ jest.mock('notistack', () => ({ }, })); -const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => { +const defaultDataState = { + contactTestId: { + emailAddresses: person.emailAddresses.nodes as EmailAddressData[], + }, +} as { [key: string]: PersonEmailAddresses }; + +type TestComponentProps = { + mocks?: ApolloErgonoMockMap; + dataState?: { [key: string]: PersonEmailAddresses }; +}; + +const TestComponent = ({ + mocks, + dataState = defaultDataState, +}: TestComponentProps) => { const handleChangeMock = jest.fn(); const handleChangePrimaryMock = jest.fn(); - const dataState = { - contactTestId: { - emailAddresses: person.emailAddresses.nodes as EmailAddressData[], - }, - } as { [key: string]: PersonEmailAddresses }; return ( @@ -85,9 +91,10 @@ const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => { @@ -235,7 +242,6 @@ describe('FixEmailAddressPerson', () => { await waitFor(() => getByTestId('delete-contactTestId-1')); userEvent.click(getByTestId('delete-contactTestId-1')); - screen.logTestingPlaygroundURL(); await waitFor(() => { expect(getByRole('heading', { name: 'Confirm' })).toBeInTheDocument(); }); @@ -273,4 +279,72 @@ describe('FixEmailAddressPerson', () => { ); }); }); + + describe('confirm button', () => { + it('should disable confirm button if there is more than one primary email', async () => { + const dataState = { + contactTestId: { + emailAddresses: [ + { + ...person.emailAddresses.nodes[0], + primary: true, + }, + { + ...person.emailAddresses.nodes[1], + primary: true, + }, + ] as EmailAddressData[], + }, + }; + + const { getByRole, queryByRole } = render( + , + ); + + await waitFor(() => { + expect(queryByRole('loading')).not.toBeInTheDocument(); + expect(getByRole('button', { name: 'Confirm' })).toBeDisabled(); + }); + }); + + it('should disable confirm button if there are no primary emails', async () => { + const dataState = { + contactTestId: { + emailAddresses: [ + { + ...person.emailAddresses.nodes[0], + primary: false, + }, + { + ...person.emailAddresses.nodes[1], + primary: false, + }, + ] as EmailAddressData[], + }, + }; + const { getByRole, queryByRole } = render( + , + ); + + await waitFor(() => { + expect(queryByRole('loading')).not.toBeInTheDocument(); + expect(getByRole('button', { name: 'Confirm' })).toBeDisabled(); + }); + }); + + it('should not disable confirm button if there is exactly one primary email', async () => { + const { getByRole, queryByRole } = render(); + + expect(handleSingleConfirm).toHaveBeenCalledTimes(0); + + await waitFor(() => { + expect(queryByRole('loading')).not.toBeInTheDocument(); + expect(getByRole('button', { name: 'Confirm' })).not.toBeDisabled(); + }); + + userEvent.click(getByRole('button', { name: 'Confirm' })); + + expect(handleSingleConfirm).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx index e791e5efd..e644aac85 100644 --- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx +++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx @@ -25,7 +25,7 @@ import { dateFormatShort } from 'src/lib/intlFormat'; import theme from 'src/theme'; import { ConfirmButtonIcon } from '../../ConfirmButtonIcon'; import EmailValidationForm from '../EmailValidationForm'; -import { PersonEmailAddresses } from '../FixEmailAddresses'; +import { EmailAddressData, PersonEmailAddresses } from '../FixEmailAddresses'; import { PersonInvalidEmailFragment } from '../FixEmailAddresses.generated'; const PersonCard = styled(Box)(({ theme }) => ({ @@ -105,6 +105,10 @@ export interface FixEmailAddressPersonProps { event: React.ChangeEvent, ) => void; handleChangePrimary: (personId: string, emailIndex: number) => void; + handleSingleConfirm: ( + person: PersonInvalidEmailFragment, + emails: EmailAddressData[], + ) => void; setContactFocus: SetContactFocus; } @@ -131,6 +135,7 @@ export const FixEmailAddressPerson: React.FC = ({ accountListId, handleChange, handleChangePrimary, + handleSingleConfirm, setContactFocus, }) => { const { t } = useTranslation(); @@ -214,6 +219,10 @@ export const FixEmailAddressPerson: React.FC = ({ setEmailToDelete(null); }; + const hasOnePrimaryEmail = (): boolean => { + return emails.filter((email) => email.isPrimary)?.length === 1; + }; + return ( <> @@ -287,7 +296,10 @@ export const FixEmailAddressPerson: React.FC = ({ {email.isPrimary ? ( - + handleChangePrimary(id, index)} + > ) : ( @@ -358,7 +370,10 @@ export const FixEmailAddressPerson: React.FC = ({ justifyContent="flex-start" px={2} > - + @@ -373,7 +388,14 @@ export const FixEmailAddressPerson: React.FC = ({ style={{ paddingLeft: theme.spacing(1) }} > - diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx index c1d08e0e3..3f90ddde5 100644 --- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx +++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx @@ -1,17 +1,22 @@ import React from 'react'; import { ThemeProvider } from '@mui/material/styles'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ApolloErgonoMockMap } from 'graphql-ergonomock'; import { SnackbarProvider } from 'notistack'; import TestRouter from '__tests__/util/TestRouter'; +import TestWrapper from '__tests__/util/TestWrapper'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; -import { GetInvalidEmailAddressesQuery } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated'; +import { + GetInvalidEmailAddressesQuery, + UpdateEmailAddressesMutation, +} from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated'; import theme from '../../../theme'; import { EmailAddressesMutation } from './AddEmailAddress.generated'; import { FixEmailAddresses } from './FixEmailAddresses'; import { contactId, + contactOneEmailAddressNodes, mockInvalidEmailAddressesResponse, newEmail, } from './FixEmailAddressesMocks'; @@ -51,18 +56,21 @@ const Components = ({ mocks = defaultGraphQLMock }: ComponentsProps) => ( - - mocks={mocks} - onCall={mutationSpy} - > - - + + + mocks={mocks} + onCall={mutationSpy} + > + + + @@ -89,15 +97,76 @@ describe('FixEmailAddresses-Home', () => { expect(queryByTestId('no-data')).not.toBeInTheDocument(); }); - it('change primary of first email', async () => { - const { getByTestId, queryByTestId } = render(); + describe('handleChangePrimary()', () => { + it('changes primary of first email', async () => { + const { getByTestId, queryByTestId } = render(); + + const star1 = await waitFor(() => + getByTestId('starOutlineIcon-testid-1'), + ); + userEvent.click(star1); + + expect(queryByTestId('starIcon-testid-0')).not.toBeInTheDocument(); + expect(getByTestId('starIcon-testid-1')).toBeInTheDocument(); + expect(getByTestId('starOutlineIcon-testid-0')).toBeInTheDocument(); + }); - const star1 = await waitFor(() => getByTestId('starOutlineIcon-testid-1')); - userEvent.click(star1); + it('should choose primary and deselect primary from others', async () => { + const { getByTestId, queryByTestId } = render( + , + ); + + let newPrimary; + await waitFor(() => { + expect(getByTestId('starIcon-testid-0')).toBeInTheDocument(); + expect(getByTestId('starIcon-testid-1')).toBeInTheDocument(); + newPrimary = getByTestId('starIcon-testid-2'); + expect(newPrimary).toBeInTheDocument(); + }); + userEvent.click(newPrimary); - expect(queryByTestId('starIcon-testid-0')).not.toBeInTheDocument(); - expect(getByTestId('starIcon-testid-1')).toBeInTheDocument(); - expect(getByTestId('starOutlineIcon-testid-0')).toBeInTheDocument(); + await waitFor(() => { + expect(queryByTestId('starIcon-testid-0')).not.toBeInTheDocument(); + expect(queryByTestId('starIcon-testid-1')).not.toBeInTheDocument(); + expect(getByTestId('starIcon-testid-2')).toBeInTheDocument(); + expect(getByTestId('starOutlineIcon-testid-0')).toBeInTheDocument(); + expect(getByTestId('starOutlineIcon-testid-1')).toBeInTheDocument(); + expect( + queryByTestId('starOutlineIcon-testid-2'), + ).not.toBeInTheDocument(); + }); + }); }); it('should add an new email address, firing a GraphQL mutation and resetting the form', async () => { @@ -133,17 +202,18 @@ describe('FixEmailAddresses-Home', () => { expect(textFieldNew).toHaveValue(''); }); - //TODO: Fix during MPDX-7936 - it.skip('delete third email from first person', async () => { - const { getByTestId, queryByTestId } = render(); + it('delete third email from first person', async () => { + const { getByTestId, getByText, getByRole, queryByTestId } = render( + , + ); const delete02 = await waitFor(() => getByTestId('delete-testid-2')); userEvent.click(delete02); - const deleteButton = await waitFor(() => - getByTestId('modal-delete-button'), + await waitFor(() => + getByText(`Are you sure you wish to delete this email address:`), ); - userEvent.click(deleteButton); + userEvent.click(getByRole('button', { name: 'Yes' })); await waitFor(() => { expect(queryByTestId('textfield-testid-2')).not.toBeInTheDocument(); @@ -168,7 +238,6 @@ describe('FixEmailAddresses-Home', () => { ); userEvent.click(getByRole('button', { name: 'Yes' })); - screen.logTestingPlaygroundURL(); await waitFor(() => { expect(queryByTestId('starIcon-testid2-1')).not.toBeInTheDocument(); expect(getByTestId('starIcon-testid2-0')).toBeInTheDocument(); @@ -246,8 +315,71 @@ describe('FixEmailAddresses-Home', () => { await waitFor(() => expect(getByTestId('starOutlineIcon-testid-2')).toBeInTheDocument(), ); + }); + }); + + describe('handleSingleConfirm', () => { + it('should successfully submit changes to multiple emails', async () => { + const personName = 'Test Contact'; + const { getAllByRole, getByTestId, getByText, queryByText } = render( + , + ); + + await waitFor(() => { + expect(getByTestId('starOutlineIcon-testid-1')).toBeInTheDocument(); + }); + + expect(getByText(personName)).toBeInTheDocument(); + + const confirmButton = getAllByRole('button', { name: 'Confirm' })[0]; + userEvent.click(confirmButton); + + await waitFor(() => { + expect(mockEnqueue).toHaveBeenCalledWith( + `Successfully updated email addresses for ${personName}`, + { variant: 'success' }, + ); + expect(queryByText(personName)).not.toBeInTheDocument(); + }); + }, 999999); + + it('should handle an error', async () => { + const { getAllByRole, getByTestId } = render( + { + throw new Error('Server Error'); + }, + }} + />, + ); - screen.logTestingPlaygroundURL(); + await waitFor(() => + expect(getByTestId('starOutlineIcon-testid-1')).toBeInTheDocument(), + ); + + const confirmButton = getAllByRole('button', { name: 'Confirm' })[0]; + userEvent.click(confirmButton); + + await waitFor(() => { + expect(mockEnqueue).toHaveBeenCalledWith( + 'Error updating email addresses for Test Contact', + { variant: 'error', autoHideDuration: 7000 }, + ); + }); }); }); }); diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx index dcf8849b8..697f948d2 100644 --- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx +++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx @@ -9,9 +9,15 @@ import { Typography, } from '@mui/material'; import { styled } from '@mui/material/styles'; +import { useSnackbar } from 'notistack'; import { Trans, useTranslation } from 'react-i18next'; import { SetContactFocus } from 'pages/accountLists/[accountListId]/tools/useToolsHelper'; -import { useGetInvalidEmailAddressesQuery } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated'; +import { + PersonInvalidEmailFragment, + useGetInvalidEmailAddressesQuery, + useUpdateEmailAddressesMutation, +} from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated'; +import { PersonEmailAddressInput } from 'src/graphql/types.generated'; import theme from 'src/theme'; import { ConfirmButtonIcon } from '../ConfirmButtonIcon'; import NoData from '../NoData'; @@ -103,10 +109,13 @@ export const FixEmailAddresses: React.FC = ({ }) => { const [defaultSource, setDefaultSource] = useState('MPDX'); const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); const { data, loading } = useGetInvalidEmailAddressesQuery({ variables: { accountListId }, }); + const [updateEmailAddressesMutation] = useUpdateEmailAddressesMutation(); + const [dataState, setDataState] = useState<{ [key: string]: PersonEmailAddresses; }>({}); @@ -169,6 +178,52 @@ export const FixEmailAddresses: React.FC = ({ setDefaultSource(event.target.value); }; + const handleSingleConfirm = async ( + person: PersonInvalidEmailFragment, + emails: EmailAddressData[], + ) => { + const personName = `${person.firstName} ${person.lastName}`; + const emailAddresses = [] as PersonEmailAddressInput[]; + emails.map((emailAddress) => { + emailAddresses.push({ + email: emailAddress.email, + id: emailAddress.id, + primary: emailAddress.primary, + validValues: true, + }); + }); + + await updateEmailAddressesMutation({ + variables: { + input: { + accountListId, + attributes: { + id: person.id, + emailAddresses, + }, + }, + }, + update: (cache) => { + cache.evict({ id: `Person:${person.id}` }); + cache.gc(); + }, + onCompleted: () => { + enqueueSnackbar( + t(`Successfully updated email addresses for ${personName}`), + { + variant: 'success', + }, + ); + }, + onError: () => { + enqueueSnackbar(t(`Error updating email addresses for ${personName}`), { + variant: 'error', + autoHideDuration: 7000, + }); + }, + }); + }; + return ( {!loading && data && dataState ? ( @@ -177,7 +232,7 @@ export const FixEmailAddresses: React.FC = ({ {t('Fix Email Addresses')} - {data.people.nodes.length && ( + {!!data.people.nodes.length && ( <> @@ -216,7 +271,7 @@ export const FixEmailAddresses: React.FC = ({ )} - {data.people.nodes.length > 0 ? ( + {!!data.people.nodes.length ? ( <> {data?.people.nodes.map((person) => ( @@ -227,6 +282,7 @@ export const FixEmailAddresses: React.FC = ({ accountListId={accountListId} handleChange={handleChange} handleChangePrimary={handleChangePrimary} + handleSingleConfirm={handleSingleConfirm} setContactFocus={setContactFocus} /> ))}