Skip to content
16 changes: 16 additions & 0 deletions frontend/src/__tests__/helpers/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mockDeep } from 'jest-mock-extended';
import {
LetterVariant,
ProofRequest,
RoutingConfig,
} from 'nhs-notify-web-template-management-types';
import {
Expand Down Expand Up @@ -187,3 +188,18 @@ export const makeLetterVariant = (
type: 'STANDARD',
...overrides,
});

const PROOF_REQUEST: ProofRequest = {
id: 'proof-request-id',
templateId: 'email-template-id',
templateType: 'EMAIL' as const,
contactDetailValue: 'test@example.com',
testPatientNhsNumber: '9000000009',
createdAt: '2026-01-01T00:00:00.000Z',
createdBy: 'user-id',
};

export const makeProofRequest = (overrides: Partial<ProofRequest> = {}) => ({
...PROOF_REQUEST,
...overrides,
});
46 changes: 46 additions & 0 deletions frontend/src/__tests__/utils/form-actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
saveTemplate,
patchTemplate,
getTemplate,
getProofingRequest,
getTemplates,
uploadLetterTemplate,
setTemplateToDeleted,
Expand All @@ -33,6 +34,7 @@ import {
letterVariantApiClient,
} from 'nhs-notify-backend-client';
import { logger } from 'nhs-notify-web-template-management-utils/logger';
import { makeProofRequest } from '@testhelpers/helpers';

const mockedTemplateClient = jest.mocked(templateApiClient);
const mockedLetterVariantClient = jest.mocked(letterVariantApiClient);
Expand Down Expand Up @@ -664,6 +666,50 @@ describe('form-actions', () => {
);
});

test('getProofingRequest', async () => {
const responseData = makeProofRequest();

mockedTemplateClient.getProofingRequest.mockResolvedValueOnce({
data: responseData,
});

const response = await getProofingRequest('proof-id');

expect(mockedTemplateClient.getProofingRequest).toHaveBeenCalledWith(
'proof-id',
'token'
);
expect(response).toEqual(responseData);
});

test('getProofingRequest - should return undefined when no data', async () => {
mockedTemplateClient.getProofingRequest.mockResolvedValueOnce({
data: undefined,
error: {
errorMeta: {
code: 404,
description: 'Not found',
},
},
});

const response = await getProofingRequest('proof-id');

expect(mockedTemplateClient.getProofingRequest).toHaveBeenCalledWith(
'proof-id',
'token'
);
expect(response).toEqual(undefined);
});

test('getProofingRequest - should throw error when no token', async () => {
authIdTokenServerMock.mockResolvedValueOnce({});

await expect(getProofingRequest('proof-id')).rejects.toThrow(
'Failed to get access token'
);
});

test('getTemplates', async () => {
const responseData = {
id: 'id',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`TestEmailMessageSentPage should match snapshot 1`] = `
<DocumentFragment>
<div
class="nhsuk-width-container"
>
<main
class="nhsuk-main-wrapper"
id="maincontent"
role="main"
>
<div
class="nhsuk-grid-row"
>
<div
class="nhsuk-grid-column-two-thirds"
>
<div
class="nhsuk-panel"
>
<h1
class="nhsuk-heading-l nhsuk-u-margin-bottom-0"
data-testid="banner-heading"
>
Test email sent
</h1>
<p>
We've sent a test email to
<br />
<strong>
test@example.com
</strong>
</p>
<p>
Your test email will come from
<strong>
NHS Notify - test
</strong>
</p>
</div>
<a
class="nhsuk-link"
data-testid="back-to-template-link"
href="/preview-email-template/email-template-id"
>
Back to template
</a>
</div>
</div>
</main>
</div>
</DocumentFragment>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import TestEmailMessageSentPage, {
generateMetadata,
} from '@app/test-email-message-sent/[proofingRequestId]/page';
import { getProofingRequest } from '@utils/form-actions';
import { fetchClient } from '@utils/server-features';
import { redirect } from 'next/navigation';
import { render } from '@testing-library/react';
import { makeProofRequest } from '@testhelpers/helpers';
import content from '@content/content';

const { pageTitle, bannerHeading, backLink } =
content.pages.testEmailMessageSentPage;

jest.mock('@utils/form-actions');
jest.mock('@utils/server-features');
jest.mock('next/navigation');

const getProofingRequestMock = jest.mocked(getProofingRequest);
const redirectMock = jest.mocked(redirect);
const fetchClientMock = jest.mocked(fetchClient);

describe('TestEmailMessageSentPage', () => {
beforeEach(() => {
jest.resetAllMocks();
fetchClientMock.mockResolvedValue({
features: { digitalProofingEmail: true },
});
});

it('should generate correct metadata', async () => {
const metadata = await generateMetadata();

expect(metadata).toEqual({ title: pageTitle });
});

it('should render the page with email address from proofing request', async () => {
getProofingRequestMock.mockResolvedValueOnce(makeProofRequest());

const page = await TestEmailMessageSentPage({
params: Promise.resolve({ proofingRequestId: 'proof-request-id' }),
});
const { getByTestId, getByText } = render(page);

expect(getProofingRequestMock).toHaveBeenCalledWith('proof-request-id');
expect(redirectMock).not.toHaveBeenCalled();
expect(getByTestId('banner-heading')).toHaveTextContent(bannerHeading);
expect(getByText('test@example.com')).toBeInTheDocument();
expect(getByText(/NHS Notify - test/)).toBeInTheDocument();
expect(getByTestId('back-to-template-link')).toHaveAttribute(
'href',
'/preview-email-template/email-template-id'
);
expect(getByTestId('back-to-template-link')).toHaveTextContent(
backLink.text
);
});

it('should redirect to message-templates when digitalProofingEmail is disabled', async () => {
fetchClientMock.mockResolvedValueOnce({
features: { digitalProofingEmail: false },
});

await TestEmailMessageSentPage({
params: Promise.resolve({ proofingRequestId: 'proof-request-id' }),
});

expect(redirectMock).toHaveBeenCalledWith('/message-templates', 'replace');
expect(getProofingRequestMock).not.toHaveBeenCalled();
});

it('should redirect to message-templates when client is null', async () => {
fetchClientMock.mockResolvedValueOnce(null);

await TestEmailMessageSentPage({
params: Promise.resolve({ proofingRequestId: 'proof-request-id' }),
});

expect(redirectMock).toHaveBeenCalledWith('/message-templates', 'replace');
expect(getProofingRequestMock).not.toHaveBeenCalled();
});

it('should redirect to invalid-template when proofing request is not found', async () => {
Comment thread
alexnuttall marked this conversation as resolved.
getProofingRequestMock.mockResolvedValueOnce(undefined);

await TestEmailMessageSentPage({
params: Promise.resolve({ proofingRequestId: 'not-found' }),
});

expect(redirectMock).toHaveBeenCalledWith('/invalid-template', 'replace');
});

it('should match snapshot', async () => {
getProofingRequestMock.mockResolvedValueOnce(makeProofRequest());

const page = await TestEmailMessageSentPage({
params: Promise.resolve({ proofingRequestId: 'proof-request-id' }),
});
const { asFragment } = render(page);

expect(asFragment()).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use server';

import { Metadata } from 'next';
import { redirect, RedirectType } from 'next/navigation';
import { getProofingRequest } from '@utils/form-actions';
import { fetchClient } from '@utils/server-features';
import { NHSNotifyContainer } from '@layouts/container/container';
import { NHSNotifyMain } from '@atoms/NHSNotifyMain/NHSNotifyMain';
import Link from 'next/link';
import content from '@content/content';
import { ContentRenderer } from '@molecules/ContentRenderer/ContentRenderer';
import { interpolate } from '@utils/interpolate';

const { pageTitle, bannerHeading, bannerBody, backLink } =
content.pages.testEmailMessageSentPage;

export async function generateMetadata(): Promise<Metadata> {
return {
title: pageTitle,
};
}

type TestEmailMessageSentPageProps = {
params: Promise<{ proofingRequestId: string }>;
};

const TestEmailMessageSentPage = async (
props: TestEmailMessageSentPageProps
) => {
const { proofingRequestId } = await props.params;

const client = await fetchClient();

if (!client?.features.digitalProofingEmail) {
return redirect('/message-templates', RedirectType.replace);
}

const proofingRequest = await getProofingRequest(proofingRequestId);

if (!proofingRequest) {
return redirect('/invalid-template', RedirectType.replace);
}

return (
<NHSNotifyContainer>
<NHSNotifyMain>
<div className='nhsuk-grid-row'>
<div className='nhsuk-grid-column-two-thirds'>
<div className='nhsuk-panel'>
<h1
className='nhsuk-heading-l nhsuk-u-margin-bottom-0'
data-testid='banner-heading'
>
{bannerHeading}
</h1>
<ContentRenderer
content={bannerBody}
variables={{
contactDetail: proofingRequest.contactDetailValue.trim(),
}}
/>
</div>
<Link
href={interpolate(backLink.href, {
templateId: proofingRequest.templateId,
})}
data-testid='back-to-template-link'
className='nhsuk-link'
>
{backLink.text}
</Link>
</div>
</div>
</NHSNotifyMain>
</NHSNotifyContainer>
);
};

export default TestEmailMessageSentPage;
20 changes: 20 additions & 0 deletions frontend/src/content/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,25 @@ const sendTestEmailMessagePage = {
pageHeading: 'Send a test email',
};

const testEmailMessageSentPage = {
pageTitle: generatePageTitle('Test email sent'),
bannerHeading: 'Test email sent',
bannerBody: [
{
type: 'text',
text: "We've sent a test email to \n**{{contactDetail}}**",
},
{
type: 'text',
text: 'Your test email will come from **NHS Notify - test**',
},
] satisfies ContentBlock[],
backLink: {
href: '/preview-email-template/{{templateId}}',
text: 'Back to template',
},
};

const messagePlanFallbackConditions: Record<
TemplateType,
FallbackConditionBlock
Expand Down Expand Up @@ -2430,6 +2449,7 @@ const content = {
sendTestEmailMessagePage,
sendTestNhsAppMessagePage,
sendTestSmsMessagePage,
testEmailMessageSentPage,
submitLetterTemplate: submitLetterTemplatePage,
uploadDocxLetterTemplatePage,
},
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ const protectedPaths = [
/^\/message-plans\/edit-message-plan\/[^/]+$/,
/^\/message-plans\/get-ready-to-move\/[^/]+$/,
/^\/message-plans\/invalid$/,
/^\/message-plans\/preview-message-plan\/[^/]+$/,
/^\/message-plans\/preview-message-plan\/[^/]+\/preview-template\/[^/]+$/,
/^\/message-plans\/preview-message-plan\/[^/]+$/,
/^\/message-plans\/rename-message-plan\/[^/]+$/,
/^\/message-plans\/review-and-move-to-production\/[^/]+$/,
/^\/message-plans\/review-and-move-to-production\/[^/]+\/preview-template\/[^/]+$/,
/^\/message-plans\/review-and-move-to-production\/[^/]+$/,
/^\/message-plans$/,
/^\/message-templates$/,
/^\/nhs-app-template-submitted\/[^/]+$/,
Expand All @@ -67,6 +67,7 @@ const protectedPaths = [
/^\/submit-letter-template\/[^/]+$/,
/^\/submit-nhs-app-template\/[^/]+$/,
/^\/submit-text-message-template\/[^/]+$/,
/^\/test-email-message-sent\/[^/]+$/,
/^\/text-message-template-submitted\/[^/]+$/,
/^\/upload-british-sign-language-letter-template$/,
/^\/upload-large-print-letter-template$/,
Expand Down
Loading
Loading