+ Send a test email +
++ Preview how your email will be displayed by sending a test to your email address. +
+ +diff --git a/.gitleaksignore b/.gitleaksignore index 196f6cb44..e52a7eb4b 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -10,3 +10,4 @@ bc79df4f82052918ae6bf69d36279e5dd391d61e:tests/test-team/auth/user.json:jwt:25 306d9ec55d3498b86d5506da9a90ac486fc66563:frontend/src/components/molecules/MessagePlanFallbackConditions/MessagePlanFallbackConditions.tsx:ipv4:92 a408293ecbc3a95684246fd16b111b6e09d6e194:lambdas/backend-client/src/__tests__/schemas/contact-details/email.test.ts:ipv4:80 a408293ecbc3a95684246fd16b111b6e09d6e194:lambdas/backend-client/src/__tests__/schemas/contact-details/email.test.ts:ipv4:81 +9ff9d02e5f132ff57aaee7e28b4002c17c3041a3:.tool-versions:ipv4:27 diff --git a/frontend/src/__tests__/utils/contact-details.test.ts b/frontend/src/__tests__/utils/contact-details.test.ts index 554a9bbd7..b6760b2e2 100644 --- a/frontend/src/__tests__/utils/contact-details.test.ts +++ b/frontend/src/__tests__/utils/contact-details.test.ts @@ -1,22 +1,179 @@ -import { contactDetailApiClient } from 'nhs-notify-backend-client'; -import { createContactDetail } from '@utils/contact-details'; +/** + * @jest-environment node + */ import { getSessionServer } from '@utils/amplify-utils'; +import { + createContactDetail, + getVerifiedContactDetails, +} from '@utils/contact-details'; +import { contactDetailApiClient } from 'nhs-notify-backend-client'; +import { logger } from 'nhs-notify-web-template-management-utils/logger'; +import type { ContactDetailDto } from 'nhs-notify-web-template-management-types'; -jest.mock('nhs-notify-backend-client'); -jest.mock('nhs-notify-web-template-management-utils/logger'); jest.mock('@utils/amplify-utils'); +jest.mock('nhs-notify-backend-client'); +jest.mock('nhs-notify-web-template-management-utils/logger', () => ({ + logger: { + error: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + debug: jest.fn(), + }, +})); + +const getSessionServerMock = jest.mocked(getSessionServer); +const contactDetailApiClientMock = jest.mocked(contactDetailApiClient); +const loggerMock = jest.mocked(logger); + +describe('getVerifiedContactDetails', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should throw when access token is missing', async () => { + getSessionServerMock.mockResolvedValueOnce({ + accessToken: undefined, + clientId: undefined, + }); + + await expect(getVerifiedContactDetails('EMAIL')).rejects.toThrow( + 'Failed to get access token' + ); + + expect(contactDetailApiClientMock.list).not.toHaveBeenCalled(); + }); + + it('should return id and rawValue for verified contact details', async () => { + getSessionServerMock.mockResolvedValueOnce({ + accessToken: 'token', + clientId: 'client-1', + }); + + const contactDetails = [ + { + id: 'contact-1', + type: 'EMAIL', + status: 'VERIFIED', + value: 'one@example.com', + rawValue: 'oNe@example.com', + } as ContactDetailDto, + { + id: 'contact-2', + type: 'EMAIL', + status: 'VERIFIED', + value: 'two@example.com', + rawValue: 'Two@example.com', + } as ContactDetailDto, + ]; -beforeEach(() => { - jest.resetAllMocks(); + contactDetailApiClientMock.list.mockResolvedValueOnce({ + data: contactDetails, + }); + + const result = await getVerifiedContactDetails('EMAIL'); + + expect(contactDetailApiClientMock.list).toHaveBeenCalledWith('token', { + type: 'EMAIL', + status: 'VERIFIED', + }); + + expect(result).toEqual([ + { + id: 'contact-1', + rawValue: 'oNe@example.com', + }, + { + id: 'contact-2', + rawValue: 'Two@example.com', + }, + ]); + }); + + it('should log and filter out invalid contact details from API response', async () => { + getSessionServerMock.mockResolvedValueOnce({ + accessToken: 'token', + clientId: 'client-1', + }); - jest - .mocked(getSessionServer) - .mockResolvedValue({ accessToken: 'access-token' }); + const contactDetails = [ + { + id: 'contact-1', + type: 'EMAIL', + status: 'VERIFIED', + value: 'one@example.com', + rawValue: 'One@example.com', + }, + { + id: 'contact-2', + type: 'EMAIL', + status: 'VERIFIED', + rawValue: 'Two@example.com', + }, + ] as ContactDetailDto[]; + + contactDetailApiClientMock.list.mockResolvedValueOnce({ + data: contactDetails, + }); + + const result = await getVerifiedContactDetails('EMAIL'); + + expect(result).toEqual([ + { + id: 'contact-1', + rawValue: 'One@example.com', + }, + ]); + + expect(loggerMock.error).toHaveBeenCalledWith( + 'Listed invalid contact detail', + expect.objectContaining({ + clientId: 'client-1', + type: 'EMAIL', + }) + ); + }); + + it('should return empty list and log when API call fails', async () => { + getSessionServerMock.mockResolvedValueOnce({ + accessToken: 'token', + clientId: 'client-1', + }); + + const error = { + errorMeta: { + code: 500, + description: 'Unexpected error', + }, + }; + + contactDetailApiClientMock.list.mockResolvedValueOnce({ + error, + }); + + const result = await getVerifiedContactDetails('EMAIL'); + + expect(result).toEqual([]); + expect(loggerMock.error).toHaveBeenCalledWith( + 'Failed to get verified contact details', + { + clientId: 'client-1', + type: 'EMAIL', + error, + } + ); + }); }); describe('createContactDetail', () => { beforeEach(() => { - jest.mocked(contactDetailApiClient.create).mockImplementation((input) => + jest.resetAllMocks(); + + getSessionServerMock.mockResolvedValue({ + accessToken: 'access-token', + clientId: 'client-1', + }); + + contactDetailApiClientMock.create.mockImplementation((input) => Promise.resolve({ data: { ...input, @@ -53,7 +210,10 @@ describe('createContactDetail', () => { }); it('errors if unable to get an access token', async () => { - jest.mocked(getSessionServer).mockResolvedValueOnce({}); + getSessionServerMock.mockResolvedValueOnce({ + accessToken: undefined, + clientId: undefined, + }); await expect(() => createContactDetail({ @@ -66,7 +226,7 @@ describe('createContactDetail', () => { }); it('errors if api client call returns an error', async () => { - jest.mocked(contactDetailApiClient.create).mockResolvedValueOnce({ + contactDetailApiClientMock.create.mockResolvedValueOnce({ error: { errorMeta: { code: 500, diff --git a/frontend/src/app/send-test-email/[templateId]/__tests__/__snapshots__/page.test.tsx.snap b/frontend/src/app/send-test-email/[templateId]/__tests__/__snapshots__/page.test.tsx.snap new file mode 100644 index 000000000..834166cf7 --- /dev/null +++ b/frontend/src/app/send-test-email/[templateId]/__tests__/__snapshots__/page.test.tsx.snap @@ -0,0 +1,776 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SendTestEmailMessagePage should match snapshot with a single verified email 1`] = ` +
+ Preview how your email will be displayed by sending a test to your email address. +
+ ++ Preview how your email will be displayed by sending a test to your email address. +
+ ++
Preview how your message will be displayed by sending it to your test patient NHS App account.
-+
If you do not have a test patient account,
Send a test NHS App message
-
+
Preview how your message will be displayed by sending it to your test patient NHS App account.
+
If you do not have a test patient account,
Back to template
diff --git a/frontend/src/app/test-email-message-sent/[proofingRequestId]/__tests__/page.test.tsx b/frontend/src/app/test-email-message-sent/[proofingRequestId]/__tests__/page.test.tsx
index bc932ff31..9542d343a 100644
--- a/frontend/src/app/test-email-message-sent/[proofingRequestId]/__tests__/page.test.tsx
+++ b/frontend/src/app/test-email-message-sent/[proofingRequestId]/__tests__/page.test.tsx
@@ -46,13 +46,11 @@ describe('TestEmailMessageSentPage', () => {
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(
+ expect(getByTestId('back-link-bottom')).toHaveAttribute(
'href',
'/preview-email-template/template-id'
);
- expect(getByTestId('back-to-template-link')).toHaveTextContent(
- backLink.text
- );
+ expect(getByTestId('back-link-bottom')).toHaveTextContent(backLink.text);
});
it('should redirect to message-templates when digitalProofingEmail is disabled', async () => {
diff --git a/frontend/src/app/test-email-message-sent/[proofingRequestId]/page.tsx b/frontend/src/app/test-email-message-sent/[proofingRequestId]/page.tsx
index f30fa6cba..250bc1545 100644
--- a/frontend/src/app/test-email-message-sent/[proofingRequestId]/page.tsx
+++ b/frontend/src/app/test-email-message-sent/[proofingRequestId]/page.tsx
@@ -58,7 +58,7 @@ const TestEmailMessageSentPage = async (props: ProofingRequestPageProps) => {
href={interpolate(backLink.href, {
templateId: proofingRequest.templateId,
})}
- data-testid='back-to-template-link'
+ data-testid='back-link-bottom'
className='nhsuk-link'
>
{backLink.text}
diff --git a/frontend/src/components/forms/SendTestMessage/SendTestMessage.tsx b/frontend/src/components/forms/SendTestMessage/SendTestMessage.tsx
index f0b4f9641..c13281305 100644
--- a/frontend/src/components/forms/SendTestMessage/SendTestMessage.tsx
+++ b/frontend/src/components/forms/SendTestMessage/SendTestMessage.tsx
@@ -10,7 +10,7 @@ import {
import { NHSNotifyButton } from '@atoms/NHSNotifyButton/NHSNotifyButton';
import * as NHSNotifyForm from '@atoms/NHSNotifyForm';
import { NHSNotifyBackLink } from '@atoms/NHSNotifyBackLink/NHSNotifyBackLink';
-import { HintText } from 'nhsuk-react-components';
+import { Details, HintText, Label } from 'nhsuk-react-components';
import {
SHORT_EXAMPLE_RECIPIENTS,
LONG_EXAMPLE_RECIPIENTS,
@@ -18,12 +18,35 @@ import {
import { ContentRenderer } from '@molecules/ContentRenderer/ContentRenderer';
import { TestPersonalisationForm } from '@forms/TestPersonalisationForm/TestPersonalisationForm';
import { NHSNotifyFormProvider } from '@providers/form-provider';
-import { sendTestNhsAppMessageAction } from './server-action';
+import {
+ sendTestEmailAction,
+ sendTestNhsAppMessageAction,
+ sendTestSmsAction,
+} from './server-action';
import type { FormState } from '@utils/types';
-
-import copy from '@content/content';
+import type { VerifiedContactDetail } from '@utils/contact-details';
import { NHSNotifyContainer } from '@layouts/container/container';
import { NHSNotifyMain } from '@atoms/NHSNotifyMain/NHSNotifyMain';
+import type { ContactDetailType } from 'nhs-notify-web-template-management-types';
+
+import copy from '@content/content';
+
+const CONTACT_DETAIL_CONFIG: Record<
+ ContactDetailType,
+ {
+ fieldName: 'testEmail' | 'testMobileNumber';
+ serverAction: (state: FormState, data: FormData) => Promise
+ Preview how your email will be displayed by sending a test to your email address.
+
+ Preview how your email will be displayed by sending a test to your email address.
+
+ Preview how your email will be displayed by sending a test to your email address.
+
+ Preview how your email will be displayed by sending a test to your email address.
+
+ Preview how your email will be displayed by sending a test to your email address.
+
+ Preview how your message will be displayed by sending a test text message to your mobile phone.
+
+ Preview how your message will be displayed by sending a test text message to your mobile phone.
+
+ Preview how your message will be displayed by sending a test text message to your mobile phone.
+
+ Preview how your message will be displayed by sending a test text message to your mobile phone.
+
+ Preview how your message will be displayed by sending a test text message to your mobile phone.
+
+
Preview how your message will be displayed by sending it to your test patient NHS App account.
+
If you do not have a test patient account,
Send a test NHS App message
-
+
Preview how your message will be displayed by sending it to your test patient NHS App account.
+
+ {contactDetailField.heading}
+
+
+
+ There is a problem
+
+
+
+ Send a test email
+
+
+ Send a test email
+
+
+ There is a problem
+
+
+
+ Send a test email
+
+
+ Send a test email
+
+
+ Send a test email
+
+
+ There is a problem
+
+
+
+ Send a test text message
+
+
+ Send a test text message
+
+
+ There is a problem
+
+
+
+ Send a test text message
+
+
+ Send a test text message
+
+
+ Send a test text message
+
+