Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preferences notifications #821

Merged
merged 25 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f823b1f
Moving NavReportsList to shared folder, and then getting it ready for…
dr-bizz Nov 9, 2023
898a1c5
Adding wrapper and notification page
dr-bizz Nov 13, 2023
3b69efa
Adding skeleton and graphQL calls
dr-bizz Nov 13, 2023
7343a90
Notifications table
dr-bizz Nov 13, 2023
19ee973
Hooking up new Notifications constants query.
dr-bizz Nov 14, 2023
261f9c5
Implementing notifications constants and cleaning TS errors
dr-bizz Nov 14, 2023
6ab2121
Merge branch 'main' into preferences-notifications
dr-bizz Nov 14, 2023
91cda35
Switching useMemo for useEffect to ensure it's run on initial render.
dr-bizz Nov 15, 2023
349bf95
Fixing test errors.
dr-bizz Nov 15, 2023
df7262e
Adding tests to up code coverage
dr-bizz Nov 15, 2023
1266a87
reverted to having testId on <Checkbox> as type error when passing it…
dr-bizz Nov 15, 2023
37b287d
Small fixes to improve code quailty
dr-bizz Nov 20, 2023
55e6038
Typography changes
dr-bizz Nov 30, 2023
60162be
Change tests to use render React components rather than function.
dr-bizz Nov 30, 2023
31de4fb
Type fixes.
dr-bizz Nov 30, 2023
02711c2
Merge branch 'main' into preferences-notifications
dr-bizz Nov 30, 2023
7f4e37a
Merge branch 'main' into preferences-notifications
dr-bizz Nov 30, 2023
a9265cd
Updating Organization query as conflicts with another PR yet to be me…
dr-bizz Nov 30, 2023
436ba5c
last edits on notifications
dr-bizz Dec 12, 2023
6f9c87d
merging main into this branch
dr-bizz Dec 15, 2023
5902d9d
Merge branch 'preferences-notifications' of https://github.com/CruGlo…
dr-bizz Dec 15, 2023
f066704
lint issues
dr-bizz Dec 15, 2023
c1997b5
prettier fixes
dr-bizz Dec 15, 2023
54a118b
Updating notifications to fix bug with main account viewing notificat…
dr-bizz Dec 15, 2023
40f9e21
tests for last commit
dr-bizz Dec 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions pages/accountLists/[accountListId]/settings/notifications.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Box } from '@mui/material';
import { SettingsWrapper } from './wrapper';
import { NotificationsTable } from 'src/components/Settings/notifications/NotificationsTable';

const Notifications: React.FC = () => {
const { t } = useTranslation();

return (
<SettingsWrapper
pageTitle={t('Notifications')}
pageHeading={t('Notifications')}
>
<Box component="section" marginTop={3}>
<p>
{t(`Based on an analysis of a partner&apos;s giving history, MPDX can
dr-bizz marked this conversation as resolved.
Show resolved Hide resolved
notify you of events that you will probably want to follow up on. The
detection logic is based on a set of rules that are right most of the
time, but you will still want to verify an event manually before
contacting the partner.`)}
</p>
</Box>
<Box component="section" marginTop={1}>
<p>
{t(`For each event MPDX can notify you via email and also create a task
entry reminding you to do something about it. The options below allow
you to control that behavior.`)}
</p>
</Box>
<NotificationsTable />
</SettingsWrapper>
);
};

export default Notifications;
74 changes: 74 additions & 0 deletions pages/accountLists/[accountListId]/settings/wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Box, Container } from '@mui/material';
import { styled } from '@mui/material/styles';
import React, { useState } from 'react';
import Head from 'next/head';
import useGetAppSettings from 'src/hooks/useGetAppSettings';
import { SidePanelsLayout } from 'src/components/Layouts/SidePanelsLayout';
import {
MultiPageMenu,
NavTypeEnum,
} from 'src/components/Shared/MultiPageLayout/MultiPageMenu/MultiPageMenu';
import {
MultiPageHeader,
HeaderTypeEnum,
} from 'src/components/Shared/MultiPageLayout/MultiPageHeader';

const PageContentWrapper = styled(Container)(({ theme }) => ({
paddingTop: theme.spacing(3),
paddingBottom: theme.spacing(3),
}));

interface SettingsWrapperProps {
pageTitle: string;
pageHeading: string;
children?: React.ReactNode;
}

export const SettingsWrapper: React.FC<SettingsWrapperProps> = ({
pageTitle,
pageHeading,
children,
}) => {
const { appName } = useGetAppSettings();
const [isNavListOpen, setNavListOpen] = useState(false);
const handleNavListToggle = () => {
setNavListOpen(!isNavListOpen);
};

return (
<>
<Head>
<title>
{appName} | {pageTitle}
</title>
</Head>
<Box component="main">
<SidePanelsLayout
isScrollBox={false}
leftPanel={
<MultiPageMenu
isOpen={isNavListOpen}
selectedId="responsibilityCenters"
onClose={handleNavListToggle}
navType={NavTypeEnum.Settings}
/>
}
leftOpen={isNavListOpen}
leftWidth="290px"
mainContent={
<>
<MultiPageHeader
isNavListOpen={isNavListOpen}
onNavListToggle={handleNavListToggle}
title={pageHeading}
rightExtra={null}
dr-bizz marked this conversation as resolved.
Show resolved Hide resolved
headerType={HeaderTypeEnum.Settings}
/>
<PageContentWrapper maxWidth="lg">{children}</PageContentWrapper>
</>
}
/>
</Box>
</>
);
};
25 changes: 25 additions & 0 deletions src/components/Settings/notifications/Notifications.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
query PreferencesNotifications($accountListId: ID!) {
notificationPreferences(accountListId: $accountListId) {
nodes {
id
app
email
notificationType {
id
descriptionTemplate
type
}
task
}
}
}

query NotificationConstants {
constant {
notificationTranslatedHashes {
id
key
value
}
}
}
233 changes: 233 additions & 0 deletions src/components/Settings/notifications/NotificationsTable.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NotificationsTable } from './NotificationsTable';
import { ThemeProvider } from '@mui/material/styles';
import { SnackbarProvider } from 'notistack';
import { GqlMockedProvider } from '../../../../__tests__/util/graphqlMocking';
import TestRouter from '../../../../__tests__/util/TestRouter';
import theme from '../../../../src/theme';
import { NotificationTypeTypeEnum } from '../../../../graphql/types.generated';

const mockEnqueue = jest.fn();

jest.mock('notistack', () => ({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
...jest.requireActual('notistack'),
useSnackbar: () => {
return {
enqueueSnackbar: mockEnqueue,
};
},
}));

const accountListId = 'test121';

const router = {
query: { accountListId },
isReady: true,
};
const createNotification = (type, id) => ({
app: false,
email: false,
task: false,
notificationType: {
id,
descriptionTemplate: type,
type,
},
});

const createConstant = (type, id) => ({
id: type,
key: id,
value: type,
});
const mocks = {
PreferencesNotifications: {
notificationPreferences: {
nodes: [
createNotification(
NotificationTypeTypeEnum.CallPartnerOncePerYear,
'111',
),
createNotification(NotificationTypeTypeEnum.LargerGift, '222'),
createNotification(NotificationTypeTypeEnum.LongTimeFrameGift, '333'),
],
},
},
NotificationConstants: {
constant: {
notificationTranslatedHashes: [
createConstant(NotificationTypeTypeEnum.CallPartnerOncePerYear, '111'),
createConstant(NotificationTypeTypeEnum.LargerGift, '222'),
createConstant(NotificationTypeTypeEnum.LongTimeFrameGift, '333'),
],
},
},
};
const mutationSpy = jest.fn();
const Components = (
dr-bizz marked this conversation as resolved.
Show resolved Hide resolved
<SnackbarProvider>
<ThemeProvider theme={theme}>
<TestRouter router={router}>
<GqlMockedProvider onCall={mutationSpy} mocks={mocks}>
<NotificationsTable />
</GqlMockedProvider>
</TestRouter>
</ThemeProvider>
</SnackbarProvider>
);

describe('NotificationsTable', () => {
beforeEach(() => {
mutationSpy.mockReset();
});
it('Should render the Table and request data', async () => {
const { getByTestId, queryByTestId, getByText } = render(Components);

expect(getByTestId('skeleton-notifications')).toBeInTheDocument();

await waitFor(() => {
expect(queryByTestId('skeleton-notifications')).not.toBeInTheDocument(),
dr-bizz marked this conversation as resolved.
Show resolved Hide resolved
expect(
mutationSpy.mock.calls[0][0].operation.variables.accountListId,
).toEqual(accountListId);
expect(mutationSpy.mock.calls[0][0].operation.operationName).toEqual(
'PreferencesNotifications',
);
expect(mutationSpy.mock.calls[1][0].operation.operationName).toEqual(
'NotificationConstants',
);
});

await waitFor(() => {
expect(getByText('CALL_PARTNER_ONCE_PER_YEAR')).toBeInTheDocument();
});

await waitFor(() => {
expect(
getByTestId('CALL_PARTNER_ONCE_PER_YEAR-app-checkbox'),
).not.toBeChecked();
});

await waitFor(() => {
expect(getByText('LARGER_GIFT')).toBeInTheDocument();
});
});

it('Should select all', async () => {
const { queryByTestId, getByTestId, getAllByRole } = render(Components);

await waitFor(() =>
expect(queryByTestId('skeleton-notifications')).not.toBeInTheDocument(),
);
const checkboxes = getAllByRole('checkbox');
expect(checkboxes[0]).not.toBeChecked();
expect(checkboxes[1]).not.toBeChecked();
expect(checkboxes[2]).not.toBeChecked();
expect(checkboxes[3]).not.toBeChecked();
expect(checkboxes[4]).not.toBeChecked();
expect(checkboxes[5]).not.toBeChecked();
expect(checkboxes[6]).not.toBeChecked();
expect(checkboxes[7]).not.toBeChecked();
expect(checkboxes[8]).not.toBeChecked();

// Select all app
userEvent.click(getByTestId('select-all-app'));
expect(checkboxes[0]).toBeChecked();
expect(checkboxes[3]).toBeChecked();
expect(checkboxes[6]).toBeChecked();

// Select all email
userEvent.click(getByTestId('select-all-email'));
expect(checkboxes[1]).toBeChecked();
expect(checkboxes[4]).toBeChecked();
expect(checkboxes[7]).toBeChecked();

// Select all tasks
userEvent.click(getByTestId('select-all-task'));
expect(checkboxes[2]).toBeChecked();
expect(checkboxes[5]).toBeChecked();
expect(checkboxes[8]).toBeChecked();
});

it('Should select Call Partner Once Per Year checkboxes', async () => {
const { queryByTestId, getByTestId } = render(Components);

await waitFor(() =>
dr-bizz marked this conversation as resolved.
Show resolved Hide resolved
expect(queryByTestId('skeleton-notifications')).not.toBeInTheDocument(),
);

const appCheckbox = getByTestId(
'CALL_PARTNER_ONCE_PER_YEAR-app-checkbox',
).querySelectorAll("input[type='checkbox']")[0] as HTMLInputElement;
const emailCheckbox = getByTestId(
'CALL_PARTNER_ONCE_PER_YEAR-email-checkbox',
).querySelectorAll("input[type='checkbox']")[0] as HTMLInputElement;
const taskCheckbox = getByTestId(
'CALL_PARTNER_ONCE_PER_YEAR-task-checkbox',
).querySelectorAll("input[type='checkbox']")[0] as HTMLInputElement;

expect(appCheckbox).not.toBeChecked();
expect(emailCheckbox).not.toBeChecked();
expect(taskCheckbox).not.toBeChecked();

// Check first row
userEvent.click(appCheckbox);
userEvent.click(emailCheckbox);
userEvent.click(taskCheckbox);

expect(appCheckbox).toBeChecked();
expect(emailCheckbox).toBeChecked();
expect(taskCheckbox).toBeChecked();
});

it('Should send data to server on submit', async () => {
const { queryByTestId, getByTestId, getAllByRole } = render(Components);

await waitFor(() =>
dr-bizz marked this conversation as resolved.
Show resolved Hide resolved
expect(queryByTestId('skeleton-notifications')).not.toBeInTheDocument(),
);
// Select all app
userEvent.click(getByTestId('select-all-app'));

userEvent.click(
getAllByRole('button', {
name: 'Save Changes',
})[0],
);

await waitFor(() => {
expect(mutationSpy.mock.calls[2][0].operation.operationName).toEqual(
'UpdateNotificationPreferences',
);

expect(mutationSpy.mock.calls[2][0].operation.variables.input).toEqual({
accountListId: accountListId,
attributes: [
{
app: true,
email: false,
task: false,
notificationType: 'CALL_PARTNER_ONCE_PER_YEAR',
},
{
app: true,
email: false,
task: false,
notificationType: 'LARGER_GIFT',
},
{
app: true,
email: false,
task: false,
notificationType: 'LONG_TIME_FRAME_GIFT',
},
],
});
expect(mockEnqueue).toHaveBeenCalled();
});
});
});
Loading
Loading