Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 57 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 Down Expand Up @@ -665,6 +666,62 @@ describe('form-actions', () => {
);
});

test('getProofingRequest', async () => {
const responseData = {
id: 'proof-id',
createdBy: 'owner-id',
templateId: 'template-id',
templateType: 'EMAIL' as const,
contactDetailValue: 'test@example.com',
testPatientNhsNumber: '9000000009',
personalisation: {
personalisation1: 'value1',
personalisation2: 'value2',
},
createdAt: '2026-01-01T00:00:00.000Z',
};

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,128 @@
import TestEmailMessageSentPage, {
generateMetadata,
} from '@app/test-email-message-sent/[proofingRequestId]/page';
import { getProofingRequest, getTemplate } from '@utils/form-actions';
import { fetchClient } from '@utils/server-features';
import { redirect } from 'next/navigation';
import { render } from '@testing-library/react';
import { EMAIL_TEMPLATE } 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 getTemplateMock = jest.mocked(getTemplate);
const redirectMock = jest.mocked(redirect);
const fetchClientMock = jest.mocked(fetchClient);

const PROOF_REQUEST = {
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',
};

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(PROOF_REQUEST);
getTemplateMock.mockResolvedValueOnce(EMAIL_TEMPLATE);

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

expect(getProofingRequestMock).toHaveBeenCalledWith('proof-request-id');
expect(getTemplateMock).toHaveBeenCalledWith('email-template-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 },
} as never);

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 () => {
getProofingRequestMock.mockResolvedValueOnce(undefined);

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

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

it('should redirect to invalid-template when template is not found', async () => {
getProofingRequestMock.mockResolvedValueOnce(PROOF_REQUEST);
getTemplateMock.mockResolvedValueOnce(undefined);

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

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

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

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,85 @@
'use server';

import { Metadata } from 'next';
import { redirect, RedirectType } from 'next/navigation';
import { getPreviewURL } from 'nhs-notify-web-template-management-utils';
import { getProofingRequest, getTemplate } 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';

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);
}

const template = await getTemplate(proofingRequest.templateId);

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

const backUrl = getPreviewURL(template);

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,
}}
/>
</div>
<Link
href={backUrl}
data-testid='back-to-template-link'
className='nhsuk-link'
>
{backLink.text}
</Link>
</div>
</div>
</NHSNotifyMain>
</NHSNotifyContainer>
);
};

export default TestEmailMessageSentPage;
19 changes: 19 additions & 0 deletions frontend/src/content/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,24 @@ 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: {
text: 'Back to template',
},
};

const messagePlanFallbackConditions: Record<
TemplateType,
FallbackConditionBlock
Expand Down Expand Up @@ -2521,6 +2539,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 @@ -68,6 +68,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