From 3f40c59f6acf90acb3d2f760fd7195691893069f Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Thu, 19 Sep 2024 16:36:39 -0500 Subject: [PATCH 1/4] Add duplicateId filter to merge contacts tool --- .../merge/contacts/[[...contactId]].page.tsx | 5 +++++ .../GetContactDuplicates.graphql | 9 +++++++-- .../Tool/MergeContacts/MergeContacts.test.tsx | 19 +++++++++++++++++++ .../Tool/MergeContacts/MergeContacts.tsx | 7 ++++++- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/pages/accountLists/[accountListId]/tools/merge/contacts/[[...contactId]].page.tsx b/pages/accountLists/[accountListId]/tools/merge/contacts/[[...contactId]].page.tsx index d717309fe..5b2643e52 100644 --- a/pages/accountLists/[accountListId]/tools/merge/contacts/[[...contactId]].page.tsx +++ b/pages/accountLists/[accountListId]/tools/merge/contacts/[[...contactId]].page.tsx @@ -1,3 +1,4 @@ +import { useRouter } from 'next/router'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { loadSession } from 'pages/api/utils/pagePropsHelpers'; @@ -7,6 +8,7 @@ import { SetContactFocus, useToolsHelper } from '../../useToolsHelper'; const MergeContactsPage: React.FC = () => { const { t } = useTranslation(); + const { query } = useRouter(); const { accountListId, handleSelectContact } = useToolsHelper(); const pageUrl = 'tools/merge/contacts'; @@ -22,6 +24,9 @@ const MergeContactsPage: React.FC = () => { > diff --git a/src/components/Tool/MergeContacts/GetContactDuplicates.graphql b/src/components/Tool/MergeContacts/GetContactDuplicates.graphql index 006013c37..dba511b73 100644 --- a/src/components/Tool/MergeContacts/GetContactDuplicates.graphql +++ b/src/components/Tool/MergeContacts/GetContactDuplicates.graphql @@ -1,6 +1,11 @@ -query GetContactDuplicates($accountListId: ID!) { +query GetContactDuplicates($accountListId: ID!, $contactIds: [ID!]) { # TODO: Eventually needs pagination (Jira issue: MPDX-7642) - contactDuplicates(accountListId: $accountListId, ignore: false, first: 10) { + contactDuplicates( + accountListId: $accountListId + contactIds: $contactIds + ignore: false + first: 10 + ) { totalCount nodes { id diff --git a/src/components/Tool/MergeContacts/MergeContacts.test.tsx b/src/components/Tool/MergeContacts/MergeContacts.test.tsx index b7a0ef8f3..d62c8eacc 100644 --- a/src/components/Tool/MergeContacts/MergeContacts.test.tsx +++ b/src/components/Tool/MergeContacts/MergeContacts.test.tsx @@ -32,11 +32,13 @@ jest.mock('notistack', () => ({ interface MergeContactsWrapperProps { mutationSpy?: () => void; mocks?: ApolloErgonoMockMap; + contactId?: string; } const MergeContactsWrapper: React.FC = ({ mutationSpy, mocks = getContactDuplicatesMocks, + contactId, }) => { return ( @@ -60,6 +62,7 @@ const MergeContactsWrapper: React.FC = ({ @@ -200,6 +203,22 @@ describe('Tools - MergeContacts', () => { ).toBeInTheDocument(); }); + it('should only load duplicates of a specific contact', async () => { + const mutationSpy = jest.fn(); + render( + + + , + ); + + await waitFor(() => + expect(mutationSpy).toHaveGraphqlOperation('GetContactDuplicates', { + accountListId: '123', + contactIds: ['contact-1'], + }), + ); + }); + it('should show error', async () => { const mutationSpy = jest.fn(); diff --git a/src/components/Tool/MergeContacts/MergeContacts.tsx b/src/components/Tool/MergeContacts/MergeContacts.tsx index f4d20037b..0098315d4 100644 --- a/src/components/Tool/MergeContacts/MergeContacts.tsx +++ b/src/components/Tool/MergeContacts/MergeContacts.tsx @@ -36,11 +36,13 @@ export interface ActionType { interface Props { accountListId: string; + contactId?: string; setContactFocus: SetContactFocus; } const MergeContacts: React.FC = ({ accountListId, + contactId, setContactFocus, }: Props) => { const { classes } = useStyles(); @@ -48,7 +50,10 @@ const MergeContacts: React.FC = ({ const { t } = useTranslation(); const { enqueueSnackbar } = useSnackbar(); const { data, loading } = useGetContactDuplicatesQuery({ - variables: { accountListId }, + variables: { + accountListId, + contactIds: contactId ? [contactId] : undefined, + }, }); const { appName } = useGetAppSettings(); const [contactsMerge, { loading: updating }] = useMassActionsMergeMutation(); From 920f0096c8e019308a8c618876540451a470d083 Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Thu, 19 Sep 2024 16:36:51 -0500 Subject: [PATCH 2/4] Add duplicate contact banner --- .../ContactDetailsHeader.graphql | 23 +- .../ContactDetailsHeader.test.tsx | 303 ++++++++---------- .../ContactDetailsHeader.tsx | 73 ++++- 3 files changed, 220 insertions(+), 179 deletions(-) diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.graphql b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.graphql index 5aa70c201..1fe548943 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.graphql +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.graphql @@ -15,8 +15,29 @@ fragment ContactDetailsHeader on Contact { ...ContactHeaderNewsletter } -query GetContactDetailsHeader($accountListId: ID!, $contactId: ID!) { +query GetContactDetailsHeader( + $accountListId: ID! + $contactId: ID! + $loadDuplicate: Boolean! +) { contact(accountListId: $accountListId, id: $contactId) { ...ContactDetailsHeader } + contactDuplicates( + accountListId: $accountListId + contactIds: [$contactId] + ignore: false + first: 1 + ) @include(if: $loadDuplicate) { + nodes { + id + reason + recordOne { + id + } + recordTwo { + id + } + } + } } diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.test.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.test.tsx index 3606c1776..edf432372 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.test.tsx @@ -16,148 +16,144 @@ import { GetContactDetailsHeaderQuery } from './ContactDetailsHeader.generated'; const accountListId = 'abc'; const contactId = 'contact-1'; -const router = { - query: { accountListId }, -}; - -const mocks = { - GetContactDetailsHeader: { - contact: { - name: 'Lname, Fname', - avatar: `https://cru.org/assets/image.jpg`, - primaryPerson: null, - pledgeCurrency: 'USD', - lastDonation: null, - }, - }, -}; +const mutationSpy = jest.fn(); + +interface TestComponentProps { + duplicateRecord?: 1 | 2; + pathname?: string; +} + +const TestComponent: React.FC = ({ + duplicateRecord, + pathname, +}) => ( + + + + + + mocks={{ + GetContactDetailsHeader: { + contact: { + name: 'Lname, Fname', + avatar: 'https://cru.org/assets/image.jpg', + primaryPerson: null, + pledgeCurrency: 'USD', + lastDonation: null, + }, + contactDuplicates: { + nodes: + typeof duplicateRecord === 'number' + ? [ + { + recordOne: { + id: + duplicateRecord === 1 + ? contactId + : 'duplicate-contact', + }, + recordTwo: { + id: + duplicateRecord === 1 + ? 'duplicate-contact' + : contactId, + }, + }, + ] + : [], + }, + }, + }} + onCall={mutationSpy} + > + + + {}} + setContactDetailsLoaded={() => {}} + contactDetailsLoaded={false} + /> + + + + + + + +); describe('ContactDetails', () => { it('should show loading state', async () => { - const { queryByTestId } = render( - - - - - - - {}} - setContactDetailsLoaded={() => {}} - contactDetailsLoaded={false} - /> - - - - - - , - ); + const { getByTestId } = render(); + + expect(getByTestId('Skeleton')).toBeInTheDocument(); - expect(queryByTestId('Skeleton')).toBeInTheDocument(); + await waitFor(() => + expect(mutationSpy).toHaveGraphqlOperation('GetContactDetailsHeader', { + accountListId, + contactId, + loadDuplicate: true, + }), + ); }); - it('should render with contact details', async () => { - const { findByText, queryByTestId } = render( - - - - - mocks={{ - GetContactDetailsHeader: { - contact: { - name: 'Fname Lname', - lastDonation: null, - pledgeCurrency: 'USD', - }, - }, - }} - > - - - {}} - setContactDetailsLoaded={() => {}} - contactDetailsLoaded={false} - /> - - - - - - , + describe('duplicate contact', () => { + it.each([[1], [2]] as const)( + 'should render duplicate contact when the current contact is record %i', + async (duplicateRecord) => { + const { findByRole, getByRole } = render( + , + ); + + const matchButton = await findByRole('link', { name: 'See Match' }); + expect(matchButton).toHaveAttribute( + 'href', + '/accountLists/abc/tools/merge/contacts?duplicateId=duplicate-contact', + ); + + userEvent.click(getByRole('button', { name: 'Dismiss Duplicate' })); + expect(matchButton).not.toBeInTheDocument(); + }, ); - expect(await findByText('Fname Lname')).toBeVisible(); + it('does not render duplicate contact where there is no duplicate', async () => { + const { queryByRole } = render(); + + expect( + queryByRole('link', { name: 'See Match' }), + ).not.toBeInTheDocument(); + }); + + it('does not load on the merge contacts page', async () => { + render( + , + ); - expect(queryByTestId('Skeleton')).toBeNull(); + await waitFor(() => + expect(mutationSpy).toHaveGraphqlOperation('GetContactDetailsHeader', { + loadDuplicate: false, + }), + ); + }); }); - it('should render without primaryPerson', async () => { - const { findByText, queryByTestId } = render( - - - - - mocks={mocks} - > - - - {}} - setContactDetailsLoaded={() => {}} - contactDetailsLoaded={false} - /> - - - - - - , - ); + it('should render with contact details', async () => { + const { findByText, queryByTestId } = render(); expect(await findByText('Lname, Fname')).toBeVisible(); - expect(queryByTestId('Skeleton')).toBeNull(); + expect(queryByTestId('Skeleton')).not.toBeInTheDocument(); }); it('should open Edit Partnership modal', async () => { const { queryByText, getAllByLabelText } = render( - - - - - - mocks={mocks} - > - - - {}} - setContactDetailsLoaded={() => {}} - contactDetailsLoaded={false} - /> - - - - - - - , + + + , ); await waitFor(() => expect(getAllByLabelText('Edit Partnership Info')[0]).toBeInTheDocument(), @@ -170,32 +166,11 @@ describe('ContactDetails', () => { it('should close Edit Partnership modal', async () => { const { queryByText, getAllByLabelText, getByLabelText } = render( - - - - - - mocks={mocks} - > - - - {}} - setContactDetailsLoaded={() => {}} - contactDetailsLoaded={false} - /> - - - - - - - , + + + , ); + await waitFor(() => expect(getAllByLabelText('Edit Partnership Info')[0]).toBeInTheDocument(), ); @@ -208,34 +183,10 @@ describe('ContactDetails', () => { expect(queryByText('Edit Partnership')).not.toBeInTheDocument(), ); }); + it('should render avatar', async () => { - const { queryByText, getAllByLabelText } = render( - - - - - - mocks={mocks} - > - - - {}} - setContactDetailsLoaded={() => {}} - contactDetailsLoaded={false} - /> - - - - - - - , - ); + const { queryByText, getAllByLabelText } = render(); + await waitFor(() => expect(getAllByLabelText('Edit Partnership Info')[0]).toBeInTheDocument(), ); diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx index b9fcd23d3..340c13ae4 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx @@ -1,6 +1,16 @@ +import Link from 'next/link'; +import { useRouter } from 'next/router'; import React, { useEffect, useState } from 'react'; import Close from '@mui/icons-material/Close'; -import { Avatar, Box, IconButton, Skeleton, Typography } from '@mui/material'; +import { + Alert, + Avatar, + Box, + Button, + IconButton, + Skeleton, + Typography, +} from '@mui/material'; import { styled } from '@mui/material/styles'; import { useTranslation } from 'react-i18next'; import { StatusEnum } from 'src/graphql/types.generated'; @@ -26,6 +36,12 @@ interface Props { contextType?: ContactContextTypesEnum; } +const DuplicateAlert = styled(Alert)(({ theme }) => ({ + marginBottom: theme.spacing(2), + display: 'flex', + alignItems: 'center', +})); + const HeaderBar = styled(Box)(({}) => ({ display: 'flex', paddingBottom: theme.spacing(1), @@ -67,8 +83,16 @@ export const ContactDetailsHeader: React.FC = ({ contactDetailsLoaded, contextType, }: Props) => { + const { pathname } = useRouter(); const { data } = useGetContactDetailsHeaderQuery({ - variables: { accountListId, contactId }, + variables: { + accountListId, + contactId, + // Don't show the duplicate banner on the merge contacts page + loadDuplicate: + pathname !== + '/accountLists/[accountListId]/tools/merge/contacts/[[...contactId]]', + }, }); const loading = !data; const { t } = useTranslation(); @@ -76,6 +100,10 @@ export const ContactDetailsHeader: React.FC = ({ const [editPartnershipModalOpen, setEditPartnershipModalOpen] = useState(false); + const [duplicateContactId, setDuplicateContactId] = useState( + null, + ); + useEffect(() => { if (!loading && !contactDetailsLoaded) { setContactDetailsLoaded(true); @@ -83,8 +111,49 @@ export const ContactDetailsHeader: React.FC = ({ return () => setContactDetailsLoaded(false); }, [loading]); + // Populate duplicateContactId once the data loads + useEffect(() => { + const duplicate = data?.contactDuplicates?.nodes[0]; + if (duplicate) { + setDuplicateContactId( + duplicate.recordOne.id === contactId + ? duplicate.recordTwo.id + : duplicate.recordOne.id, + ); + } else { + setDuplicateContactId(null); + } + }, [data]); + return ( + {duplicateContactId && ( + {}} + action={ + <> + + + + setDuplicateContactId(null)} + > + + + + } + > + {t('It looks like this contact may have a duplicate.')} + + )} From d378443beb0da818a16a9666e5f819c7d4691c13 Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Fri, 20 Sep 2024 09:30:33 -0500 Subject: [PATCH 3/4] Fix PersonModal tests --- .../Items/PersonModal/PersonModal.test.tsx | 1023 +++++++++-------- .../People/Items/PersonModal/PersonModal.tsx | 17 +- .../PersonModal/PersonModalSave.test.tsx | 111 +- 3 files changed, 622 insertions(+), 529 deletions(-) diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModal.test.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModal.test.tsx index 1e49ff614..b1687425a 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModal.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModal.test.tsx @@ -6,6 +6,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { cleanup, render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SnackbarProvider } from 'notistack'; +import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider, gqlMock } from '__tests__/util/graphqlMocking'; import { ContactDetailProvider } from 'src/components/Contacts/ContactDetails/ContactDetailContext'; import theme from '../../../../../../../theme'; @@ -146,20 +147,22 @@ describe('PersonModal', () => { it('should render edit person modal', () => { const { getByText } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -169,20 +172,22 @@ describe('PersonModal', () => { it('should close edit contact modal', () => { const { getByLabelText, getByText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -193,20 +198,22 @@ describe('PersonModal', () => { it('should handle cancel click', () => { const { getByText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -218,20 +225,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -247,20 +256,22 @@ describe('PersonModal', () => { it('should handle Show More click', async () => { const { queryAllByText, getByText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -271,20 +282,22 @@ describe('PersonModal', () => { it('should handle Show Less click', async () => { const { queryAllByText, getByText, queryByText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -299,28 +312,30 @@ describe('PersonModal', () => { it('should show invalid dates and highlight them as errors', async () => { const { getByText, getByRole, queryAllByText } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -350,20 +365,22 @@ describe('PersonModal', () => { it('should handle uploading an avatar', async () => { const { getByRole, getByTestId } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -401,20 +418,22 @@ describe('PersonModal', () => { mockPerson.avatar = 'https://cru.org/assets/image.jpg'; render( - - - - - - - - - + + + + + + + + + + + , ); @@ -431,20 +450,22 @@ describe('PersonModal', () => { const { getByTestId } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -465,20 +486,22 @@ describe('PersonModal', () => { const { getByRole, getByTestId } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -507,20 +530,22 @@ describe('PersonModal', () => { const { getByText, getByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -562,20 +587,22 @@ describe('PersonModal', () => { const newPersonPhoneNumber = '888-888-8888'; const { getByText, getAllByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -639,20 +666,22 @@ describe('PersonModal', () => { const { queryByText, queryByRole } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -701,20 +730,22 @@ describe('PersonModal', () => { const { getByText, getAllByRole } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -731,20 +762,22 @@ describe('PersonModal', () => { it('should handle invalid phone numbers', async () => { const { findByText, getByRole, getAllByRole } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -762,20 +795,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByRole, getAllByRole } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -823,20 +858,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getByRole, getAllByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -874,20 +911,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getAllByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -913,20 +952,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getAllByLabelText, getByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -942,20 +983,22 @@ describe('PersonModal', () => { const newPersonEmailAddress = 'testguy@fake.com'; const { getByText, getByLabelText, getAllByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); userEvent.clear(getAllByLabelText('Email Address')[0]); @@ -1002,20 +1045,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByRole, getAllByRole } = render( - - - - - - - - - + + + + + + + + + + + , ); @@ -1033,20 +1078,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getAllByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -1072,20 +1119,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getAllByLabelText, getByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -1104,20 +1153,22 @@ describe('PersonModal', () => { const newPersonLegalFirstName = 'Jim'; const { getByText, getByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -1174,20 +1225,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -1213,20 +1266,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -1256,20 +1311,22 @@ describe('PersonModal', () => { const newPersonWebsite = 'testguy.com'; const { getByText, getAllByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -1323,20 +1380,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getAllByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -1373,20 +1432,22 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getAllByLabelText, getByLabelText } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Person')).toBeInTheDocument(); @@ -1401,20 +1462,22 @@ describe('PersonModal', () => { it('should handle deleting a person', async () => { const { getByRole, getByText } = render( - - - - - - - - - + + + + + + + + + + + , ); userEvent.click(getByRole('button', { hidden: true, name: 'Delete' })); @@ -1563,24 +1626,28 @@ describe('PersonModal', () => { const { getByText, getByLabelText } = render( - - - - mocks={mocks} - cache={cache} - onCall={mutationSpy} - > - - - - - - + + + + + mocks={mocks} + cache={cache} + onCall={mutationSpy} + > + + + + + + + , ); @@ -1619,23 +1686,25 @@ describe('PersonModal', () => { const mutationSpy = jest.fn(); const { getByText, getByRole } = render( - - - - - - - - - + + + + + + + + + + + , ); expect(getByText('Edit Details')).toBeInTheDocument(); diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModal.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModal.tsx index 2d79ab3fb..315d3f529 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModal.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModal.tsx @@ -1,3 +1,4 @@ +import { useRouter } from 'next/router'; import React, { ReactElement, useEffect, useState } from 'react'; import { useApolloClient } from '@apollo/client'; import { @@ -28,6 +29,7 @@ import Modal from '../../../../../../common/Modal/Modal'; import { GetContactDetailsHeaderDocument, GetContactDetailsHeaderQuery, + GetContactDetailsHeaderQueryVariables, } from '../../../../ContactDetailsHeader/ContactDetailsHeader.generated'; import { ContactDetailsTabDocument, @@ -116,6 +118,7 @@ export const PersonModal: React.FC = ({ const userProfile = person?.__typename === 'User'; const { t } = useTranslation(); + const { pathname } = useRouter(); const { enqueueSnackbar } = useSnackbar(); const [personEditShowMore, setPersonEditShowMore] = useState(false); const [removeDialogOpen, handleRemoveDialogOpen] = useState(false); @@ -299,10 +302,18 @@ export const PersonModal: React.FC = ({ update: (cache) => { const query = { query: GetContactDetailsHeaderDocument, - variables: { accountListId, contactId }, + variables: { + accountListId, + contactId, + loadDuplicate: + pathname !== + '/accountLists/[accountListId]/tools/merge/contacts/[[...contactId]]', + }, }; - const dataFromCache = - cache.readQuery(query); + const dataFromCache = cache.readQuery< + GetContactDetailsHeaderQuery, + GetContactDetailsHeaderQueryVariables + >(query); if (dataFromCache) { const data = { ...dataFromCache, diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModalSave.test.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModalSave.test.tsx index f4c0ec88d..dd35fe220 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModalSave.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonModalSave.test.tsx @@ -6,6 +6,7 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SnackbarProvider } from 'notistack'; +import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider, gqlMock } from '__tests__/util/graphqlMocking'; import { ContactDetailProvider } from 'src/components/Contacts/ContactDetails/ContactDetailContext'; import theme from '../../../../../../../theme'; @@ -109,23 +110,31 @@ jest.mock('notistack', () => ({ }, })); -const components = (mutationSpy, contactData) => ( +const mutationSpy = jest.fn(); + +interface TestComponentProps { + contactData: ContactPeopleFragment; +} + +const TestComponent: React.FC = ({ contactData }) => ( - - - - - - - - - + + + + + + + + + + + ); @@ -139,15 +148,16 @@ describe('PersonModal - Saving Deceased', () => { }); it('deceases Jill and updates greetings (Jill listed last)', async () => { - const mutationSpy = jest.fn(); const { getByText, getByLabelText } = render( - components(mutationSpy, { - id: '123-456', - name: 'Hill, Jack and Jill', - people: mock.people, - greeting: 'Jack and Jill', - envelopeGreeting: 'Jack and Jill Hill', - }), + , ); expect(getByText('Edit Person')).toBeInTheDocument(); userEvent.click(getByText('Show More')); @@ -188,15 +198,16 @@ describe('PersonModal - Saving Deceased', () => { }); it('deceases Jill and updates greetings (Jill listed first)', async () => { - const mutationSpy = jest.fn(); const { getByText, getByLabelText } = render( - components(mutationSpy, { - id: '123-456', - name: 'Hill, Jill and Jack', // Added extra space to ensure algorithm replacing it. - people: mock.people, - greeting: 'Jill and Jack', - envelopeGreeting: 'Jill and Jack Hill', - }), + , ); expect(getByText('Edit Person')).toBeInTheDocument(); userEvent.click(getByText('Show More')); @@ -237,15 +248,16 @@ describe('PersonModal - Saving Deceased', () => { }); it('deceases Jill and updates greetings (Jill listed middle)', async () => { - const mutationSpy = jest.fn(); const { getByText, getByLabelText } = render( - components(mutationSpy, { - id: '123-456', - name: 'Hill, Bill and Jill and Jack', - people: mock.people, - greeting: 'Bill and Jill and Jack', - envelopeGreeting: 'Bill and Jill and Jack Hill', - }), + , ); expect(getByText('Edit Person')).toBeInTheDocument(); userEvent.click(getByText('Show More')); @@ -292,16 +304,17 @@ describe('PersonModal - Saving Deceased', () => { }); it('sets the new primary contact as Jill is the primary contact', async () => { - const mutationSpy = jest.fn(); const { getByText, getByLabelText } = render( - components(mutationSpy, { - id: '123-456', - primaryPerson: mockPerson, - name: 'Hill, Bill and Jill and Jack', - people: mock.people, - greeting: 'Bill and Jill and Jack', - envelopeGreeting: 'Bill and Jill and Jack Hill', - }), + , ); expect(getByText('Edit Person')).toBeInTheDocument(); userEvent.click(getByText('Show More')); From 7ccb511987208a4b76294a7141c18bcc77d007ab Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Fri, 20 Sep 2024 09:45:05 -0500 Subject: [PATCH 4/4] Make dismiss button smaller --- .../ContactDetailsHeader/ContactDetailsHeader.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx index 340c13ae4..327f6ca8e 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx @@ -40,6 +40,10 @@ const DuplicateAlert = styled(Alert)(({ theme }) => ({ marginBottom: theme.spacing(2), display: 'flex', alignItems: 'center', + '.MuiAlert-action': { + // Remove the padding above the button so that it is centered vertically + paddingTop: 0, + }, })); const HeaderBar = styled(Box)(({}) => ({ @@ -146,7 +150,7 @@ export const ContactDetailsHeader: React.FC = ({ aria-label={t('Dismiss Duplicate')} onClick={() => setDuplicateContactId(null)} > - + }