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/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..327f6ca8e 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,16 @@ interface Props {
contextType?: ContactContextTypesEnum;
}
+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)(({}) => ({
display: 'flex',
paddingBottom: theme.spacing(1),
@@ -67,8 +87,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 +104,10 @@ export const ContactDetailsHeader: React.FC = ({
const [editPartnershipModalOpen, setEditPartnershipModalOpen] =
useState(false);
+ const [duplicateContactId, setDuplicateContactId] = useState(
+ null,
+ );
+
useEffect(() => {
if (!loading && !contactDetailsLoaded) {
setContactDetailsLoaded(true);
@@ -83,8 +115,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.')}
+
+ )}
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'));
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();