From f6e98c3124015afd6c2339d5443426f36f38976e Mon Sep 17 00:00:00 2001 From: Andrew Folga Date: Tue, 7 Apr 2026 16:03:46 +0100 Subject: [PATCH 1/3] Pass the fixced ChecboxesField type of component inside the controller itself rather than in yaml files (cherry picked from commit 4d71d908efe080817b030a9a60f871785d4882fe) --- .../definitions/example-grant-with-auth.yaml | 4 --- .../example-grant-with-task-list.yaml | 4 --- ...mmon-select-land-parcel-page.controller.js | 33 +++++++++++++++++-- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/server/common/forms/definitions/example-grant-with-auth.yaml b/src/server/common/forms/definitions/example-grant-with-auth.yaml index 745674a38..07b106753 100644 --- a/src/server/common/forms/definitions/example-grant-with-auth.yaml +++ b/src/server/common/forms/definitions/example-grant-with-auth.yaml @@ -394,10 +394,6 @@ pages: id: 88d7b067-68c2-4fe1-9a9a-ea98f0752e03 - title: Select all the eligible land parcels for the location of your woodland controller: CommonSelectLandParcelPageController - components: - - name: landParcels - type: CheckboxesField - title: Select land parcels config: enableMultipleParcelSelect: true topSection: | diff --git a/src/server/common/forms/definitions/example-grant-with-task-list.yaml b/src/server/common/forms/definitions/example-grant-with-task-list.yaml index 4b8396b59..af09141c2 100644 --- a/src/server/common/forms/definitions/example-grant-with-task-list.yaml +++ b/src/server/common/forms/definitions/example-grant-with-task-list.yaml @@ -236,10 +236,6 @@ pages: - title: Select land parcel for actions controller: CommonSelectLandParcelPageController - components: - - name: landParcels - type: CheckboxesField - title: Select land parcels config: enableMultipleParcelSelect: false bottomSection: | diff --git a/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.js b/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.js index 3147aac45..73d866f77 100644 --- a/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.js +++ b/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.js @@ -3,6 +3,7 @@ import LandGrantsQuestionWithAuthCheckController from '../../controllers/auth/la import { fetchParcels } from '../../services/land-grants.service.js' import { mapParcelsToViewModel } from '../../view-models/parcel.view-model.js' import { getParcelIdFromQuery, getParcelIdsFromPayload } from '../../utils/parcel-request.utils.js' +import { ComponentType } from '@defra/forms-model' export default class CommonSelectLandParcelPageController extends LandGrantsQuestionWithAuthCheckController { viewName = 'common-select-land-parcel' @@ -15,11 +16,36 @@ export default class CommonSelectLandParcelPageController extends LandGrantsQues * enableMultipleParcelSelect: true * * @param {FormModel} model - * @param {import('@defra/forms-model').Page} pageDef + * @param {PageQuestion} pageDef */ constructor(model, pageDef) { - super(model, pageDef) const config = model.def.metadata?.pageConfig?.[pageDef.path] ?? {} + + const existing = pageDef.components?.find((c) => c.name === 'landParcels') + // Inject Html (placeholder) and RadiosField components into the page def BEFORE super() so they are + // included in the collection's formSchema/stateSchema from the start. + // Using RadiosField because YesNoField does not support custom error messages. + // Placeholder ensures RadiosField is not treated as sole component by DXT, avoiding H1 legend. + /** @type {import('@defra/forms-model').PageQuestion} */ + const patchedPageDef = { + ...pageDef, + components: existing + ? pageDef.components + : [ + ...(pageDef.components ?? []), + { + type: ComponentType.CheckboxesField, + name: 'landParcels', + title: 'Select land parcels', + list: 'landParcels', + options: { + required: true + } + } + ] + } + super(model, patchedPageDef) + this.enableMultipleParcelSelect = config.enableMultipleParcelSelect === true this.topSection = config.topSection || '' this.bottomSection = config.bottomSection || '' @@ -141,6 +167,7 @@ export default class CommonSelectLandParcelPageController extends LandGrantsQues /** * @import { FormContext, AnyFormRequest } from '@defra/forms-engine-plugin/engine/types.js' + * @import { PageQuestion } from '@defra/forms-model' * @import { FormModel } from '@defra/forms-engine-plugin/engine/models/index.js' - * @import { ResponseObject, ResponseToolkit } from '@hapi/hapi' + * @import { ResponseToolkit } from '@hapi/hapi' */ From 49e6cb71e01ee63f36aae26eefd33995ba0a6c19 Mon Sep 17 00:00:00 2001 From: Andrew Folga Date: Wed, 8 Apr 2026 10:09:17 +0100 Subject: [PATCH 2/3] Fix incorrect comment (cherry picked from commit 6f9db69be9a4a6a36259b1a19fc41f7659fa4699) --- .../common-select-land-parcel-page.controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.js b/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.js index 73d866f77..197c225fb 100644 --- a/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.js +++ b/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.js @@ -15,6 +15,10 @@ export default class CommonSelectLandParcelPageController extends LandGrantsQues * config: * enableMultipleParcelSelect: true * + * Ensures a `landParcels` CheckboxesField component exists on the page. + * If not defined in YAML, it is injected here so the form schema and state + * are consistent across all journeys. + * * @param {FormModel} model * @param {PageQuestion} pageDef */ @@ -22,10 +26,6 @@ export default class CommonSelectLandParcelPageController extends LandGrantsQues const config = model.def.metadata?.pageConfig?.[pageDef.path] ?? {} const existing = pageDef.components?.find((c) => c.name === 'landParcels') - // Inject Html (placeholder) and RadiosField components into the page def BEFORE super() so they are - // included in the collection's formSchema/stateSchema from the start. - // Using RadiosField because YesNoField does not support custom error messages. - // Placeholder ensures RadiosField is not treated as sole component by DXT, avoiding H1 legend. /** @type {import('@defra/forms-model').PageQuestion} */ const patchedPageDef = { ...pageDef, From 5c07e2598c4d822102edb6f151a2f685d0843eed Mon Sep 17 00:00:00 2001 From: Andrew Folga Date: Wed, 8 Apr 2026 10:41:24 +0100 Subject: [PATCH 3/3] Add constructor test to improve sonar coverage (cherry picked from commit ae46d02f1308826e76f7ce536e13b46b6f3859fc) --- ...select-land-parcel-page.controller.test.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.test.js b/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.test.js index 45b53154a..944d8995b 100644 --- a/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.test.js +++ b/src/server/land-grants/common/common-select-parcel/common-select-land-parcel-page.controller.test.js @@ -68,6 +68,32 @@ describe('CommonSelectLandParcelPageController', () => { afterEach(vi.clearAllMocks) + describe('constructor', () => { + it('does NOT inject CheckboxesField if landParcels already exists', () => { + const model = { + def: { metadata: { pageConfig: {} } } + } + + const existingComponent = { + type: 'CheckboxesField', + name: 'landParcels', + title: 'Existing' + } + + const pageDef = { + path: '/test', + components: [existingComponent] + } + + const controller = new CommonSelectLandParcelPageController(model, pageDef) + + const components = + controller.form?.definition?.pages?.[0]?.components || controller.pageDef?.components || pageDef.components + + expect(components).toEqual([existingComponent]) + }) + }) + describe('resolveParcelIds', () => { it('returns payload values for POST', () => { const controller = createController()