Skip to content

Commit

Permalink
Merge pull request #1083 from CruGlobal/8229-duplicate-contact-message
Browse files Browse the repository at this point in the history
[MPDX-8229] Add duplicate contact button
  • Loading branch information
canac authored Sep 20, 2024
2 parents e584ed2 + 7ccb511 commit a8a2f6e
Show file tree
Hide file tree
Showing 10 changed files with 883 additions and 711 deletions.
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';

Expand All @@ -22,6 +24,9 @@ const MergeContactsPage: React.FC = () => {
>
<MergeContacts
accountListId={accountListId || ''}
contactId={
typeof query.duplicateId === 'string' ? query.duplicateId : undefined
}
setContactFocus={setContactFocus}
/>
</ToolsWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestComponentProps> = ({
duplicateRecord,
pathname,
}) => (
<SnackbarProvider>
<LocalizationProvider dateAdapter={AdapterLuxon}>
<TestRouter router={{ query: { accountListId }, pathname }}>
<ThemeProvider theme={theme}>
<GqlMockedProvider<{
GetContactDetailsHeader: GetContactDetailsHeaderQuery;
}>
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}
>
<ContactsWrapper>
<ContactDetailProvider>
<ContactDetailsHeader
accountListId={accountListId}
contactId={contactId}
onClose={() => {}}
setContactDetailsLoaded={() => {}}
contactDetailsLoaded={false}
/>
</ContactDetailProvider>
</ContactsWrapper>
</GqlMockedProvider>
</ThemeProvider>
</TestRouter>
</LocalizationProvider>
</SnackbarProvider>
);

describe('ContactDetails', () => {
it('should show loading state', async () => {
const { queryByTestId } = render(
<SnackbarProvider>
<TestRouter router={router}>
<ThemeProvider theme={theme}>
<GqlMockedProvider>
<ContactsWrapper>
<ContactDetailProvider>
<ContactDetailsHeader
accountListId={accountListId}
contactId={contactId}
onClose={() => {}}
setContactDetailsLoaded={() => {}}
contactDetailsLoaded={false}
/>
</ContactDetailProvider>
</ContactsWrapper>
</GqlMockedProvider>
</ThemeProvider>
</TestRouter>
</SnackbarProvider>,
);
const { getByTestId } = render(<TestComponent />);

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(
<SnackbarProvider>
<TestRouter router={router}>
<ThemeProvider theme={theme}>
<GqlMockedProvider<{
GetContactDetailsHeader: GetContactDetailsHeaderQuery;
}>
mocks={{
GetContactDetailsHeader: {
contact: {
name: 'Fname Lname',
lastDonation: null,
pledgeCurrency: 'USD',
},
},
}}
>
<ContactsWrapper>
<ContactDetailProvider>
<ContactDetailsHeader
accountListId={accountListId}
contactId={contactId}
onClose={() => {}}
setContactDetailsLoaded={() => {}}
contactDetailsLoaded={false}
/>
</ContactDetailProvider>
</ContactsWrapper>
</GqlMockedProvider>
</ThemeProvider>
</TestRouter>
</SnackbarProvider>,
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(
<TestComponent duplicateRecord={duplicateRecord} />,
);

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(<TestComponent />);

expect(
queryByRole('link', { name: 'See Match' }),
).not.toBeInTheDocument();
});

it('does not load on the merge contacts page', async () => {
render(
<TestComponent pathname="/accountLists/[accountListId]/tools/merge/contacts/[[...contactId]]" />,
);

expect(queryByTestId('Skeleton')).toBeNull();
await waitFor(() =>
expect(mutationSpy).toHaveGraphqlOperation('GetContactDetailsHeader', {
loadDuplicate: false,
}),
);
});
});

it('should render without primaryPerson', async () => {
const { findByText, queryByTestId } = render(
<SnackbarProvider>
<TestRouter router={router}>
<ThemeProvider theme={theme}>
<GqlMockedProvider<{
GetContactDetailsHeader: GetContactDetailsHeaderQuery;
}>
mocks={mocks}
>
<ContactsWrapper>
<ContactDetailProvider>
<ContactDetailsHeader
accountListId={accountListId}
contactId={contactId}
onClose={() => {}}
setContactDetailsLoaded={() => {}}
contactDetailsLoaded={false}
/>
</ContactDetailProvider>
</ContactsWrapper>
</GqlMockedProvider>
</ThemeProvider>
</TestRouter>
</SnackbarProvider>,
);
it('should render with contact details', async () => {
const { findByText, queryByTestId } = render(<TestComponent />);

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(
<SnackbarProvider>
<LocalizationProvider dateAdapter={AdapterLuxon}>
<TestRouter router={router}>
<ThemeProvider theme={theme}>
<GqlMockedProvider<{
GetContactDetailsHeader: GetContactDetailsHeaderQuery;
}>
mocks={mocks}
>
<ContactsWrapper>
<ContactDetailProvider>
<ContactDetailsHeader
accountListId={accountListId}
contactId={contactId}
onClose={() => {}}
setContactDetailsLoaded={() => {}}
contactDetailsLoaded={false}
/>
</ContactDetailProvider>
</ContactsWrapper>
</GqlMockedProvider>
</ThemeProvider>
</TestRouter>
</LocalizationProvider>
</SnackbarProvider>,
<LocalizationProvider dateAdapter={AdapterLuxon}>
<TestComponent />
</LocalizationProvider>,
);
await waitFor(() =>
expect(getAllByLabelText('Edit Partnership Info')[0]).toBeInTheDocument(),
Expand All @@ -170,32 +166,11 @@ describe('ContactDetails', () => {

it('should close Edit Partnership modal', async () => {
const { queryByText, getAllByLabelText, getByLabelText } = render(
<SnackbarProvider>
<LocalizationProvider dateAdapter={AdapterLuxon}>
<TestRouter router={router}>
<ThemeProvider theme={theme}>
<GqlMockedProvider<{
GetContactDetailsHeader: GetContactDetailsHeaderQuery;
}>
mocks={mocks}
>
<ContactsWrapper>
<ContactDetailProvider>
<ContactDetailsHeader
accountListId={accountListId}
contactId={contactId}
onClose={() => {}}
setContactDetailsLoaded={() => {}}
contactDetailsLoaded={false}
/>
</ContactDetailProvider>
</ContactsWrapper>
</GqlMockedProvider>
</ThemeProvider>
</TestRouter>
</LocalizationProvider>
</SnackbarProvider>,
<LocalizationProvider dateAdapter={AdapterLuxon}>
<TestComponent />
</LocalizationProvider>,
);

await waitFor(() =>
expect(getAllByLabelText('Edit Partnership Info')[0]).toBeInTheDocument(),
);
Expand All @@ -208,34 +183,10 @@ describe('ContactDetails', () => {
expect(queryByText('Edit Partnership')).not.toBeInTheDocument(),
);
});

it('should render avatar', async () => {
const { queryByText, getAllByLabelText } = render(
<SnackbarProvider>
<LocalizationProvider dateAdapter={AdapterLuxon}>
<TestRouter router={router}>
<ThemeProvider theme={theme}>
<GqlMockedProvider<{
GetContactDetailsHeader: GetContactDetailsHeaderQuery;
}>
mocks={mocks}
>
<ContactsWrapper>
<ContactDetailProvider>
<ContactDetailsHeader
accountListId={accountListId}
contactId={contactId}
onClose={() => {}}
setContactDetailsLoaded={() => {}}
contactDetailsLoaded={false}
/>
</ContactDetailProvider>
</ContactsWrapper>
</GqlMockedProvider>
</ThemeProvider>
</TestRouter>
</LocalizationProvider>
</SnackbarProvider>,
);
const { queryByText, getAllByLabelText } = render(<TestComponent />);

await waitFor(() =>
expect(getAllByLabelText('Edit Partnership Info')[0]).toBeInTheDocument(),
);
Expand Down
Loading

0 comments on commit a8a2f6e

Please sign in to comment.