diff --git a/.tool-versions b/.tool-versions index 58b6eff99..ce2dbc854 100644 --- a/.tool-versions +++ b/.tool-versions @@ -8,10 +8,8 @@ terraform-docs 0.19.0 trivy 0.69.2 vale 3.6.0 python 3.13.2 - # ============================================================================== # The section below is reserved for Docker image versions. - # TODO: Move this section - consider using a different file for the repository template dependencies. # docker/ghcr.io/anchore/grype v0.104.3@sha256:d340f4f8b3b7e6e72a6c9c0152f25402ed8a2d7375dba1dfce4e53115242feb6 # SEE: https://github.com/anchore/grype/pkgs/container/grype # docker/ghcr.io/anchore/syft v1.39.0@sha256:6f13bb010923c33fb197047c8f88888e77071bd32596b3f605d62a133e493ce4 # SEE: https://github.com/anchore/syft/pkgs/container/syft diff --git a/frontend/src/__tests__/app/choose-a-template-type/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/choose-a-template-type/__snapshots__/page.test.tsx.snap index a03a6e9f5..8a03fda0f 100644 --- a/frontend/src/__tests__/app/choose-a-template-type/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/choose-a-template-type/__snapshots__/page.test.tsx.snap @@ -128,29 +128,40 @@ exports[`ChooseATemplateTypePage 1`] = ` Text message (SMS) -
- - -
+
+
+

+ To create a letter template +

+

+ You cannot upload a letter template using this service. +

+

+ Follow our guidance to + + upload a letter template (opens in a new tab) + +

+
+
+ + +`; + +exports[`Renders ChooseTemplateTypeRadios - renders without validation error field 1`] = ` + +
+ + +
+
+ +

+ Page heading +

+
+
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+`; + +exports[`Renders ChooseTemplateTypeRadios correctly with errors 1`] = ` + +
+ + +
+
+ +

+ Page heading +

+
+
+ + + Error: + + Field error + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+`; + +exports[`Renders ChooseTemplateTypeRadios correctly without errors 1`] = ` + +
+ + +
+
+ +

+ Page heading +

+
+
+
+ Example hint +
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+`; + +exports[`Renders ChooseTemplateTypeRadios with conditional content for a radio option 1`] = ` + +
+ + +
+
+ +

+ Page heading +

+
+
+
+
+ + +
+
+ + +
+
+
+

+ This is conditional content +

+
+
+
+
+
+
+ +
+
+`; + +exports[`Renders ChooseTemplateTypeRadios with conditional radios and validation error on nested field 1`] = ` + +
+ + +
+
+ +

+ Page heading +

+
+
+
+
+ + +
+
+ + +
+
+
+ + + Error: + + Please select a nested option + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ +
+
+`; + +exports[`Renders ChooseTemplateTypeRadios with learn more link 1`] = ` + +
+ + +
+
+ +

+ Page heading +

+
+

+ + learn more + +

+
+
+ Example hint +
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+`; + +exports[`Renders ChooseTemplateTypeRadios with nested conditional radio buttons 1`] = ` + +
+ + +
+
+ +

+ Page heading +

+
+
+
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ +
+
+`; + +exports[`Renders ChooseTemplateTypeRadios without learn more link if learnMoreLink is not provided 1`] = ` + +
+ + +
+
+ +

+ Page heading +

+
+
+
+ Example hint +
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+`; + +exports[`Renders ChooseTemplateTypeRadios without learn more link if learnMoreText is not provided 1`] = ` + +
+ + +
+
+ +

+ Page heading +

+
+
+
+ Example hint +
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+`; diff --git a/frontend/src/__tests__/components/organisms/PreviewDigitalTemplate.test.tsx b/frontend/src/__tests__/components/organisms/PreviewDigitalTemplate.test.tsx index 086f21c27..cf531be4b 100644 --- a/frontend/src/__tests__/components/organisms/PreviewDigitalTemplate.test.tsx +++ b/frontend/src/__tests__/components/organisms/PreviewDigitalTemplate.test.tsx @@ -88,9 +88,11 @@ const renderPreviewTemplate = ( }; describe('PreviewDigitalTemplate', () => { - describe('Routing disabled', () => { + describe('Routing disabled with letter authoring enabled', () => { beforeEach(() => { - jest.mocked(useFeatureFlags).mockReturnValue({ routing: false }); + jest + .mocked(useFeatureFlags) + .mockReturnValue({ routing: false, letterAuthoring: true }); }); it('matches snapshot', () => { @@ -149,9 +151,11 @@ describe('PreviewDigitalTemplate', () => { }); }); - describe('Routing enabled', () => { + describe('Routing enabled with letter authoring enabled', () => { beforeEach(() => { - jest.mocked(useFeatureFlags).mockReturnValue({ routing: true }); + jest + .mocked(useFeatureFlags) + .mockReturnValue({ routing: true, letterAuthoring: true }); }); it('matches snapshot', () => { diff --git a/frontend/src/__tests__/components/organisms/__snapshots__/PreviewDigitalTemplate.test.tsx.snap b/frontend/src/__tests__/components/organisms/__snapshots__/PreviewDigitalTemplate.test.tsx.snap index 755130d31..90958eda2 100644 --- a/frontend/src/__tests__/components/organisms/__snapshots__/PreviewDigitalTemplate.test.tsx.snap +++ b/frontend/src/__tests__/components/organisms/__snapshots__/PreviewDigitalTemplate.test.tsx.snap @@ -147,7 +147,7 @@ exports[`PreviewDigitalTemplate Digital proofing when digitalProofingSms is enab `; -exports[`PreviewDigitalTemplate Routing disabled matches error snapshot 1`] = ` +exports[`PreviewDigitalTemplate Routing disabled with letter authoring enabled matches error snapshot 1`] = `
`; -exports[`PreviewDigitalTemplate Routing disabled matches snapshot 1`] = ` +exports[`PreviewDigitalTemplate Routing disabled with letter authoring enabled matches snapshot 1`] = `
`; -exports[`PreviewDigitalTemplate Routing enabled matches snapshot 1`] = ` +exports[`PreviewDigitalTemplate Routing enabled with letter authoring enabled matches snapshot 1`] = `
type !== 'LETTER'); + const $ChooseTemplateType = features.letterAuthoring ? $ChooseTemplateTypeWithLetterAuthoring : $ChooseTemplateTypeBase; const formValidate = validate($ChooseTemplateType, setErrorState); - const content = copy.components.chooseTemplateType; - const letterTypes = ( ); - const templateTypeOptions = templateTypes.map((templateType) => ({ + const templateTypeOptions = filteredTemplateTypes.map((templateType) => ({ id: templateType, text: content.templateTypes[templateType], conditional: @@ -81,7 +86,8 @@ export const ChooseTemplateType = ({ - + > + {features.letterAuthoring ? null : ( +
+
+

+ {content.warningCalloutContent.headingLabel} +

+

+ {content.warningCalloutContent.firstParagraph} +

+

+ +

+
+
+ )} +
); diff --git a/frontend/src/components/molecules/ChooseTemplateTypeRadios/ChooseTemplateTypeRadios.tsx b/frontend/src/components/molecules/ChooseTemplateTypeRadios/ChooseTemplateTypeRadios.tsx new file mode 100644 index 000000000..522ebdbef --- /dev/null +++ b/frontend/src/components/molecules/ChooseTemplateTypeRadios/ChooseTemplateTypeRadios.tsx @@ -0,0 +1,117 @@ +import { Radios, Fieldset } from 'nhsuk-react-components'; +import { FormState } from 'nhs-notify-web-template-management-utils'; +import { NHSNotifyFormWrapper } from '@molecules/NHSNotifyFormWrapper/NHSNotifyFormWrapper'; +import { NHSNotifyButton } from '@atoms/NHSNotifyButton/NHSNotifyButton'; +import { DetailedHTMLProps, FormHTMLAttributes, ReactNode } from 'react'; +import Link from 'next/link'; + +export type NHSNotifyRadioButtonFormProps = { + formId: string; + radiosId: string; + action: string | ((payload: FormData) => void); + state: FormState; + pageHeading: string; + options: { + id: string; + text: string; + checked?: boolean; + conditional?: ReactNode; + }[]; + buttonText: string; + hint?: string; + legend?: { + isPgeHeading: boolean; + size: 'l' | 'm' | 's'; + }; + learnMoreLink?: string; + learnMoreText?: string; + formAttributes?: DetailedHTMLProps< + FormHTMLAttributes, + HTMLFormElement + >; + backLink?: { + text: string; + url: string; + }; + children?: React.ReactNode; +}; + +const normaliseId = (id: string) => + id.toLowerCase().replaceAll('_', '').replaceAll(',', '-'); + +export const ChooseTemplateTypeRadios = ({ + formId, + radiosId, + action, + state, + pageHeading, + options, + buttonText, + formAttributes, + legend = { isPgeHeading: true, size: 'l' }, + hint = '', + learnMoreLink = '', + learnMoreText = '', + backLink, + children, +}: NHSNotifyRadioButtonFormProps) => ( + +
+ + {pageHeading} + + {learnMoreLink && learnMoreText && ( +

+ + {learnMoreText} + +

+ )} + + {options.map(({ id, text, checked, conditional }) => ( + + {text} + + ))} + +
+ {/* Render any passed children here */} + {children} + + {buttonText} + + {backLink && ( + + {backLink.text} + + )} +
+); diff --git a/frontend/src/components/molecules/NHSNotifyRadioButtonForm/NHSNotifyRadioButtonForm.tsx b/frontend/src/components/molecules/NHSNotifyRadioButtonForm/NHSNotifyRadioButtonForm.tsx index c01bc244b..e8039028f 100644 --- a/frontend/src/components/molecules/NHSNotifyRadioButtonForm/NHSNotifyRadioButtonForm.tsx +++ b/frontend/src/components/molecules/NHSNotifyRadioButtonForm/NHSNotifyRadioButtonForm.tsx @@ -33,6 +33,7 @@ export type NHSNotifyRadioButtonFormProps = { text: string; url: string; }; + isLetterAuthoringEnabled?: boolean; }; const normaliseId = (id: string) => diff --git a/frontend/src/components/organisms/PreviewDigitalTemplate/PreviewDigitalTemplate.tsx b/frontend/src/components/organisms/PreviewDigitalTemplate/PreviewDigitalTemplate.tsx index 3e8f56106..19a00cf16 100644 --- a/frontend/src/components/organisms/PreviewDigitalTemplate/PreviewDigitalTemplate.tsx +++ b/frontend/src/components/organisms/PreviewDigitalTemplate/PreviewDigitalTemplate.tsx @@ -102,6 +102,7 @@ export function PreviewDigitalTemplate(props: PreviewTemplateProps) { isPgeHeading: false, size: 'm', }} + isLetterAuthoringEnabled={features.letterAuthoring} /> )} diff --git a/frontend/src/content/content.ts b/frontend/src/content/content.ts index 35c96a33e..e2f9e72c1 100644 --- a/frontend/src/content/content.ts +++ b/frontend/src/content/content.ts @@ -844,6 +844,12 @@ const chooseTemplateType = { x1: 'Large print letter', language: 'Other language letter', }, + warningCalloutContent: { + headingLabel: 'To create a letter template', + firstParagraph: 'You cannot upload a letter template using this service.', + secondParagraph: + 'Follow our guidance to [upload a letter template (opens in a new tab)](https://notify.nhs.uk/using-nhs-notify/upload-a-letter)', + }, }; const templateNameGuidance = (type?: TemplateType) => { diff --git a/tests/test-team/template-mgmt-component-tests/template-mgmt-choose-page.component.spec.ts b/tests/test-team/template-mgmt-component-tests/template-mgmt-choose-page.component.spec.ts index 93b9acda5..9e1501627 100644 --- a/tests/test-team/template-mgmt-component-tests/template-mgmt-choose-page.component.spec.ts +++ b/tests/test-team/template-mgmt-component-tests/template-mgmt-choose-page.component.spec.ts @@ -61,14 +61,13 @@ test.describe('Choose Template Type Page', () => { await chooseTemplateTypePage.loadPage(); await expect(chooseTemplateTypePage.templateTypeRadioButtons).toHaveCount( - 4 + 3 ); for (const [templateType, label] of [ ['nhsapp', 'NHS App message'], ['email', 'Email'], ['sms', 'Text message (SMS)'], - ['letter', 'Letter'], ] as const) { const radio = chooseTemplateTypePage.getTemplateTypeRadio(templateType); await expect(radio).toBeVisible(); @@ -114,17 +113,6 @@ test.describe('Choose Template Type Page', () => { await page.waitForURL(`${baseURL}/templates/create-${path}-template`); }); - - test('should not show letter type conditional radios when Letter is selected', async ({ - page, - }) => { - const chooseTemplateTypePage = new TemplateMgmtChoosePage(page); - - await chooseTemplateTypePage.loadPage(); - await chooseTemplateTypePage.getTemplateTypeRadio('letter').check(); - - await expect(chooseTemplateTypePage.letterTypeRadioButtons).toHaveCount(0); - }); }); test.describe('Choose Template Type Page - Letter Authoring Enabled', () => { @@ -142,6 +130,29 @@ test.describe('Choose Template Type Page - Letter Authoring Enabled', () => { await loginAsUser(userLetterAuthoringEnabled, page); }); + test('should display correct number of radio button options for template type', async ({ + page, + }) => { + const chooseTemplateTypePage = new TemplateMgmtChoosePage(page); + + await chooseTemplateTypePage.loadPage(); + + await expect(chooseTemplateTypePage.templateTypeRadioButtons).toHaveCount( + 4 + ); + + for (const [templateType, label] of [ + ['nhsapp', 'NHS App message'], + ['email', 'Email'], + ['sms', 'Text message (SMS)'], + ['letter', 'Letter'], + ] as const) { + const radio = chooseTemplateTypePage.getTemplateTypeRadio(templateType); + await expect(radio).toBeVisible(); + await expect(radio).toHaveAccessibleName(label); + } + }); + test('should only show letter type conditional radios when Letter template type is selected', async ({ page, }) => {