Skip to content

Commit 82dcd59

Browse files
committed
CCM-11148: create message plan page
1 parent fd97d66 commit 82dcd59

File tree

12 files changed

+910
-0
lines changed

12 files changed

+910
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
import { getRoutingConfigs } from '@utils/form-actions';
5+
import {
6+
type MessagePlansProps,
7+
MessagePlans,
8+
} from '@molecules/MessagePlans/MessagePlans';
9+
import { serverIsFeatureEnabled } from '@utils/server-features';
10+
import MessagePlansPage, { generateMetadata } from '@app/message-plans/page';
11+
import { redirect } from 'next/navigation';
12+
import { ReactElement } from 'react';
13+
14+
jest.mock('next/navigation');
15+
jest.mock('@utils/form-actions');
16+
jest.mock('@utils/server-features');
17+
18+
const serverIsFeatureEnabledMock = jest.mocked(serverIsFeatureEnabled);
19+
const getRoutingConfigsMock = jest.mocked(getRoutingConfigs);
20+
const redirectMock = jest.mocked(redirect);
21+
22+
describe('MessagePlansPage', () => {
23+
beforeEach(() => {
24+
jest.clearAllMocks();
25+
serverIsFeatureEnabledMock.mockResolvedValueOnce(true);
26+
});
27+
28+
test('page metadata', async () => {
29+
const metadata = await generateMetadata();
30+
31+
expect(metadata).toEqual({
32+
title: 'Message plans - NHS Notify',
33+
});
34+
});
35+
36+
test('redirects to invalid page when routing feature is disabled', async () => {
37+
serverIsFeatureEnabledMock.mockReset();
38+
serverIsFeatureEnabledMock.mockResolvedValueOnce(false);
39+
40+
await MessagePlansPage();
41+
42+
expect(redirectMock).toHaveBeenCalledWith('/invalid-template', 'replace');
43+
});
44+
45+
test('renders the page with drafts and production routing plans', async () => {
46+
getRoutingConfigsMock.mockResolvedValueOnce([
47+
{
48+
id: '1',
49+
name: 'Draft A',
50+
lastUpdated: '2025-09-10T10:00:00Z',
51+
status: 'DRAFT',
52+
},
53+
{
54+
id: '2',
55+
name: 'Prod X',
56+
lastUpdated: '2025-09-09T11:00:00Z',
57+
status: 'PRODUCTION',
58+
},
59+
{
60+
id: '3',
61+
name: 'Prod Y',
62+
lastUpdated: '2025-09-08T12:00:00Z',
63+
status: 'PRODUCTION',
64+
},
65+
]);
66+
67+
const page = (await MessagePlansPage()) as ReactElement<
68+
MessagePlansProps,
69+
typeof MessagePlans
70+
>;
71+
72+
expect(page.props).toBeDefined();
73+
74+
expect(redirectMock).not.toHaveBeenCalled();
75+
76+
expect(getRoutingConfigsMock).toHaveBeenCalledTimes(1);
77+
78+
expect(page.props.draft).toEqual({
79+
plans: [
80+
{
81+
id: '1',
82+
name: 'Draft A',
83+
lastUpdated: '2025-09-10T10:00:00Z',
84+
status: 'DRAFT',
85+
},
86+
],
87+
count: 1,
88+
});
89+
90+
expect(page.props.production).toEqual({
91+
plans: [
92+
{
93+
id: '2',
94+
name: 'Prod X',
95+
lastUpdated: '2025-09-09T11:00:00Z',
96+
status: 'PRODUCTION',
97+
},
98+
{
99+
id: '3',
100+
name: 'Prod Y',
101+
lastUpdated: '2025-09-08T12:00:00Z',
102+
status: 'PRODUCTION',
103+
},
104+
],
105+
count: 2,
106+
});
107+
});
108+
109+
test('renders the page with no items', async () => {
110+
getRoutingConfigsMock.mockResolvedValueOnce([]);
111+
112+
const page = (await MessagePlansPage()) as ReactElement<
113+
MessagePlansProps,
114+
typeof MessagePlans
115+
>;
116+
117+
expect(page.props).toBeDefined();
118+
119+
expect(page.props.draft).toEqual({
120+
plans: [],
121+
count: 0,
122+
});
123+
124+
expect(page.props.production).toEqual({
125+
plans: [],
126+
count: 0,
127+
});
128+
});
129+
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { render } from '@testing-library/react';
2+
import { MessagePlans } from '@molecules/MessagePlans/MessagePlans';
3+
import { MessagePlansList } from '@molecules/MessagePlansList/MessagePlansList';
4+
5+
jest.mock('@molecules/MessagePlansList/MessagePlansList');
6+
7+
const MessagePlansListMock = jest.mocked(MessagePlansList);
8+
9+
describe('MessagePlans', () => {
10+
test('matches snapshot', () => {
11+
const production = {
12+
count: 2,
13+
plans: [
14+
{
15+
id: '1',
16+
name: 'Prod A',
17+
lastUpdated: '2025-09-10T10:00:00Z',
18+
},
19+
{
20+
id: '2',
21+
name: 'Prod X',
22+
lastUpdated: '2025-09-09T11:00:00Z',
23+
},
24+
],
25+
};
26+
const draft = {
27+
count: 1,
28+
plans: [
29+
{
30+
id: '3',
31+
name: 'Draft Y',
32+
lastUpdated: '2025-09-08T12:00:00Z',
33+
},
34+
],
35+
};
36+
const container = render(
37+
<MessagePlans draft={draft} production={production} />
38+
);
39+
expect(container.asFragment()).toMatchSnapshot();
40+
});
41+
42+
test('expect MessagePlansList to be called with Draft and Production', () => {
43+
const production = {
44+
count: 2,
45+
plans: [
46+
{
47+
id: '1',
48+
name: 'Prod A',
49+
lastUpdated: '2025-09-10T10:00:00Z',
50+
},
51+
{
52+
id: '2',
53+
name: 'Prod X',
54+
lastUpdated: '2025-09-09T11:00:00Z',
55+
},
56+
],
57+
};
58+
const draft = {
59+
count: 1,
60+
plans: [
61+
{
62+
id: '3',
63+
name: 'Draft Y',
64+
lastUpdated: '2025-09-08T12:00:00Z',
65+
},
66+
],
67+
};
68+
render(<MessagePlans draft={draft} production={production} />);
69+
70+
expect(MessagePlansListMock).toHaveBeenCalledTimes(2);
71+
72+
expect(MessagePlansListMock).toHaveBeenNthCalledWith(
73+
1,
74+
{
75+
count: draft.count,
76+
plans: draft.plans,
77+
planType: 'Draft',
78+
},
79+
undefined
80+
);
81+
82+
expect(MessagePlansListMock).toHaveBeenNthCalledWith(
83+
2,
84+
{
85+
count: production.count,
86+
plans: production.plans,
87+
planType: 'Production',
88+
},
89+
undefined
90+
);
91+
});
92+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { render, screen } from '@testing-library/react';
2+
import { MessagePlansList } from '@molecules/MessagePlansList/MessagePlansList';
3+
4+
describe('MessagePlansList', () => {
5+
it('matches snapshot when data is available', async () => {
6+
const draft = {
7+
count: 1,
8+
plans: [
9+
{
10+
id: '3',
11+
name: 'Draft Y',
12+
lastUpdated: '2025-09-08T12:00:00Z',
13+
},
14+
],
15+
};
16+
17+
const container = render(
18+
<MessagePlansList
19+
planType='Draft'
20+
count={draft.count}
21+
plans={draft.plans}
22+
/>
23+
);
24+
25+
expect(container.asFragment()).toMatchSnapshot();
26+
});
27+
28+
it('matches snapshot when no data is available', async () => {
29+
const container = render(
30+
<MessagePlansList planType='Draft' count={0} plans={[]} />
31+
);
32+
33+
expect(container.asFragment()).toMatchSnapshot();
34+
});
35+
36+
it('formats lastEdited date and time', async () => {
37+
const draft = {
38+
count: 1,
39+
plans: [
40+
{
41+
id: '3',
42+
name: 'Draft Y',
43+
lastUpdated: '2025-09-08T12:00:00Z',
44+
},
45+
],
46+
};
47+
48+
render(
49+
<MessagePlansList
50+
planType='Draft'
51+
count={draft.count}
52+
plans={draft.plans}
53+
/>
54+
);
55+
56+
expect(
57+
screen.getByTestId('message-plans-list-lastEdited-date')
58+
).toHaveTextContent('8th Sep 2025');
59+
60+
expect(
61+
screen.getByTestId('message-plans-list-lastEdited-time')
62+
).toHaveTextContent('13:00');
63+
});
64+
});
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`MessagePlans matches snapshot 1`] = `
4+
<DocumentFragment>
5+
<main
6+
class="nhsuk-main-wrapper"
7+
id="maincontent"
8+
role="main"
9+
>
10+
<div
11+
class="nhsuk-grid-row"
12+
>
13+
<div
14+
class="nhsuk-grid-column-full"
15+
>
16+
<h1>
17+
Message plans
18+
</h1>
19+
<details
20+
class="nhsuk-details"
21+
>
22+
<summary
23+
class="nhsuk-details__summary"
24+
>
25+
<span
26+
class="nhsuk-details__summary-text"
27+
>
28+
What draft and production mean
29+
</span>
30+
</summary>
31+
<div
32+
class="nhsuk-details__text"
33+
>
34+
<h2
35+
class="nhsuk-heading-s nhsuk-u-margin-bottom-1"
36+
>
37+
Draft
38+
</h2>
39+
<p>
40+
Message plans that you're working on and are not ready to be sent. You can test these, using our:
41+
</p>
42+
<ul>
43+
<li>
44+
<a
45+
href="https://digital.nhs.uk/developer/api-catalogue/nhs-notify#overview--environments-and-testing"
46+
rel="noopener noreferrer"
47+
target="_blank"
48+
>
49+
API integration environment
50+
</a>
51+
</li>
52+
<li>
53+
<a
54+
href="https://digital.nhs.uk/developer/api-catalogue/nhs-notify-mesh/sending-a-message#sending-your-request"
55+
rel="noopener noreferrer"
56+
target="_blank"
57+
>
58+
Integration MESH mailbox
59+
</a>
60+
</li>
61+
</ul>
62+
<h2
63+
class="nhsuk-heading-s nhsuk-u-margin-bottom-1"
64+
>
65+
Production
66+
</h2>
67+
<p>
68+
Message plans that are ready to be sent using
69+
<a
70+
href="https://digital.nhs.uk/developer/api-catalogue/nhs-notify"
71+
rel="noopener noreferrer"
72+
target="_blank"
73+
>
74+
NHS Notify API
75+
</a>
76+
or
77+
<a
78+
href="https://digital.nhs.uk/developer/api-catalogue/nhs-notify-mesh/"
79+
rel="noopener noreferrer"
80+
target="_blank"
81+
>
82+
NHS Notify MESH.
83+
</a>
84+
</p>
85+
</div>
86+
</details>
87+
<a
88+
aria-disabled="false"
89+
class="nhsuk-button"
90+
draggable="false"
91+
href="/create-new-message-plan"
92+
role="button"
93+
>
94+
New message plan
95+
</a>
96+
</div>
97+
</div>
98+
</main>
99+
</DocumentFragment>
100+
`;

0 commit comments

Comments
 (0)