Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
dd2dd4e
CCM-12038: WIP preview production message plan
harrim91 Jan 6, 2026
bbbb304
Merge branch 'main' into feature/CCM-12038_readonly-production-messag…
harrim91 Jan 6, 2026
a8b2598
CCM-12038: tidy
harrim91 Jan 6, 2026
da95ea8
CCM-12038: details toggle, more decomposition
harrim91 Jan 7, 2026
28cef4d
CMM-12038: rework choose templates page to use new composable components
harrim91 Jan 7, 2026
269253b
CCM-12038: lint, test ids
harrim91 Jan 8, 2026
daf1b95
CCM-12038: wip unit test rework
harrim91 Jan 9, 2026
1db30bb
Merge branch 'main' into feature/CCM-12038_readonly-production-messag…
harrim91 Jan 13, 2026
f7268cb
CCM-12038: tidy
harrim91 Jan 13, 2026
0b6116d
CCM-12038: get existing playwright tests passing
harrim91 Jan 14, 2026
2983be0
CCM-12038: unit tests
harrim91 Jan 16, 2026
4ef530f
CCM-12038: wip playwright
harrim91 Jan 16, 2026
861b494
CCM-12038: fix test build command
harrim91 Jan 16, 2026
da45515
Merge branch 'main' into feature/CCM-12038_readonly-production-messag…
harrim91 Jan 19, 2026
647f4a3
CCM-12038: playwright
harrim91 Jan 19, 2026
179cc1d
Merge branch 'main' into feature/CCM-12038_readonly-production-messag…
harrim91 Jan 19, 2026
29eed0a
CCM-12038: content
harrim91 Jan 20, 2026
2d7d0d9
CCM-12038: some feedback
harrim91 Jan 20, 2026
6b974cd
CCM-12038: remove copy link
harrim91 Jan 21, 2026
aedabec
Merge branch 'main' into feature/CCM-12038_readonly-production-messag…
harrim91 Jan 21, 2026
8201172
Merge branch 'main' into feature/CCM-12038_readonly-production-messag…
harrim91 Jan 21, 2026
f34b6bf
CCM-12038: feedback
harrim91 Jan 21, 2026
492c17d
Merge branch 'main' into feature/CCM-12038_readonly-production-messag…
harrim91 Jan 26, 2026
b3c4be0
CCM-12038: fix test data
harrim91 Jan 26, 2026
587d9f8
Merge branch 'main' into feature/CCM-12038_readonly-production-messag…
harrim91 Jan 29, 2026
2334bc2
CCM-12038: controlled details section
harrim91 Jan 29, 2026
cbe669f
Merge branch 'main' into feature/CCM-12038_readonly-production-messag…
harrim91 Jan 29, 2026
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

Large diffs are not rendered by default.

967 changes: 786 additions & 181 deletions frontend/src/__tests__/app/choose-templates/page.test.tsx

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

292 changes: 11 additions & 281 deletions frontend/src/__tests__/components/molecules/MessagePlanBlock.test.tsx
Original file line number Diff line number Diff line change
@@ -1,292 +1,22 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { render } from '@testing-library/react';
import { MessagePlanBlock } from '@molecules/MessagePlanBlock/MessagePlanBlock';
import type { CascadeItem, Channel } from 'nhs-notify-backend-client';
import type { TemplateDto } from 'nhs-notify-backend-client';
import { EMAIL_TEMPLATE, LETTER_TEMPLATE } from '@testhelpers/helpers';
import { MessagePlanTemplates } from '@utils/routing-utils';
import { ORDINALS } from 'nhs-notify-web-template-management-utils';

function buildCascadeItem(channel: Channel): CascadeItem {
return {
cascadeGroups: [],
channel,
channelType: 'primary',
defaultTemplateId: '',
};
}

const mockTemplate: TemplateDto = {
...EMAIL_TEMPLATE,
name: 'Test email template',
};

const emptyConditionalTemplates: MessagePlanTemplates = {};
const cases = ORDINALS.map((ordinal, index) => ({ ordinal, index }));

describe('MessagePlanBlock', () => {
it('should render the step number and the heading for the first cascade item', () => {
const channelItem = buildCascadeItem('EMAIL');

const { container } = render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
routingConfigId='test-routing-config-id'
conditionalTemplates={emptyConditionalTemplates}
lockNumber={42}
/>
);

const stepNumber = container.querySelector('.message-plan-block-number');
expect(stepNumber).toHaveTextContent('1');

expect(
screen.getByRole('heading', { level: 2, name: 'First message' })
).toBeInTheDocument();
});

it('should render the step number and the interpolated heading for a third item', () => {
const channelItem = buildCascadeItem('NHSAPP');

const { container } = render(
<MessagePlanBlock
index={2}
channelItem={channelItem}
routingConfigId='test-routing-config-id'
conditionalTemplates={emptyConditionalTemplates}
lockNumber={42}
/>
);

const stepNumber = container.querySelector('.message-plan-block-number');
expect(stepNumber).toHaveTextContent('3');
expect(
screen.getByRole('heading', { level: 2, name: 'Third message' })
).toBeInTheDocument();
});

it('should render the channel template section with the correct channel subheading', () => {
const channelItem = buildCascadeItem('EMAIL');

render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
routingConfigId='test-routing-config-id'
conditionalTemplates={emptyConditionalTemplates}
lockNumber={42}
/>
);

expect(
screen.getByRole('heading', { level: 3, name: 'Email' })
).toBeInTheDocument();
});

describe('when the channel has a template', () => {
it('should display the template name', () => {
const channelItem = buildCascadeItem('EMAIL');

render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
defaultTemplate={mockTemplate}
routingConfigId='test-routing-config-id'
conditionalTemplates={emptyConditionalTemplates}
lockNumber={42}
/>
);
expect(screen.getByText('Test email template')).toBeInTheDocument();
});

it('should show Change/Remove links (and no Choose link)', () => {
const channelItem = buildCascadeItem('EMAIL');

render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
defaultTemplate={mockTemplate}
routingConfigId='test-routing-config-id'
conditionalTemplates={emptyConditionalTemplates}
lockNumber={42}
/>
);

expect(
screen.getByRole('link', { name: 'Change Email template' })
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Remove Email template' })
).toBeInTheDocument();
expect(
screen.queryByRole('link', { name: 'Choose Email template' })
).not.toBeInTheDocument();
});
});

describe('when the channel does not have a template', () => {
it('should show Choose link (and no Change/Remove links)', () => {
const channelItem = buildCascadeItem('SMS');

render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
routingConfigId='test-routing-config-id'
conditionalTemplates={emptyConditionalTemplates}
lockNumber={42}
/>
);

expect(
screen.getByRole('link', { name: 'Choose Text message (SMS) template' })
).toBeInTheDocument();
expect(
screen.queryByRole('link', {
name: 'Change Text message (SMS) template',
})
).not.toBeInTheDocument();
expect(
screen.queryByRole('button', {
name: 'Remove Text message (SMS) template',
})
).not.toBeInTheDocument();
});
});

describe('when channel is LETTER', () => {
it('should render conditional templates section', () => {
const channelItem = buildCascadeItem('LETTER');

render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
routingConfigId='test-routing-config-id'
lockNumber={42}
conditionalTemplates={emptyConditionalTemplates}
/>
);

expect(
screen.getByTestId('message-plan-conditional-templates')
).toBeInTheDocument();
});

it('should render accessible format templates', () => {
const channelItem = buildCascadeItem('LETTER');

render(
it.each(cases)(
'matches snapshot - index $index renders "$ordinal"',
({ index, ordinal }) => {
const { asFragment } = render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
routingConfigId='test-routing-config-id'
lockNumber={42}
conditionalTemplates={emptyConditionalTemplates}
index={index}
className='extra-message-plan-block-class'
data-testid={`message-plan-block-${ordinal}`}
/>
);

expect(
screen.getByRole('heading', {
level: 3,
name: 'Large print letter (optional)',
})
).toBeInTheDocument();
});

it('should render language templates section', () => {
const channelItem = buildCascadeItem('LETTER');

render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
routingConfigId='test-routing-config-id'
lockNumber={42}
conditionalTemplates={emptyConditionalTemplates}
/>
);

expect(
screen.getByRole('heading', {
level: 3,
name: 'Other language letters (optional)',
})
).toBeInTheDocument();
});

it('should display conditional template names when provided', () => {
const largePrintTemplate: TemplateDto = {
...LETTER_TEMPLATE,
id: 'large-print-id',
name: 'Large print template',
};

const channelItem: CascadeItem = {
...buildCascadeItem('LETTER'),
conditionalTemplates: [
{
accessibleFormat: 'x1',
templateId: 'large-print-id',
},
],
};

const conditionalTemplates: MessagePlanTemplates = {
'large-print-id': largePrintTemplate,
};

render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
routingConfigId='test-routing-config-id'
lockNumber={42}
conditionalTemplates={conditionalTemplates}
/>
);

expect(screen.getByText('Large print template')).toBeInTheDocument();
});
});

describe.each(['NHSAPP', 'EMAIL', 'SMS', 'LETTER'] as const)(
'for channel %s with template',
(channel) => {
it('should match snapshot', async () => {
const channelItem = buildCascadeItem(channel);
const { asFragment } = render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
defaultTemplate={mockTemplate}
routingConfigId='test-routing-config-id'
conditionalTemplates={emptyConditionalTemplates}
lockNumber={42}
/>
);
expect(asFragment()).toMatchSnapshot();
});
}
);

describe.each(['NHSAPP', 'EMAIL', 'SMS', 'LETTER'] as const)(
'for channel %s with no template',
(channel) => {
it('should match snapshot', async () => {
const channelItem = buildCascadeItem(channel);
const { asFragment } = render(
<MessagePlanBlock
index={0}
channelItem={channelItem}
routingConfigId='test-routing-config-id'
conditionalTemplates={emptyConditionalTemplates}
lockNumber={42}
/>
);
expect(asFragment()).toMatchSnapshot();
});
expect(asFragment()).toMatchSnapshot();
}
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { render } from '@testing-library/react';
import { MessagePlanChannelCard } from '@molecules/MessagePlanChannelCard/MessagePlanChannelCard';

describe('MessagePlanChannelCard', () => {
it('matches snapshot', () => {
const { asFragment } = render(
<MessagePlanChannelCard
heading='Card Heading'
className='extra-channel-card-class'
data-testid='channel-card'
>
Card Content
</MessagePlanChannelCard>
);

expect(asFragment()).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { render } from '@testing-library/react';
import { MessagePlanChannelList } from '@molecules/MessagePlanChannelList/MessagePlanChannelList';

describe('MessagePlanChannelList', () => {
it('matches snapshot', () => {
expect(
render(
<MessagePlanChannelList
className='extra-channel-list-class'
data-testid='channel-list'
>
List content
</MessagePlanChannelList>
).asFragment()
).toMatchSnapshot();
});
});
Loading