From d94831d300e492c6023f402f90f23649de78397b Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Fri, 5 Dec 2025 12:58:36 +0000 Subject: [PATCH 01/13] CCM-13415: Validate unsupported personalisation in digital templates --- .../EmailTemplateForm.test.tsx.snap | 15 --------- .../EmailTemplateForm/server-action.test.ts | 22 +++++++++++++ .../NhsAppTemplateForm.test.tsx.snap | 18 ---------- .../NhsAppTemplateForm/server-action.test.ts | 22 +++++++++++++ .../SmsTemplateForm.test.tsx.snap | 15 --------- .../SmsTemplateForm/server-action.test.ts | 22 +++++++++++++ .../Personalisation.test.tsx.snap | 3 -- .../forms/EmailTemplateForm/server-action.ts | 7 +++- .../forms/NhsAppTemplateForm/server-action.ts | 8 ++++- .../forms/SmsTemplateForm/server-action.ts | 7 +++- frontend/src/content/content.ts | 5 ++- frontend/src/utils/constants.ts | 11 +++++++ ...e-mgmt-create-email-page.component.spec.ts | 33 +++++++++++++++++++ ...te-nhs-app-template-page.component.spec.ts | 21 ++++++++++++ ...ate-mgmt-create-sms-page.component.spec.ts | 27 +++++++++++++++ 15 files changed, 181 insertions(+), 55 deletions(-) diff --git a/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap b/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap index 5a5533484..f815496e7 100644 --- a/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap @@ -237,9 +237,6 @@ exports[`Client-side validation triggers 1`] = `
  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -985,9 +982,6 @@ exports[`renders page one error 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -1761,9 +1755,6 @@ exports[`renders page with multiple errors 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -2464,9 +2455,6 @@ exports[`renders page with preloaded field values 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -3160,9 +3148,6 @@ exports[`renders page without back link for initial state with id - edit mode 1`

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. diff --git a/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts b/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts index 45c1e9305..b2bfd4ba5 100644 --- a/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts @@ -91,6 +91,28 @@ describe('CreateEmailTemplate server actions', () => { }); }); + it('create-email-template - should return response when when template message contains unsupported personalisation', async () => { + const response = await processFormActions( + initialState, + getMockFormData({ + 'form-id': 'create-email-template', + emailTemplateName: 'template-name', + emailTemplateSubjectLine: 'template-subject-line', + emailTemplateMessage: 'a template message containing ((date))', + }) + ); + + expect(response).toEqual({ + ...initialState, + errorState: { + formErrors: [], + fieldErrors: { + emailTemplateMessage: ['Template message contains invalid personalisation fields'], + }, + }, + }); + }); + test('should save the template and redirect', async () => { saveTemplateMock.mockResolvedValue({ ...initialState, diff --git a/frontend/src/__tests__/components/forms/NhsAppTemplateForm/__snapshots__/NhsAppTemplateForm.test.tsx.snap b/frontend/src/__tests__/components/forms/NhsAppTemplateForm/__snapshots__/NhsAppTemplateForm.test.tsx.snap index 2ff664299..d8654daaf 100644 --- a/frontend/src/__tests__/components/forms/NhsAppTemplateForm/__snapshots__/NhsAppTemplateForm.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/NhsAppTemplateForm/__snapshots__/NhsAppTemplateForm.test.tsx.snap @@ -228,9 +228,6 @@ exports[`Client-side validation triggers 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -912,9 +909,6 @@ exports[`renders page 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -1636,9 +1630,6 @@ exports[`renders page one error 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -2374,9 +2365,6 @@ exports[`renders page with multiple errors 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -3058,9 +3046,6 @@ exports[`renders page with preloaded field values 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -3736,9 +3721,6 @@ exports[`renders page without back link for initial state with id - edit mode 1`

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. diff --git a/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts b/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts index fe7fa05e3..6b8421b65 100644 --- a/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts @@ -94,6 +94,28 @@ describe('CreateNHSAppTemplate server actions', () => { }); }); + it('create-nhs-app-template - should return response when when template message contains unsupported personalisation', async () => { + const response = await processFormActions( + initialState, + getMockFormData({ + 'form-id': 'create-nhs-app-template', + nhsAppTemplateName: 'template-name', + nhsAppTemplateMessage: + 'a template message containing ((date))', + }) + ); + + expect(response).toEqual({ + ...initialState, + errorState: { + formErrors: [], + fieldErrors: { + nhsAppTemplateMessage: ['Template message contains invalid personalisation fields'], + }, + }, + }); + }); + it('create-nhs-app-template - should return response when when template message contains link with angle brackets', async () => { const response = await processFormActions( initialState, diff --git a/frontend/src/__tests__/components/forms/SmsTemplateForm/__snapshots__/SmsTemplateForm.test.tsx.snap b/frontend/src/__tests__/components/forms/SmsTemplateForm/__snapshots__/SmsTemplateForm.test.tsx.snap index 1ba75be6e..69cdf6c58 100644 --- a/frontend/src/__tests__/components/forms/SmsTemplateForm/__snapshots__/SmsTemplateForm.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/SmsTemplateForm/__snapshots__/SmsTemplateForm.test.tsx.snap @@ -290,9 +290,6 @@ exports[`CreateSmsTemplate component Client-side validation triggers 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -743,9 +740,6 @@ exports[`CreateSmsTemplate component renders page one error 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -1160,9 +1154,6 @@ exports[`CreateSmsTemplate component renders page with back link if initial stat

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -1627,9 +1618,6 @@ exports[`CreateSmsTemplate component renders page with multiple errors 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. @@ -2038,9 +2026,6 @@ exports[`CreateSmsTemplate component renders page with no back link if initial s

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. diff --git a/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts b/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts index 97a0722b7..fdb9c9e6d 100644 --- a/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts @@ -88,6 +88,28 @@ describe('CreateSmsTemplate server actions', () => { }); }); + it('create-sms-template - should return response when when template message contains unsupported personalisation', async () => { + const response = await processFormActions( + initialState, + getMockFormData({ + 'form-id': 'create-sms-template', + smsTemplateName: 'template-name', + smsTemplateMessage: + 'a template message containing ((date))', + }) + ); + + expect(response).toEqual({ + ...initialState, + errorState: { + formErrors: [], + fieldErrors: { + smsTemplateMessage: ['Template message contains invalid personalisation fields'], + }, + }, + }); + }); + test('should save the template and redirect', async () => { saveTemplateMock.mockResolvedValue({ ...initialState, diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/Personalisation.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/Personalisation.test.tsx.snap index 8fbd58d3d..06f4cc3b6 100644 --- a/frontend/src/__tests__/components/molecules/__snapshots__/Personalisation.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/__snapshots__/Personalisation.test.tsx.snap @@ -66,9 +66,6 @@ exports[`Personalisation component matches snapshot 1`] = `

  • ((nhsNumber))
  • -
  • - ((date)) -
  • Make sure your personalisation fields exactly match the PDS personalisation fields. This includes using the correct order of upper and lower case letters. diff --git a/frontend/src/components/forms/EmailTemplateForm/server-action.ts b/frontend/src/components/forms/EmailTemplateForm/server-action.ts index dbc7f897a..a3ce64fc4 100644 --- a/frontend/src/components/forms/EmailTemplateForm/server-action.ts +++ b/frontend/src/components/forms/EmailTemplateForm/server-action.ts @@ -6,7 +6,7 @@ import { import { z } from 'zod'; import { createTemplate, saveTemplate } from '@utils/form-actions'; import { redirect, RedirectType } from 'next/navigation'; -import { MAX_EMAIL_CHARACTER_LENGTH } from '@utils/constants'; +import { MAX_EMAIL_CHARACTER_LENGTH, INVALID_PERSONALISATION_FIELDS } from '@utils/constants'; import content from '@content/content'; const { @@ -30,6 +30,11 @@ export const $EmailTemplateFormSchema = z.object({ }) .refine((templateMessage) => !templateMessage.includes('http://'), { message: form.emailTemplateMessage.error.insecureLink, + }) + .refine((templateMessage) => INVALID_PERSONALISATION_FIELDS.some( + (field) => !templateMessage.includes(`((${field}))`) + ), { + message: form.emailTemplateMessage.error.invalidPersonalisation, }), }); diff --git a/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts b/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts index bbef5f83a..a47e73f7f 100644 --- a/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts +++ b/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts @@ -7,6 +7,7 @@ import { import { z } from 'zod'; import { saveTemplate, createTemplate } from '@utils/form-actions'; import { redirect, RedirectType } from 'next/navigation'; +import { INVALID_PERSONALISATION_FIELDS } from '@utils/constants'; import content from '@content/content'; const { @@ -49,7 +50,12 @@ export const $CreateNhsAppTemplateSchema = z.object({ .refine( (templateMessage) => !hasInvalidCharactersInLinks(templateMessage), { message: form.nhsAppTemplateMessage.error.invalidUrlCharacter } - ), + ) + .refine((templateMessage) => INVALID_PERSONALISATION_FIELDS.some( + (field) => !templateMessage.includes(`((${field}))`) + ), { + message: form.nhsAppTemplateMessage.error.invalidPersonalisation, + }), }); export async function processFormActions( diff --git a/frontend/src/components/forms/SmsTemplateForm/server-action.ts b/frontend/src/components/forms/SmsTemplateForm/server-action.ts index f3b5e1914..85f706bd7 100644 --- a/frontend/src/components/forms/SmsTemplateForm/server-action.ts +++ b/frontend/src/components/forms/SmsTemplateForm/server-action.ts @@ -6,7 +6,7 @@ import { import { z } from 'zod'; import { saveTemplate, createTemplate } from '@utils/form-actions'; import { redirect, RedirectType } from 'next/navigation'; -import { MAX_SMS_CHARACTER_LENGTH } from '@utils/constants'; +import { MAX_SMS_CHARACTER_LENGTH, INVALID_PERSONALISATION_FIELDS } from '@utils/constants'; import content from '@content/content'; const { @@ -27,6 +27,11 @@ export const $CreateSmsTemplateSchema = z.object({ }) .refine((templateMessage) => !templateMessage.includes('http://'), { message: form.smsTemplateMessage.error.insecureLink, + }) + .refine((templateMessage) => INVALID_PERSONALISATION_FIELDS.some( + (field) => !templateMessage.includes(`((${field}))`) + ), { + message: form.smsTemplateMessage.error.invalidPersonalisation, }), }); diff --git a/frontend/src/content/content.ts b/frontend/src/content/content.ts index ce4c39818..39ad186d1 100644 --- a/frontend/src/content/content.ts +++ b/frontend/src/content/content.ts @@ -12,6 +12,7 @@ const enterATemplateName = 'Enter a template name'; const enterATemplateMessage = 'Enter a template message'; const templateMessageTooLong = 'Template message too long'; const templateMessageHasInsecureLink = 'URLs must start with https://'; +const templateMessageContainsInvalidPersonalisation = 'Template message contains invalid personalisation fields'; const selectAnOption = 'Select an option'; const header = { @@ -123,7 +124,6 @@ const personalisation: { '((firstName))', '((lastName))', '((nhsNumber))', - '((date))', ]), }, { @@ -816,6 +816,7 @@ const templateFormNhsApp = { empty: enterATemplateMessage, max: templateMessageTooLong, insecureLink: templateMessageHasInsecureLink, + invalidPersonalisation: templateMessageContainsInvalidPersonalisation, invalidUrlCharacter: 'URLs cannot include the symbols < or >', }, }, @@ -921,6 +922,7 @@ const templateFormEmail = { empty: enterATemplateMessage, max: templateMessageTooLong, insecureLink: templateMessageHasInsecureLink, + invalidPersonalisation: templateMessageContainsInvalidPersonalisation, }, }, }, @@ -963,6 +965,7 @@ const templateFormSms = { empty: enterATemplateMessage, max: templateMessageTooLong, insecureLink: templateMessageHasInsecureLink, + invalidPersonalisation: templateMessageContainsInvalidPersonalisation, }, }, }, diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 3dfb115ca..4cf083f2b 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -1,2 +1,13 @@ export const MAX_SMS_CHARACTER_LENGTH = 918 as const; export const MAX_EMAIL_CHARACTER_LENGTH = 100_000 as const; + +export const INVALID_PERSONALISATION_FIELDS = [ + 'date', + 'address_line_1', + 'address_line_2', + 'address_line_3', + 'address_line_4', + 'address_line_5', + 'address_line_6', + 'address_line_7', +] as const; diff --git a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts index 396bfee1c..08e381111 100644 --- a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts @@ -371,5 +371,38 @@ test.describe('Create Email message template Page', () => { await expect(createEmailTemplatePage.messageTextArea).toBeFocused(); }); + + test('when user submits form with unsupported personalisation, then an error is displayed', async ({ + page, + }) => { + const errorMessage = 'Template message contains invalid personalisation fields'; + + const createEmailTemplatePage = new TemplateMgmtCreateEmailPage(page); + + await createEmailTemplatePage.loadPage(); + + await createEmailTemplatePage.nameInput.fill('template-name'); + + await createEmailTemplatePage.subjectLineInput.fill( + 'template-subject-line' + ); + + await createEmailTemplatePage.messageTextArea.fill( + 'a template message containing ((date))' + ); + + await createEmailTemplatePage.clickSaveAndPreviewButton(); + + const emailMessageErrorLink = + createEmailTemplatePage.errorSummary.locator( + '[href="#emailTemplateMessage"]' + ); + + await expect(emailMessageErrorLink).toHaveText(errorMessage); + + await emailMessageErrorLink.click(); + + await expect(createEmailTemplatePage.messageTextArea).toBeFocused(); + }); }); }); diff --git a/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts index 7d124a871..50d91645e 100644 --- a/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts @@ -177,6 +177,27 @@ test.describe('Create NHS App Template Page', () => { ).toHaveText(['URLs must start with https://']); }); + test('Validate error messages on the create NHS App message template page with unsupported personalisation in message', async ({ + page, + }) => { + const createTemplatePage = new TemplateMgmtCreateNhsAppPage(page); + + await createTemplatePage.loadPage(); + await expect(createTemplatePage.pageHeading).toHaveText( + 'Create NHS App message template' + ); + await page.locator('[id="nhsAppTemplateName"]').fill('template-name'); + await page + .locator('[id="nhsAppTemplateMessage"]') + .fill('a template message containing ((date))'); + await createTemplatePage.clickSaveAndPreviewButton(); + await expect(page.locator('.nhsuk-error-summary')).toBeVisible(); + + await expect( + page.locator('ul[class="nhsuk-list nhsuk-error-summary__list"] > li') + ).toHaveText(['Template message contains invalid personalisation fields']); + }); + test('Validate error messages on the create NHS App message template page with angle brackets in linked url', async ({ page, }) => { diff --git a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts index e57daec78..20c7f71b3 100644 --- a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts @@ -338,5 +338,32 @@ test.describe('Create SMS message template Page', () => { await expect(createSmsTemplatePage.messageTextArea).toBeFocused(); }); + + test('when user submits form with unsupported personalisation, then an error is displayed', async ({ + page, + }) => { + const errorMessage = 'Template message contains invalid personalisation fields'; + + const createSmsTemplatePage = new TemplateMgmtCreateSmsPage(page); + + await createSmsTemplatePage.loadPage(); + + await createSmsTemplatePage.nameInput.fill('template-name'); + await createSmsTemplatePage.messageTextArea.fill( + 'a template message containing ((date))' + ); + + await createSmsTemplatePage.clickSaveAndPreviewButton(); + + const smsMessageErrorLink = createSmsTemplatePage.errorSummary.locator( + '[href="#smsTemplateMessage"]' + ); + + await expect(smsMessageErrorLink).toHaveText(errorMessage); + + await smsMessageErrorLink.click(); + + await expect(createSmsTemplatePage.messageTextArea).toBeFocused(); + }); }); }); From 45745a3960988218adbde82112f86bd17d4533e1 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Fri, 5 Dec 2025 13:34:34 +0000 Subject: [PATCH 02/13] CCM-13415: Fix CI --- .../src/components/forms/EmailTemplateForm/server-action.ts | 4 ++-- .../email/template-mgmt-create-email-page.component.spec.ts | 3 ++- .../sms/template-mgmt-create-sms-page.component.spec.ts | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/forms/EmailTemplateForm/server-action.ts b/frontend/src/components/forms/EmailTemplateForm/server-action.ts index a3ce64fc4..040d4e1b6 100644 --- a/frontend/src/components/forms/EmailTemplateForm/server-action.ts +++ b/frontend/src/components/forms/EmailTemplateForm/server-action.ts @@ -31,8 +31,8 @@ export const $EmailTemplateFormSchema = z.object({ .refine((templateMessage) => !templateMessage.includes('http://'), { message: form.emailTemplateMessage.error.insecureLink, }) - .refine((templateMessage) => INVALID_PERSONALISATION_FIELDS.some( - (field) => !templateMessage.includes(`((${field}))`) + .refine((templateMessage) => !INVALID_PERSONALISATION_FIELDS.some( + (personalisationFieldName) => templateMessage.includes(`((${personalisationFieldName}))`) ), { message: form.emailTemplateMessage.error.invalidPersonalisation, }), diff --git a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts index 08e381111..f923d0353 100644 --- a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts @@ -375,7 +375,8 @@ test.describe('Create Email message template Page', () => { test('when user submits form with unsupported personalisation, then an error is displayed', async ({ page, }) => { - const errorMessage = 'Template message contains invalid personalisation fields'; + const errorMessage = + 'Template message contains invalid personalisation fields'; const createEmailTemplatePage = new TemplateMgmtCreateEmailPage(page); diff --git a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts index 20c7f71b3..444cbc080 100644 --- a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts @@ -342,7 +342,8 @@ test.describe('Create SMS message template Page', () => { test('when user submits form with unsupported personalisation, then an error is displayed', async ({ page, }) => { - const errorMessage = 'Template message contains invalid personalisation fields'; + const errorMessage = + 'Template message contains invalid personalisation fields'; const createSmsTemplatePage = new TemplateMgmtCreateSmsPage(page); From 1e944bb69e51df1611bcdc012764203da9421cb5 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Fri, 5 Dec 2025 13:39:35 +0000 Subject: [PATCH 03/13] CCM-13415: Fix CI --- .../EmailTemplateForm/server-action.test.ts | 4 +++- .../NhsAppTemplateForm/server-action.test.ts | 7 ++++--- .../SmsTemplateForm/server-action.test.ts | 7 ++++--- .../forms/EmailTemplateForm/server-action.ts | 19 +++++++++++++------ .../forms/NhsAppTemplateForm/server-action.ts | 14 +++++++++----- .../forms/SmsTemplateForm/server-action.ts | 19 +++++++++++++------ frontend/src/content/content.ts | 3 ++- frontend/src/utils/constants.ts | 16 ++++++++-------- 8 files changed, 56 insertions(+), 33 deletions(-) diff --git a/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts b/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts index b2bfd4ba5..946a332ff 100644 --- a/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts @@ -107,7 +107,9 @@ describe('CreateEmailTemplate server actions', () => { errorState: { formErrors: [], fieldErrors: { - emailTemplateMessage: ['Template message contains invalid personalisation fields'], + emailTemplateMessage: [ + 'Template message contains invalid personalisation fields', + ], }, }, }); diff --git a/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts b/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts index 6b8421b65..db2731c95 100644 --- a/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts @@ -100,8 +100,7 @@ describe('CreateNHSAppTemplate server actions', () => { getMockFormData({ 'form-id': 'create-nhs-app-template', nhsAppTemplateName: 'template-name', - nhsAppTemplateMessage: - 'a template message containing ((date))', + nhsAppTemplateMessage: 'a template message containing ((date))', }) ); @@ -110,7 +109,9 @@ describe('CreateNHSAppTemplate server actions', () => { errorState: { formErrors: [], fieldErrors: { - nhsAppTemplateMessage: ['Template message contains invalid personalisation fields'], + nhsAppTemplateMessage: [ + 'Template message contains invalid personalisation fields', + ], }, }, }); diff --git a/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts b/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts index fdb9c9e6d..8bb6dfee4 100644 --- a/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts @@ -94,8 +94,7 @@ describe('CreateSmsTemplate server actions', () => { getMockFormData({ 'form-id': 'create-sms-template', smsTemplateName: 'template-name', - smsTemplateMessage: - 'a template message containing ((date))', + smsTemplateMessage: 'a template message containing ((date))', }) ); @@ -104,7 +103,9 @@ describe('CreateSmsTemplate server actions', () => { errorState: { formErrors: [], fieldErrors: { - smsTemplateMessage: ['Template message contains invalid personalisation fields'], + smsTemplateMessage: [ + 'Template message contains invalid personalisation fields', + ], }, }, }); diff --git a/frontend/src/components/forms/EmailTemplateForm/server-action.ts b/frontend/src/components/forms/EmailTemplateForm/server-action.ts index 040d4e1b6..5c99e3127 100644 --- a/frontend/src/components/forms/EmailTemplateForm/server-action.ts +++ b/frontend/src/components/forms/EmailTemplateForm/server-action.ts @@ -6,7 +6,10 @@ import { import { z } from 'zod'; import { createTemplate, saveTemplate } from '@utils/form-actions'; import { redirect, RedirectType } from 'next/navigation'; -import { MAX_EMAIL_CHARACTER_LENGTH, INVALID_PERSONALISATION_FIELDS } from '@utils/constants'; +import { + MAX_EMAIL_CHARACTER_LENGTH, + INVALID_PERSONALISATION_FIELDS, +} from '@utils/constants'; import content from '@content/content'; const { @@ -31,11 +34,15 @@ export const $EmailTemplateFormSchema = z.object({ .refine((templateMessage) => !templateMessage.includes('http://'), { message: form.emailTemplateMessage.error.insecureLink, }) - .refine((templateMessage) => !INVALID_PERSONALISATION_FIELDS.some( - (personalisationFieldName) => templateMessage.includes(`((${personalisationFieldName}))`) - ), { - message: form.emailTemplateMessage.error.invalidPersonalisation, - }), + .refine( + (templateMessage) => + !INVALID_PERSONALISATION_FIELDS.some((personalisationFieldName) => + templateMessage.includes(`((${personalisationFieldName}))`) + ), + { + message: form.emailTemplateMessage.error.invalidPersonalisation, + } + ), }); export async function processFormActions( diff --git a/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts b/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts index a47e73f7f..fe6f35bdc 100644 --- a/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts +++ b/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts @@ -51,11 +51,15 @@ export const $CreateNhsAppTemplateSchema = z.object({ (templateMessage) => !hasInvalidCharactersInLinks(templateMessage), { message: form.nhsAppTemplateMessage.error.invalidUrlCharacter } ) - .refine((templateMessage) => INVALID_PERSONALISATION_FIELDS.some( - (field) => !templateMessage.includes(`((${field}))`) - ), { - message: form.nhsAppTemplateMessage.error.invalidPersonalisation, - }), + .refine( + (templateMessage) => + !INVALID_PERSONALISATION_FIELDS.some((personalisationFieldName) => + templateMessage.includes(`((${personalisationFieldName}))`) + ), + { + message: form.nhsAppTemplateMessage.error.invalidPersonalisation, + } + ), }); export async function processFormActions( diff --git a/frontend/src/components/forms/SmsTemplateForm/server-action.ts b/frontend/src/components/forms/SmsTemplateForm/server-action.ts index 85f706bd7..19a9b0da7 100644 --- a/frontend/src/components/forms/SmsTemplateForm/server-action.ts +++ b/frontend/src/components/forms/SmsTemplateForm/server-action.ts @@ -6,7 +6,10 @@ import { import { z } from 'zod'; import { saveTemplate, createTemplate } from '@utils/form-actions'; import { redirect, RedirectType } from 'next/navigation'; -import { MAX_SMS_CHARACTER_LENGTH, INVALID_PERSONALISATION_FIELDS } from '@utils/constants'; +import { + MAX_SMS_CHARACTER_LENGTH, + INVALID_PERSONALISATION_FIELDS, +} from '@utils/constants'; import content from '@content/content'; const { @@ -28,11 +31,15 @@ export const $CreateSmsTemplateSchema = z.object({ .refine((templateMessage) => !templateMessage.includes('http://'), { message: form.smsTemplateMessage.error.insecureLink, }) - .refine((templateMessage) => INVALID_PERSONALISATION_FIELDS.some( - (field) => !templateMessage.includes(`((${field}))`) - ), { - message: form.smsTemplateMessage.error.invalidPersonalisation, - }), + .refine( + (templateMessage) => + !INVALID_PERSONALISATION_FIELDS.some((personalisationFieldName) => + templateMessage.includes(`((${personalisationFieldName}))`) + ), + { + message: form.smsTemplateMessage.error.invalidPersonalisation, + } + ), }); export async function processFormActions( diff --git a/frontend/src/content/content.ts b/frontend/src/content/content.ts index 39ad186d1..8cbbece7c 100644 --- a/frontend/src/content/content.ts +++ b/frontend/src/content/content.ts @@ -12,7 +12,8 @@ const enterATemplateName = 'Enter a template name'; const enterATemplateMessage = 'Enter a template message'; const templateMessageTooLong = 'Template message too long'; const templateMessageHasInsecureLink = 'URLs must start with https://'; -const templateMessageContainsInvalidPersonalisation = 'Template message contains invalid personalisation fields'; +const templateMessageContainsInvalidPersonalisation = + 'Template message contains invalid personalisation fields'; const selectAnOption = 'Select an option'; const header = { diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 4cf083f2b..b5ed7bdae 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -2,12 +2,12 @@ export const MAX_SMS_CHARACTER_LENGTH = 918 as const; export const MAX_EMAIL_CHARACTER_LENGTH = 100_000 as const; export const INVALID_PERSONALISATION_FIELDS = [ - 'date', - 'address_line_1', - 'address_line_2', - 'address_line_3', - 'address_line_4', - 'address_line_5', - 'address_line_6', - 'address_line_7', + 'date', + 'address_line_1', + 'address_line_2', + 'address_line_3', + 'address_line_4', + 'address_line_5', + 'address_line_6', + 'address_line_7', ] as const; From 1c46d382b183f5510d521ead23ea5fdac2e4a5ca Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Wed, 10 Dec 2025 09:50:49 +0000 Subject: [PATCH 04/13] CCM-13415: Content changes --- .../components/forms/EmailTemplateForm/server-action.test.ts | 2 +- .../components/forms/NhsAppTemplateForm/server-action.test.ts | 2 +- .../components/forms/SmsTemplateForm/server-action.test.ts | 2 +- .../src/components/forms/EmailTemplateForm/server-action.ts | 2 +- .../src/components/forms/NhsAppTemplateForm/server-action.ts | 2 +- .../src/components/forms/SmsTemplateForm/server-action.ts | 2 +- frontend/src/content/content.ts | 2 +- .../email/template-mgmt-create-email-page.component.spec.ts | 2 +- ...mplate-mgmt-create-nhs-app-template-page.component.spec.ts | 4 +++- .../sms/template-mgmt-create-sms-page.component.spec.ts | 2 +- 10 files changed, 12 insertions(+), 10 deletions(-) diff --git a/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts b/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts index 946a332ff..b6c7a019a 100644 --- a/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/EmailTemplateForm/server-action.test.ts @@ -108,7 +108,7 @@ describe('CreateEmailTemplate server actions', () => { formErrors: [], fieldErrors: { emailTemplateMessage: [ - 'Template message contains invalid personalisation fields', + 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7', ], }, }, diff --git a/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts b/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts index db2731c95..223f44256 100644 --- a/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/NhsAppTemplateForm/server-action.test.ts @@ -110,7 +110,7 @@ describe('CreateNHSAppTemplate server actions', () => { formErrors: [], fieldErrors: { nhsAppTemplateMessage: [ - 'Template message contains invalid personalisation fields', + 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7', ], }, }, diff --git a/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts b/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts index 8bb6dfee4..37cfc92d4 100644 --- a/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/SmsTemplateForm/server-action.test.ts @@ -104,7 +104,7 @@ describe('CreateSmsTemplate server actions', () => { formErrors: [], fieldErrors: { smsTemplateMessage: [ - 'Template message contains invalid personalisation fields', + 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7', ], }, }, diff --git a/frontend/src/components/forms/EmailTemplateForm/server-action.ts b/frontend/src/components/forms/EmailTemplateForm/server-action.ts index 5c99e3127..4fa59aa17 100644 --- a/frontend/src/components/forms/EmailTemplateForm/server-action.ts +++ b/frontend/src/components/forms/EmailTemplateForm/server-action.ts @@ -40,7 +40,7 @@ export const $EmailTemplateFormSchema = z.object({ templateMessage.includes(`((${personalisationFieldName}))`) ), { - message: form.emailTemplateMessage.error.invalidPersonalisation, + message: `${form.emailTemplateMessage.error.invalidPersonalisation} ${INVALID_PERSONALISATION_FIELDS.join(', ')}`, } ), }); diff --git a/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts b/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts index fe6f35bdc..ec376b81c 100644 --- a/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts +++ b/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts @@ -57,7 +57,7 @@ export const $CreateNhsAppTemplateSchema = z.object({ templateMessage.includes(`((${personalisationFieldName}))`) ), { - message: form.nhsAppTemplateMessage.error.invalidPersonalisation, + message: `${form.nhsAppTemplateMessage.error.invalidPersonalisation} ${INVALID_PERSONALISATION_FIELDS.join(', ')}`, } ), }); diff --git a/frontend/src/components/forms/SmsTemplateForm/server-action.ts b/frontend/src/components/forms/SmsTemplateForm/server-action.ts index 19a9b0da7..98efa17cb 100644 --- a/frontend/src/components/forms/SmsTemplateForm/server-action.ts +++ b/frontend/src/components/forms/SmsTemplateForm/server-action.ts @@ -37,7 +37,7 @@ export const $CreateSmsTemplateSchema = z.object({ templateMessage.includes(`((${personalisationFieldName}))`) ), { - message: form.smsTemplateMessage.error.invalidPersonalisation, + message: `${form.smsTemplateMessage.error.invalidPersonalisation} ${INVALID_PERSONALISATION_FIELDS.join(', ')}`, } ), }); diff --git a/frontend/src/content/content.ts b/frontend/src/content/content.ts index 8cbbece7c..66c9d78dc 100644 --- a/frontend/src/content/content.ts +++ b/frontend/src/content/content.ts @@ -13,7 +13,7 @@ const enterATemplateMessage = 'Enter a template message'; const templateMessageTooLong = 'Template message too long'; const templateMessageHasInsecureLink = 'URLs must start with https://'; const templateMessageContainsInvalidPersonalisation = - 'Template message contains invalid personalisation fields'; + 'You cannot use the following custom personalisation fields in your message:'; const selectAnOption = 'Select an option'; const header = { diff --git a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts index f923d0353..ea0e872d3 100644 --- a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts @@ -376,7 +376,7 @@ test.describe('Create Email message template Page', () => { page, }) => { const errorMessage = - 'Template message contains invalid personalisation fields'; + 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7'; const createEmailTemplatePage = new TemplateMgmtCreateEmailPage(page); diff --git a/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts index 50d91645e..5eb4d2787 100644 --- a/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts @@ -195,7 +195,9 @@ test.describe('Create NHS App Template Page', () => { await expect( page.locator('ul[class="nhsuk-list nhsuk-error-summary__list"] > li') - ).toHaveText(['Template message contains invalid personalisation fields']); + ).toHaveText([ + 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7', + ]); }); test('Validate error messages on the create NHS App message template page with angle brackets in linked url', async ({ diff --git a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts index 444cbc080..a0eb63b25 100644 --- a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts @@ -343,7 +343,7 @@ test.describe('Create SMS message template Page', () => { page, }) => { const errorMessage = - 'Template message contains invalid personalisation fields'; + 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7'; const createSmsTemplatePage = new TemplateMgmtCreateSmsPage(page); From f1f0ddb0dd89985052dae437a9277391355976bf Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 16 Dec 2025 15:05:14 +0000 Subject: [PATCH 05/13] CCM-13415: Use error code and render bullet pointed list --- .../EmailTemplateForm.test.tsx.snap | 10 ---- .../EmailTemplateForm/server-action.test.ts | 3 +- .../LetterTemplateForm.test.tsx.snap | 40 ---------------- .../NhsAppTemplateForm.test.tsx.snap | 5 -- .../NhsAppTemplateForm/server-action.test.ts | 3 +- .../SmsTemplateForm.test.tsx.snap | 5 -- .../SmsTemplateForm/server-action.test.ts | 3 +- .../NhsNotifyErrorSummary.test.tsx.snap | 9 +++- .../forms/EmailTemplateForm/server-action.ts | 3 +- .../forms/NhsAppTemplateForm/server-action.ts | 3 +- .../forms/SmsTemplateForm/server-action.ts | 3 +- .../NhsNotifyErrorSummary.tsx | 48 ++++++++++++++----- frontend/src/content/content.ts | 8 ++-- frontend/src/utils/error-codes.ts | 3 ++ 14 files changed, 63 insertions(+), 83 deletions(-) create mode 100644 frontend/src/utils/error-codes.ts diff --git a/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap b/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap index f815496e7..ca7941f83 100644 --- a/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap @@ -733,16 +733,6 @@ exports[`renders page one error 1`] = ` Template name error -

  • - -
  • -
  • - -
  • { formErrors: [], fieldErrors: { emailTemplateMessage: [ - 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7', + ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME, ], }, }, diff --git a/frontend/src/__tests__/components/forms/LetterTemplateForm/__snapshots__/LetterTemplateForm.test.tsx.snap b/frontend/src/__tests__/components/forms/LetterTemplateForm/__snapshots__/LetterTemplateForm.test.tsx.snap index 78489b89e..c6ba9af88 100644 --- a/frontend/src/__tests__/components/forms/LetterTemplateForm/__snapshots__/LetterTemplateForm.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/LetterTemplateForm/__snapshots__/LetterTemplateForm.test.tsx.snap @@ -960,31 +960,6 @@ exports[`renders page one error 1`] = ` Template name error -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • { formErrors: [], fieldErrors: { nhsAppTemplateMessage: [ - 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7', + ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME, ], }, }, diff --git a/frontend/src/__tests__/components/forms/SmsTemplateForm/__snapshots__/SmsTemplateForm.test.tsx.snap b/frontend/src/__tests__/components/forms/SmsTemplateForm/__snapshots__/SmsTemplateForm.test.tsx.snap index 69cdf6c58..941b999f7 100644 --- a/frontend/src/__tests__/components/forms/SmsTemplateForm/__snapshots__/SmsTemplateForm.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/SmsTemplateForm/__snapshots__/SmsTemplateForm.test.tsx.snap @@ -495,11 +495,6 @@ exports[`CreateSmsTemplate component renders page one error 1`] = ` Template name error -
  • - -
  • { formErrors: [], fieldErrors: { smsTemplateMessage: [ - 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7', + ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME, ], }, }, diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/NhsNotifyErrorSummary.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/NhsNotifyErrorSummary.test.tsx.snap index c9c9837d0..e75dcf5c9 100644 --- a/frontend/src/__tests__/components/molecules/__snapshots__/NhsNotifyErrorSummary.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/__snapshots__/NhsNotifyErrorSummary.test.tsx.snap @@ -22,7 +22,14 @@ exports[`Renders NhsNotifyErrorSummary correctly with errors 1`] = ` - Radio error 1, Radio error 2 + Radio error 1 + + +
  • + + Radio error 2
  • diff --git a/frontend/src/components/forms/EmailTemplateForm/server-action.ts b/frontend/src/components/forms/EmailTemplateForm/server-action.ts index 4fa59aa17..731f33384 100644 --- a/frontend/src/components/forms/EmailTemplateForm/server-action.ts +++ b/frontend/src/components/forms/EmailTemplateForm/server-action.ts @@ -11,6 +11,7 @@ import { INVALID_PERSONALISATION_FIELDS, } from '@utils/constants'; import content from '@content/content'; +import { ErrorCodes } from '@utils/error-codes'; const { components: { @@ -40,7 +41,7 @@ export const $EmailTemplateFormSchema = z.object({ templateMessage.includes(`((${personalisationFieldName}))`) ), { - message: `${form.emailTemplateMessage.error.invalidPersonalisation} ${INVALID_PERSONALISATION_FIELDS.join(', ')}`, + message: ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME, } ), }); diff --git a/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts b/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts index ec376b81c..72a4f6f3e 100644 --- a/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts +++ b/frontend/src/components/forms/NhsAppTemplateForm/server-action.ts @@ -9,6 +9,7 @@ import { saveTemplate, createTemplate } from '@utils/form-actions'; import { redirect, RedirectType } from 'next/navigation'; import { INVALID_PERSONALISATION_FIELDS } from '@utils/constants'; import content from '@content/content'; +import { ErrorCodes } from '@utils/error-codes'; const { components: { @@ -57,7 +58,7 @@ export const $CreateNhsAppTemplateSchema = z.object({ templateMessage.includes(`((${personalisationFieldName}))`) ), { - message: `${form.nhsAppTemplateMessage.error.invalidPersonalisation} ${INVALID_PERSONALISATION_FIELDS.join(', ')}`, + message: ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME, } ), }); diff --git a/frontend/src/components/forms/SmsTemplateForm/server-action.ts b/frontend/src/components/forms/SmsTemplateForm/server-action.ts index 98efa17cb..fea0b084a 100644 --- a/frontend/src/components/forms/SmsTemplateForm/server-action.ts +++ b/frontend/src/components/forms/SmsTemplateForm/server-action.ts @@ -11,6 +11,7 @@ import { INVALID_PERSONALISATION_FIELDS, } from '@utils/constants'; import content from '@content/content'; +import { ErrorCodes } from '@utils/error-codes'; const { components: { @@ -37,7 +38,7 @@ export const $CreateSmsTemplateSchema = z.object({ templateMessage.includes(`((${personalisationFieldName}))`) ), { - message: `${form.smsTemplateMessage.error.invalidPersonalisation} ${INVALID_PERSONALISATION_FIELDS.join(', ')}`, + message: ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME, } ), }); diff --git a/frontend/src/components/molecules/NhsNotifyErrorSummary/NhsNotifyErrorSummary.tsx b/frontend/src/components/molecules/NhsNotifyErrorSummary/NhsNotifyErrorSummary.tsx index 2aa62e2bd..459befbe8 100644 --- a/frontend/src/components/molecules/NhsNotifyErrorSummary/NhsNotifyErrorSummary.tsx +++ b/frontend/src/components/molecules/NhsNotifyErrorSummary/NhsNotifyErrorSummary.tsx @@ -1,7 +1,11 @@ import { ErrorSummary, HintText } from 'nhsuk-react-components'; import { ErrorState } from 'nhs-notify-web-template-management-utils'; -import { FC, HTMLProps, useEffect, useRef } from 'react'; -import content from '@content/content'; +import { FC, HTMLProps, JSX, useEffect, useRef } from 'react'; +import content, { + templateMessageContainsInvalidPersonalisationErrorText, +} from '@content/content'; +import { INVALID_PERSONALISATION_FIELDS } from '@utils/constants'; +import { ErrorCodes } from '@utils/error-codes'; const UnlinkedErrorSummaryItem: FC> = (props) => (
  • @@ -9,6 +13,23 @@ const UnlinkedErrorSummaryItem: FC> = (props) => (
  • ); +const errorComponents: Record = { + [ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME]: ( + <> + {templateMessageContainsInvalidPersonalisationErrorText} +
      + {INVALID_PERSONALISATION_FIELDS.map((item) => ( +
    • {item}
    • + ))} +
    + + ), +}; + +const renderError = (error: string) => { + return errorComponents[error] || error; +}; + export type NhsNotifyErrorSummaryProps = { hint?: string; errorState?: ErrorState; @@ -33,6 +54,19 @@ export const NhsNotifyErrorSummary = ({ const { fieldErrors, formErrors } = errorState; + const renderedFieldErrors = + fieldErrors && + Object.entries(fieldErrors).map(([id, errors]) => + errors.map((error) => ( + + {renderError(error)} + + )) + ); + return ( @@ -40,15 +74,7 @@ export const NhsNotifyErrorSummary = ({ {hint && {hint}} - {fieldErrors && - Object.entries(fieldErrors).map(([id, errors]) => ( - - {errors.join(', ')} - - ))} + {renderedFieldErrors} {formErrors && formErrors.map((error, id) => ( diff --git a/frontend/src/content/content.ts b/frontend/src/content/content.ts index 66c9d78dc..ec3623a9b 100644 --- a/frontend/src/content/content.ts +++ b/frontend/src/content/content.ts @@ -12,10 +12,11 @@ const enterATemplateName = 'Enter a template name'; const enterATemplateMessage = 'Enter a template message'; const templateMessageTooLong = 'Template message too long'; const templateMessageHasInsecureLink = 'URLs must start with https://'; -const templateMessageContainsInvalidPersonalisation = - 'You cannot use the following custom personalisation fields in your message:'; const selectAnOption = 'Select an option'; +export const templateMessageContainsInvalidPersonalisationErrorText = + 'You cannot use the following custom personalisation fields in your message:'; + const header = { serviceName: 'Notify', logoLink: { @@ -817,7 +818,6 @@ const templateFormNhsApp = { empty: enterATemplateMessage, max: templateMessageTooLong, insecureLink: templateMessageHasInsecureLink, - invalidPersonalisation: templateMessageContainsInvalidPersonalisation, invalidUrlCharacter: 'URLs cannot include the symbols < or >', }, }, @@ -923,7 +923,6 @@ const templateFormEmail = { empty: enterATemplateMessage, max: templateMessageTooLong, insecureLink: templateMessageHasInsecureLink, - invalidPersonalisation: templateMessageContainsInvalidPersonalisation, }, }, }, @@ -966,7 +965,6 @@ const templateFormSms = { empty: enterATemplateMessage, max: templateMessageTooLong, insecureLink: templateMessageHasInsecureLink, - invalidPersonalisation: templateMessageContainsInvalidPersonalisation, }, }, }, diff --git a/frontend/src/utils/error-codes.ts b/frontend/src/utils/error-codes.ts new file mode 100644 index 000000000..60c7e2d92 --- /dev/null +++ b/frontend/src/utils/error-codes.ts @@ -0,0 +1,3 @@ +export enum ErrorCodes { + MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME = 'MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME', +} From 36616f02d8f813708a625702d2216dcc69922dbb Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 16 Dec 2025 15:26:17 +0000 Subject: [PATCH 06/13] CCM-13415: Fix tests --- .../email/template-mgmt-create-email-page.component.spec.ts | 2 +- ...template-mgmt-create-nhs-app-template-page.component.spec.ts | 2 +- .../sms/template-mgmt-create-sms-page.component.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts index ea0e872d3..88aedc8d7 100644 --- a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts @@ -376,7 +376,7 @@ test.describe('Create Email message template Page', () => { page, }) => { const errorMessage = - 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7'; + 'You cannot use the following custom personalisation fields in your message'; const createEmailTemplatePage = new TemplateMgmtCreateEmailPage(page); diff --git a/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts index 5eb4d2787..512f9c807 100644 --- a/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts @@ -196,7 +196,7 @@ test.describe('Create NHS App Template Page', () => { await expect( page.locator('ul[class="nhsuk-list nhsuk-error-summary__list"] > li') ).toHaveText([ - 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7', + 'You cannot use the following custom personalisation fields in your message', ]); }); diff --git a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts index a0eb63b25..3c027a3c9 100644 --- a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts @@ -343,7 +343,7 @@ test.describe('Create SMS message template Page', () => { page, }) => { const errorMessage = - 'You cannot use the following custom personalisation fields in your message: date, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, address_line_6, address_line_7'; + 'You cannot use the following custom personalisation fields in your message'; const createSmsTemplatePage = new TemplateMgmtCreateSmsPage(page); From 7e788f8f2fe01721e843e5c94394fb4c04afa2ae Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 16 Dec 2025 17:04:53 +0000 Subject: [PATCH 07/13] CCM-13415: Fix spacing --- .../molecules/NhsNotifyErrorSummary.test.tsx | 3 +- .../NhsNotifyErrorSummary.test.tsx.snap | 55 +++++++++++++++++++ .../NhsNotifyErrorSummary.tsx | 6 +- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/frontend/src/__tests__/components/molecules/NhsNotifyErrorSummary.test.tsx b/frontend/src/__tests__/components/molecules/NhsNotifyErrorSummary.test.tsx index fbfddf8d1..39bc0c916 100644 --- a/frontend/src/__tests__/components/molecules/NhsNotifyErrorSummary.test.tsx +++ b/frontend/src/__tests__/components/molecules/NhsNotifyErrorSummary.test.tsx @@ -1,5 +1,6 @@ import { render, screen, waitFor } from '@testing-library/react'; import { NhsNotifyErrorSummary } from '@molecules/NhsNotifyErrorSummary/NhsNotifyErrorSummary'; +import { ErrorCodes } from '@utils/error-codes'; const focusMock = jest.spyOn(window.HTMLElement.prototype, 'focus'); const scrollIntoViewMock = jest.spyOn( @@ -25,7 +26,7 @@ test('Renders NhsNotifyErrorSummary correctly with errors', async () => { errorState={{ fieldErrors: { 'radios-id': ['Radio error 1', 'Radio error 2'], - 'select-id': ['Select error'], + 'select-id': ['Select error', ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME], }, formErrors: ['Form error', 'Form error 2'], }} diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/NhsNotifyErrorSummary.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/NhsNotifyErrorSummary.test.tsx.snap index e75dcf5c9..3956bb438 100644 --- a/frontend/src/__tests__/components/molecules/__snapshots__/NhsNotifyErrorSummary.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/__snapshots__/NhsNotifyErrorSummary.test.tsx.snap @@ -39,6 +39,61 @@ exports[`Renders NhsNotifyErrorSummary correctly with errors 1`] = ` Select error +
  • + +
    + You cannot use the following custom personalisation fields in your message: +
    +
      +
    • + date +
    • +
    • + address_line_1 +
    • +
    • + address_line_2 +
    • +
    • + address_line_3 +
    • +
    • + address_line_4 +
    • +
    • + address_line_5 +
    • +
    • + address_line_6 +
    • +
    • + address_line_7 +
    • +
    +
    +
  • > = (props) => ( const errorComponents: Record = { [ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME]: ( <> - {templateMessageContainsInvalidPersonalisationErrorText} +
    + {templateMessageContainsInvalidPersonalisationErrorText} +
      {INVALID_PERSONALISATION_FIELDS.map((item) => ( -
    • {item}
    • +
    • {item}
    • ))}
    From 170961a539b7cb644a9a05de45351f61402e7644 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 16 Dec 2025 17:09:12 +0000 Subject: [PATCH 08/13] CCM-13145: Fix linting --- .../components/molecules/NhsNotifyErrorSummary.test.tsx | 5 ++++- .../NhsNotifyErrorSummary/NhsNotifyErrorSummary.tsx | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/src/__tests__/components/molecules/NhsNotifyErrorSummary.test.tsx b/frontend/src/__tests__/components/molecules/NhsNotifyErrorSummary.test.tsx index 39bc0c916..659917b45 100644 --- a/frontend/src/__tests__/components/molecules/NhsNotifyErrorSummary.test.tsx +++ b/frontend/src/__tests__/components/molecules/NhsNotifyErrorSummary.test.tsx @@ -26,7 +26,10 @@ test('Renders NhsNotifyErrorSummary correctly with errors', async () => { errorState={{ fieldErrors: { 'radios-id': ['Radio error 1', 'Radio error 2'], - 'select-id': ['Select error', ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME], + 'select-id': [ + 'Select error', + ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME, + ], }, formErrors: ['Form error', 'Form error 2'], }} diff --git a/frontend/src/components/molecules/NhsNotifyErrorSummary/NhsNotifyErrorSummary.tsx b/frontend/src/components/molecules/NhsNotifyErrorSummary/NhsNotifyErrorSummary.tsx index 1293c907f..0f528dee9 100644 --- a/frontend/src/components/molecules/NhsNotifyErrorSummary/NhsNotifyErrorSummary.tsx +++ b/frontend/src/components/molecules/NhsNotifyErrorSummary/NhsNotifyErrorSummary.tsx @@ -16,12 +16,17 @@ const UnlinkedErrorSummaryItem: FC> = (props) => ( const errorComponents: Record = { [ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME]: ( <> -
    +
    {templateMessageContainsInvalidPersonalisationErrorText}
      {INVALID_PERSONALISATION_FIELDS.map((item) => ( -
    • {item}
    • +
    • + {item} +
    • ))}
    From 38e7a677bd7527d55045e5e187a5f06686ac4133 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 16 Dec 2025 17:30:32 +0000 Subject: [PATCH 09/13] CCM-13145: Fix tests --- .../email/template-mgmt-create-email-page.component.spec.ts | 2 +- ...template-mgmt-create-nhs-app-template-page.component.spec.ts | 2 +- .../sms/template-mgmt-create-sms-page.component.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts index 88aedc8d7..84ec3e629 100644 --- a/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/email/template-mgmt-create-email-page.component.spec.ts @@ -399,7 +399,7 @@ test.describe('Create Email message template Page', () => { '[href="#emailTemplateMessage"]' ); - await expect(emailMessageErrorLink).toHaveText(errorMessage); + await expect(emailMessageErrorLink).toContainText(errorMessage); await emailMessageErrorLink.click(); diff --git a/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts index 512f9c807..5c08bc480 100644 --- a/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/nhs-app/template-mgmt-create-nhs-app-template-page.component.spec.ts @@ -195,7 +195,7 @@ test.describe('Create NHS App Template Page', () => { await expect( page.locator('ul[class="nhsuk-list nhsuk-error-summary__list"] > li') - ).toHaveText([ + ).toContainText([ 'You cannot use the following custom personalisation fields in your message', ]); }); diff --git a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts index 3c027a3c9..db4cd625e 100644 --- a/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/sms/template-mgmt-create-sms-page.component.spec.ts @@ -360,7 +360,7 @@ test.describe('Create SMS message template Page', () => { '[href="#smsTemplateMessage"]' ); - await expect(smsMessageErrorLink).toHaveText(errorMessage); + await expect(smsMessageErrorLink).toContainText(errorMessage); await smsMessageErrorLink.click(); From 6ec00251b904771e798c2d0456b2e76754f38dba Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Thu, 18 Dec 2025 17:17:06 +0000 Subject: [PATCH 10/13] CCM-13415: Add custom textarea component --- .../EmailTemplateForm.test.tsx | 6 +- .../EmailTemplateForm.test.tsx.snap | 109 +++++++++++++++++- .../NhsAppTemplateForm.test.tsx | 6 +- .../NhsAppTemplateForm.test.tsx.snap | 104 +++++++++++++++++ .../SmsTemplateForm/SmsTemplateForm.test.tsx | 6 +- .../SmsTemplateForm.test.tsx.snap | 104 +++++++++++++++++ .../NHSNotifyTextArea/NHSNotifyTextArea.tsx | 43 +++++++ .../EmailTemplateForm/EmailTemplateForm.tsx | 30 +++-- .../NhsAppTemplateForm/NhsAppTemplateForm.tsx | 31 +++-- .../forms/SmsTemplateForm/SmsTemplateForm.tsx | 33 ++++-- .../NhsNotifyErrorItem/NHSNotifyErrorItem.tsx | 26 +++++ .../NhsNotifyErrorSummary.tsx | 35 +----- 12 files changed, 460 insertions(+), 73 deletions(-) create mode 100644 frontend/src/components/atoms/NHSNotifyTextArea/NHSNotifyTextArea.tsx create mode 100644 frontend/src/components/molecules/NhsNotifyErrorItem/NHSNotifyErrorItem.tsx diff --git a/frontend/src/__tests__/components/forms/EmailTemplateForm/EmailTemplateForm.test.tsx b/frontend/src/__tests__/components/forms/EmailTemplateForm/EmailTemplateForm.test.tsx index be34cfb2a..88e1e30c9 100644 --- a/frontend/src/__tests__/components/forms/EmailTemplateForm/EmailTemplateForm.test.tsx +++ b/frontend/src/__tests__/components/forms/EmailTemplateForm/EmailTemplateForm.test.tsx @@ -5,6 +5,7 @@ import { EmailTemplate, } from 'nhs-notify-web-template-management-utils'; import { EmailTemplateForm } from '@forms/EmailTemplateForm/EmailTemplateForm'; +import { ErrorCodes } from '@utils/error-codes'; jest.mock('@utils/amplify-utils'); @@ -80,7 +81,10 @@ test('renders page with multiple errors', () => { fieldErrors: { emailTemplateName: ['Template name error'], emailTemplateSubjectLine: ['Template subject line error'], - emailTemplateMessage: ['Template message error'], + emailTemplateMessage: [ + 'Template message error', + ErrorCodes.MESSAGE_CONTAINS_INVALID_PERSONALISATION_FIELD_NAME, + ], }, }, name: '', diff --git a/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap b/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap index ca7941f83..255c5d3e5 100644 --- a/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/EmailTemplateForm/__snapshots__/EmailTemplateForm.test.tsx.snap @@ -150,7 +150,6 @@ exports[`Client-side validation triggers 1`] = `