Skip to content

Commit 5fbff7c

Browse files
authored
CCM-11496: get ready to move page (#773)
1 parent 4c18c9e commit 5fbff7c

File tree

24 files changed

+815
-259
lines changed

24 files changed

+815
-259
lines changed

frontend/src/__tests__/app/choose-templates/__snapshots__/page.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ exports[`ChooseTemplatesPage renders correctly for a message plan with multiple
674674
class="nhsuk-button"
675675
data-testid="move-to-production-cta"
676676
draggable="false"
677-
href="/message-plans/move-to-production/fbb81055-79b9-4759-ac07-d191ae57be34"
677+
href="/message-plans/get-ready-to-move/fbb81055-79b9-4759-ac07-d191ae57be34"
678678
role="button"
679679
>
680680
Move to production

frontend/src/__tests__/app/message-plans/create-message-plan/server-action.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import type { CascadeItem, RoutingConfig } from 'nhs-notify-backend-client';
44
import type { MessageOrder } from 'nhs-notify-web-template-management-utils';
55
import { createMessagePlanServerAction } from '@app/message-plans/create-message-plan/server-action';
66
import { NextRedirectError } from '@testhelpers/next-redirect';
7-
import { createRoutingConfig } from '@utils/form-actions';
7+
import { createRoutingConfig } from '@utils/message-plans';
88

99
jest.mock('next/navigation');
1010
jest.mocked(redirect).mockImplementation((url, type) => {
1111
throw new NextRedirectError(url, type);
1212
});
1313

14-
jest.mock('@utils/form-actions');
14+
jest.mock('@utils/message-plans');
1515
jest.mocked(createRoutingConfig).mockResolvedValue(
1616
mock<RoutingConfig>({
1717
id: 'mock-routing-config-id',
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`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-two-thirds"
15+
>
16+
<span
17+
class="nhsuk-caption-xl"
18+
>
19+
Step 1 of 2
20+
</span>
21+
<h1
22+
class="nhsuk-heading-xl"
23+
>
24+
Get ready to move message plan to production
25+
</h1>
26+
<dl
27+
class="nhsuk-summary-list"
28+
>
29+
<div
30+
class="nhsuk-summary-list__row"
31+
>
32+
<dt
33+
class="nhsuk-summary-list__key"
34+
>
35+
Name
36+
</dt>
37+
<dd
38+
class="nhsuk-summary-list__value"
39+
>
40+
My Routing Config
41+
</dd>
42+
</div>
43+
</dl>
44+
<p>
45+
Moving message plans from draft to production means they are ready to send.
46+
</p>
47+
<p>
48+
Any templates used in these message plans will be locked.
49+
</p>
50+
<p>
51+
Messages will only be sent to recipients when you make a request with
52+
<a
53+
href="https://digital.nhs.uk/developer/api-catalogue/nhs-notify"
54+
rel="noopener noreferrer"
55+
target="_blank"
56+
>
57+
NHS Notify API (opens in a new tab)
58+
</a>
59+
or
60+
<a
61+
href="https://digital.nhs.uk/developer/api-catalogue/nhs-notify-mesh"
62+
rel="noopener noreferrer"
63+
target="_blank"
64+
>
65+
NHS Notify MESH (opens in a new tab)
66+
</a>
67+
.
68+
</p>
69+
<h2
70+
class="nhsuk-heading-m"
71+
id="before-you-continue"
72+
>
73+
Before you continue
74+
</h2>
75+
<p>
76+
Make sure:
77+
</p>
78+
<ul
79+
class="nhsuk-list nhsuk-list--bullet"
80+
>
81+
<li>
82+
the relevant stakeholders in your team have approved your templates and message plan
83+
</li>
84+
<li>
85+
your templates have no errors
86+
</li>
87+
</ul>
88+
<div
89+
class="nhsuk-warning-callout"
90+
>
91+
<h3
92+
class="nhsuk-warning-callout__label"
93+
>
94+
Important
95+
<span
96+
class="nhsuk-u-visually-hidden"
97+
>
98+
:
99+
</span>
100+
</h3>
101+
<p>
102+
You cannot edit anything that is in production.
103+
</p>
104+
<p>
105+
If you need to edit your templates or message plans, you can copy and replace them.
106+
</p>
107+
</div>
108+
<div
109+
class="nhsuk-form-group"
110+
>
111+
<a
112+
aria-disabled="false"
113+
class="nhsuk-button"
114+
data-testid="continue-link"
115+
draggable="false"
116+
href="/templates/message-plans/review-and-move-to-production/routing-config-id"
117+
role="button"
118+
>
119+
Continue
120+
</a>
121+
<a
122+
aria-disabled="false"
123+
class="nhsuk-button nhsuk-button--secondary nhsuk-u-margin-left-3"
124+
data-testid="cancel-link"
125+
draggable="false"
126+
href="/templates/message-plans"
127+
role="button"
128+
>
129+
Keep in draft
130+
</a>
131+
</div>
132+
</div>
133+
</div>
134+
</main>
135+
</DocumentFragment>
136+
`;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { render } from '@testing-library/react';
2+
import { mock } from 'jest-mock-extended';
3+
import { redirect, RedirectType } from 'next/navigation';
4+
import { RoutingConfig } from 'nhs-notify-backend-client';
5+
import { getRoutingConfig } from '@utils/message-plans';
6+
import Page, {
7+
metadata,
8+
} from '../../../../app/message-plans/get-ready-to-move/[routingConfigId]/page';
9+
10+
jest.mock('next/navigation');
11+
jest.mock('@utils/message-plans');
12+
13+
const routingConfig = mock<RoutingConfig>({
14+
name: 'My Routing Config',
15+
});
16+
17+
beforeEach(() => {
18+
jest.clearAllMocks();
19+
});
20+
21+
test('metadata', () => {
22+
expect(metadata).toEqual({
23+
title: 'Get ready to move message plan to production - NHS Notify',
24+
});
25+
});
26+
27+
test('matches snapshot', async () => {
28+
jest.mocked(getRoutingConfig).mockResolvedValueOnce(routingConfig);
29+
30+
const page = await Page({
31+
params: Promise.resolve({ routingConfigId: 'routing-config-id' }),
32+
});
33+
34+
const container = render(page);
35+
36+
expect(container.asFragment()).toMatchSnapshot();
37+
});
38+
39+
test('redirects if routing config is not found from path parameter', async () => {
40+
await Page({
41+
params: Promise.resolve({ routingConfigId: 'routing-config-id' }),
42+
});
43+
44+
expect(getRoutingConfig).toHaveBeenCalledWith('routing-config-id');
45+
46+
expect(redirect).toHaveBeenCalledWith(
47+
'/message-plans/invalid',
48+
RedirectType.replace
49+
);
50+
});

frontend/src/__tests__/components/molecules/ContentRenderer.test.tsx

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
ContentBlock,
55
ContentItem,
66
} from '@molecules/ContentRenderer/ContentRenderer';
7+
import { markdownList } from '@utils/markdown-list';
78

89
describe('ContentRenderer', () => {
910
it('renders nothing for empty content array', () => {
@@ -14,7 +15,8 @@ describe('ContentRenderer', () => {
1415
});
1516

1617
it('renders a plain string as MarkdownContent in inline mode', () => {
17-
render(<ContentRenderer content='Just a string block' />);
18+
const container = render(<ContentRenderer content='Just a string block' />);
19+
expect(container.asFragment()).toMatchSnapshot();
1820
expect(screen.getByText('Just a string block')).toBeInTheDocument();
1921
});
2022

@@ -45,7 +47,9 @@ describe('ContentRenderer', () => {
4547
{ type: 'text', text: 'Another paragraph.' },
4648
];
4749

48-
render(<ContentRenderer content={content} />);
50+
const container = render(<ContentRenderer content={content} />);
51+
52+
expect(container.asFragment()).toMatchSnapshot();
4953

5054
const listItems = screen.getAllByRole('paragraph');
5155
expect(listItems).toHaveLength(2);
@@ -58,14 +62,16 @@ describe('ContentRenderer', () => {
5862
{ type: 'inline-text', text: 'Inline content' },
5963
];
6064

61-
render(
65+
const container = render(
6266
<ul>
6367
<li data-testid='list-item'>
6468
<ContentRenderer content={content} />
6569
</li>
6670
</ul>
6771
);
6872

73+
expect(container.asFragment()).toMatchSnapshot();
74+
6975
const listItem = screen.getByTestId('list-item');
7076
expect(listItem).toHaveTextContent('Inline content');
7177
expect(listItem.querySelector('p')).toBeNull();
@@ -74,14 +80,16 @@ describe('ContentRenderer', () => {
7480
it('treats a string as inline text (no paragraph wrapper)', () => {
7581
const content: string = 'Inline via string';
7682

77-
render(
83+
const container = render(
7884
<ul>
7985
<li data-testid='list-item'>
8086
<ContentRenderer content={content} />
8187
</li>
8288
</ul>
8389
);
8490

91+
expect(container.asFragment()).toMatchSnapshot();
92+
8593
const listItem = screen.getByTestId('list-item');
8694
expect(listItem).toHaveTextContent('Inline via string');
8795
expect(listItem.querySelector('p')).toBeNull();
@@ -90,14 +98,16 @@ describe('ContentRenderer', () => {
9098
it('treats a string array as inline text (no paragraph wrapper)', () => {
9199
const content: ContentItem[] = ['Inline via string'];
92100

93-
render(
101+
const container = render(
94102
<ul>
95103
<li data-testid='list-item'>
96104
<ContentRenderer content={content} />
97105
</li>
98106
</ul>
99107
);
100108

109+
expect(container.asFragment()).toMatchSnapshot();
110+
101111
const listItem = screen.getByTestId('list-item');
102112
expect(listItem).toHaveTextContent('Inline via string');
103113
expect(listItem.querySelector('p')).toBeNull();
@@ -115,7 +125,9 @@ describe('ContentRenderer', () => {
115125
},
116126
];
117127

118-
render(<ContentRenderer content={content} />);
128+
const container = render(<ContentRenderer content={content} />);
129+
130+
expect(container.asFragment()).toMatchSnapshot();
119131

120132
const hiddenText = screen.getByText('An example of heading markdown');
121133
expect(hiddenText).toHaveAttribute('id', 'heading-markdown-description');
@@ -127,19 +139,84 @@ describe('ContentRenderer', () => {
127139
);
128140
});
129141

130-
it('renders list blocks', () => {
142+
it('renders unordered list blocks', () => {
131143
const content: ContentBlock[] = [
132144
{
133-
type: 'list',
134-
items: ['Item 1', 'Item 2', 'Item 3'],
145+
type: 'text',
146+
text: markdownList('ul', ['Item 1', 'Item 2', 'Item 3']),
135147
},
136148
];
137149

138-
render(<ContentRenderer content={content} />);
150+
const container = render(<ContentRenderer content={content} />);
151+
152+
expect(container.asFragment()).toMatchSnapshot();
153+
154+
const listItems = screen.getAllByRole('listitem');
155+
expect(listItems).toHaveLength(3);
156+
expect(listItems[0]?.parentElement?.tagName).toBe('UL');
157+
expect(listItems[0]).toHaveTextContent('Item 1');
158+
expect(listItems[1]).toHaveTextContent('Item 2');
159+
expect(listItems[2]).toHaveTextContent('Item 3');
160+
});
161+
162+
it('renders ordered list blocks', () => {
163+
const content: ContentBlock[] = [
164+
{
165+
type: 'text',
166+
text: markdownList('ol', ['Item 1', 'Item 2', 'Item 3']),
167+
},
168+
];
169+
170+
const container = render(<ContentRenderer content={content} />);
171+
172+
expect(container.asFragment()).toMatchSnapshot();
173+
139174
const listItems = screen.getAllByRole('listitem');
140175
expect(listItems).toHaveLength(3);
176+
expect(listItems[0]?.parentElement?.tagName).toBe('OL');
141177
expect(listItems[0]).toHaveTextContent('Item 1');
142178
expect(listItems[1]).toHaveTextContent('Item 2');
143179
expect(listItems[2]).toHaveTextContent('Item 3');
144180
});
181+
182+
it('renders with overrides on text blocks', () => {
183+
const content: ContentBlock[] = [
184+
{
185+
type: 'text',
186+
text: 'This is a paragraph.',
187+
overrides: { p: { props: { className: 'foo' } } },
188+
},
189+
{
190+
type: 'text',
191+
text: 'Another paragraph.',
192+
overrides: { p: { props: { className: 'bar' } } },
193+
},
194+
];
195+
196+
const container = render(<ContentRenderer content={content} />);
197+
198+
expect(container.asFragment()).toMatchSnapshot();
199+
200+
const paragraphs = screen.getAllByRole('paragraph');
201+
expect(paragraphs).toHaveLength(2);
202+
expect(paragraphs[0]).toHaveClass('foo');
203+
expect(paragraphs[1]).toHaveClass('bar');
204+
});
205+
206+
it('renders with overrides on inline-text blocks', () => {
207+
const content: ContentBlock[] = [
208+
{
209+
type: 'inline-text',
210+
text: 'This is a [link](https://example.com).',
211+
overrides: { a: { props: { className: 'foo' } } },
212+
},
213+
];
214+
215+
const container = render(<ContentRenderer content={content} />);
216+
217+
expect(container.asFragment()).toMatchSnapshot();
218+
219+
const link = screen.getByRole('link');
220+
expect(link).toHaveClass('foo');
221+
});
145222
});

0 commit comments

Comments
 (0)