diff --git a/frontend/public/lib/assets/icons/icon-arrow-down.svg b/frontend/public/lib/assets/icons/icon-arrow-down.svg index 40ab19280..36c451025 100644 --- a/frontend/public/lib/assets/icons/icon-arrow-down.svg +++ b/frontend/public/lib/assets/icons/icon-arrow-down.svg @@ -8,7 +8,7 @@ > diff --git a/frontend/public/lib/assets/icons/icon-arrow-left.svg b/frontend/public/lib/assets/icons/icon-arrow-left.svg index d370ebe25..152a954f1 100644 --- a/frontend/public/lib/assets/icons/icon-arrow-left.svg +++ b/frontend/public/lib/assets/icons/icon-arrow-left.svg @@ -1,6 +1,6 @@ diff --git a/frontend/public/lib/assets/icons/icon-arrow-right.svg b/frontend/public/lib/assets/icons/icon-arrow-right.svg index ee4cd5a6c..e5ef36502 100644 --- a/frontend/public/lib/assets/icons/icon-arrow-right.svg +++ b/frontend/public/lib/assets/icons/icon-arrow-right.svg @@ -1,6 +1,6 @@ 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 41a90f3c8..11086d6c4 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 @@ -4,7 +4,7 @@ exports[`ChooseATemplateTypePage 1`] = ` Back to all templates diff --git a/frontend/src/__tests__/app/choose-templates/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/choose-templates/__snapshots__/page.test.tsx.snap index 13a44b25e..a5e6214a0 100644 --- a/frontend/src/__tests__/app/choose-templates/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/choose-templates/__snapshots__/page.test.tsx.snap @@ -45,7 +45,8 @@ exports[`ChooseTemplatesPage renders correctly for a message plan with multiple Routing Plan ID
fbb81055-79b9-4759-ac07-d191ae57be34
@@ -89,6 +90,7 @@ exports[`ChooseTemplatesPage renders correctly for a message plan with multiple
NHS App -

- app template name -

+

+ app template name +

+
diff --git a/frontend/src/__tests__/app/message-plans/choose-email-template/preview-template/page.test.tsx b/frontend/src/__tests__/app/message-plans/choose-email-template/preview-template/page.test.tsx index ad4007065..6226b6feb 100644 --- a/frontend/src/__tests__/app/message-plans/choose-email-template/preview-template/page.test.tsx +++ b/frontend/src/__tests__/app/message-plans/choose-email-template/preview-template/page.test.tsx @@ -13,6 +13,38 @@ const getTemplateMock = jest.mocked(getTemplate); const redirectMock = jest.mocked(redirect); describe('PreviewEmailTemplateFromMessagePlan page', () => { + it('should redirect to choose-templates when lockNumber is invalid', async () => { + await PreviewEmailTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({ + lockNumber: 'invalid', + }), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + + it('should redirect to choose-templates when lockNumber is missing', async () => { + await PreviewEmailTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({}), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + it('should redirect to invalid page with invalid template id', async () => { getTemplateMock.mockResolvedValueOnce(undefined); @@ -21,6 +53,9 @@ describe('PreviewEmailTemplateFromMessagePlan page', () => { routingConfigId: 'routing-config-id', templateId: 'invalid-template-id', }), + searchParams: Promise.resolve({ + lockNumber: '0', + }), }); expect(getTemplateMock).toHaveBeenCalledWith('invalid-template-id'); @@ -39,6 +74,9 @@ describe('PreviewEmailTemplateFromMessagePlan page', () => { routingConfigId: ROUTING_CONFIG.id, templateId: EMAIL_TEMPLATE.id, }), + searchParams: Promise.resolve({ + lockNumber: '5', + }), }); const container = render(page); diff --git a/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/__snapshots__/page.test.tsx.snap new file mode 100644 index 000000000..fc53d5315 --- /dev/null +++ b/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/__snapshots__/page.test.tsx.snap @@ -0,0 +1,244 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ChooseLargePrintLetterTemplate page renders large print letter template selection 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose a large print letter template +

+
+ + + + +
+
+
+ Choose one option +
+
+
+ + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + large print letter template name + + + Large print letter + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+ + + Go back + +
+ +
+
+
+
+`; diff --git a/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/page.test.tsx b/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/page.test.tsx new file mode 100644 index 000000000..a15c42805 --- /dev/null +++ b/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/page.test.tsx @@ -0,0 +1,135 @@ +import ChooseLargePrintLetterTemplate, { + generateMetadata, +} from '@app/message-plans/choose-large-print-letter-template/[routingConfigId]/page'; +import { + LARGE_PRINT_LETTER_TEMPLATE, + ROUTING_CONFIG, +} from '@testhelpers/helpers'; +import { render } from '@testing-library/react'; +import { getTemplates } from '@utils/form-actions'; +import { getRoutingConfig } from '@utils/message-plans'; +import { redirect } from 'next/navigation'; + +jest.mock('@utils/message-plans'); +jest.mock('@utils/form-actions'); +jest.mock('next/navigation'); + +const getRoutingConfigMock = jest.mocked(getRoutingConfig); +const getTemplatesMock = jest.mocked(getTemplates); +const redirectMock = jest.mocked(redirect); + +describe('ChooseLargePrintLetterTemplate page', () => { + it('should redirect to invalid page for invalid routing config id', async () => { + getRoutingConfigMock.mockResolvedValueOnce(undefined); + getTemplatesMock.mockResolvedValueOnce([LARGE_PRINT_LETTER_TEMPLATE]); + + await ChooseLargePrintLetterTemplate({ + params: Promise.resolve({ + routingConfigId: 'invalid-id', + }), + searchParams: Promise.resolve({ + lockNumber: '42', + }), + }); + + expect(getRoutingConfigMock).toHaveBeenCalledWith('invalid-id'); + expect(getTemplatesMock).toHaveBeenCalledWith({ + templateType: 'LETTER', + language: 'en', + letterType: 'x1', + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/invalid', + 'replace' + ); + }); + + it('should redirect to invalid page if plan has no letter cascade entry', async () => { + getRoutingConfigMock.mockResolvedValueOnce({ + ...ROUTING_CONFIG, + cascade: ROUTING_CONFIG.cascade.filter( + (item) => item.channel !== 'LETTER' + ), + }); + getTemplatesMock.mockResolvedValueOnce([LARGE_PRINT_LETTER_TEMPLATE]); + + await ChooseLargePrintLetterTemplate({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + }), + searchParams: Promise.resolve({ + lockNumber: '42', + }), + }); + + expect(getRoutingConfigMock).toHaveBeenCalledWith(ROUTING_CONFIG.id); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/invalid', + 'replace' + ); + }); + + it('fetches routing config and templates in parallel', async () => { + getRoutingConfigMock.mockResolvedValueOnce(ROUTING_CONFIG); + getTemplatesMock.mockResolvedValueOnce([LARGE_PRINT_LETTER_TEMPLATE]); + + await ChooseLargePrintLetterTemplate({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + }), + searchParams: Promise.resolve({ + lockNumber: '42', + }), + }); + + expect(getRoutingConfigMock).toHaveBeenCalledWith(ROUTING_CONFIG.id); + expect(getTemplatesMock).toHaveBeenCalledWith({ + templateType: 'LETTER', + language: 'en', + letterType: 'x1', + }); + }); + + it('renders large print letter template selection', async () => { + getRoutingConfigMock.mockResolvedValueOnce(ROUTING_CONFIG); + getTemplatesMock.mockResolvedValueOnce([LARGE_PRINT_LETTER_TEMPLATE]); + + const page = await ChooseLargePrintLetterTemplate({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + }), + searchParams: Promise.resolve({ + lockNumber: '42', + }), + }); + + const container = render(page); + + expect(getRoutingConfigMock).toHaveBeenCalledWith(ROUTING_CONFIG.id); + expect(getTemplatesMock).toHaveBeenCalledWith({ + templateType: 'LETTER', + language: 'en', + letterType: 'x1', + }); + + expect(await generateMetadata()).toEqual({ + title: 'Choose a large print letter template - NHS Notify', + }); + expect(container.asFragment()).toMatchSnapshot(); + }); + + it('redirects to choose templates page if the lockNumber is missing', async () => { + await ChooseLargePrintLetterTemplate({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + }), + }); + + expect(redirectMock).toHaveBeenCalledWith( + `/message-plans/choose-templates/${ROUTING_CONFIG.id}`, + 'replace' + ); + }); +}); diff --git a/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/preview-template/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/preview-template/__snapshots__/page.test.tsx.snap new file mode 100644 index 000000000..23b81f913 --- /dev/null +++ b/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/preview-template/__snapshots__/page.test.tsx.snap @@ -0,0 +1,122 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PreviewLargePrintLetterTemplateFromMessagePlan page renders large print letter template preview 1`] = ` + + + Go back + +
+
+
+
+ + Template + +

+ large print letter template name +

+
+
+
+
+
+ Template ID +
+
+ large-print-letter-template-id +
+
+
+
+ Type +
+
+ Large print letter +
+
+
+
+ Template file +
+
+
+ +

+ large-print-template.pdf +

+
+
+
+
+
+ + Go back + +
+
+
+
+`; diff --git a/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/preview-template/page.test.tsx b/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/preview-template/page.test.tsx new file mode 100644 index 000000000..7a768be20 --- /dev/null +++ b/frontend/src/__tests__/app/message-plans/choose-large-print-letter-template/preview-template/page.test.tsx @@ -0,0 +1,96 @@ +import PreviewLargePrintLetterTemplateFromMessagePlan, { + generateMetadata, +} from '@app/message-plans/choose-large-print-letter-template/[routingConfigId]/preview-template/[templateId]/page'; +import { + LARGE_PRINT_LETTER_TEMPLATE, + ROUTING_CONFIG, +} from '@testhelpers/helpers'; +import { render } from '@testing-library/react'; +import { getTemplate } from '@utils/form-actions'; +import { redirect } from 'next/navigation'; + +jest.mock('@utils/form-actions'); +jest.mock('next/navigation'); + +const getTemplateMock = jest.mocked(getTemplate); +const redirectMock = jest.mocked(redirect); + +describe('PreviewLargePrintLetterTemplateFromMessagePlan page', () => { + it('should redirect to choose-templates when lockNumber is invalid', async () => { + await PreviewLargePrintLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({ + lockNumber: 'invalid', + }), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + + it('should redirect to choose-templates when lockNumber is missing', async () => { + await PreviewLargePrintLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({}), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + + it('should redirect to invalid page for invalid template id', async () => { + getTemplateMock.mockResolvedValueOnce(undefined); + + await PreviewLargePrintLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'invalid-template-id', + }), + searchParams: Promise.resolve({ + lockNumber: '0', + }), + }); + + expect(getTemplateMock).toHaveBeenCalledWith('invalid-template-id'); + + expect(redirectMock).toHaveBeenCalledWith('/invalid-template', 'replace'); + }); + + it('renders large print letter template preview', async () => { + getTemplateMock.mockResolvedValueOnce({ + ...LARGE_PRINT_LETTER_TEMPLATE, + templateStatus: 'SUBMITTED', + }); + + const page = await PreviewLargePrintLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + templateId: LARGE_PRINT_LETTER_TEMPLATE.id, + }), + searchParams: Promise.resolve({ + lockNumber: '5', + }), + }); + + const container = render(page); + + expect(getTemplateMock).toHaveBeenCalledWith( + LARGE_PRINT_LETTER_TEMPLATE.id + ); + + expect(await generateMetadata()).toEqual({ + title: 'Preview large print letter template - NHS Notify', + }); + expect(container.asFragment()).toMatchSnapshot(); + }); +}); diff --git a/frontend/src/__tests__/app/message-plans/choose-message-order/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-message-order/__snapshots__/page.test.tsx.snap index 74721dac9..26f886e7b 100644 --- a/frontend/src/__tests__/app/message-plans/choose-message-order/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/message-plans/choose-message-order/__snapshots__/page.test.tsx.snap @@ -219,6 +219,7 @@ exports[`ChooseMessageOrderPage 1`] = ` Go back diff --git a/frontend/src/__tests__/app/message-plans/choose-nhs-app-template/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-nhs-app-template/__snapshots__/page.test.tsx.snap index c8548fc54..7b946541f 100644 --- a/frontend/src/__tests__/app/message-plans/choose-nhs-app-template/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/message-plans/choose-nhs-app-template/__snapshots__/page.test.tsx.snap @@ -13,16 +13,20 @@ exports[`ChooseNHSAppTemplate page renders NHS App template selection 1`] = `
- - Autumn Campaign Plan - -

- Choose an NHS App template -

+ + Autumn Campaign Plan + +

+ Choose an NHS App template +

+
@@ -46,7 +50,7 @@ exports[`ChooseNHSAppTemplate page renders NHS App template selection 1`] = ` />
-

- - Go back - -

+ + Go back + diff --git a/frontend/src/__tests__/app/message-plans/choose-nhs-app-template/preview-template/page.test.tsx b/frontend/src/__tests__/app/message-plans/choose-nhs-app-template/preview-template/page.test.tsx index c88a8b0a8..0d23d96f0 100644 --- a/frontend/src/__tests__/app/message-plans/choose-nhs-app-template/preview-template/page.test.tsx +++ b/frontend/src/__tests__/app/message-plans/choose-nhs-app-template/preview-template/page.test.tsx @@ -13,6 +13,38 @@ const getTemplateMock = jest.mocked(getTemplate); const redirectMock = jest.mocked(redirect); describe('PreviewNhsAppTemplateFromMessagePlan page', () => { + it('should redirect to choose-templates when lockNumber is invalid', async () => { + await PreviewNhsAppTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({ + lockNumber: 'invalid', + }), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + + it('should redirect to choose-templates when lockNumber is missing', async () => { + await PreviewNhsAppTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({}), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + it('should redirect to invalid page with invalid template id', async () => { getTemplateMock.mockResolvedValueOnce(undefined); @@ -21,6 +53,9 @@ describe('PreviewNhsAppTemplateFromMessagePlan page', () => { routingConfigId: 'routing-config-id', templateId: 'invalid-template-id', }), + searchParams: Promise.resolve({ + lockNumber: '0', + }), }); expect(getTemplateMock).toHaveBeenCalledWith('invalid-template-id'); @@ -39,6 +74,9 @@ describe('PreviewNhsAppTemplateFromMessagePlan page', () => { routingConfigId: ROUTING_CONFIG.id, templateId: NHS_APP_TEMPLATE.id, }), + searchParams: Promise.resolve({ + lockNumber: '5', + }), }); const container = render(page); diff --git a/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/__snapshots__/page.test.tsx.snap new file mode 100644 index 000000000..2860ada61 --- /dev/null +++ b/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/__snapshots__/page.test.tsx.snap @@ -0,0 +1,348 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ChooseOtherLanguageLetterTemplate page renders foreign language letter template selection 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose other language letter templates +

+
+ + + + +
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+ + + Go back + +
+ +
+
+
+
+`; diff --git a/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/page.test.tsx b/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/page.test.tsx new file mode 100644 index 000000000..9876013bb --- /dev/null +++ b/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/page.test.tsx @@ -0,0 +1,149 @@ +import ChooseOtherLanguageLetterTemplate, { + generateMetadata, +} from '@app/message-plans/choose-other-language-letter-template/[routingConfigId]/page'; +import { LETTER_TEMPLATE, ROUTING_CONFIG } from '@testhelpers/helpers'; +import { render } from '@testing-library/react'; +import { getForeignLanguageLetterTemplates } from '@utils/form-actions'; +import { getRoutingConfig } from '@utils/message-plans'; +import { redirect } from 'next/navigation'; +import { Language } from 'nhs-notify-backend-client'; + +jest.mock('@utils/message-plans'); +jest.mock('@utils/form-actions'); +jest.mock('next/navigation'); + +const getRoutingConfigMock = jest.mocked(getRoutingConfig); +const getForeignLanguageLetterTemplatesMock = jest.mocked( + getForeignLanguageLetterTemplates +); +const redirectMock = jest.mocked(redirect); + +const FRENCH_LETTER_TEMPLATE = { + ...LETTER_TEMPLATE, + id: 'french-letter-id', + name: 'French letter template', + language: 'fr' as Language, +}; + +const POLISH_LETTER_TEMPLATE = { + ...LETTER_TEMPLATE, + id: 'polish-letter-id', + name: 'Polish letter template', + language: 'pl' as Language, +}; + +describe('ChooseOtherLanguageLetterTemplate page', () => { + it('should redirect to invalid page for invalid routing config id', async () => { + getRoutingConfigMock.mockResolvedValueOnce(undefined); + getForeignLanguageLetterTemplatesMock.mockResolvedValueOnce([ + FRENCH_LETTER_TEMPLATE, + POLISH_LETTER_TEMPLATE, + ]); + + await ChooseOtherLanguageLetterTemplate({ + params: Promise.resolve({ + routingConfigId: 'invalid-id', + }), + searchParams: Promise.resolve({ + lockNumber: '42', + }), + }); + + expect(getRoutingConfigMock).toHaveBeenCalledWith('invalid-id'); + expect(getForeignLanguageLetterTemplatesMock).toHaveBeenCalled(); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/invalid', + 'replace' + ); + }); + + it('should redirect to invalid page if plan has no letter cascade entry', async () => { + getRoutingConfigMock.mockResolvedValueOnce({ + ...ROUTING_CONFIG, + cascade: ROUTING_CONFIG.cascade.filter( + (item) => item.channel !== 'LETTER' + ), + }); + getForeignLanguageLetterTemplatesMock.mockResolvedValueOnce([ + FRENCH_LETTER_TEMPLATE, + POLISH_LETTER_TEMPLATE, + ]); + + await ChooseOtherLanguageLetterTemplate({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + }), + searchParams: Promise.resolve({ + lockNumber: '42', + }), + }); + + expect(getRoutingConfigMock).toHaveBeenCalledWith(ROUTING_CONFIG.id); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/invalid', + 'replace' + ); + }); + + it('fetches routing config and templates in parallel', async () => { + getRoutingConfigMock.mockResolvedValueOnce(ROUTING_CONFIG); + getForeignLanguageLetterTemplatesMock.mockResolvedValueOnce([ + FRENCH_LETTER_TEMPLATE, + POLISH_LETTER_TEMPLATE, + ]); + + await ChooseOtherLanguageLetterTemplate({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + }), + searchParams: Promise.resolve({ + lockNumber: '42', + }), + }); + + expect(getRoutingConfigMock).toHaveBeenCalledWith(ROUTING_CONFIG.id); + expect(getForeignLanguageLetterTemplatesMock).toHaveBeenCalled(); + }); + + it('renders foreign language letter template selection', async () => { + getRoutingConfigMock.mockResolvedValueOnce(ROUTING_CONFIG); + getForeignLanguageLetterTemplatesMock.mockResolvedValueOnce([ + FRENCH_LETTER_TEMPLATE, + POLISH_LETTER_TEMPLATE, + ]); + + const page = await ChooseOtherLanguageLetterTemplate({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + }), + searchParams: Promise.resolve({ + lockNumber: '42', + }), + }); + + const container = render(page); + + expect(getRoutingConfigMock).toHaveBeenCalledWith(ROUTING_CONFIG.id); + expect(getForeignLanguageLetterTemplatesMock).toHaveBeenCalled(); + + expect(await generateMetadata()).toEqual({ + title: 'Choose other language letter templates - NHS Notify', + }); + expect(container.asFragment()).toMatchSnapshot(); + }); + + it('redirects to choose templates page if the lockNumber is missing', async () => { + await ChooseOtherLanguageLetterTemplate({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + }), + }); + + expect(redirectMock).toHaveBeenCalledWith( + `/message-plans/choose-templates/${ROUTING_CONFIG.id}`, + 'replace' + ); + }); +}); diff --git a/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/preview-template/[templateId]/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/preview-template/[templateId]/__snapshots__/page.test.tsx.snap new file mode 100644 index 000000000..341aa90c9 --- /dev/null +++ b/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/preview-template/[templateId]/__snapshots__/page.test.tsx.snap @@ -0,0 +1,122 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PreviewOtherLanguageLetterTemplateFromMessagePlan page renders foreign language letter template preview 1`] = ` + + + Go back + +
+
+
+
+ + Template + +

+ French letter template +

+
+
+
+
+
+ Template ID +
+
+ french-letter-id +
+
+
+
+ Type +
+
+ Standard letter - French +
+
+
+
+ Template file +
+
+
+ +

+ template.pdf +

+
+
+
+
+
+ + Go back + +
+
+
+
+`; diff --git a/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/preview-template/[templateId]/page.test.tsx b/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/preview-template/[templateId]/page.test.tsx new file mode 100644 index 000000000..ff2c6ce1f --- /dev/null +++ b/frontend/src/__tests__/app/message-plans/choose-other-language-letter-template/[routingConfigId]/preview-template/[templateId]/page.test.tsx @@ -0,0 +1,99 @@ +import PreviewOtherLanguageLetterTemplateFromMessagePlan, { + generateMetadata, +} from '@app/message-plans/choose-other-language-letter-template/[routingConfigId]/preview-template/[templateId]/page'; +import { LETTER_TEMPLATE, ROUTING_CONFIG } from '@testhelpers/helpers'; +import { render } from '@testing-library/react'; +import { getTemplate } from '@utils/form-actions'; +import { redirect } from 'next/navigation'; +import { Language } from 'nhs-notify-backend-client'; + +jest.mock('@utils/form-actions'); +jest.mock('next/navigation'); + +const getTemplateMock = jest.mocked(getTemplate); +const redirectMock = jest.mocked(redirect); + +const FRENCH_LETTER_TEMPLATE = { + ...LETTER_TEMPLATE, + id: 'french-letter-id', + name: 'French letter template', + language: 'fr' as Language, +}; + +describe('PreviewOtherLanguageLetterTemplateFromMessagePlan page', () => { + it('should redirect to choose-templates when lockNumber is invalid', async () => { + await PreviewOtherLanguageLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({ + lockNumber: 'invalid', + }), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + + it('should redirect to choose-templates when lockNumber is missing', async () => { + await PreviewOtherLanguageLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({}), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + + it('should redirect to invalid page with invalid template id', async () => { + getTemplateMock.mockResolvedValueOnce(undefined); + + await PreviewOtherLanguageLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'invalid-template-id', + }), + searchParams: Promise.resolve({ + lockNumber: '0', + }), + }); + + expect(getTemplateMock).toHaveBeenCalledWith('invalid-template-id'); + + expect(redirectMock).toHaveBeenCalledWith('/invalid-template', 'replace'); + }); + + it('renders foreign language letter template preview', async () => { + getTemplateMock.mockResolvedValueOnce({ + ...FRENCH_LETTER_TEMPLATE, + templateStatus: 'SUBMITTED', + }); + + const page = await PreviewOtherLanguageLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: ROUTING_CONFIG.id, + templateId: FRENCH_LETTER_TEMPLATE.id, + }), + searchParams: Promise.resolve({ + lockNumber: '5', + }), + }); + + const container = render(page); + + expect(getTemplateMock).toHaveBeenCalledWith(FRENCH_LETTER_TEMPLATE.id); + + expect(await generateMetadata()).toEqual({ + title: 'Preview other language letter template - NHS Notify', + }); + expect(container.asFragment()).toMatchSnapshot(); + }); +}); diff --git a/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/__snapshots__/page.test.tsx.snap index a3ace34e5..06f5ecf8c 100644 --- a/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/__snapshots__/page.test.tsx.snap @@ -13,16 +13,20 @@ exports[`ChooseStandardEnglishLetterTemplate page renders letter template select
- - Autumn Campaign Plan - -

- Choose a letter template -

+ + Autumn Campaign Plan + +

+ Choose a letter template +

+
@@ -46,7 +50,7 @@ exports[`ChooseStandardEnglishLetterTemplate page renders letter template select />
Choose one option
@@ -213,18 +218,15 @@ exports[`ChooseStandardEnglishLetterTemplate page renders letter template select > - + Preview + @@ -247,7 +249,8 @@ exports[`ChooseStandardEnglishLetterTemplate page renders letter template select Save and continue Go back diff --git a/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/preview-template/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/preview-template/__snapshots__/page.test.tsx.snap index 6d7075e51..5ad873d99 100644 --- a/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/preview-template/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/preview-template/__snapshots__/page.test.tsx.snap @@ -4,7 +4,8 @@ exports[`PreviewStandardEnglishLetterTemplateFromMessagePlan page renders letter Go back @@ -49,7 +50,8 @@ exports[`PreviewStandardEnglishLetterTemplateFromMessagePlan page renders letter Template ID
letter-template-id
@@ -106,13 +108,13 @@ exports[`PreviewStandardEnglishLetterTemplateFromMessagePlan page renders letter
-

- - Go back - -

+ + Go back + diff --git a/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/preview-template/page.test.tsx b/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/preview-template/page.test.tsx index 9d89e68f2..7e8512195 100644 --- a/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/preview-template/page.test.tsx +++ b/frontend/src/__tests__/app/message-plans/choose-standard-english-letter-template/preview-template/page.test.tsx @@ -13,6 +13,38 @@ const getTemplateMock = jest.mocked(getTemplate); const redirectMock = jest.mocked(redirect); describe('PreviewStandardEnglishLetterTemplateFromMessagePlan page', () => { + it('should redirect to choose-templates when lockNumber is invalid', async () => { + await PreviewStandardEnglishLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({ + lockNumber: 'invalid', + }), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + + it('should redirect to choose-templates when lockNumber is missing', async () => { + await PreviewStandardEnglishLetterTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({}), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + it('should redirect to invalid page with invalid template id', async () => { getTemplateMock.mockResolvedValueOnce(undefined); @@ -21,6 +53,9 @@ describe('PreviewStandardEnglishLetterTemplateFromMessagePlan page', () => { routingConfigId: 'routing-config-id', templateId: 'invalid-template-id', }), + searchParams: Promise.resolve({ + lockNumber: '0', + }), }); expect(getTemplateMock).toHaveBeenCalledWith('invalid-template-id'); @@ -39,6 +74,9 @@ describe('PreviewStandardEnglishLetterTemplateFromMessagePlan page', () => { routingConfigId: ROUTING_CONFIG.id, templateId: LETTER_TEMPLATE.id, }), + searchParams: Promise.resolve({ + lockNumber: '5', + }), }); const container = render(page); diff --git a/frontend/src/__tests__/app/message-plans/choose-text-message-template/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-text-message-template/__snapshots__/page.test.tsx.snap index 4d5c91e34..888ea5477 100644 --- a/frontend/src/__tests__/app/message-plans/choose-text-message-template/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/message-plans/choose-text-message-template/__snapshots__/page.test.tsx.snap @@ -13,16 +13,20 @@ exports[`ChooseTextMessageTemplate page renders sms template selection 1`] = `
- - Autumn Campaign Plan - -

- Choose a text message (SMS) template -

+ + Autumn Campaign Plan + +

+ Choose a text message (SMS) template +

+
@@ -46,7 +50,7 @@ exports[`ChooseTextMessageTemplate page renders sms template selection 1`] = ` />
Choose one option
@@ -213,18 +218,15 @@ exports[`ChooseTextMessageTemplate page renders sms template selection 1`] = ` > - + Preview + @@ -247,7 +249,8 @@ exports[`ChooseTextMessageTemplate page renders sms template selection 1`] = ` Save and continue Go back diff --git a/frontend/src/__tests__/app/message-plans/choose-text-message-template/preview-template/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/choose-text-message-template/preview-template/__snapshots__/page.test.tsx.snap index ade1415a1..ff0cc55ff 100644 --- a/frontend/src/__tests__/app/message-plans/choose-text-message-template/preview-template/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/message-plans/choose-text-message-template/preview-template/__snapshots__/page.test.tsx.snap @@ -4,7 +4,8 @@ exports[`PreviewTextMessageTemplateFromMessagePlan page renders SMS template pre Go back @@ -49,7 +50,8 @@ exports[`PreviewTextMessageTemplateFromMessagePlan page renders SMS template pre Template ID
sms-template-id
@@ -103,13 +105,13 @@ exports[`PreviewTextMessageTemplateFromMessagePlan page renders SMS template pre
-

- - Go back - -

+ + Go back + diff --git a/frontend/src/__tests__/app/message-plans/choose-text-message-template/preview-template/page.test.tsx b/frontend/src/__tests__/app/message-plans/choose-text-message-template/preview-template/page.test.tsx index 7cfc2a6a8..9873296de 100644 --- a/frontend/src/__tests__/app/message-plans/choose-text-message-template/preview-template/page.test.tsx +++ b/frontend/src/__tests__/app/message-plans/choose-text-message-template/preview-template/page.test.tsx @@ -15,6 +15,38 @@ const redirectMock = jest.mocked(redirect); describe('PreviewTextMessageTemplateFromMessagePlan page', () => { beforeEach(jest.resetAllMocks); + it('should redirect to choose-templates when lockNumber is invalid', async () => { + await PreviewTextMessageTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({ + lockNumber: 'invalid', + }), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + + it('should redirect to choose-templates when lockNumber is missing', async () => { + await PreviewTextMessageTemplateFromMessagePlan({ + params: Promise.resolve({ + routingConfigId: 'routing-config-id', + templateId: 'template-id', + }), + searchParams: Promise.resolve({}), + }); + + expect(redirectMock).toHaveBeenCalledWith( + '/message-plans/choose-templates/routing-config-id', + 'replace' + ); + }); + it('should redirect to invalid page with invalid template id', async () => { getTemplateMock.mockResolvedValueOnce(undefined); @@ -23,6 +55,9 @@ describe('PreviewTextMessageTemplateFromMessagePlan page', () => { routingConfigId: 'routing-config-id', templateId: 'invalid-template-id', }), + searchParams: Promise.resolve({ + lockNumber: '0', + }), }); expect(getTemplateMock).toHaveBeenCalledWith('invalid-template-id'); @@ -41,6 +76,9 @@ describe('PreviewTextMessageTemplateFromMessagePlan page', () => { routingConfigId: ROUTING_CONFIG.id, templateId: SMS_TEMPLATE.id, }), + searchParams: Promise.resolve({ + lockNumber: '5', + }), }); const container = render(page); diff --git a/frontend/src/__tests__/app/message-plans/create-message-plan/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/create-message-plan/__snapshots__/page.test.tsx.snap index 75a8fe434..75ba53b63 100644 --- a/frontend/src/__tests__/app/message-plans/create-message-plan/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/message-plans/create-message-plan/__snapshots__/page.test.tsx.snap @@ -212,7 +212,7 @@ exports[`CreateMessagePlanPage on submit it invokes the server action and render Go back @@ -388,7 +388,7 @@ exports[`CreateMessagePlanPage renders the page 1`] = ` Go back diff --git a/frontend/src/__tests__/app/message-plans/edit-message-plan-settings/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/message-plans/edit-message-plan-settings/__snapshots__/page.test.tsx.snap index 91de8b53c..6005e779e 100644 --- a/frontend/src/__tests__/app/message-plans/edit-message-plan-settings/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/message-plans/edit-message-plan-settings/__snapshots__/page.test.tsx.snap @@ -156,7 +156,7 @@ exports[`multiple campaigns matches snapshot 1`] = ` Go back @@ -392,7 +392,7 @@ exports[`multiple campaigns renders errors when form is submitted in invalid sta Go back @@ -558,7 +558,7 @@ exports[`single campaign matches snapshot 1`] = ` Go back @@ -761,7 +761,7 @@ exports[`single campaign renders errors when form is submitted in invalid state Go back diff --git a/frontend/src/__tests__/app/upload-letter-template/client-id-and-campaign-id-required/__snapshots__/page.test.tsx.snap b/frontend/src/__tests__/app/upload-letter-template/client-id-and-campaign-id-required/__snapshots__/page.test.tsx.snap index 13cc4868e..2f35cfe4c 100644 --- a/frontend/src/__tests__/app/upload-letter-template/client-id-and-campaign-id-required/__snapshots__/page.test.tsx.snap +++ b/frontend/src/__tests__/app/upload-letter-template/client-id-and-campaign-id-required/__snapshots__/page.test.tsx.snap @@ -32,7 +32,7 @@ exports[`ClientIdAndCampaignIdRequiredPage 1`] = `

Go back diff --git a/frontend/src/__tests__/components/atoms/__snapshots__/NHSNotifyBackLink.test.tsx.snap b/frontend/src/__tests__/components/atoms/__snapshots__/NHSNotifyBackLink.test.tsx.snap index 76a6c4286..61424b0bc 100644 --- a/frontend/src/__tests__/components/atoms/__snapshots__/NHSNotifyBackLink.test.tsx.snap +++ b/frontend/src/__tests__/components/atoms/__snapshots__/NHSNotifyBackLink.test.tsx.snap @@ -4,6 +4,7 @@ exports[`NotifyBackLink matches snapshot (default anchor) 1`] = ` Back @@ -15,6 +16,7 @@ exports[`NotifyBackLink matches snapshot when rendered as a button 1`] = ` diff --git a/frontend/src/__tests__/components/forms/ChooseChannelTemplate/ChooseChannelTemplate.test.tsx b/frontend/src/__tests__/components/forms/ChooseChannelTemplate/ChooseChannelTemplate.test.tsx index c20f1ff9a..76697f6ee 100644 --- a/frontend/src/__tests__/components/forms/ChooseChannelTemplate/ChooseChannelTemplate.test.tsx +++ b/frontend/src/__tests__/components/forms/ChooseChannelTemplate/ChooseChannelTemplate.test.tsx @@ -1,7 +1,8 @@ import { ChooseChannelTemplate } from '@forms/ChooseChannelTemplate'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, within } from '@testing-library/react'; import { EMAIL_TEMPLATE, + LARGE_PRINT_LETTER_TEMPLATE, LETTER_TEMPLATE, NHS_APP_TEMPLATE, ROUTING_CONFIG, @@ -10,6 +11,7 @@ import { import { useActionState } from 'react'; import { ChooseChannelTemplateFormState } from '@forms/ChooseChannelTemplate/server-action'; import { usePathname } from 'next/navigation'; +import { LetterType } from 'nhs-notify-backend-client'; jest.mock('next/navigation'); @@ -36,118 +38,388 @@ jest .mocked(usePathname) .mockReturnValue('message-plans/choose-email-template/testid'); +const createEmptyMessagePlan = (channel: string, cascadeIndex = 0) => { + const emptyCascade = { + cascadeGroups: ['standard'], + channel, + channelType: 'primary' as const, + defaultTemplateId: null, + }; + + const cascade = Array.from({ length: cascadeIndex + 1 }, (_, i) => { + if (i === cascadeIndex) { + return emptyCascade; + } + return ROUTING_CONFIG.cascade[i] || emptyCascade; + }); + + return { + ...ROUTING_CONFIG, + cascade, + }; +}; + +const createMessagePlanWithConditionalTemplate = ( + templateId: string, + accessibleFormat: Exclude +) => ({ + ...ROUTING_CONFIG, + cascade: [ + ...ROUTING_CONFIG.cascade.slice(0, 3), + { + ...ROUTING_CONFIG.cascade[3], + conditionalTemplates: [ + { + templateId, + accessibleFormat, + }, + ], + }, + ], +}); + +const propsByChannel = { + NHSAPP: { + pageHeading: 'Choose an NHS App template', + cascadeIndex: 0, + templateList: [NHS_APP_TEMPLATE], + }, + EMAIL: { + pageHeading: 'Choose an email template', + cascadeIndex: 1, + templateList: [EMAIL_TEMPLATE], + }, + SMS: { + pageHeading: 'Choose a text message (SMS) template', + cascadeIndex: 2, + templateList: [SMS_TEMPLATE], + }, + LETTER: { + pageHeading: 'Choose a letter template', + cascadeIndex: 3, + templateList: [LETTER_TEMPLATE], + }, + LARGE_PRINT_LETTER: { + pageHeading: 'Choose a large print letter template', + cascadeIndex: 3, + templateList: [LARGE_PRINT_LETTER_TEMPLATE], + accessibleFormat: 'x1' as Exclude, + }, +}; + +const renderComponent = (overrides = {}) => { + const defaultProps = { + messagePlan: ROUTING_CONFIG, + lockNumber: 42, + ...propsByChannel.NHSAPP, + }; + + return render(); +}; + +const nhsAppTemplates = [ + NHS_APP_TEMPLATE, + { + ...NHS_APP_TEMPLATE, + id: 'template-2', + name: 'Second template', + }, + { + ...NHS_APP_TEMPLATE, + id: 'template-3', + name: 'Third template', + }, +]; + +const largePrintLetterTemplates = [ + { + ...LARGE_PRINT_LETTER_TEMPLATE, + id: 'large-print-template-2', + name: 'Second large print template', + }, + LARGE_PRINT_LETTER_TEMPLATE, + { + ...LARGE_PRINT_LETTER_TEMPLATE, + id: 'large-print-template-3', + name: 'Third large print template', + }, +]; + describe('ChooseChannelTemplate', () => { - it('renders nhs app form', () => { - const container = render( - - ); - expect(container.asFragment()).toMatchSnapshot(); + it('displays correct message plan name', () => { + renderComponent(); + + expect(screen.getByText(ROUTING_CONFIG.name)).toBeInTheDocument(); }); - it('renders email form', () => { - const container = render( - - ); - expect(container.asFragment()).toMatchSnapshot(); + it('displays correct page heading', () => { + const heading = 'Choose a large print letter template'; + renderComponent({ + pageHeading: heading, + }); + + expect( + screen.getByRole('heading', { + name: heading, + }) + ).toBeInTheDocument(); }); - it('renders sms form', () => { - const container = render( - + it('displays back link with correct URL', () => { + renderComponent(); + + const backLinks = screen.getAllByRole('link', { name: 'Go back' }); + expect(backLinks.length).toBeGreaterThan(0); + expect(backLinks[0]).toHaveAttribute( + 'href', + `/message-plans/choose-templates/${ROUTING_CONFIG.id}` ); + }); + + describe('when templates are available', () => { + it('displays correct number of templates', () => { + renderComponent({ + templateList: nhsAppTemplates, + }); + + const radios = screen.getAllByRole('radio'); + expect(radios).toHaveLength(nhsAppTemplates.length); + + const table = screen.getByTestId('channel-templates-table'); + expect( + within(table).getByText(nhsAppTemplates[0].name) + ).toBeInTheDocument(); + expect( + within(table).getByText(nhsAppTemplates[1].name) + ).toBeInTheDocument(); + expect( + within(table).getByText(nhsAppTemplates[2].name) + ).toBeInTheDocument(); + }); + + it('displays "Save and continue" button', () => { + renderComponent(); + + const saveButton = screen.getByRole('button', { + name: 'Save and continue', + }); + expect(saveButton).toBeInTheDocument(); + expect(saveButton).toHaveAttribute('type', 'submit'); + }); + + it('renders multiple options', () => { + const container = renderComponent({ + templateList: nhsAppTemplates, + }); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + describe('when there are no templates', () => { + it('displays "You do not have any templates" message', () => { + renderComponent({ + messagePlan: createEmptyMessagePlan('NHSAPP'), + templateList: [], + }); + + expect( + screen.getByText('You do not have any templates yet.') + ).toBeInTheDocument(); + expect(screen.queryByRole('radio')).not.toBeInTheDocument(); + }); + + it('displays "Go to templates" link', () => { + renderComponent({ + messagePlan: createEmptyMessagePlan('NHSAPP'), + templateList: [], + }); + + const goToTemplatesLink = screen.getByRole('link', { + name: 'Go to templates', + }); + expect(goToTemplatesLink).toHaveAttribute('href', '/message-templates'); + expect( + screen.queryByRole('button', { name: 'Save and continue' }) + ).not.toBeInTheDocument(); + }); + + it('renders correctly', () => { + const container = renderComponent({ + messagePlan: createEmptyMessagePlan('NHSAPP'), + templateList: [], + }); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + describe('when there is no template preselected', () => { + it('does not display previously selected template summary', () => { + renderComponent({ + messagePlan: createEmptyMessagePlan('EMAIL', 1), + ...propsByChannel.EMAIL, + }); + + expect( + screen.queryByTestId('previous-selection-details') + ).not.toBeInTheDocument(); + }); + + it('does not preselect any radio button', () => { + renderComponent({ + messagePlan: createEmptyMessagePlan('EMAIL', 1), + ...propsByChannel.EMAIL, + }); + + const radio = screen.getByRole('radio'); + expect(radio).not.toBeChecked(); + }); + + it('renders correctly', () => { + const container = renderComponent({ + messagePlan: createEmptyMessagePlan('NHSAPP'), + }); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + describe('when there is a default template selected', () => { + it('displays previously selected template', () => { + renderComponent(); + + const summary = screen.getByTestId('previous-selection-details'); + expect(summary).toBeInTheDocument(); + expect( + screen.getByText('Previously selected template') + ).toBeInTheDocument(); + expect( + within(summary).getByText(NHS_APP_TEMPLATE.name) + ).toBeInTheDocument(); + }); + + it('preselects the correct radio button', () => { + const cascadeIndex = 0; + + renderComponent({ + templateList: nhsAppTemplates, + cascadeIndex, + }); + + const radios = screen.getAllByRole('radio'); + expect(radios).toHaveLength(3); + + const selectedTemplate = + ROUTING_CONFIG.cascade[cascadeIndex].defaultTemplateId; + + const selectedRadio = radios.find( + (radio) => (radio as HTMLInputElement).value === selectedTemplate + ); + expect(selectedRadio).toBeChecked(); + + const unselectedRadios = radios.filter( + (radio) => (radio as HTMLInputElement).value !== selectedTemplate + ); + for (const radio of unselectedRadios) { + expect(radio).not.toBeChecked(); + } + }); + + it('renders correctly', () => { + const container = renderComponent({ + templateList: nhsAppTemplates, + }); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + describe('when there is a conditional template selected', () => { + it('displays previously selected template summary', () => { + renderComponent({ + ...propsByChannel.LARGE_PRINT_LETTER, + templateList: largePrintLetterTemplates, + messagePlan: createMessagePlanWithConditionalTemplate( + LARGE_PRINT_LETTER_TEMPLATE.id, + 'x1' + ), + }); + + const summary = screen.getByTestId('previous-selection-details'); + + expect(summary).toBeInTheDocument(); + expect( + screen.getByText('Previously selected template') + ).toBeInTheDocument(); + expect( + within(summary).getByText('large print letter template name') + ).toBeInTheDocument(); + }); + + it('preselects the correct radio button', () => { + renderComponent({ + ...propsByChannel.LARGE_PRINT_LETTER, + templateList: largePrintLetterTemplates, + messagePlan: createMessagePlanWithConditionalTemplate( + LARGE_PRINT_LETTER_TEMPLATE.id, + 'x1' + ), + }); + + const radios = screen.getAllByRole('radio'); + expect(radios).toHaveLength(3); + + const selectedTemplateId = LARGE_PRINT_LETTER_TEMPLATE.id; + + const selectedRadio = radios.find( + (radio) => (radio as HTMLInputElement).value === selectedTemplateId + ); + expect(selectedRadio).toBeChecked(); + + const unselectedRadios = radios.filter( + (radio) => (radio as HTMLInputElement).value !== selectedTemplateId + ); + for (const radio of unselectedRadios) { + expect(radio).not.toBeChecked(); + } + }); + + it('renders correctly', () => { + const container = renderComponent({ + ...propsByChannel.LARGE_PRINT_LETTER, + templateList: largePrintLetterTemplates, + messagePlan: createMessagePlanWithConditionalTemplate( + LARGE_PRINT_LETTER_TEMPLATE.id, + 'x1' + ), + }); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + it('renders nhs app form', () => { + const container = renderComponent(); expect(container.asFragment()).toMatchSnapshot(); }); - it('renders letter form', () => { - const container = render( - - ); + it('renders email form', () => { + const container = renderComponent(propsByChannel.EMAIL); expect(container.asFragment()).toMatchSnapshot(); }); - it('renders multiple options', () => { - const container = render( - - ); + it('renders sms form', () => { + const container = renderComponent(propsByChannel.SMS); expect(container.asFragment()).toMatchSnapshot(); }); - it('renders correctly when template is unselected', () => { - const container = render( - - ); + it('renders letter form', () => { + const container = renderComponent(propsByChannel.LETTER); expect(container.asFragment()).toMatchSnapshot(); }); - it('renders correctly when no templates are available', () => { - const container = render( - - ); + it('renders large print letter form with accessibleFormat x1', () => { + const container = renderComponent({ + ...propsByChannel.LARGE_PRINT_LETTER, + messagePlan: createEmptyMessagePlan('LETTER', 3), + accessibleFormat: 'x1' as Exclude, + }); expect(container.asFragment()).toMatchSnapshot(); }); @@ -166,38 +438,16 @@ describe('ChooseChannelTemplate', () => { jest.mocked(useActionState).mockImplementation(mockUseActionState); - const container = render( - - ); + const container = renderComponent({ + messagePlan: createEmptyMessagePlan('NHSAPP'), + }); expect(container.asFragment()).toMatchSnapshot(); }); test('Client-side validation triggers', () => { - const container = render( - - ); + const container = renderComponent({ + messagePlan: createEmptyMessagePlan('NHSAPP'), + }); const submitButton = screen.getByTestId('submit-button'); fireEvent.click(submitButton); expect(container.asFragment()).toMatchSnapshot(); diff --git a/frontend/src/__tests__/components/forms/ChooseChannelTemplate/__snapshots__/ChooseChannelTemplate.test.tsx.snap b/frontend/src/__tests__/components/forms/ChooseChannelTemplate/__snapshots__/ChooseChannelTemplate.test.tsx.snap index 12a549101..cbb4af314 100644 --- a/frontend/src/__tests__/components/forms/ChooseChannelTemplate/__snapshots__/ChooseChannelTemplate.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/ChooseChannelTemplate/__snapshots__/ChooseChannelTemplate.test.tsx.snap @@ -43,16 +43,20 @@ exports[`ChooseChannelTemplate Client-side validation triggers 1`] = `

@@ -81,7 +85,8 @@ exports[`ChooseChannelTemplate Client-side validation triggers 1`] = ` class="nhsuk-grid-column-full" >
Choose one option
@@ -235,18 +240,15 @@ exports[`ChooseChannelTemplate Client-side validation triggers 1`] = ` > -
+ Preview + @@ -269,7 +271,8 @@ exports[`ChooseChannelTemplate Client-side validation triggers 1`] = ` Save and continue Go back @@ -282,7 +285,7 @@ exports[`ChooseChannelTemplate Client-side validation triggers 1`] = ` `; -exports[`ChooseChannelTemplate renders correctly when no templates are available 1`] = ` +exports[`ChooseChannelTemplate renders email form 1`] = `
- - Autumn Campaign Plan - -

- Choose an NHS App template -

+ + Autumn Campaign Plan + +

+ Choose an email template +

+ @@ -326,79 +333,25 @@ exports[`ChooseChannelTemplate renders correctly when no templates are available type="hidden" value="42" /> -

- You do not have any templates yet. -

-
- - - -
-
-`; - -exports[`ChooseChannelTemplate renders correctly when template is unselected 1`] = ` - -
-
-
- - Autumn Campaign Plan - -

- Choose an NHS App template -

-
- - - +
+ Previously selected template +
+
+ email template name +
+
+
@@ -406,7 +359,8 @@ exports[`ChooseChannelTemplate renders correctly when template is unselected 1`] class="nhsuk-grid-column-full" >
Choose one option
@@ -484,17 +438,18 @@ exports[`ChooseChannelTemplate renders correctly when template is unselected 1`] class="nhsuk-radios__item" > @@ -510,7 +465,7 @@ exports[`ChooseChannelTemplate renders correctly when template is unselected 1`] > Name - app template name + email template name Type - NHS App message + Email - + Preview + @@ -582,7 +534,8 @@ exports[`ChooseChannelTemplate renders correctly when template is unselected 1`] Save and continue Go back @@ -595,29 +548,63 @@ exports[`ChooseChannelTemplate renders correctly when template is unselected 1`] `; -exports[`ChooseChannelTemplate renders email form 1`] = ` +exports[`ChooseChannelTemplate renders error component 1`] = `
+
- - Autumn Campaign Plan - -

- Choose an email template -

+ + Autumn Campaign Plan + +

+ Choose an NHS App template +

+
@@ -639,25 +626,6 @@ exports[`ChooseChannelTemplate renders email form 1`] = ` type="hidden" value="42" /> -
-
-
- Previously selected template -
-
- email template name -
-
-
@@ -665,14 +633,27 @@ exports[`ChooseChannelTemplate renders email form 1`] = ` class="nhsuk-grid-column-full" >
Choose one option
+ + + Error: + + You must select a template +
@@ -743,18 +724,17 @@ exports[`ChooseChannelTemplate renders email form 1`] = ` class="nhsuk-radios__item" > @@ -770,7 +750,7 @@ exports[`ChooseChannelTemplate renders email form 1`] = ` > Name - email template name + app template name Type - Email + NHS App message - + Preview + @@ -842,7 +819,8 @@ exports[`ChooseChannelTemplate renders email form 1`] = ` Save and continue Go back @@ -855,59 +833,33 @@ exports[`ChooseChannelTemplate renders email form 1`] = ` `; -exports[`ChooseChannelTemplate renders error component 1`] = ` +exports[`ChooseChannelTemplate renders large print letter form with accessibleFormat x1 1`] = `
-
- - Autumn Campaign Plan - -

- Choose an NHS App template -

+ + Autumn Campaign Plan + +

+ Choose a large print letter template +

+
@@ -929,25 +881,6 @@ exports[`ChooseChannelTemplate renders error component 1`] = ` type="hidden" value="42" /> -
-
-
- Previously selected template -
-
- app template name -
-
-
@@ -955,26 +888,15 @@ exports[`ChooseChannelTemplate renders error component 1`] = ` class="nhsuk-grid-column-full" >
Choose one option
- - - Error: - - You must select a template -
@@ -1045,18 +967,17 @@ exports[`ChooseChannelTemplate renders error component 1`] = ` class="nhsuk-radios__item" > @@ -1072,7 +993,7 @@ exports[`ChooseChannelTemplate renders error component 1`] = ` > Name - app template name + large print letter template name Type - NHS App message + Large print letter - + Preview + @@ -1144,7 +1062,8 @@ exports[`ChooseChannelTemplate renders error component 1`] = ` Save and continue Go back @@ -1170,16 +1089,20 @@ exports[`ChooseChannelTemplate renders letter form 1`] = `
- - Autumn Campaign Plan - -

- Choose a letter template -

+ + Autumn Campaign Plan + +

+ Choose a letter template +

+
@@ -1203,7 +1126,7 @@ exports[`ChooseChannelTemplate renders letter form 1`] = ` />
@@ -1463,7 +1389,7 @@ exports[`ChooseChannelTemplate renders multiple options 1`] = ` />
@@ -1818,7 +1652,7 @@ exports[`ChooseChannelTemplate renders nhs app form 1`] = ` />
- app template name + sms template name
@@ -1842,7 +1676,8 @@ exports[`ChooseChannelTemplate renders nhs app form 1`] = ` class="nhsuk-grid-column-full" >
Choose one option
@@ -1922,16 +1757,16 @@ exports[`ChooseChannelTemplate renders nhs app form 1`] = ` @@ -1947,7 +1782,7 @@ exports[`ChooseChannelTemplate renders nhs app form 1`] = ` > Name - app template name + sms template name Type - NHS App message + Text message (SMS) -
+ Preview + @@ -2019,7 +1851,8 @@ exports[`ChooseChannelTemplate renders nhs app form 1`] = ` Save and continue Go back @@ -2032,7 +1865,7 @@ exports[`ChooseChannelTemplate renders nhs app form 1`] = ` `; -exports[`ChooseChannelTemplate renders sms form 1`] = ` +exports[`ChooseChannelTemplate when templates are available renders multiple options 1`] = `
- - Autumn Campaign Plan - -

- Choose a text message (SMS) template -

+ + Autumn Campaign Plan + +

+ Choose an NHS App template +

+
@@ -2078,7 +1915,7 @@ exports[`ChooseChannelTemplate renders sms form 1`] = ` />
- sms template name + app template name
@@ -2102,7 +1939,8 @@ exports[`ChooseChannelTemplate renders sms form 1`] = ` class="nhsuk-grid-column-full" >
Choose one option
@@ -2182,16 +2020,16 @@ exports[`ChooseChannelTemplate renders sms form 1`] = ` @@ -2207,7 +2045,7 @@ exports[`ChooseChannelTemplate renders sms form 1`] = ` > Name - sms template name + app template name Type - Text message (SMS) + NHS App message + + + + 13th Jan 2025 +
+ 10:19 + + + +
+ Preview + + + + + + +
+ + +
+ + + + Second template + + + + NHS App message + + + Preview + + + + + + + + + Third template + + + + NHS App message + + + + 13th Jan 2025 +
+ 10:19 + + + + + Preview + + + + + +
+
+
+
+
+ + + Go back + +
+ +
+
+
+ +`; + +exports[`ChooseChannelTemplate when there are no templates renders correctly 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose an NHS App template +

+
+
+ + + +

+ You do not have any templates yet. +

+ +
+
+
+
+
+`; + +exports[`ChooseChannelTemplate when there is a conditional template selected renders correctly 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose a large print letter template +

+
+
+ + + +
+
+
+ Previously selected template +
+
+ large print letter template name +
+
+
+
+
+
+ Choose one option +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + Second large print template + + + Large print letter + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + large print letter template name + + + Large print letter + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + Third large print template + + + Large print letter + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+ + + Go back + +
+
+
+
+
+
+`; + +exports[`ChooseChannelTemplate when there is a default template selected renders correctly 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose an NHS App template +

+
+
+ + + +
+
+
+ Previously selected template +
+
+ app template name +
+
+
+
+
+
+ Choose one option +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + app template name + + + NHS App message + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + Second template + + + NHS App message + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + Third template + + + NHS App message + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+ + + Go back + +
+
+
+
+
+
+`; + +exports[`ChooseChannelTemplate when there is no template preselected renders correctly 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose an NHS App template +

+
+
+ + + +
+
+
+ Choose one option +
+
+
+ + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + app template name + + + NHS App message + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
@@ -2279,7 +3514,8 @@ exports[`ChooseChannelTemplate renders sms form 1`] = ` Save and continue Go back diff --git a/frontend/src/__tests__/components/forms/ChooseChannelTemplate/server-action.test.ts b/frontend/src/__tests__/components/forms/ChooseChannelTemplate/server-action.test.ts index f1d495cd1..71152fffe 100644 --- a/frontend/src/__tests__/components/forms/ChooseChannelTemplate/server-action.test.ts +++ b/frontend/src/__tests__/components/forms/ChooseChannelTemplate/server-action.test.ts @@ -6,6 +6,7 @@ import { ROUTING_CONFIG, SMS_TEMPLATE, LETTER_TEMPLATE, + LARGE_PRINT_LETTER_TEMPLATE, } from '@testhelpers/helpers'; import { updateRoutingConfig } from '@utils/message-plans'; import { redirect, RedirectType } from 'next/navigation'; @@ -15,6 +16,10 @@ jest.mock('@utils/message-plans'); jest.mock('@utils/amplify-utils'); +beforeEach(() => { + jest.clearAllMocks(); +}); + test('submit form - validation error', async () => { const response = await chooseChannelTemplateAction( { @@ -161,3 +166,69 @@ test('submit form - success updates config and redirects to choose templates for RedirectType.push ); }); + +test('submit form - success adds conditional template and updates cascade group overrides', async () => { + const mockRedirect = jest.mocked(redirect); + const mockUpdateRoutingConfig = jest.mocked(updateRoutingConfig); + + const largePrintTemplate = { + ...LARGE_PRINT_LETTER_TEMPLATE, + id: 'large-print-template-id', + name: 'Large print letter', + letterType: 'x1' as const, + supplierReferences: { MBA: 'large-print-ref' }, + }; + + await chooseChannelTemplateAction( + { + messagePlan: { + ...ROUTING_CONFIG, + cascade: [ + { + cascadeGroups: ['standard'], + channel: 'LETTER', + channelType: 'primary', + defaultTemplateId: LETTER_TEMPLATE.id, + }, + ], + cascadeGroupOverrides: [], + }, + pageHeading: 'Choose a large print letter template', + templateList: [largePrintTemplate], + cascadeIndex: 0, + accessibleFormat: 'x1', + }, + getMockFormData({ + channelTemplate: largePrintTemplate.id, + lockNumber: String(largePrintTemplate.lockNumber), + }) + ); + + expect(mockUpdateRoutingConfig).toHaveBeenCalledWith( + ROUTING_CONFIG.id, + { + cascade: [ + { + cascadeGroups: ['standard'], + channel: 'LETTER', + channelType: 'primary', + defaultTemplateId: LETTER_TEMPLATE.id, + conditionalTemplates: [ + { + accessibleFormat: 'x1', + templateId: largePrintTemplate.id, + supplierReferences: { MBA: 'large-print-ref' }, + }, + ], + }, + ], + cascadeGroupOverrides: [{ name: 'accessible', accessibleFormat: ['x1'] }], + }, + largePrintTemplate.lockNumber + ); + + expect(mockRedirect).toHaveBeenCalledWith( + `/message-plans/choose-templates/${ROUTING_CONFIG.id}`, + RedirectType.push + ); +}); diff --git a/frontend/src/__tests__/components/forms/ChooseLanguageLetterTemplates/ChooseLanguageLetterTemplates.test.tsx b/frontend/src/__tests__/components/forms/ChooseLanguageLetterTemplates/ChooseLanguageLetterTemplates.test.tsx new file mode 100644 index 000000000..c67da626e --- /dev/null +++ b/frontend/src/__tests__/components/forms/ChooseLanguageLetterTemplates/ChooseLanguageLetterTemplates.test.tsx @@ -0,0 +1,600 @@ +import { ChooseLanguageLetterTemplates } from '@forms/ChooseLanguageLetterTemplates/ChooseLanguageLetterTemplates'; +import { fireEvent, render, screen, within } from '@testing-library/react'; +import { LETTER_TEMPLATE, ROUTING_CONFIG } from '@testhelpers/helpers'; +import { useActionState } from 'react'; +import { ChooseLanguageLetterTemplatesFormState } from '@forms/ChooseLanguageLetterTemplates/server-action'; +import { LetterTemplate } from 'nhs-notify-web-template-management-utils'; +import { ConditionalTemplateLanguage } from 'nhs-notify-backend-client'; + +jest.mock('react', () => { + const originalModule = jest.requireActual('react'); + + return { + ...originalModule, + useActionState: jest + .fn() + .mockImplementation( + ( + _: ( + formState: ChooseLanguageLetterTemplatesFormState, + formData: FormData + ) => Promise, + initialState: ChooseLanguageLetterTemplatesFormState + ) => [initialState, '/action'] + ), + }; +}); + +const FRENCH_LETTER_TEMPLATE: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'french-letter-id', + name: 'French letter template', + language: 'fr', +}; + +const POLISH_LETTER_TEMPLATE: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'polish-letter-id', + name: 'Polish letter template', + language: 'pl', +}; + +const SPANISH_LETTER_TEMPLATE: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'spanish-letter-id', + name: 'Spanish letter template', + language: 'es', +}; + +const languageLetterTemplates = [ + FRENCH_LETTER_TEMPLATE, + POLISH_LETTER_TEMPLATE, + SPANISH_LETTER_TEMPLATE, +]; + +const createMessagePlanWithLanguageTemplates = ( + languageTemplates: ConditionalTemplateLanguage[] +) => ({ + ...ROUTING_CONFIG, + cascade: [ + ...ROUTING_CONFIG.cascade.slice(0, 3), + { + ...ROUTING_CONFIG.cascade[3], + conditionalTemplates: languageTemplates.map( + ({ templateId, language }) => ({ + templateId, + language, + }) + ), + }, + ], +}); + +const renderComponent = (overrides = {}) => { + const defaultProps = { + messagePlan: ROUTING_CONFIG, + pageHeading: 'Choose language letter templates', + templateList: languageLetterTemplates, + cascadeIndex: 3, + lockNumber: 42, + }; + + return render( + + ); +}; + +describe('ChooseLanguageLetterTemplates', () => { + it('displays correct message plan name', () => { + renderComponent(); + + expect(screen.getByText(ROUTING_CONFIG.name)).toBeInTheDocument(); + }); + + it('displays correct page heading', () => { + const heading = 'Choose other language letter templates'; + + renderComponent({ + pageHeading: heading, + }); + + expect( + screen.getByRole('heading', { + name: heading, + }) + ).toBeInTheDocument(); + }); + + it('displays back link with correct URL', () => { + renderComponent(); + + const backLink = screen.getByRole('link', { name: 'Go back' }); + expect(backLink).toHaveAttribute( + 'href', + `/message-plans/choose-templates/${ROUTING_CONFIG.id}` + ); + }); + + describe('when templates are available', () => { + it('displays correct number of templates to choose from', () => { + renderComponent({ + templateList: languageLetterTemplates, + }); + + const checkboxes = screen.getAllByRole('checkbox'); + expect(checkboxes).toHaveLength(languageLetterTemplates.length); + + const table = screen.getByTestId('language-templates-table'); + expect( + within(table).getByText(FRENCH_LETTER_TEMPLATE.name) + ).toBeInTheDocument(); + expect( + within(table).getByText(POLISH_LETTER_TEMPLATE.name) + ).toBeInTheDocument(); + expect( + within(table).getByText(SPANISH_LETTER_TEMPLATE.name) + ).toBeInTheDocument(); + }); + + it('displays "Save and continue" button', () => { + renderComponent(); + + const saveButton = screen.getByRole('button', { + name: 'Save and continue', + }); + expect(saveButton).toBeInTheDocument(); + expect(saveButton).toHaveAttribute('type', 'submit'); + }); + + it('displays table hint text', () => { + renderComponent(); + + const hintText = screen.getByText( + 'Choose all the templates that you want to include in this message plan. You can only choose one template for each language.' + ); + expect(hintText).toBeInTheDocument(); + expect(hintText).toHaveClass('nhsuk-hint'); + }); + + it('renders multiple options', () => { + const container = renderComponent({ + templateList: languageLetterTemplates, + }); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + describe('when there are no templates', () => { + it('displays "You do not have any other language letter templates" message', () => { + renderComponent({ + templateList: [], + }); + + expect( + screen.getByText( + 'You do not have any other language letter templates yet.' + ) + ).toBeInTheDocument(); + expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); + }); + + it('displays "Go to templates" link', () => { + renderComponent({ + templateList: [], + }); + + const goToTemplatesLink = screen.getByRole('link', { + name: 'Go to templates', + }); + expect(goToTemplatesLink).toHaveAttribute('href', '/message-templates'); + expect( + screen.queryByRole('button', { name: 'Save and continue' }) + ).not.toBeInTheDocument(); + }); + + it('renders correctly', () => { + const container = renderComponent({ + templateList: [], + }); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + describe('when there are no language templates preselected', () => { + it('does not display previously selected templates details', () => { + renderComponent(); + + expect( + screen.queryByTestId('previous-selection-details') + ).not.toBeInTheDocument(); + }); + + it('does not preselect any checkboxes', () => { + renderComponent(); + + const checkboxes = screen.getAllByRole('checkbox'); + for (const checkbox of checkboxes) { + expect(checkbox).not.toBeChecked(); + } + }); + + it('renders correctly', () => { + const container = renderComponent(); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + describe('when there are language templates preselected', () => { + it('displays previously selected templates in details component', () => { + const messagePlan = createMessagePlanWithLanguageTemplates([ + { templateId: FRENCH_LETTER_TEMPLATE.id, language: 'fr' }, + { templateId: POLISH_LETTER_TEMPLATE.id, language: 'pl' }, + ]); + + renderComponent({ messagePlan }); + + const previouslySelected = screen.getByTestId( + 'previous-selection-details' + ); + expect(previouslySelected).toBeInTheDocument(); + + const previousSelectionTexts = screen.getAllByText( + 'Previously selected templates' + ); + expect(previousSelectionTexts.length).toBeGreaterThan(0); + + expect( + within(previouslySelected).getByText(FRENCH_LETTER_TEMPLATE.name) + ).toBeInTheDocument(); + expect( + within(previouslySelected).getByText(POLISH_LETTER_TEMPLATE.name) + ).toBeInTheDocument(); + }); + + it('preselects the correct checkboxes', () => { + const messagePlan = createMessagePlanWithLanguageTemplates([ + { templateId: FRENCH_LETTER_TEMPLATE.id, language: 'fr' }, + { templateId: SPANISH_LETTER_TEMPLATE.id, language: 'es' }, + ]); + + renderComponent({ messagePlan }); + + const checkboxes = screen.getAllByRole('checkbox'); + const frenchCheckbox = checkboxes.find( + (checkbox) => + checkbox.getAttribute('name') === + `template_${FRENCH_LETTER_TEMPLATE.id}` + ); + const polishCheckbox = checkboxes.find( + (checkbox) => + checkbox.getAttribute('name') === + `template_${POLISH_LETTER_TEMPLATE.id}` + ); + const spanishCheckbox = checkboxes.find( + (checkbox) => + checkbox.getAttribute('name') === + `template_${SPANISH_LETTER_TEMPLATE.id}` + ); + + expect(frenchCheckbox).toBeChecked(); + expect(spanishCheckbox).toBeChecked(); + expect(polishCheckbox).not.toBeChecked(); + }); + + it('renders correctly', () => { + const messagePlan = createMessagePlanWithLanguageTemplates([ + { templateId: FRENCH_LETTER_TEMPLATE.id, language: 'fr' }, + ]); + + const container = renderComponent({ messagePlan }); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + describe('error handling', () => { + describe('when no template is selected', () => { + it('displays error summary with missing selection hint text', () => { + const mockUseActionState = jest.mocked(useActionState); + mockUseActionState.mockReturnValueOnce([ + { + messagePlan: ROUTING_CONFIG, + pageHeading: 'Choose language letter templates', + templateList: languageLetterTemplates, + cascadeIndex: 3, + errorState: { + formErrors: ['You have not chosen any templates'], + }, + errorType: 'missing', + }, + jest.fn(), + false, + ]); + + const { container } = renderComponent(); + + const errorSummary = container.querySelector( + '.nhsuk-error-summary' + ) as HTMLElement; + + const errorSummaryHeading = screen.getByTestId('error-summary'); + expect(errorSummaryHeading).toHaveTextContent('There is a problem'); + + const errorSummaryHint = errorSummary.querySelector( + '.nhsuk-hint' + ) as HTMLElement; + expect(errorSummaryHint).toHaveTextContent( + 'You have not chosen any templates' + ); + const errorListItem = within(errorSummary).getByRole('listitem'); + expect( + within(errorListItem).getByText('You have not chosen any templates') + ).toBeInTheDocument(); + }); + + it('displays error on language templates component', () => { + const mockUseActionState = jest.mocked(useActionState); + mockUseActionState.mockReturnValueOnce([ + { + messagePlan: ROUTING_CONFIG, + pageHeading: 'Choose language letter templates', + templateList: languageLetterTemplates, + cascadeIndex: 3, + errorState: { + fieldErrors: { + 'language-templates': ['Choose one or more templates'], + }, + }, + errorType: 'missing', + }, + jest.fn(), + false, + ]); + + const { container } = renderComponent(); + + const formGroup = container.querySelector('.nhsuk-form-group'); + expect(formGroup).toHaveClass('nhsuk-form-group--error'); + + const formErrorMessage = container.querySelector( + '#language-templates--error-message' + ); + expect(formErrorMessage).toHaveClass('nhsuk-error-message'); + expect(formErrorMessage).toHaveTextContent( + 'Choose one or more templates' + ); + }); + + it('renders correctly', () => { + const mockUseActionState = jest.mocked(useActionState); + mockUseActionState.mockReturnValueOnce([ + { + messagePlan: ROUTING_CONFIG, + pageHeading: 'Choose language letter templates', + templateList: languageLetterTemplates, + cascadeIndex: 3, + errorState: { + fieldErrors: { + 'language-templates': ['Choose one or more templates'], + }, + }, + errorType: 'missing', + }, + jest.fn(), + false, + ]); + + const container = renderComponent(); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + + describe('when duplicate languages are selected', () => { + it('displays error summary with duplicate language hint text', () => { + const mockUseActionState = jest.mocked(useActionState); + mockUseActionState.mockReturnValueOnce([ + { + messagePlan: ROUTING_CONFIG, + pageHeading: 'Choose language letter templates', + templateList: languageLetterTemplates, + cascadeIndex: 3, + errorState: { + fieldErrors: { + 'language-templates': [ + 'Choose only one template for each language', + ], + }, + }, + errorType: 'duplicate', + }, + jest.fn(), + false, + ]); + + const { container } = renderComponent(); + + const errorSummary = container.querySelector( + '.nhsuk-error-summary' + ) as HTMLElement; + + const errorSummaryHeading = screen.getByTestId('error-summary'); + expect(errorSummaryHeading).toHaveTextContent('There is a problem'); + + const errorSummaryHint = errorSummary.querySelector( + '.nhsuk-hint' + ) as HTMLElement; + expect(errorSummaryHint).toHaveTextContent( + 'You can only choose one template for each language' + ); + + const errorListItem = within(errorSummary).getByRole('listitem'); + expect( + within(errorListItem).getByText( + 'Choose only one template for each language' + ) + ).toBeInTheDocument(); + }); + + it('displays form error for duplicate language selection', () => { + const mockUseActionState = jest.mocked(useActionState); + mockUseActionState.mockReturnValueOnce([ + { + messagePlan: ROUTING_CONFIG, + pageHeading: 'Choose language letter templates', + templateList: languageLetterTemplates, + cascadeIndex: 3, + errorState: { + fieldErrors: { + 'language-templates': [ + 'Choose only one template for each language', + ], + }, + }, + errorType: 'duplicate', + }, + jest.fn(), + false, + ]); + + const { container } = renderComponent(); + + const formGroup = container.querySelector('.nhsuk-form-group'); + expect(formGroup).toHaveClass('nhsuk-form-group--error'); + + const formErrorMessage = container.querySelector( + '#language-templates--error-message' + ); + expect(formErrorMessage).toHaveClass('nhsuk-error-message'); + expect(formErrorMessage).toHaveTextContent( + 'Choose only one template for each language' + ); + }); + + it('renders correctly', () => { + const mockUseActionState = jest.mocked(useActionState); + mockUseActionState.mockReturnValueOnce([ + { + messagePlan: ROUTING_CONFIG, + pageHeading: 'Choose language letter templates', + templateList: languageLetterTemplates, + cascadeIndex: 3, + errorState: { + fieldErrors: { + 'language-templates': [ + 'Choose only one template for each language', + ], + }, + }, + errorType: 'duplicate', + }, + jest.fn(), + false, + ]); + + const container = renderComponent(); + expect(container.asFragment()).toMatchSnapshot(); + }); + }); + }); + + describe('form submission', () => { + it('renders form with correct form-id hidden input', () => { + const { container } = renderComponent(); + + const form = container.querySelector('form'); + expect(form).toBeInTheDocument(); + + const formIdInput = within(form!).getByDisplayValue( + 'choose-language-letter-templates' + ); + expect(formIdInput).toHaveAttribute('name', 'form-id'); + expect(formIdInput).toHaveAttribute('type', 'hidden'); + }); + + it('includes selected template IDs in form data when checkboxes are checked', () => { + const { container } = renderComponent(); + + const checkboxes = screen.getAllByRole('checkbox'); + const frenchCheckbox = checkboxes.find( + (checkbox) => + checkbox.getAttribute('name') === + `template_${FRENCH_LETTER_TEMPLATE.id}` + ) as HTMLInputElement; + const polishCheckbox = checkboxes.find( + (checkbox) => + checkbox.getAttribute('name') === + `template_${POLISH_LETTER_TEMPLATE.id}` + ) as HTMLInputElement; + const spanishCheckbox = checkboxes.find( + (checkbox) => + checkbox.getAttribute('name') === + `template_${SPANISH_LETTER_TEMPLATE.id}` + ) as HTMLInputElement; + + fireEvent.click(frenchCheckbox); + fireEvent.click(polishCheckbox); + + expect(frenchCheckbox.checked).toBe(true); + expect(polishCheckbox.checked).toBe(true); + expect(spanishCheckbox.checked).toBe(false); + + const form = container.querySelector('form')!; + const formData = new FormData(form); + + expect(formData.get('form-id')).toBe('choose-language-letter-templates'); + expect(formData.get(`template_${FRENCH_LETTER_TEMPLATE.id}`)).toBe( + `${FRENCH_LETTER_TEMPLATE.id}:fr` + ); + expect(formData.get(`template_${POLISH_LETTER_TEMPLATE.id}`)).toBe( + `${POLISH_LETTER_TEMPLATE.id}:pl` + ); + expect(formData.get(`template_${SPANISH_LETTER_TEMPLATE.id}`)).toBeNull(); + }); + + it('submits form with selected templates when Save and continue is clicked', () => { + const { container } = renderComponent(); + + const checkboxes = screen.getAllByRole('checkbox'); + const frenchCheckbox = checkboxes.find( + (checkbox) => + checkbox.getAttribute('name') === + `template_${FRENCH_LETTER_TEMPLATE.id}` + ) as HTMLInputElement; + const polishCheckbox = checkboxes.find( + (checkbox) => + checkbox.getAttribute('name') === + `template_${POLISH_LETTER_TEMPLATE.id}` + ) as HTMLInputElement; + + fireEvent.click(frenchCheckbox); + fireEvent.click(polishCheckbox); + + const saveButton = screen.getByRole('button', { + name: 'Save and continue', + }); + + const form = container.querySelector('form')!; + let capturedFormData: FormData | null = null; + + form.addEventListener('submit', (e) => { + e.preventDefault(); + capturedFormData = new FormData(form); + }); + + fireEvent.click(saveButton); + + expect(capturedFormData).not.toBeNull(); + expect(capturedFormData!.get('form-id')).toBe( + 'choose-language-letter-templates' + ); + expect( + capturedFormData!.get(`template_${FRENCH_LETTER_TEMPLATE.id}`) + ).toBe(`${FRENCH_LETTER_TEMPLATE.id}:fr`); + expect( + capturedFormData!.get(`template_${POLISH_LETTER_TEMPLATE.id}`) + ).toBe(`${POLISH_LETTER_TEMPLATE.id}:pl`); + expect( + capturedFormData!.get(`template_${SPANISH_LETTER_TEMPLATE.id}`) + ).toBeNull(); + }); + }); +}); diff --git a/frontend/src/__tests__/components/forms/ChooseLanguageLetterTemplates/__snapshots__/ChooseLanguageLetterTemplates.test.tsx.snap b/frontend/src/__tests__/components/forms/ChooseLanguageLetterTemplates/__snapshots__/ChooseLanguageLetterTemplates.test.tsx.snap new file mode 100644 index 000000000..d8a88958b --- /dev/null +++ b/frontend/src/__tests__/components/forms/ChooseLanguageLetterTemplates/__snapshots__/ChooseLanguageLetterTemplates.test.tsx.snap @@ -0,0 +1,2431 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ChooseLanguageLetterTemplates error handling when duplicate languages are selected renders correctly 1`] = ` + +
+ +
+
+
+ + Autumn Campaign Plan + +

+ Choose language letter templates +

+
+ + + + +
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+ + + Error: + + Choose only one template for each language + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Spanish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+ + + Go back + +
+ +
+
+
+ +`; + +exports[`ChooseLanguageLetterTemplates error handling when no template is selected renders correctly 1`] = ` + +
+ +
+
+
+ + Autumn Campaign Plan + +

+ Choose language letter templates +

+
+
+ + + +
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+ + + Error: + + Choose one or more templates + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Spanish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+ + + Go back + +
+
+
+
+
+
+`; + +exports[`ChooseLanguageLetterTemplates when templates are available renders multiple options 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose language letter templates +

+
+
+ + + +
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Spanish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+ + + Go back + +
+
+
+
+
+
+`; + +exports[`ChooseLanguageLetterTemplates when there are language templates preselected renders correctly 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose language letter templates +

+
+
+ + + +
+ + + Previously selected templates + + +
+
+
+
+ Previously selected templates +
+
+
    +
  • + French letter template +
  • +
+
+
+
+
+
+
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Spanish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+ + + Go back + +
+
+
+
+
+
+`; + +exports[`ChooseLanguageLetterTemplates when there are no language templates preselected renders correctly 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose language letter templates +

+
+
+ + + +
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Spanish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+ + + Go back + +
+
+
+
+
+
+`; + +exports[`ChooseLanguageLetterTemplates when there are no templates renders correctly 1`] = ` + +
+
+
+
+ + Autumn Campaign Plan + +

+ Choose language letter templates +

+
+
+ + + +

+ You do not have any other language letter templates yet. +

+ +
+
+
+
+
+`; diff --git a/frontend/src/__tests__/components/forms/ChooseLanguageLetterTemplates/server-action.test.ts b/frontend/src/__tests__/components/forms/ChooseLanguageLetterTemplates/server-action.test.ts new file mode 100644 index 000000000..b919cd83d --- /dev/null +++ b/frontend/src/__tests__/components/forms/ChooseLanguageLetterTemplates/server-action.test.ts @@ -0,0 +1,359 @@ +import { + chooseLanguageLetterTemplatesAction, + $ChooseLanguageLetterTemplates, +} from '@forms/ChooseLanguageLetterTemplates/server-action'; +import { + getMockFormData, + ROUTING_CONFIG, + LETTER_TEMPLATE, +} from '@testhelpers/helpers'; +import { updateRoutingConfig } from '@utils/message-plans'; +import { redirect, RedirectType } from 'next/navigation'; +import type { LetterTemplate } from 'nhs-notify-web-template-management-utils'; + +jest.mock('next/navigation'); +jest.mock('@utils/message-plans'); + +const mockRedirect = jest.mocked(redirect); +const mockUpdateRoutingConfig = jest.mocked(updateRoutingConfig); + +const FRENCH_LETTER: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'french-id', + language: 'fr', + name: 'French Letter', +}; + +const POLISH_LETTER: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'polish-id', + language: 'pl', + name: 'Polish Letter', +}; + +const SPANISH_LETTER: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'spanish-id', + language: 'es', + name: 'Spanish Letter', +}; + +describe('chooseLanguageLetterTemplatesAction', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should update message plan with language template for cascade with no conditional templates', async () => { + await chooseLanguageLetterTemplatesAction( + { + messagePlan: { + ...ROUTING_CONFIG, + cascade: [ + { + ...ROUTING_CONFIG.cascade[0], + }, + ], + }, + cascadeIndex: 0, + templateList: [FRENCH_LETTER], + pageHeading: 'Choose language templates', + }, + getMockFormData({ + [`template_${FRENCH_LETTER.id}`]: `${FRENCH_LETTER.id}:fr`, + lockNumber: '42', + }) + ); + + expect(mockUpdateRoutingConfig).toHaveBeenCalledWith( + ROUTING_CONFIG.id, + expect.objectContaining({ + cascade: [ + expect.objectContaining({ + conditionalTemplates: [ + { + language: 'fr', + templateId: FRENCH_LETTER.id, + supplierReferences: FRENCH_LETTER.supplierReferences, + }, + ], + }), + ], + }), + 42 + ); + + expect(mockRedirect).toHaveBeenCalledWith( + `/message-plans/choose-templates/${ROUTING_CONFIG.id}`, + RedirectType.push + ); + }); + + test('should include multiple language templates in update when multiple languages are selected', async () => { + await chooseLanguageLetterTemplatesAction( + { + messagePlan: { + ...ROUTING_CONFIG, + cascade: [ + { + ...ROUTING_CONFIG.cascade[0], + }, + ], + }, + cascadeIndex: 0, + templateList: [FRENCH_LETTER, POLISH_LETTER, SPANISH_LETTER], + pageHeading: 'Choose language templates', + }, + getMockFormData({ + [`template_${FRENCH_LETTER.id}`]: `${FRENCH_LETTER.id}:fr`, + [`template_${POLISH_LETTER.id}`]: `${POLISH_LETTER.id}:pl`, + lockNumber: '42', + }) + ); + + expect(mockUpdateRoutingConfig).toHaveBeenCalledWith( + ROUTING_CONFIG.id, + expect.objectContaining({ + cascade: [ + expect.objectContaining({ + conditionalTemplates: expect.arrayContaining([ + expect.objectContaining({ + language: 'fr', + templateId: FRENCH_LETTER.id, + }), + expect.objectContaining({ + language: 'pl', + templateId: POLISH_LETTER.id, + }), + ]), + }), + ], + }), + 42 + ); + }); + + test('should replace existing language templates with new selection', async () => { + await chooseLanguageLetterTemplatesAction( + { + messagePlan: { + ...ROUTING_CONFIG, + cascade: [ + { + ...ROUTING_CONFIG.cascade[0], + conditionalTemplates: [ + { + templateId: FRENCH_LETTER.id, + language: 'fr', + supplierReferences: {}, + }, + { + templateId: POLISH_LETTER.id, + language: 'pl', + supplierReferences: {}, + }, + ], + }, + ], + }, + cascadeIndex: 0, + templateList: [FRENCH_LETTER, POLISH_LETTER, SPANISH_LETTER], + pageHeading: 'Choose language templates', + }, + getMockFormData({ + [`template_${SPANISH_LETTER.id}`]: `${SPANISH_LETTER.id}:fr`, + lockNumber: '42', + }) + ); + + expect(mockUpdateRoutingConfig).toHaveBeenCalledWith( + ROUTING_CONFIG.id, + expect.objectContaining({ + cascade: [ + expect.objectContaining({ + conditionalTemplates: [ + expect.objectContaining({ + language: 'es', + templateId: SPANISH_LETTER.id, + }), + ], + }), + ], + }), + 42 + ); + }); + + test('should preserve non-language conditional templates when updating language templates', async () => { + await chooseLanguageLetterTemplatesAction( + { + messagePlan: { + ...ROUTING_CONFIG, + cascade: [ + { + ...ROUTING_CONFIG.cascade[0], + conditionalTemplates: [ + { + templateId: 'large-print-id', + accessibleFormat: 'x1', + supplierReferences: {}, + }, + { + templateId: FRENCH_LETTER.id, + language: 'fr', + supplierReferences: {}, + }, + ], + }, + ], + }, + cascadeIndex: 0, + templateList: [FRENCH_LETTER, POLISH_LETTER], + pageHeading: 'Choose language templates', + }, + getMockFormData({ + [`template_${POLISH_LETTER.id}`]: `${POLISH_LETTER.id}:pl`, + lockNumber: '42', + }) + ); + + expect(mockUpdateRoutingConfig).toHaveBeenCalledWith( + ROUTING_CONFIG.id, + expect.objectContaining({ + cascade: [ + expect.objectContaining({ + conditionalTemplates: expect.arrayContaining([ + expect.objectContaining({ + accessibleFormat: 'x1', + templateId: 'large-print-id', + }), + expect.objectContaining({ + language: 'pl', + templateId: POLISH_LETTER.id, + }), + ]), + }), + ], + }), + 42 + ); + }); + + test('should return error when selecting templates with duplicate languages', async () => { + const FRENCH_LETTER_2: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'french-2-id', + language: 'fr', + name: 'Another French Letter', + }; + + const result = await chooseLanguageLetterTemplatesAction( + { + messagePlan: ROUTING_CONFIG, + cascadeIndex: 0, + templateList: [FRENCH_LETTER, FRENCH_LETTER_2], + pageHeading: 'Choose language templates', + }, + getMockFormData({ + [`template_${FRENCH_LETTER.id}`]: `${FRENCH_LETTER.id}:fr`, + [`template_${FRENCH_LETTER_2.id}`]: `${FRENCH_LETTER_2.id}:fr`, + lockNumber: '42', + }) + ); + + expect(result.errorType).toBe('duplicate'); + expect(result.errorState?.fieldErrors?.['language-templates']).toHaveLength( + 1 + ); + expect(result.errorState?.fieldErrors?.['language-templates']?.[0]).toBe( + 'Choose only one template for each language' + ); + }); + + test('should return error when lockNumber is missing', async () => { + const result = await chooseLanguageLetterTemplatesAction( + { + messagePlan: ROUTING_CONFIG, + cascadeIndex: 0, + templateList: [FRENCH_LETTER], + pageHeading: 'Choose language templates', + }, + getMockFormData({ + [`template_${FRENCH_LETTER.id}`]: `${FRENCH_LETTER.id}:fr`, + }) + ); + + expect(result.errorState).toBeDefined(); + expect(mockUpdateRoutingConfig).not.toHaveBeenCalled(); + expect(mockRedirect).not.toHaveBeenCalled(); + }); + + test('should return error when lockNumber is invalid', async () => { + const result = await chooseLanguageLetterTemplatesAction( + { + messagePlan: ROUTING_CONFIG, + cascadeIndex: 0, + templateList: [FRENCH_LETTER], + pageHeading: 'Choose language templates', + }, + getMockFormData({ + [`template_${FRENCH_LETTER.id}`]: `${FRENCH_LETTER.id}:fr`, + lockNumber: 'invalid', + }) + ); + + expect(result.errorState).toBeDefined(); + expect(mockUpdateRoutingConfig).not.toHaveBeenCalled(); + expect(mockRedirect).not.toHaveBeenCalled(); + }); +}); + +describe('$ChooseLanguageLetterTemplates Zod schema', () => { + const errorMessage = 'At least one template must be selected'; + const schema = $ChooseLanguageLetterTemplates(errorMessage); + + test('should pass validation when at least one template checkbox is selected', () => { + const validData = { + 'template_abc-123': 'abc-123:fr', + lockNumber: '42', + }; + + const result = schema.safeParse(validData); + + expect(result.success).toBe(true); + }); + + test('should pass validation with multiple template checkboxes selected', () => { + const validData = { + 'template_abc-123': 'abc-123:fr', + 'template_def-456': 'def-456:pl', + lockNumber: '42', + }; + + const result = schema.safeParse(validData); + + expect(result.success).toBe(true); + }); + + test('should fail validation when no template checkboxes are selected', () => { + const invalidData = { + lockNumber: '42', + }; + + const result = schema.safeParse(invalidData); + + expect(result.success).toBe(false); + expect(result.error?.issues[0].message).toBe(errorMessage); + }); + + test('should fail validation when only non-template fields are present', () => { + const invalidData = { + otherField: 'some-value', + lockNumber: '42', + }; + + const result = schema.safeParse(invalidData); + + expect(result.success).toBe(false); + expect(result.error?.issues[0].message).toBe(errorMessage); + }); +}); diff --git a/frontend/src/__tests__/components/forms/ChooseMessageOrder/__snapshots__/ChooseMessageOrder.test.tsx.snap b/frontend/src/__tests__/components/forms/ChooseMessageOrder/__snapshots__/ChooseMessageOrder.test.tsx.snap index 5a2dfa37a..d9c6cdbeb 100644 --- a/frontend/src/__tests__/components/forms/ChooseMessageOrder/__snapshots__/ChooseMessageOrder.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/ChooseMessageOrder/__snapshots__/ChooseMessageOrder.test.tsx.snap @@ -255,6 +255,7 @@ exports[`Choose message order page Client-side validation triggers 1`] = ` Go back @@ -508,6 +509,7 @@ exports[`Choose message order page renders error component 1`] = ` Go back @@ -736,6 +738,7 @@ exports[`Choose message order page renders form 1`] = ` Go back diff --git a/frontend/src/__tests__/components/forms/ChooseTemplateType/__snapshots__/ChooseTemplateType.test.tsx.snap b/frontend/src/__tests__/components/forms/ChooseTemplateType/__snapshots__/ChooseTemplateType.test.tsx.snap index 5ac57da28..b8bc8010f 100644 --- a/frontend/src/__tests__/components/forms/ChooseTemplateType/__snapshots__/ChooseTemplateType.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/ChooseTemplateType/__snapshots__/ChooseTemplateType.test.tsx.snap @@ -4,7 +4,7 @@ exports[`Choose template page Client-side validation triggers 1`] = ` Back to all templates @@ -202,7 +202,7 @@ exports[`Choose template page renders error component 1`] = ` Back to all templates @@ -389,7 +389,7 @@ exports[`Choose template page selects one radio button at a time 1`] = ` Back to all templates diff --git a/frontend/src/__tests__/components/forms/CopyTemplate/__snapshots__/CopyTemplate.test.tsx.snap b/frontend/src/__tests__/components/forms/CopyTemplate/__snapshots__/CopyTemplate.test.tsx.snap index 4bdb03052..c94c538d7 100644 --- a/frontend/src/__tests__/components/forms/CopyTemplate/__snapshots__/CopyTemplate.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/CopyTemplate/__snapshots__/CopyTemplate.test.tsx.snap @@ -4,9 +4,8 @@ exports[`Choose template page Client-side validation triggers 1`] = ` Back to all templates @@ -184,9 +183,8 @@ exports[`Choose template page renders error component 1`] = ` Back to all templates @@ -353,9 +351,8 @@ exports[`Choose template page selects one radio button at a time 1`] = ` Back to all templates 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..bfc6f5a11 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 @@ -10,6 +10,23 @@ exports[`Client-side validation triggers 1`] = `
+

Back to choose a template type @@ -1448,7 +1465,7 @@ exports[`renders page with multiple errors 1`] = ` Back to choose a template type @@ -2224,7 +2241,7 @@ exports[`renders page with preloaded field values 1`] = ` Back to choose a template type 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..40d1fc212 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 @@ -4,7 +4,7 @@ exports[`Client-side validation triggers 1`] = ` Back to choose a template type @@ -488,7 +488,7 @@ exports[`hides right-to-left language warning when language changes 1`] = ` Back to choose a template type @@ -921,7 +921,7 @@ exports[`renders page one error 1`] = ` Back to choose a template type @@ -1416,7 +1416,7 @@ exports[`renders page with multiple campaign ids 1`] = ` Back to choose a template type @@ -1891,7 +1891,7 @@ exports[`renders page with multiple errors 1`] = ` Back to choose a template type @@ -2422,7 +2422,7 @@ exports[`renders page with preloaded field values 1`] = ` Back to choose a template type @@ -2881,7 +2881,7 @@ exports[`shows right-to-left language warning when language changes 1`] = ` Back to choose a template type diff --git a/frontend/src/__tests__/components/forms/MessagePlan/__snapshots__/MessagePlan.test.tsx.snap b/frontend/src/__tests__/components/forms/MessagePlan/__snapshots__/MessagePlan.test.tsx.snap index 35d09c59e..aa719cbc6 100644 --- a/frontend/src/__tests__/components/forms/MessagePlan/__snapshots__/MessagePlan.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/MessagePlan/__snapshots__/MessagePlan.test.tsx.snap @@ -164,7 +164,7 @@ exports[`renders errors 1`] = ` Go back @@ -299,7 +299,7 @@ exports[`renders form with children 1`] = ` Go back @@ -455,7 +455,7 @@ exports[`renders form with select for multiple campaign ids 1`] = ` Go back @@ -590,7 +590,7 @@ exports[`renders form with single campaign id displayed 1`] = ` Go back 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..52ee15285 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 @@ -4,6 +4,7 @@ exports[`Client-side validation triggers 1`] = ` Back to choose a template type @@ -16,6 +17,23 @@ exports[`Client-side validation triggers 1`] = `
+

Back to choose a template type @@ -1372,6 +1391,7 @@ exports[`renders page one error 1`] = ` Back to choose a template type @@ -2096,6 +2116,7 @@ exports[`renders page with multiple errors 1`] = ` Back to choose a template type @@ -2834,6 +2855,7 @@ exports[`renders page with preloaded field values 1`] = ` Back to choose a template type diff --git a/frontend/src/__tests__/components/forms/PreviewEmailTemplate/__snapshots__/PreviewEmailTemplate.test.tsx.snap b/frontend/src/__tests__/components/forms/PreviewEmailTemplate/__snapshots__/PreviewEmailTemplate.test.tsx.snap index 002988178..ff2e3aef9 100644 --- a/frontend/src/__tests__/components/forms/PreviewEmailTemplate/__snapshots__/PreviewEmailTemplate.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/PreviewEmailTemplate/__snapshots__/PreviewEmailTemplate.test.tsx.snap @@ -4,9 +4,8 @@ exports[`Preview email form renders Routing feature flag - Disabled Client-side Back to all templates @@ -76,7 +75,8 @@ exports[`Preview email form renders Routing feature flag - Disabled Client-side Template ID
template-id
@@ -266,7 +266,7 @@ exports[`Preview email form renders Routing feature flag - Disabled Client-side

Back to all templates @@ -282,9 +282,8 @@ exports[`Preview email form renders Routing feature flag - Disabled matches erro Back to all templates @@ -354,7 +353,8 @@ exports[`Preview email form renders Routing feature flag - Disabled matches erro Template ID

template-id
@@ -544,7 +544,7 @@ exports[`Preview email form renders Routing feature flag - Disabled matches erro

Back to all templates @@ -560,9 +560,8 @@ exports[`Preview email form renders matches snapshot when navigating from edit s Back to all templates @@ -612,7 +611,8 @@ exports[`Preview email form renders matches snapshot when navigating from edit s Template ID

template-id
@@ -790,7 +790,7 @@ exports[`Preview email form renders matches snapshot when navigating from edit s

Back to all templates @@ -806,9 +806,8 @@ exports[`Preview email form renders matches snapshot when navigating from edit s Back to all templates @@ -858,7 +857,8 @@ exports[`Preview email form renders matches snapshot when navigating from edit s Template ID

template-id
@@ -960,7 +960,7 @@ exports[`Preview email form renders matches snapshot when navigating from edit s

Back to all templates @@ -976,9 +976,8 @@ exports[`Preview email form renders matches snapshot when navigating from manage Back to all templates @@ -1023,7 +1022,8 @@ exports[`Preview email form renders matches snapshot when navigating from manage Template ID

template-id
@@ -1201,7 +1201,7 @@ exports[`Preview email form renders matches snapshot when navigating from manage

Back to all templates @@ -1217,9 +1217,8 @@ exports[`Preview email form renders matches snapshot when navigating from manage Back to all templates @@ -1264,7 +1263,8 @@ exports[`Preview email form renders matches snapshot when navigating from manage Template ID

template-id
@@ -1366,7 +1366,7 @@ exports[`Preview email form renders matches snapshot when navigating from manage

Back to all templates diff --git a/frontend/src/__tests__/components/forms/PreviewNHSAppTemplate/__snapshots__/PreviewNHSAppTemplate.test.tsx.snap b/frontend/src/__tests__/components/forms/PreviewNHSAppTemplate/__snapshots__/PreviewNHSAppTemplate.test.tsx.snap index 65569faf0..407bcc058 100644 --- a/frontend/src/__tests__/components/forms/PreviewNHSAppTemplate/__snapshots__/PreviewNHSAppTemplate.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/PreviewNHSAppTemplate/__snapshots__/PreviewNHSAppTemplate.test.tsx.snap @@ -4,9 +4,8 @@ exports[`Preview nhs app form renders Routing feature flag - Disabled Client-sid Back to all templates @@ -76,7 +75,8 @@ exports[`Preview nhs app form renders Routing feature flag - Disabled Client-sid Template ID

template-id
@@ -242,7 +242,7 @@ exports[`Preview nhs app form renders Routing feature flag - Disabled Client-sid

Back to all templates @@ -258,9 +258,8 @@ exports[`Preview nhs app form renders Routing feature flag - Disabled matches er Back to all templates @@ -330,7 +329,8 @@ exports[`Preview nhs app form renders Routing feature flag - Disabled matches er Template ID

template-id
@@ -496,7 +496,7 @@ exports[`Preview nhs app form renders Routing feature flag - Disabled matches er

Back to all templates @@ -512,9 +512,8 @@ exports[`Preview nhs app form renders matches snapshot when navigating from edit Back to all templates @@ -564,7 +563,8 @@ exports[`Preview nhs app form renders matches snapshot when navigating from edit Template ID

template-id
@@ -718,7 +718,7 @@ exports[`Preview nhs app form renders matches snapshot when navigating from edit

Back to all templates @@ -734,9 +734,8 @@ exports[`Preview nhs app form renders matches snapshot when navigating from edit Back to all templates @@ -786,7 +785,8 @@ exports[`Preview nhs app form renders matches snapshot when navigating from edit Template ID

template-id
@@ -864,7 +864,7 @@ exports[`Preview nhs app form renders matches snapshot when navigating from edit

Back to all templates @@ -880,9 +880,8 @@ exports[`Preview nhs app form renders matches snapshot when navigating from mana Back to all templates @@ -927,7 +926,8 @@ exports[`Preview nhs app form renders matches snapshot when navigating from mana Template ID

template-id
@@ -1081,7 +1081,7 @@ exports[`Preview nhs app form renders matches snapshot when navigating from mana

Back to all templates @@ -1097,9 +1097,8 @@ exports[`Preview nhs app form renders matches snapshot when navigating from mana Back to all templates @@ -1144,7 +1143,8 @@ exports[`Preview nhs app form renders matches snapshot when navigating from mana Template ID

template-id
@@ -1222,7 +1222,7 @@ exports[`Preview nhs app form renders matches snapshot when navigating from mana

Back to all templates diff --git a/frontend/src/__tests__/components/forms/PreviewSMSTemplate/__snapshots__/PreviewSMSTemplate.test.tsx.snap b/frontend/src/__tests__/components/forms/PreviewSMSTemplate/__snapshots__/PreviewSMSTemplate.test.tsx.snap index 738a78982..b6e2d74e4 100644 --- a/frontend/src/__tests__/components/forms/PreviewSMSTemplate/__snapshots__/PreviewSMSTemplate.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/PreviewSMSTemplate/__snapshots__/PreviewSMSTemplate.test.tsx.snap @@ -4,9 +4,8 @@ exports[`Review sms form renders Routing feature flag - Disabled Client-side val Back to all templates @@ -76,7 +75,8 @@ exports[`Review sms form renders Routing feature flag - Disabled Client-side val Template ID

template-id
@@ -241,7 +241,7 @@ exports[`Review sms form renders Routing feature flag - Disabled Client-side val

Back to all templates @@ -257,9 +257,8 @@ exports[`Review sms form renders Routing feature flag - Disabled matches error s Back to all templates @@ -329,7 +328,8 @@ exports[`Review sms form renders Routing feature flag - Disabled matches error s Template ID

template-id
@@ -494,7 +494,7 @@ exports[`Review sms form renders Routing feature flag - Disabled matches error s

Back to all templates @@ -510,9 +510,8 @@ exports[`Review sms form renders matches snapshot when navigating from edit scre Back to all templates @@ -562,7 +561,8 @@ exports[`Review sms form renders matches snapshot when navigating from edit scre Template ID

template-id
@@ -715,7 +715,7 @@ exports[`Review sms form renders matches snapshot when navigating from edit scre

Back to all templates @@ -731,9 +731,8 @@ exports[`Review sms form renders matches snapshot when navigating from edit scre Back to all templates @@ -783,7 +782,8 @@ exports[`Review sms form renders matches snapshot when navigating from edit scre Template ID

template-id
@@ -860,7 +860,7 @@ exports[`Review sms form renders matches snapshot when navigating from edit scre

Back to all templates @@ -876,9 +876,8 @@ exports[`Review sms form renders matches snapshot when navigating from manage te Back to all templates @@ -923,7 +922,8 @@ exports[`Review sms form renders matches snapshot when navigating from manage te Template ID

template-id
@@ -1076,7 +1076,7 @@ exports[`Review sms form renders matches snapshot when navigating from manage te

Back to all templates @@ -1092,9 +1092,8 @@ exports[`Review sms form renders matches snapshot when navigating from manage te Back to all templates @@ -1139,7 +1138,8 @@ exports[`Review sms form renders matches snapshot when navigating from manage te Template ID

template-id
@@ -1216,7 +1216,7 @@ exports[`Review sms form renders matches snapshot when navigating from manage te

Back to all templates 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..a229b5cc2 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 @@ -922,6 +922,7 @@ exports[`CreateSmsTemplate component renders page with back link if initial stat Back to choose a template type diff --git a/frontend/src/__tests__/components/forms/SubmitTemplate/__snapshots__/SubmitDigitalTemplate.test.tsx.snap b/frontend/src/__tests__/components/forms/SubmitTemplate/__snapshots__/SubmitDigitalTemplate.test.tsx.snap index ea5bbae2e..071a5b88f 100644 --- a/frontend/src/__tests__/components/forms/SubmitTemplate/__snapshots__/SubmitDigitalTemplate.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/SubmitTemplate/__snapshots__/SubmitDigitalTemplate.test.tsx.snap @@ -93,10 +93,9 @@ exports[`SubmitDigitalTemplate component should render 1`] = ` Go back diff --git a/frontend/src/__tests__/components/forms/SubmitTemplate/__snapshots__/SubmitLetterTemplate.test.tsx.snap b/frontend/src/__tests__/components/forms/SubmitTemplate/__snapshots__/SubmitLetterTemplate.test.tsx.snap index 18d6aa31b..4c2c0b4ba 100644 --- a/frontend/src/__tests__/components/forms/SubmitTemplate/__snapshots__/SubmitLetterTemplate.test.tsx.snap +++ b/frontend/src/__tests__/components/forms/SubmitTemplate/__snapshots__/SubmitLetterTemplate.test.tsx.snap @@ -93,9 +93,9 @@ exports[`SubmitLetterTemplate component should render 1`] = ` Go back @@ -214,9 +214,9 @@ exports[`SubmitLetterTemplate component should render with client proofing disab Go back diff --git a/frontend/src/__tests__/components/molecules/ChannelTemplates.test.tsx b/frontend/src/__tests__/components/molecules/ChannelTemplates.test.tsx index d4ee1ea73..cfcc1f66e 100644 --- a/frontend/src/__tests__/components/molecules/ChannelTemplates.test.tsx +++ b/frontend/src/__tests__/components/molecules/ChannelTemplates.test.tsx @@ -27,6 +27,7 @@ describe('ChannelTemplates', () => { errorState={null} selectedTemplate={null} routingConfigId='abc' + lockNumber={5} /> ); @@ -45,6 +46,7 @@ describe('ChannelTemplates', () => { errorState={null} selectedTemplate={EMAIL_TEMPLATE.id} routingConfigId='abc' + lockNumber={5} /> ); @@ -70,6 +72,7 @@ describe('ChannelTemplates', () => { }} selectedTemplate={null} routingConfigId='abc' + lockNumber={5} /> ); diff --git a/frontend/src/__tests__/components/molecules/LanguageLetterTemplates.test.tsx b/frontend/src/__tests__/components/molecules/LanguageLetterTemplates.test.tsx new file mode 100644 index 000000000..55781a724 --- /dev/null +++ b/frontend/src/__tests__/components/molecules/LanguageLetterTemplates.test.tsx @@ -0,0 +1,208 @@ +import { LanguageLetterTemplates } from '@molecules/LanguageLetterTemplates/LanguageLetterTemplates'; +import { LETTER_TEMPLATE } from '@testhelpers/helpers'; +import { render } from '@testing-library/react'; +import { usePathname } from 'next/navigation'; +import { LetterTemplate } from 'nhs-notify-web-template-management-utils'; + +jest.mock('next/navigation'); + +jest + .mocked(usePathname) + .mockReturnValue( + 'message-plans/choose-other-language-letter-template/testid' + ); + +const FRENCH_LETTER_TEMPLATE: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'french-template-id', + name: 'French Letter Template', + language: 'fr', +}; + +const POLISH_LETTER_TEMPLATE: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'polish-template-id', + name: 'Polish Letter Template', + language: 'pl', +}; + +const GERMAN_LETTER_TEMPLATE: LetterTemplate = { + ...LETTER_TEMPLATE, + id: 'german-template-id', + name: 'German Letter Template', + language: 'de', +}; + +describe('LanguageLetterTemplates', () => { + it('renders list of foreign language letter templates', () => { + const container = render( +

+ + + ); + + expect(container.asFragment()).toMatchSnapshot(); + expect(container.getByText('French Letter Template')).toBeInTheDocument(); + expect(container.getByText('Polish Letter Template')).toBeInTheDocument(); + expect(container.getByText('German Letter Template')).toBeInTheDocument(); + }); + + it('renders templates list with pre-selected templates', () => { + const container = render( +
+ + + ); + + expect(container.asFragment()).toMatchSnapshot(); + expect( + container.getByTestId(`${FRENCH_LETTER_TEMPLATE.id}-checkbox`) + ).toBeChecked(); + expect( + container.getByTestId(`${POLISH_LETTER_TEMPLATE.id}-checkbox`) + ).toBeChecked(); + expect( + container.getByTestId(`${GERMAN_LETTER_TEMPLATE.id}-checkbox`) + ).not.toBeChecked(); + }); + + it('renders templates with error state', () => { + const container = render( +
+ + + ); + + expect(container.asFragment()).toMatchSnapshot(); + expect( + container.getByText('Please select at least one language template') + ).toBeInTheDocument(); + }); + + it('renders templates with duplicate language error state', () => { + const container = render( +
+ + + ); + + expect(container.asFragment()).toMatchSnapshot(); + expect( + container.getByText('Choose only one template for each language') + ).toBeInTheDocument(); + }); + + it('renders preview links with correct hrefs for each template', () => { + const container = render( +
+ + + ); + + const previewLinkFrench = container.getByTestId( + `${FRENCH_LETTER_TEMPLATE.id}-preview-link` + ); + expect(previewLinkFrench).toHaveAttribute( + 'href', + '/message-plans/choose-other-language-letter-template/test-routing-id/preview-template/french-template-id?lockNumber=5' + ); + const previewLinkPolish = container.getByTestId( + `${POLISH_LETTER_TEMPLATE.id}-preview-link` + ); + expect(previewLinkPolish).toHaveAttribute( + 'href', + '/message-plans/choose-other-language-letter-template/test-routing-id/preview-template/polish-template-id?lockNumber=5' + ); + }); + + it('renders correct checkbox names for form submission', () => { + const container = render( +
+ + + ); + + const frenchCheckbox = container.getByTestId( + `${FRENCH_LETTER_TEMPLATE.id}-checkbox` + ); + const polishCheckbox = container.getByTestId( + `${POLISH_LETTER_TEMPLATE.id}-checkbox` + ); + + expect(frenchCheckbox).toHaveAttribute( + 'name', + `template_${FRENCH_LETTER_TEMPLATE.id}` + ); + expect(polishCheckbox).toHaveAttribute( + 'name', + `template_${POLISH_LETTER_TEMPLATE.id}` + ); + }); +}); diff --git a/frontend/src/__tests__/components/molecules/MessagePlanBlock.test.tsx b/frontend/src/__tests__/components/molecules/MessagePlanBlock.test.tsx index 7bdb3eb8b..30773d4ca 100644 --- a/frontend/src/__tests__/components/molecules/MessagePlanBlock.test.tsx +++ b/frontend/src/__tests__/components/molecules/MessagePlanBlock.test.tsx @@ -3,7 +3,8 @@ import { render, screen } from '@testing-library/react'; import { MessagePlanBlock } from '@molecules/MessagePlanBlock/MessagePlanBlock'; import type { CascadeItem, Channel } from 'nhs-notify-backend-client'; import type { TemplateDto } from 'nhs-notify-backend-client'; -import { EMAIL_TEMPLATE } from '@testhelpers/helpers'; +import { EMAIL_TEMPLATE, LETTER_TEMPLATE } from '@testhelpers/helpers'; +import { MessagePlanTemplates } from '@utils/routing-utils'; function buildCascadeItem(channel: Channel): CascadeItem { return { @@ -19,6 +20,8 @@ const mockTemplate: TemplateDto = { name: 'Test email template', }; +const emptyConditionalTemplates: MessagePlanTemplates = {}; + describe('MessagePlanBlock', () => { it('should render the step number and the heading for the first cascade item', () => { const channelItem = buildCascadeItem('EMAIL'); @@ -28,6 +31,7 @@ describe('MessagePlanBlock', () => { index={0} channelItem={channelItem} routingConfigId='test-routing-config-id' + conditionalTemplates={emptyConditionalTemplates} lockNumber={42} /> ); @@ -48,6 +52,7 @@ describe('MessagePlanBlock', () => { index={2} channelItem={channelItem} routingConfigId='test-routing-config-id' + conditionalTemplates={emptyConditionalTemplates} lockNumber={42} /> ); @@ -67,6 +72,7 @@ describe('MessagePlanBlock', () => { index={0} channelItem={channelItem} routingConfigId='test-routing-config-id' + conditionalTemplates={emptyConditionalTemplates} lockNumber={42} /> ); @@ -84,8 +90,9 @@ describe('MessagePlanBlock', () => { ); @@ -99,8 +106,9 @@ describe('MessagePlanBlock', () => { ); @@ -126,6 +134,7 @@ describe('MessagePlanBlock', () => { index={0} channelItem={channelItem} routingConfigId='test-routing-config-id' + conditionalTemplates={emptyConditionalTemplates} lockNumber={42} /> ); @@ -139,13 +148,109 @@ describe('MessagePlanBlock', () => { }) ).not.toBeInTheDocument(); expect( - screen.queryByRole('link', { + screen.queryByRole('button', { name: 'Remove Text message (SMS) template', }) ).not.toBeInTheDocument(); }); }); + describe('when channel is LETTER', () => { + it('should render conditional templates section', () => { + const channelItem = buildCascadeItem('LETTER'); + + render( + + ); + + expect( + screen.getByTestId('message-plan-conditional-templates') + ).toBeInTheDocument(); + }); + + it('should render accessible format templates', () => { + const channelItem = buildCascadeItem('LETTER'); + + render( + + ); + + expect( + screen.getByRole('heading', { + level: 3, + name: 'Large print letter (optional)', + }) + ).toBeInTheDocument(); + }); + + it('should render language templates section', () => { + const channelItem = buildCascadeItem('LETTER'); + + render( + + ); + + expect( + screen.getByRole('heading', { + level: 3, + name: 'Other language letters (optional)', + }) + ).toBeInTheDocument(); + }); + + it('should display conditional template names when provided', () => { + const largePrintTemplate: TemplateDto = { + ...LETTER_TEMPLATE, + id: 'large-print-id', + name: 'Large print template', + }; + + const channelItem: CascadeItem = { + ...buildCascadeItem('LETTER'), + conditionalTemplates: [ + { + accessibleFormat: 'x1', + templateId: 'large-print-id', + }, + ], + }; + + const conditionalTemplates: MessagePlanTemplates = { + 'large-print-id': largePrintTemplate, + }; + + render( + + ); + + expect(screen.getByText('Large print template')).toBeInTheDocument(); + }); + }); + describe.each(['NHSAPP', 'EMAIL', 'SMS', 'LETTER'] as const)( 'for channel %s with template', (channel) => { @@ -155,8 +260,9 @@ describe('MessagePlanBlock', () => { ); @@ -175,6 +281,7 @@ describe('MessagePlanBlock', () => { index={0} channelItem={channelItem} routingConfigId='test-routing-config-id' + conditionalTemplates={emptyConditionalTemplates} lockNumber={42} /> ); diff --git a/frontend/src/__tests__/components/molecules/MessagePlanChannelTemplate.test.tsx b/frontend/src/__tests__/components/molecules/MessagePlanChannelTemplate.test.tsx index 5583ef37a..6d7cda97b 100644 --- a/frontend/src/__tests__/components/molecules/MessagePlanChannelTemplate.test.tsx +++ b/frontend/src/__tests__/components/molecules/MessagePlanChannelTemplate.test.tsx @@ -1,5 +1,9 @@ import { render, screen } from '@testing-library/react'; -import { MessagePlanChannelTemplate } from '@molecules/MessagePlanChannelTemplate/MessagePlanChannelTemplate'; +import { + MessagePlanChannelTemplate, + MessagePlanAccessibleFormatTemplate, + MessagePlanLanguageTemplate, +} from '@molecules/MessagePlanChannelTemplate/MessagePlanChannelTemplate'; import type { TemplateDto } from 'nhs-notify-backend-client'; describe('MessagePlanChannelTemplate', () => { @@ -33,7 +37,10 @@ describe('MessagePlanChannelTemplate', () => { it('should display the heading with the "(optional)" suffix', () => { expect( - screen.getByRole('heading', { level: 3, name: 'Letter (optional)' }) + screen.getByRole('heading', { + level: 3, + name: 'Standard English letter (optional)', + }) ).toBeInTheDocument(); }); }); @@ -64,7 +71,7 @@ describe('MessagePlanChannelTemplate', () => { screen.queryByRole('link', { name: 'Change NHS App template' }) ).not.toBeInTheDocument(); expect( - screen.queryByRole('link', { name: 'Remove NHS App template' }) + screen.queryByRole('button', { name: 'Remove NHS App template' }) ).not.toBeInTheDocument(); }); }); @@ -109,14 +116,15 @@ describe('MessagePlanChannelTemplate', () => { expect(removeButton).toBeInTheDocument(); expect(form).toBeInTheDocument(); - const channelInput = form?.querySelector('input[name="channel"]'); const routingConfigIdInput = form?.querySelector( 'input[name="routingConfigId"]' ); - expect(channelInput).toHaveAttribute('type', 'hidden'); - expect(channelInput).toHaveAttribute('value', 'SMS'); + const templateIdInput = form?.querySelector('input[name="templateId"]'); + expect(routingConfigIdInput).toHaveAttribute('type', 'hidden'); expect(routingConfigIdInput).toHaveAttribute('value', routingConfigId); + expect(templateIdInput).toHaveAttribute('type', 'hidden'); + expect(templateIdInput).toHaveAttribute('value', testTemplate.id); }); it('should not display the "Choose template" link', () => { @@ -163,3 +171,247 @@ describe('MessagePlanChannelTemplate', () => { } ); }); + +describe('MessagePlanAccessibleFormatTemplate', () => { + const routingConfigId = 'test-routing-config-id'; + + it('should display the accessible format heading', () => { + render( + + ); + + expect( + screen.getByRole('heading', { + level: 3, + name: 'Large print letter (optional)', + }) + ).toBeInTheDocument(); + }); + + describe('when no template is selected', () => { + beforeEach(() => { + render( + + ); + }); + + it('should not display template name', () => { + expect(screen.queryByTestId('template-names')).not.toBeInTheDocument(); + }); + + it('should show the "Choose template" link (singular) with accessible name and href', () => { + const link = screen.getByRole('link', { + name: 'Choose Large print letter template', + }); + expect(link).toHaveAttribute( + 'href', + `/message-plans/choose-large-print-letter-template/${routingConfigId}?lockNumber=42` + ); + }); + }); + + describe('when a template has been selected', () => { + const testTemplate = { + id: 'test-large-print-id', + name: 'Large print letter template', + } as TemplateDto; + + beforeEach(() => { + render( + + ); + }); + + it('should display the selected template name', () => { + expect(screen.getByText(testTemplate.name)).toBeInTheDocument(); + }); + + it('should display the "Change template" link', () => { + const link = screen.getByRole('link', { + name: 'Change Large print letter template', + }); + expect(link).toHaveAttribute( + 'href', + `/message-plans/choose-large-print-letter-template/${routingConfigId}?lockNumber=42` + ); + }); + + it('should display the "Remove template" button', () => { + const removeButton = screen.getByRole('button', { + name: 'Remove Large print letter template', + }); + expect(removeButton).toBeInTheDocument(); + }); + + it.each(['x1'] as const)( + 'should match snapshot for empty state for letter type (%s)', + (accessibleFormat) => { + const { container } = render( + + ); + + expect(container).toMatchSnapshot(); + } + ); + + it.each(['x1'] as const)( + 'should match snapshot for template selected state for letter type (%s)', + (accessibleFormat) => { + const { container } = render( + + ); + + expect(container).toMatchSnapshot(); + } + ); + }); +}); + +describe('MessagePlanLanguageTemplate', () => { + const routingConfigId = 'test-routing-config-id'; + + it('should display the language templates heading', () => { + render( + + ); + + expect( + screen.getByRole('heading', { + level: 3, + name: 'Other language letters (optional)', + }) + ).toBeInTheDocument(); + }); + + describe('when no templates are selected', () => { + beforeEach(() => { + render( + + ); + }); + + it('should show the "Choose templates" link (plural) with accessible name and href', () => { + const link = screen.getByRole('link', { + name: 'Choose Other language letters templates', + }); + expect(link).toHaveAttribute( + 'href', + `/message-plans/choose-other-language-letter-template/${routingConfigId}?lockNumber=42` + ); + }); + }); + + describe('when multiple templates are selected', () => { + const testTemplates = [ + { + id: 'welsh-template-id', + name: 'Welsh letter template', + } as TemplateDto, + { + id: 'polish-template-id', + name: 'Polish letter template', + } as TemplateDto, + ]; + + beforeEach(() => { + render( + + ); + }); + + it('should display all selected template names', () => { + expect(screen.getByText('Welsh letter template')).toBeInTheDocument(); + expect(screen.getByText('Polish letter template')).toBeInTheDocument(); + }); + + it('should display the "Change templates" link (plural)', () => { + const link = screen.getByRole('link', { + name: 'Change Other language letters templates', + }); + expect(link).toHaveAttribute( + 'href', + `/message-plans/choose-other-language-letter-template/${routingConfigId}?lockNumber=42` + ); + }); + + it('should display the "Remove all templates" button with all template IDs', () => { + const removeButton = screen.getByRole('button', { + name: 'Remove all Other language letters templates', + }); + expect(removeButton).toBeInTheDocument(); + + const form = removeButton.closest('form'); + const templateIdInputs = form?.querySelectorAll( + 'input[name="templateId"]' + ); + + expect(templateIdInputs).toHaveLength(2); + expect(templateIdInputs?.[0]).toHaveAttribute( + 'value', + 'welsh-template-id' + ); + expect(templateIdInputs?.[1]).toHaveAttribute( + 'value', + 'polish-template-id' + ); + }); + + it('should match snapshot for empty state', () => { + const { container } = render( + + ); + + expect(container).toMatchSnapshot(); + }); + + it('should match snapshot with multiple templates selected', () => { + const { container } = render( + + ); + + expect(container).toMatchSnapshot(); + }); + }); +}); diff --git a/frontend/src/__tests__/components/molecules/MessagePlanConditionalTemplates.test.tsx b/frontend/src/__tests__/components/molecules/MessagePlanConditionalTemplates.test.tsx new file mode 100644 index 000000000..5c28c063a --- /dev/null +++ b/frontend/src/__tests__/components/molecules/MessagePlanConditionalTemplates.test.tsx @@ -0,0 +1,416 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { MessagePlanConditionalLetterTemplates } from '@molecules/MessagePlanConditionalTemplates/MessagePlanConditionalTemplates'; +import type { CascadeItem, TemplateDto } from 'nhs-notify-backend-client'; +import { MessagePlanTemplates } from '@utils/routing-utils'; + +const routingConfigId = 'test-routing-config-id'; + +function buildLetterCascadeItem( + conditionalTemplates?: CascadeItem['conditionalTemplates'] +): CascadeItem { + return { + cascadeGroups: [], + channel: 'LETTER', + channelType: 'primary', + defaultTemplateId: 'standard-letter-id', + conditionalTemplates, + }; +} + +describe('MessagePlanConditionalLetterTemplates', () => { + it('should return null when channel is not LETTER', () => { + const cascadeItem: CascadeItem = { + cascadeGroups: [], + channel: 'EMAIL', + channelType: 'primary', + defaultTemplateId: 'email-id', + }; + + const { container } = render( + + ); + + expect(container.firstChild).toBeNull(); + expect(container).toBeEmptyDOMElement(); + }); + + it('should render fallback conditions for LETTER channel', () => { + const cascadeItem = buildLetterCascadeItem(); + + render( + + ); + + expect( + screen.getByTestId('message-plan-fallback-conditions-LETTER') + ).toBeInTheDocument(); + }); + + it('should render large print letter template section', () => { + const cascadeItem = buildLetterCascadeItem(); + + render( + + ); + + expect( + screen.getByRole('heading', { + level: 3, + name: 'Large print letter (optional)', + }) + ).toBeInTheDocument(); + }); + + it('should render language templates section', () => { + const cascadeItem = buildLetterCascadeItem(); + + render( + + ); + + expect( + screen.getByRole('heading', { + level: 3, + name: 'Other language letters (optional)', + }) + ).toBeInTheDocument(); + }); + + describe('with no templates selected', () => { + it('should not display template names', () => { + const cascadeItem = buildLetterCascadeItem(); + + render( + + ); + + expect(screen.queryByTestId('template-names')).not.toBeInTheDocument(); + }); + + it('should show the "Choose" links with correct href', () => { + const cascadeItem = buildLetterCascadeItem(); + + render( + + ); + + const largePrintChooseLink = screen.getByRole('link', { + name: 'Choose Large print letter template', + }); + expect(largePrintChooseLink).toHaveAttribute( + 'href', + `/message-plans/choose-large-print-letter-template/${routingConfigId}?lockNumber=42` + ); + + const languageChooseLink = screen.getByRole('link', { + name: 'Choose Other language letters templates', + }); + expect(languageChooseLink).toHaveAttribute( + 'href', + `/message-plans/choose-other-language-letter-template/${routingConfigId}?lockNumber=42` + ); + }); + + it('should not show the "Change" or "Remove" links', () => { + const cascadeItem = buildLetterCascadeItem(); + + render( + + ); + + expect( + screen.queryByRole('link', { + name: 'Change Large print letter template', + }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole('button', { + name: 'Remove Large print letter template', + }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole('link', { + name: 'Change Other language letters templates', + }) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole('button', { + name: 'Remove Other language letters templates', + }) + ).not.toBeInTheDocument(); + }); + + it('should match snapshot', () => { + const cascadeItem = buildLetterCascadeItem(); + + const { container } = render( + + ); + + expect(container).toMatchSnapshot(); + }); + + it('should handle cascade item with empty conditionalTemplates array', () => { + const cascadeItem = buildLetterCascadeItem([]); + + render( + + ); + + const largePrintChooseLink = screen.getByRole('link', { + name: 'Choose Large print letter template', + }); + expect(largePrintChooseLink).toBeInTheDocument(); + + const languageChooseLink = screen.getByRole('link', { + name: 'Choose Other language letters templates', + }); + expect(languageChooseLink).toBeInTheDocument(); + }); + }); + + describe('with templates selected', () => { + describe('accessible format', () => { + const largePrintTemplate: TemplateDto = { + id: 'large-print-id', + name: 'Large print covid reminder', + } as TemplateDto; + + let cascadeItem: CascadeItem; + let templates: MessagePlanTemplates; + + beforeEach(() => { + cascadeItem = buildLetterCascadeItem([ + { + accessibleFormat: 'x1', + templateId: 'large-print-id', + }, + ]); + + templates = { + 'large-print-id': largePrintTemplate, + }; + }); + + it('should display the name of the selected large print letter template', () => { + render( + + ); + + const templateName = screen.getByTestId('template-name-x1'); + expect(templateName).toHaveTextContent('Large print covid reminder'); + }); + + it('should display the "Change" and "Remove" links', () => { + render( + + ); + + const changeLink = screen.getByRole('link', { + name: 'Change Large print letter template', + }); + expect(changeLink).toBeInTheDocument(); + expect(changeLink).toHaveAttribute( + 'href', + `/message-plans/choose-large-print-letter-template/${routingConfigId}?lockNumber=42` + ); + + expect( + screen.getByRole('button', { + name: 'Remove Large print letter template', + }) + ).toBeInTheDocument(); + }); + }); + + describe('translated languages', () => { + const polishTemplate: TemplateDto = { + id: 'polish-id', + name: 'Polish covid reminder', + } as TemplateDto; + + const frenchTemplate: TemplateDto = { + id: 'french-id', + name: 'French covid reminder', + } as TemplateDto; + + let cascadeItem: CascadeItem; + let templates: MessagePlanTemplates; + + beforeEach(() => { + cascadeItem = buildLetterCascadeItem([ + { + language: 'pl', + templateId: 'polish-id', + }, + { + language: 'fr', + templateId: 'french-id', + }, + ]); + + templates = { + 'polish-id': polishTemplate, + 'french-id': frenchTemplate, + }; + }); + + it('should display multiple template names when multiple languages are selected', () => { + render( + + ); + + const templateNames = screen.getAllByTestId( + 'template-name-foreign-language' + ); + expect(templateNames).toHaveLength(2); + expect(templateNames[0]).toHaveTextContent('Polish covid reminder'); + expect(templateNames[1]).toHaveTextContent('French covid reminder'); + }); + + it('should show the "Change" and "Remove all" links', () => { + render( + + ); + + const changeLink = screen.getByRole('link', { + name: 'Change Other language letters templates', + }); + expect(changeLink).toBeInTheDocument(); + expect(changeLink).toHaveAttribute( + 'href', + `/message-plans/choose-other-language-letter-template/${routingConfigId}?lockNumber=42` + ); + + expect( + screen.getByRole('button', { + name: 'Remove all Other language letters templates', + }) + ).toBeInTheDocument(); + }); + }); + + it('should match snapshot with all conditional template types selected', () => { + const cascadeItem = buildLetterCascadeItem([ + { + accessibleFormat: 'x1', + templateId: 'large-print-id', + }, + { + language: 'pl', + templateId: 'polish-id', + }, + { + language: 'fr', + templateId: 'french-id', + }, + ]); + + const templates: MessagePlanTemplates = { + 'large-print-id': { + id: 'large-print-id', + name: 'Large print template', + } as TemplateDto, + 'polish-id': { + id: 'polish-id', + name: 'Polish template', + } as TemplateDto, + 'french-id': { + id: 'french-id', + name: 'French template', + } as TemplateDto, + }; + + const { container } = render( + + ); + + expect(container).toMatchSnapshot(); + }); + }); +}); diff --git a/frontend/src/__tests__/components/molecules/PreviewTemplateFromMessagePlan.test.tsx b/frontend/src/__tests__/components/molecules/PreviewTemplateFromMessagePlan.test.tsx new file mode 100644 index 000000000..0ba1d95a7 --- /dev/null +++ b/frontend/src/__tests__/components/molecules/PreviewTemplateFromMessagePlan.test.tsx @@ -0,0 +1,70 @@ +import { PreviewTemplateFromMessagePlan } from '@molecules/PreviewTemplateFromMessagePlan/PreviewTemplateFromMessagePlan'; +import { render, screen } from '@testing-library/react'; +import { + EMAIL_TEMPLATE, + LARGE_PRINT_LETTER_TEMPLATE, + LETTER_TEMPLATE, + NHS_APP_TEMPLATE, + ROUTING_CONFIG, + SMS_TEMPLATE, +} from '@testhelpers/helpers'; +import PreviewTemplateDetailsLetter from '@molecules/PreviewTemplateDetails/PreviewTemplateDetailsLetter'; +import PreviewTemplateDetailsEmail from '@molecules/PreviewTemplateDetails/PreviewTemplateDetailsEmail'; +import PreviewTemplateDetailsSms from '@molecules/PreviewTemplateDetails/PreviewTemplateDetailsSms'; +import PreviewTemplateDetailsNhsApp from '@molecules/PreviewTemplateDetails/PreviewTemplateDetailsNhsApp'; + +describe('PreviewTemplateFromMessagePlan', () => { + it.each([ + { + name: 'NHS App', + template: { ...NHS_APP_TEMPLATE }, + previewComponent: PreviewTemplateDetailsNhsApp, + expectedBackLink: `/message-plans/choose-nhs-app-template/${ROUTING_CONFIG.id}?lockNumber=5`, + }, + { + name: 'Email', + template: { ...EMAIL_TEMPLATE }, + previewComponent: PreviewTemplateDetailsEmail, + expectedBackLink: `/message-plans/choose-email-template/${ROUTING_CONFIG.id}?lockNumber=5`, + }, + { + name: 'SMS', + template: { ...SMS_TEMPLATE }, + previewComponent: PreviewTemplateDetailsSms, + expectedBackLink: `/message-plans/choose-text-message-template/${ROUTING_CONFIG.id}?lockNumber=5`, + }, + { + name: 'Letter', + template: { ...LETTER_TEMPLATE }, + previewComponent: PreviewTemplateDetailsLetter, + expectedBackLink: `/message-plans/choose-standard-english-letter-template/${ROUTING_CONFIG.id}?lockNumber=5`, + }, + { + name: 'Large Print Letter', + template: { ...LARGE_PRINT_LETTER_TEMPLATE }, + previewComponent: PreviewTemplateDetailsLetter, + expectedBackLink: `/message-plans/choose-large-print-letter-template/${ROUTING_CONFIG.id}?lockNumber=5`, + }, + ])( + 'renders $name template preview with the correct back links', + ({ template, previewComponent, expectedBackLink }) => { + const container = render( + + ); + + expect(screen.getByText(template.name)).toBeInTheDocument(); + + const backLinks = screen.getAllByText('Go back'); + for (const backLink of backLinks) { + expect(backLink).toHaveAttribute('href', expectedBackLink); + } + + expect(container.asFragment()).toMatchSnapshot(); + } + ); +}); diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/ChannelTemplates.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/ChannelTemplates.test.tsx.snap index 5922e2e1c..67a65f9be 100644 --- a/frontend/src/__tests__/components/molecules/__snapshots__/ChannelTemplates.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/__snapshots__/ChannelTemplates.test.tsx.snap @@ -9,7 +9,8 @@ exports[`ChannelTemplates renders templates 1`] = ` class="nhsuk-grid-column-full" >
Choose one option
@@ -151,18 +152,15 @@ exports[`ChannelTemplates renders templates 1`] = ` > -
+ + Preview + - + + Preview + - + + Preview + - + + Preview + @@ -468,7 +457,8 @@ exports[`ChannelTemplates renders templates with error state 1`] = ` class="nhsuk-grid-column-full" >
Choose one option
@@ -622,18 +612,15 @@ exports[`ChannelTemplates renders templates with error state 1`] = ` > - + + Preview + - + + Preview + - + + Preview + - + + Preview + @@ -939,7 +917,8 @@ exports[`ChannelTemplates renders templates with pre-selected template 1`] = ` class="nhsuk-grid-column-full" >
Choose one option
@@ -1081,18 +1060,15 @@ exports[`ChannelTemplates renders templates with pre-selected template 1`] = ` > - + + Preview + - + + Preview + - + + Preview + - + + Preview + diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/InvalidConfig.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/InvalidConfig.test.tsx.snap index 20ca154ab..da36bd395 100644 --- a/frontend/src/__tests__/components/molecules/__snapshots__/InvalidConfig.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/__snapshots__/InvalidConfig.test.tsx.snap @@ -32,7 +32,7 @@ exports[`InvalidConfig 1`] = `

Example Back Link Text diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/LanguageLetterTemplates.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/LanguageLetterTemplates.test.tsx.snap new file mode 100644 index 000000000..e7815e755 --- /dev/null +++ b/frontend/src/__tests__/components/molecules/__snapshots__/LanguageLetterTemplates.test.tsx.snap @@ -0,0 +1,1531 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LanguageLetterTemplates renders list of foreign language letter templates 1`] = ` + +

+
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - German + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+ + +`; + +exports[`LanguageLetterTemplates renders templates list with pre-selected templates 1`] = ` + +
+
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - German + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+
+`; + +exports[`LanguageLetterTemplates renders templates with duplicate language error state 1`] = ` + +
+
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+ + + Error: + + Choose only one template for each language + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - German + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+
+`; + +exports[`LanguageLetterTemplates renders templates with error state 1`] = ` + +
+
+
+
+ Choose all the templates that you want to include in this message plan. You can only choose one template for each language. +
+
+ + + Error: + + Please select at least one language template + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Select + + Name + + Type + + Last edited + +
+ +
+ + +
+
+ + + + + Standard letter - French + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - Polish + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+ +
+ + +
+
+ + + + + Standard letter - German + + + 13th Jan 2025 +
+ 10:19 +
+ + + Preview + +
+
+
+
+
+
+
+`; diff --git a/frontend/src/__tests__/components/molecules/__snapshots__/MessagePlanBlock.test.tsx.snap b/frontend/src/__tests__/components/molecules/__snapshots__/MessagePlanBlock.test.tsx.snap index 251a382f7..6fb32b15f 100644 --- a/frontend/src/__tests__/components/molecules/__snapshots__/MessagePlanBlock.test.tsx.snap +++ b/frontend/src/__tests__/components/molecules/__snapshots__/MessagePlanBlock.test.tsx.snap @@ -19,6 +19,7 @@ exports[`MessagePlanBlock for channel EMAIL with no template should match snapsh

+
    +
  • + +
    + + + Conditions for accessible and language letters + + +
    +
      +
    • + + The relevant accessible or language letter will be sent instead of the standard English letter if, both: +
        +
      • + the recipient has requested an accessible or language letter in PDS +
      • +
      • + you've included the relevant template in this message plan +
      • +
      +
    • +
    +
    +
    +
  • +
  • +
    +
    +

    + Large print letter (optional) +

    + +
    +
    +
  • +
  • +
    +
    +

    + Other language letters (optional) +

    + +
    +
    +
  • +
`; @@ -315,6 +651,7 @@ exports[`MessagePlanBlock for channel NHSAPP with no template should match snaps
NHS App -

- Test email template -

+

+ Test email template +

+
    @@ -403,12 +746,6 @@ exports[`MessagePlanBlock for channel NHSAPP with template should match snapshot
  • - +
+`; + +exports[`MessagePlanAccessibleFormatTemplate when a template has been selected should match snapshot for template selected state for letter type (x1) 1`] = ` +
+
+
+

+ Large print letter (optional) +

+
+

+ Large print letter template +

+
+ +
+
+
+`; + exports[`MessagePlanChannelTemplate should match snapshot for empty state (EMAIL) 1`] = `
- Letter + Standard English letter
30fd1e1c-a608-47cf-9cc2-eabaeeeebeca
@@ -652,6 +819,7 @@ exports[`CreateEditMessagePlan should match snapshot for a typical message plan
NHS App -

- app template name -

+

+ app template name +

+