Repository: CruGlobal/mpdx-react
Pull request #1275: [No-Jira] Add Error Message for invalid MailChimp connections
Author: login: caleballdrin
Labels: name: Preview Environment
description: Add this label to create an Amplify Preview
Created: 2025-03-11T18:38:51Z | Merged: 2025-03-12T19:11:25Z

## Description
[HS-1320872](https://secure.helpscout.net/conversation/2872011976/1320872?viewId=7669074)
The user is not seeing any buttons to connect a MailChimp account. He has a connection but there is an error with the API key so `validateKey` is set to false.

- Add an error message and disconnect button when `validateKey` is false.

## Checklist:

- [ ] I have given my PR a title with the format "MPDX-(JIRA#) (summary sentence max 80 chars)"
- [ ] I have applied the appropriate labels. (_Add the label "On Staging" to get the branch automatically merged into staging._)
- [ ] I have requested a review from another person on the project



Diff:
diff --git a/src/components/Settings/integrations/Mailchimp/MailchimpAccordion.test.tsx b/src/components/Settings/integrations/Mailchimp/MailchimpAccordion.test.tsx
index fbdfd1f152..696d559d04 100644
--- a/src/components/Settings/integrations/Mailchimp/MailchimpAccordion.test.tsx
+++ b/src/components/Settings/integrations/Mailchimp/MailchimpAccordion.test.tsx
@@ -1,4 +1,3 @@
-import { PropsWithChildren } from 'react';
 import { ThemeProvider } from '@mui/material/styles';
 import { act, render, waitFor, within } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
@@ -33,11 +32,34 @@ jest.mock('notistack', () => ({
 }));
 
 const handleAccordionChange = jest.fn();
-
-const Components = ({ children }: PropsWithChildren) => (
+const mutationSpy = jest.fn();
+
+const Components = ({
+  expandedAccordion,
+  mailchimpAccount,
+}: {
+  expandedAccordion: IntegrationAccordion | null;
+  mailchimpAccount: Types.MailchimpAccount | null;
+}) => (
   <SnackbarProvider>
     <TestRouter router={router}>
-      <ThemeProvider theme={theme}>{children}</ThemeProvider>
+      <ThemeProvider theme={theme}>
+        <GqlMockedProvider<{
+          MailchimpAccount: MailchimpAccountQuery | undefined;
+        }>
+          mocks={{
+            MailchimpAccount: {
+              mailchimpAccount: [mailchimpAccount],
+            },
+          }}
+          onCall={mutationSpy}
+        >
+          <MailchimpAccordion
+            handleAccordionChange={handleAccordionChange}
+            expandedAccordion={expandedAccordion}
+          />
+        </GqlMockedProvider>
+      </ThemeProvider>
     </TestRouter>
   </SnackbarProvider>
 );
@@ -76,14 +98,10 @@ describe('MailchimpAccount', () => {
   process.env.OAUTH_URL = 'https://auth.mpdx.org';
   it('should render accordion closed', async () => {
     const { getByText, queryByRole } = render(
-      <Components>
-        <GqlMockedProvider>
-          <MailchimpAccordion
-            handleAccordionChange={handleAccordionChange}
-            expandedAccordion={null}
-          />
-        </GqlMockedProvider>
-      </Components>,
+      <Components
+        expandedAccordion={null}
+        mailchimpAccount={standardMailchimpAccount}
+      />,
     );
     expect(getByText('MailChimp')).toBeInTheDocument();
     const mailchimpImage = queryByRole('img', {
@@ -93,14 +111,10 @@ describe('MailchimpAccount', () => {
   });
   it('should render accordion open', async () => {
     const { queryByRole } = render(
-      <Components>
-        <GqlMockedProvider>
-          <MailchimpAccordion
-            handleAccordionChange={handleAccordionChange}
-            expandedAccordion={IntegrationAccordion.Mailchimp}
-          />
-        </GqlMockedProvider>
-      </Components>,
+      <Components
+        expandedAccordion={IntegrationAccordion.Mailchimp}
+        mailchimpAccount={standardMailchimpAccount}
+      />,
     );
     const mailchimpImage = queryByRole('img', {
       name: /mailchimp/i,
@@ -110,25 +124,11 @@ describe('MailchimpAccount', () => {
 
   describe('Not Connected', () => {
     it('should render Mailchimp Overview', async () => {
-      const mutationSpy = jest.fn();
       const { getByText } = render(
-        <Components>
-          <GqlMockedProvider<{
-            MailchimpAccount: MailchimpAccountQuery | undefined;
-          }>
-            mocks={{
-              MailchimpAccount: {
-                mailchimpAccount: [],
-              },
-            }}
-            onCall={mutationSpy}
-          >
-            <MailchimpAccordion
-              handleAccordionChange={handleAccordionChange}
-              expandedAccordion={IntegrationAccordion.Mailchimp}
-            />
-          </GqlMockedProvider>
-        </Components>,
+        <Components
+          expandedAccordion={IntegrationAccordion.Mailchimp}
+          mailchimpAccount={null}
+        />,
       );
 
       await waitFor(() => {
@@ -149,27 +149,39 @@ describe('MailchimpAccount', () => {
     beforeEach(() => {
       mailchimpAccount = { ...standardMailchimpAccount };
     });
+    it('is connected but with an API key error', async () => {
+      mailchimpAccount.validateKey = false;
+      const { findByText, findByRole, queryByText } = render(
+        <Components
+          expandedAccordion={IntegrationAccordion.Mailchimp}
+          mailchimpAccount={mailchimpAccount}
+        />,
+      );
+
+      expect(
+        await findByText(
+          'There is an error with your MailChimp connection. Please disconnect and reconnect to MailChimp.',
+        ),
+      ).toBeInTheDocument();
+
+      expect(
+        await findByRole('button', {
+          name: /disconnect/i,
+        }),
+      ).toBeInTheDocument();
+
+      expect(
+        queryByText('Pick a list to use for your newsletter'),
+      ).not.toBeInTheDocument();
+    });
+
     it('is connected but no lists present', async () => {
       mailchimpAccount.listsPresent = false;
-      const mutationSpy = jest.fn();
       const { queryByText } = render(
-        <Components>
-          <GqlMockedProvider<{
-            MailchimpAccount: MailchimpAccountQuery | undefined;
-          }>
-            mocks={{
-              MailchimpAccount: {
-                mailchimpAccount: [mailchimpAccount],
-              },
-            }}
-            onCall={mutationSpy}
-          >
-            <MailchimpAccordion
-              handleAccordionChange={handleAccordionChange}
-              expandedAccordion={IntegrationAccordion.Mailchimp}
-            />
-          </GqlMockedProvider>
-        </Components>,
+        <Components
+          expandedAccordion={IntegrationAccordion.Mailchimp}
+          mailchimpAccount={mailchimpAccount}
+        />,
       );
 
       await waitFor(() => {
@@ -194,25 +206,11 @@ describe('MailchimpAccount', () => {
     it('is connected but no lists present & no lists link', async () => {
       mailchimpAccount.listsPresent = false;
       mailchimpAccount.listsLink = '';
-      const mutationSpy = jest.fn();
       const { queryByText } = render(
-        <Components>
-          <GqlMockedProvider<{
-            MailchimpAccount: MailchimpAccountQuery;
-          }>
-            mocks={{
-              MailchimpAccount: {
-                mailchimpAccount: [mailchimpAccount],
-              },
-            }}
-            onCall={mutationSpy}
-          >
-            <MailchimpAccordion
-              handleAccordionChange={handleAccordionChange}
-              expandedAccordion={IntegrationAccordion.Mailchimp}
-            />
-          </GqlMockedProvider>
-        </Components>,
+        <Components
+          expandedAccordion={IntegrationAccordion.Mailchimp}
+          mailchimpAccount={mailchimpAccount}
+        />,
       );
 
       await waitFor(() => {
@@ -229,25 +227,11 @@ describe('MailchimpAccount', () => {
     });
 
     it('should call updateMailchimpAccount', async () => {
-      const mutationSpy = jest.fn();
       const { getByText, getByRole } = render(
-        <Components>
-          <GqlMockedProvider<{
-            MailchimpAccount: MailchimpAccountQuery | undefined;
-          }>
-            mocks={{
-              MailchimpAccount: {
-                mailchimpAccount: [mailchimpAccount],
-              },
-            }}
-            onCall={mutationSpy}
-          >
-            <MailchimpAccordion
-              handleAccordionChange={handleAccordionChange}
-              expandedAccordion={IntegrationAccordion.Mailchimp}
-            />
-          </GqlMockedProvider>
-        </Components>,
+        <Components
+          expandedAccordion={IntegrationAccordion.Mailchimp}
+          mailchimpAccount={mailchimpAccount}
+        />,
       );
 
       await waitFor(() => {
@@ -296,25 +280,11 @@ describe('MailchimpAccount', () => {
     });
 
     it('should call deleteMailchimpAccount', async () => {
-      const mutationSpy = jest.fn();
       const { getByText, getByRole } = render(
-        <Components>
-          <GqlMockedProvider<{
-            MailchimpAccount: MailchimpAccountQuery | undefined;
-          }>
-            mocks={{
-              MailchimpAccount: {
-                mailchimpAccount: [mailchimpAccount],
-              },
-            }}
-            onCall={mutationSpy}
-          >
-            <MailchimpAccordion
-              handleAccordionChange={handleAccordionChange}
-              expandedAccordion={IntegrationAccordion.Mailchimp}
-            />
-          </GqlMockedProvider>
-        </Components>,
+        <Components
+          expandedAccordion={IntegrationAccordion.Mailchimp}
+          mailchimpAccount={mailchimpAccount}
+        />,
       );
 
       await waitFor(() => {
@@ -363,25 +333,11 @@ describe('MailchimpAccount', () => {
     });
     it('should show settings overview', async () => {
       mailchimpAccount.valid = true;
-      const mutationSpy = jest.fn();
       const { getByText } = render(
-        <Components>
-          <GqlMockedProvider<{
-            MailchimpAccount: MailchimpAccountQuery | undefined;
-          }>
-            mocks={{
-              MailchimpAccount: {
-                mailchimpAccount: [mailchimpAccount],
-              },
-            }}
-            onCall={mutationSpy}
-          >
-            <MailchimpAccordion
-              handleAccordionChange={handleAccordionChange}
-              expandedAccordion={IntegrationAccordion.Mailchimp}
-            />
-          </GqlMockedProvider>
-        </Components>,
+        <Components
+          expandedAccordion={IntegrationAccordion.Mailchimp}
+          mailchimpAccount={mailchimpAccount}
+        />,
       );
 
       await waitFor(() => {
@@ -399,25 +355,11 @@ describe('MailchimpAccount', () => {
     it('should call syncMailchimpAccount', async () => {
       mailchimpAccount.valid = true;
       mailchimpAccount.autoLogCampaigns = true;
-      const mutationSpy = jest.fn();
       const { getByText, getByRole } = render(
-        <Components>
-          <GqlMockedProvider<{
-            MailchimpAccount: MailchimpAccountQuery | undefined;
-          }>
-            mocks={{
-              MailchimpAccount: {
-                mailchimpAccount: [mailchimpAccount],
-              },
-            }}
-            onCall={mutationSpy}
-          >
-            <MailchimpAccordion
-              handleAccordionChange={handleAccordionChange}
-              expandedAccordion={IntegrationAccordion.Mailchimp}
-            />
-          </GqlMockedProvider>
-        </Components>,
+        <Components
+          expandedAccordion={IntegrationAccordion.Mailchimp}
+          mailchimpAccount={mailchimpAccount}
+        />,
       );
 
       await waitFor(() => {
@@ -456,25 +398,11 @@ describe('MailchimpAccount', () => {
     it('should show settings', async () => {
       mailchimpAccount.valid = true;
       mailchimpAccount.autoLogCampaigns = true;
-      const mutationSpy = jest.fn();
       const { queryByText, getByRole } = render(
-        <Components>
-          <GqlMockedProvider<{
-            MailchimpAccount: MailchimpAccountQuery | undefined;
-          }>
-            mocks={{
-              MailchimpAccount: {
-                mailchimpAccount: [mailchimpAccount],
-              },
-            }}
-            onCall={mutationSpy}
-          >
-            <MailchimpAccordion
-              handleAccordionChange={handleAccordionChange}
-              expandedAccordion={IntegrationAccordion.Mailchimp}
-            />
-          </GqlMockedProvider>
-        </Components>,
+        <Components
+          expandedAccordion={IntegrationAccordion.Mailchimp}
+          mailchimpAccount={mailchimpAccount}
+        />,
       );
 
       await waitFor(() => {
diff --git a/src/components/Settings/integrations/Mailchimp/MailchimpAccordion.tsx b/src/components/Settings/integrations/Mailchimp/MailchimpAccordion.tsx
index d7bf00b75e..8eaded7b0d 100644
--- a/src/components/Settings/integrations/Mailchimp/MailchimpAccordion.tsx
+++ b/src/components/Settings/integrations/Mailchimp/MailchimpAccordion.tsx
@@ -378,7 +378,22 @@ export const MailchimpAccordion: React.FC<AccordionProps> = ({
             </StyledButton>
           </Box>
         )}
-
+      {!loading && mailchimpAccount && !mailchimpAccount?.validateKey && (
+        <Box>
+          <Alert severity="error">
+            {t(
+              'There is an error with your MailChimp connection. Please disconnect and reconnect to MailChimp.',
+            )}
+          </Alert>
+          <StyledServicesButton
+            onClick={handleDisconnect}
+            variant="outlined"
+            color="error"
+          >
+            {t('Disconnect')}
+          </StyledServicesButton>
+        </Box>
+      )}
       {showDeleteModal && (
         <DeleteMailchimpAccountModal
           accountListId={accountListId || ''}


Reviews:
user: login: dr-bizz
body: Great work on this! Thank you.

I spotted 2 changes, the third is cleaning up the codebase for extra points.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T18:56:47Z
user: login: dr-bizz
body: Amazing work! Thank you for adding those making the codebase better.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T17:32:00Z
user: login: caleballdrin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T17:36:29Z
user: login: dr-bizz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T17:42:33Z

Review comments:
diff_hunk: @@ -378,7 +378,22 @@ export const MailchimpAccordion: React.FC<AccordionProps> = ({
             </StyledButton>
           </Box>
         )}
-
+      {!loading && mailchimpAccount && !mailchimpAccount?.validateKey && (
+        <Box>
+          <Alert severity="error">
+            {t(
+              'There is an error with your MailChimp connection. Please disconnect and connect to MailChimp again.',
path: src/components/Settings/integrations/Mailchimp/MailchimpAccordion.tsx
user: login: dr-bizz
body: 
```suggestion
              'There is an error with your MailChimp connection. Please disconnect and reconnect to Mailchimp.',
```
created_at: 2025-03-11T18:49:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -149,6 +149,48 @@ describe('MailchimpAccount', () => {
     beforeEach(() => {
       mailchimpAccount = { ...standardMailchimpAccount };
     });
+    it('is connected but with an API key error', async () => {
+      mailchimpAccount.validateKey = false;
+      const mutationSpy = jest.fn();
+      const { queryByText, getByRole } = render(
+        <Components>
+          <GqlMockedProvider<{
+            MailchimpAccount: MailchimpAccountQuery | undefined;
+          }>
+            mocks={{
+              MailchimpAccount: {
+                mailchimpAccount: [mailchimpAccount],
+              },
+            }}
+            onCall={mutationSpy}
+          >
+            <MailchimpAccordion
+              handleAccordionChange={handleAccordionChange}
+              expandedAccordion={IntegrationAccordion.Mailchimp}
+            />
+          </GqlMockedProvider>
+        </Components>,
+      );
+
+      await waitFor(() => {
+        expect(
+          queryByText(
+            'There is an error with your MailChimp connection. Please disconnect and connect to MailChimp again.',
+          ),
+        ).toBeInTheDocument();
+      });
path: src/components/Settings/integrations/Mailchimp/MailchimpAccordion.test.tsx
user: login: dr-bizz
body: It's better to use `await findByText` than ` await waitFor`.
```suggestion
      expect(
        await findByText(
          'There is an error with your MailChimp connection. Please disconnect and connect to MailChimp again.',
        ),
      ).toBeInTheDocument();
```
created_at: 2025-03-11T18:52:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -149,6 +149,48 @@ describe('MailchimpAccount', () => {
     beforeEach(() => {
       mailchimpAccount = { ...standardMailchimpAccount };
     });
+    it('is connected but with an API key error', async () => {
+      mailchimpAccount.validateKey = false;
+      const mutationSpy = jest.fn();
+      const { queryByText, getByRole } = render(
+        <Components>
+          <GqlMockedProvider<{
+            MailchimpAccount: MailchimpAccountQuery | undefined;
+          }>
+            mocks={{
+              MailchimpAccount: {
+                mailchimpAccount: [mailchimpAccount],
+              },
+            }}
+            onCall={mutationSpy}
+          >
+            <MailchimpAccordion
+              handleAccordionChange={handleAccordionChange}
+              expandedAccordion={IntegrationAccordion.Mailchimp}
+            />
+          </GqlMockedProvider>
+        </Components>,
path: src/components/Settings/integrations/Mailchimp/MailchimpAccordion.test.tsx
user: login: dr-bizz
body: If you would be willing, would you be able to make the codebase better by removeing a lot of the repeat code I added?

You can make the  `<Components />` takes props so each test doesn't have to repeat the `GqlMockedProvider` and `MailchimpAccordion` components.
created_at: 2025-03-11T18:55:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,10 +33,34 @@ jest.mock('notistack', () => ({
 
 const handleAccordionChange = jest.fn();
 
-const Components = ({ children }: PropsWithChildren) => (
+const Components = ({
+  expandedAccordion,
+  mailchimpAccount,
+  mutationSpy,
path: src/components/Settings/integrations/Mailchimp/MailchimpAccordion.test.tsx
user: login: dr-bizz
body: You could add a `mutationSpy` variable above this function and use it in this component. And it should reset before each test.
created_at: 2025-03-12T17:30:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,10 +33,34 @@ jest.mock('notistack', () => ({
 
 const handleAccordionChange = jest.fn();
 
-const Components = ({ children }: PropsWithChildren) => (
+const Components = ({
+  expandedAccordion,
+  mailchimpAccount,
+  mutationSpy,
path: src/components/Settings/integrations/Mailchimp/MailchimpAccordion.test.tsx
user: login: caleballdrin
body: @dr-bizz Should I do that in addition to passing it as a prop? Some tests check `mutationSpy` so it needs to be passed through.

What would be the purpose of the new variable?
created_at: 2025-03-12T17:36:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,10 +33,34 @@ jest.mock('notistack', () => ({
 
 const handleAccordionChange = jest.fn();
 
-const Components = ({ children }: PropsWithChildren) => (
+const Components = ({
+  expandedAccordion,
+  mailchimpAccount,
+  mutationSpy,
path: src/components/Settings/integrations/Mailchimp/MailchimpAccordion.test.tsx
user: login: dr-bizz
body: No, you don't need to pass it on as a prop. You can define it at the top and then use it everywhere. Like this page:
https://github.com/CruGlobal/mpdx-react/blob/b64dca649ecffa93e1059ca0e45d57903b489a15/src/components/Reports/FinancialAccountsReport/AccountTransactions/AccountTransactions.test.tsx
created_at: 2025-03-12T17:42:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-11T18:41:23Z
body: ### Bundle sizes [mpdx-react]

Compared against 24e31e783a95a6d011616d089eb891a1b474b944

No significant changes found
author_association: CONTRIBUTOR
user: login: caleballdrin
created_at: 2025-03-12T15:54:25Z
body: @dr-bizz I'm not sure how I can make this more simple to get CodeScene to pass. Any ideas?
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-12T15:56:04Z
body: Preview branch generated at https://HS-1320872-mailchimp-error.d3dytjb8adxkk5.amplifyapp.com
author_association: CONTRIBUTOR
user: login: dr-bizz
created_at: 2025-03-12T16:13:48Z
body: I've suppressed the error as you are correct.
author_association: CONTRIBUTOR
user: login: dr-bizz
created_at: 2025-03-12T16:14:29Z
body: CodeScene is here to help us improve and point out areas we can improve. but sometimes it can get in the way of progression
author_association: CONTRIBUTOR
user: login: caleballdrin
created_at: 2025-03-12T17:14:58Z
body: @dr-bizz is this good to deploy?
author_association: CONTRIBUTOR
user: login: dr-bizz
created_at: 2025-03-12T17:23:30Z
body: Sorry went for lunh, let me look at the recent changes
author_association: CONTRIBUTOR