From 9ec9fe7b6259122a598beaffe8b9930d6181d280 Mon Sep 17 00:00:00 2001 From: adamg-hmcts Date: Tue, 30 Jun 2026 15:12:39 +0100 Subject: [PATCH 1/4] Update generic file upload errors to link to upload button --- src/main/app/document/DocumentManagementConstants.ts | 3 +++ src/main/assets/js/upload-manager/index.ts | 7 +++++++ .../alternative-service/upload-evidence/template.njk | 6 +++--- .../common/upload-evidence/content.ts | 3 +++ .../common/upload-evidence/template.njk | 6 +++--- .../dispense-service/upload-evidence/content.ts | 3 +++ .../provide-information-to-the-court/template.njk | 6 +++--- .../applicant1/respond-to-the-courts-feedback/content.ts | 3 +++ .../respond-to-the-courts-feedback/template.njk | 6 +++--- .../steps/applicant1/upload-your-documents/content.ts | 3 +++ .../steps/applicant1/upload-your-documents/template.njk | 6 +++--- .../respond-to-the-courts-feedback/template.njk | 6 +++--- .../steps/applicant2/upload-your-documents/content.ts | 3 +++ .../steps/applicant2/upload-your-documents/template.njk | 6 +++--- src/main/steps/common/common.content.ts | 8 +++++++- .../steps/respondent/details-other-proceedings/content.ts | 3 +++ .../respondent/details-other-proceedings/template.njk | 6 +++--- 17 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/main/app/document/DocumentManagementConstants.ts b/src/main/app/document/DocumentManagementConstants.ts index c18e2fbe98..0a5e801ffc 100644 --- a/src/main/app/document/DocumentManagementConstants.ts +++ b/src/main/app/document/DocumentManagementConstants.ts @@ -2,6 +2,9 @@ import { orderedStateSequence } from '../../steps/state-sequence'; import { CaseWithId } from '../case/case'; import { ApplicationType, State } from '../case/definition'; +export const UPPY_FILE_INPUT_BUTTON_CLASS = 'uppy-FileInput-btn'; +export const UPPY_FILE_INPUT_BUTTON_ID = 'file-upload-btn'; + const APPLICANT_ONE_DOC_UPLOAD_STATES = [ State.Draft, State.AosDrafted, diff --git a/src/main/assets/js/upload-manager/index.ts b/src/main/assets/js/upload-manager/index.ts index 7732753eee..61d9d69a28 100644 --- a/src/main/assets/js/upload-manager/index.ts +++ b/src/main/assets/js/upload-manager/index.ts @@ -11,6 +11,7 @@ import { getById, hidden, qs } from '../selectors'; import { FileUploadEvents } from './FileUploadEvents'; import { UploadedFiles } from './UploadedFiles'; import { updateFileList } from './updateFileList'; +import { UPPY_FILE_INPUT_BUTTON_CLASS, UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; import '@uppy/drop-target/src/style.scss'; import '@uppy/progress-bar/src/style.scss'; @@ -76,6 +77,7 @@ const initUploadManager = (): void => { }; const upload = qs('.upload'); + if (upload) { upload.classList.remove(hidden); initUploadManager(); @@ -83,3 +85,8 @@ if (upload) { const fileInput = qs('.uppy-FileInput-input'); fileInput?.setAttribute('tabindex', '-1'); + +const btn = document.getElementsByClassName(UPPY_FILE_INPUT_BUTTON_CLASS)[0] as HTMLElement | null; +if (btn && !btn.id) { + btn.id = UPPY_FILE_INPUT_BUTTON_ID; +} diff --git a/src/main/steps/applicant1/interim-applications/alternative-service/upload-evidence/template.njk b/src/main/steps/applicant1/interim-applications/alternative-service/upload-evidence/template.njk index 46eb74f583..841a841fa2 100644 --- a/src/main/steps/applicant1/interim-applications/alternative-service/upload-evidence/template.njk +++ b/src/main/steps/applicant1/interim-applications/alternative-service/upload-evidence/template.njk @@ -12,7 +12,7 @@ errorList: [ { text: errors.applicant1InterimAppsEvidenceUploadedFiles.errorUploading, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorGeneric", class: "hidden" @@ -20,7 +20,7 @@ }, { text: errors.applicant1InterimAppsEvidenceUploadedFiles.fileSizeTooBig, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileSizeTooBig", class: "hidden" @@ -28,7 +28,7 @@ }, { text: errors.applicant1InterimAppsEvidenceUploadedFiles.fileWrongFormat, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileWrongFormat", class: "hidden" diff --git a/src/main/steps/applicant1/interim-applications/common/upload-evidence/content.ts b/src/main/steps/applicant1/interim-applications/common/upload-evidence/content.ts index 0f8e84ac1e..5d9d2ce433 100644 --- a/src/main/steps/applicant1/interim-applications/common/upload-evidence/content.ts +++ b/src/main/steps/applicant1/interim-applications/common/upload-evidence/content.ts @@ -7,6 +7,7 @@ import { TranslationFn } from '../../../../../app/controller/GetController'; import { FormContent, FormFieldsFn } from '../../../../../app/form/Form'; import { generateContent as uploadDocumentGenerateContent } from '../../../../applicant1/upload-your-documents/content'; import { CommonContent } from '../../../../common/common.content'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../../../app/document/DocumentManagementConstants'; const en = ({ partner }: CommonContent, applicant1UploadDocumentContent) => ({ title: 'Upload your evidence', @@ -90,6 +91,7 @@ export const form: FormContent = { type: 'hidden', label: l => l.uploadFiles, labelHidden: true, + errorId: UPPY_FILE_INPUT_BUTTON_ID, value: (isObject(userCase.applicant1InterimAppsEvidenceUploadedFiles) ? JSON.stringify(userCase.applicant1InterimAppsEvidenceUploadedFiles) @@ -107,6 +109,7 @@ export const form: FormContent = { type: 'checkboxes', label: l => l.cannotUpload, labelHidden: true, + errorId: UPPY_FILE_INPUT_BUTTON_ID, values: [ { name: 'applicant1InterimAppsCannotUploadDocs', diff --git a/src/main/steps/applicant1/interim-applications/common/upload-evidence/template.njk b/src/main/steps/applicant1/interim-applications/common/upload-evidence/template.njk index e246ab09c9..b97691b0c6 100644 --- a/src/main/steps/applicant1/interim-applications/common/upload-evidence/template.njk +++ b/src/main/steps/applicant1/interim-applications/common/upload-evidence/template.njk @@ -12,7 +12,7 @@ errorList: [ { text: errors.applicant1InterimAppsEvidenceUploadedFiles.errorUploading, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorGeneric", class: "hidden" @@ -20,7 +20,7 @@ }, { text: errors.applicant1InterimAppsEvidenceUploadedFiles.fileSizeTooBig, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileSizeTooBig", class: "hidden" @@ -28,7 +28,7 @@ }, { text: errors.applicant1InterimAppsEvidenceUploadedFiles.fileWrongFormat, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileWrongFormat", class: "hidden" diff --git a/src/main/steps/applicant1/interim-applications/dispense-service/upload-evidence/content.ts b/src/main/steps/applicant1/interim-applications/dispense-service/upload-evidence/content.ts index b6bd0831b4..1fe9dd2516 100644 --- a/src/main/steps/applicant1/interim-applications/dispense-service/upload-evidence/content.ts +++ b/src/main/steps/applicant1/interim-applications/dispense-service/upload-evidence/content.ts @@ -7,6 +7,7 @@ import { FormContent, FormFieldsFn } from '../../../../../app/form/Form'; import { CommonContent } from '../../../../common/common.content'; import { getDispenseLogicalTests } from '../../../../dispenseServiceApplicationSequence'; import { generateContent as uploadEvidenceGenerateContent } from '../../common/upload-evidence/content'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../../../app/document/DocumentManagementConstants'; const en = ({ partner }: CommonContent) => ({ statement: { @@ -52,6 +53,7 @@ export const form: FormContent = { applicant1InterimAppsEvidenceUploadedFiles: { type: 'hidden', label: l => l.uploadFiles, + errorId: UPPY_FILE_INPUT_BUTTON_ID, labelHidden: true, value: (isObject(userCase.applicant1InterimAppsEvidenceUploadedFiles) @@ -74,6 +76,7 @@ export const form: FormContent = { type: 'checkboxes', label: l => l.cannotUpload, labelHidden: true, + errorId: UPPY_FILE_INPUT_BUTTON_ID, validator: (value, formData) => { const hasUploadedFiles = (formData.applicant1InterimAppsEvidenceUploadedFiles as unknown as string[])?.length && diff --git a/src/main/steps/applicant1/provide-information-to-the-court/template.njk b/src/main/steps/applicant1/provide-information-to-the-court/template.njk index b0967233d3..8bd113f852 100644 --- a/src/main/steps/applicant1/provide-information-to-the-court/template.njk +++ b/src/main/steps/applicant1/provide-information-to-the-court/template.njk @@ -13,7 +13,7 @@ errorList: [ { text: errors.coClarificationUploadedFiles.errorUploading, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorGeneric", class: "hidden" @@ -21,7 +21,7 @@ }, { text: errors.coClarificationUploadedFiles.fileSizeTooBig, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileSizeTooBig", class: "hidden" @@ -29,7 +29,7 @@ }, { text: errors.coClarificationUploadedFiles.fileWrongFormat, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileWrongFormat", class: "hidden" diff --git a/src/main/steps/applicant1/respond-to-the-courts-feedback/content.ts b/src/main/steps/applicant1/respond-to-the-courts-feedback/content.ts index f6decc7b28..d42d256d11 100644 --- a/src/main/steps/applicant1/respond-to-the-courts-feedback/content.ts +++ b/src/main/steps/applicant1/respond-to-the-courts-feedback/content.ts @@ -127,6 +127,7 @@ export const form: FormContent = { type: 'textarea', classes: 'govuk-input--width-40', label: l => l.responseLabel, + errorId: 'choose-file', validator: (value, formData) => { const hasUploadedFiles = (formData.app1RfiDraftResponseUploadedFiles as unknown as string[])?.length && @@ -142,6 +143,7 @@ export const form: FormContent = { type: 'hidden', label: l => l.uploadFiles, labelHidden: true, + errorId: 'choose-file', value: (isObject(userCase.app1RfiDraftResponseUploadedFiles) ? JSON.stringify(userCase.app1RfiDraftResponseUploadedFiles) @@ -160,6 +162,7 @@ export const form: FormContent = { type: 'checkboxes', label: l => l.havingTroubleUploading, labelHidden: true, + errorId: 'choose-file', validator: (value, formData) => { const hasEnteredDetails = !isEmpty(formData.app1RfiDraftResponseDetails); const hasUploadedFiles = diff --git a/src/main/steps/applicant1/respond-to-the-courts-feedback/template.njk b/src/main/steps/applicant1/respond-to-the-courts-feedback/template.njk index 856e5c622e..a080ed93b7 100644 --- a/src/main/steps/applicant1/respond-to-the-courts-feedback/template.njk +++ b/src/main/steps/applicant1/respond-to-the-courts-feedback/template.njk @@ -12,7 +12,7 @@ errorList: [ { text: errors.app1RfiDraftResponseUploadedFiles.errorUploading, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorGeneric", class: "hidden" @@ -20,7 +20,7 @@ }, { text: errors.app1RfiDraftResponseUploadedFiles.fileSizeTooBig, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileSizeTooBig", class: "hidden" @@ -28,7 +28,7 @@ }, { text: errors.app1RfiDraftResponseUploadedFiles.fileWrongFormat, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileWrongFormat", class: "hidden" diff --git a/src/main/steps/applicant1/upload-your-documents/content.ts b/src/main/steps/applicant1/upload-your-documents/content.ts index 4ac511806e..aa59761f24 100644 --- a/src/main/steps/applicant1/upload-your-documents/content.ts +++ b/src/main/steps/applicant1/upload-your-documents/content.ts @@ -8,6 +8,7 @@ import { FormContent, FormFieldsFn } from '../../../app/form/Form'; import { atLeastOneFieldIsChecked } from '../../../app/form/validation'; import { CommonContent } from '../../common/common.content'; import { accessibleDetailsSpan } from '../../common/content.utils'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; const en = ({ isDivorce, marriage, civilPartnership, partner, isJointApplication }: CommonContent) => { const union = isDivorce ? marriage : civilPartnership; @@ -174,6 +175,7 @@ export const form: FormContent = { return { applicant1UploadedFiles: { + errorId: UPPY_FILE_INPUT_BUTTON_ID, type: 'hidden', label: l => l.uploadFiles, labelHidden: true, @@ -193,6 +195,7 @@ export const form: FormContent = { ...(checkboxes.length > 1 ? { applicant1CannotUpload: { + errorId: UPPY_FILE_INPUT_BUTTON_ID, type: 'checkboxes', label: l => l.cannotUploadDocuments, labelHidden: true, diff --git a/src/main/steps/applicant1/upload-your-documents/template.njk b/src/main/steps/applicant1/upload-your-documents/template.njk index 76391a4120..3744897528 100644 --- a/src/main/steps/applicant1/upload-your-documents/template.njk +++ b/src/main/steps/applicant1/upload-your-documents/template.njk @@ -12,7 +12,7 @@ errorList: [ { text: errors.applicant1UploadedFiles.errorUploading, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorGeneric", class: "hidden" @@ -20,7 +20,7 @@ }, { text: errors.applicant1UploadedFiles.fileSizeTooBig, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileSizeTooBig", class: "hidden" @@ -28,7 +28,7 @@ }, { text: errors.applicant1UploadedFiles.fileWrongFormat, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileWrongFormat", class: "hidden" diff --git a/src/main/steps/applicant2/respond-to-the-courts-feedback/template.njk b/src/main/steps/applicant2/respond-to-the-courts-feedback/template.njk index 03615c4bcd..7b3f1c0f37 100644 --- a/src/main/steps/applicant2/respond-to-the-courts-feedback/template.njk +++ b/src/main/steps/applicant2/respond-to-the-courts-feedback/template.njk @@ -12,7 +12,7 @@ errorList: [ { text: errors.app2RfiDraftResponseUploadedFiles.errorUploading, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorGeneric", class: "hidden" @@ -20,7 +20,7 @@ }, { text: errors.app2RfiDraftResponseUploadedFiles.fileSizeTooBig, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileSizeTooBig", class: "hidden" @@ -28,7 +28,7 @@ }, { text: errors.app2RfiDraftResponseUploadedFiles.fileWrongFormat, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileWrongFormat", class: "hidden" diff --git a/src/main/steps/applicant2/upload-your-documents/content.ts b/src/main/steps/applicant2/upload-your-documents/content.ts index a3eb9f0b5e..99fa061bf2 100644 --- a/src/main/steps/applicant2/upload-your-documents/content.ts +++ b/src/main/steps/applicant2/upload-your-documents/content.ts @@ -10,6 +10,7 @@ import { form as applicant1Form, generateContent as applicant1GenerateContent, } from '../../applicant1/upload-your-documents/content'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; const labels = applicant1Content => ({ errors: { @@ -32,6 +33,7 @@ export const form: FormContent = { type: 'hidden', label: l => l.uploadFiles, labelHidden: true, + errorId: UPPY_FILE_INPUT_BUTTON_ID, value: (isObject(userCase.applicant2UploadedFiles) ? JSON.stringify(userCase.applicant2UploadedFiles) @@ -49,6 +51,7 @@ export const form: FormContent = { type: 'checkboxes', label: l => l.cannotUploadDocuments, labelHidden: true, + errorId: UPPY_FILE_INPUT_BUTTON_ID, validator: (value, formData) => { if ((value as string[])?.includes(Checkbox.Checked)) { return atLeastOneFieldIsChecked(formData?.applicant2CannotUploadDocuments); diff --git a/src/main/steps/applicant2/upload-your-documents/template.njk b/src/main/steps/applicant2/upload-your-documents/template.njk index af66f66768..bde13cd10a 100644 --- a/src/main/steps/applicant2/upload-your-documents/template.njk +++ b/src/main/steps/applicant2/upload-your-documents/template.njk @@ -12,7 +12,7 @@ errorList: [ { text: errors.applicant2UploadedFiles.errorUploading, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorGeneric", class: "hidden" @@ -20,7 +20,7 @@ }, { text: errors.applicant2UploadedFiles.fileSizeTooBig, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileSizeTooBig", class: "hidden" @@ -28,7 +28,7 @@ }, { text: errors.applicant2UploadedFiles.fileWrongFormat, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileWrongFormat", class: "hidden" diff --git a/src/main/steps/common/common.content.ts b/src/main/steps/common/common.content.ts index 97c4f75281..a0729b9ee9 100644 --- a/src/main/steps/common/common.content.ts +++ b/src/main/steps/common/common.content.ts @@ -12,7 +12,7 @@ import { State, YesOrNo, } from '../../app/case/definition'; -import { userCanUploadDocuments } from '../../app/document/DocumentManagementConstants'; +import { UPPY_FILE_INPUT_BUTTON_ID, userCanUploadDocuments } from '../../app/document/DocumentManagementConstants'; import { findOnlineGeneralApplicationsForUser } from '../../app/utils/general-application-utils'; import { SupportedLanguages } from '../../modules/i18n'; import { formattedCaseId, getPartner, getSelectedGender, getServiceName } from '../common/content.utils'; @@ -232,6 +232,9 @@ export const en = { }, popupBlocked: 'Popup blocked. Please allow pop‑ups for this site.', }, + elementSelectors: { + uploadButtonId: `#${UPPY_FILE_INPUT_BUTTON_ID}`, + } }; const cy: typeof en = { @@ -420,6 +423,9 @@ const cy: typeof en = { }, popupBlocked: 'Naidlen wedi’i rhwystro. Caniatáu naidlen ar gyfer y wefan hon.', }, + elementSelectors: { + uploadButtonId: `#${UPPY_FILE_INPUT_BUTTON_ID}`, + } }; export const generateCommonContent = ({ diff --git a/src/main/steps/respondent/details-other-proceedings/content.ts b/src/main/steps/respondent/details-other-proceedings/content.ts index b11d9d4c47..aa0346c3bd 100644 --- a/src/main/steps/respondent/details-other-proceedings/content.ts +++ b/src/main/steps/respondent/details-other-proceedings/content.ts @@ -7,6 +7,7 @@ import { TranslationFn } from '../../../app/controller/GetController'; import { FormContent, FormFieldsFn } from '../../../app/form/Form'; import { isFieldFilledIn } from '../../../app/form/validation'; import { generateContent as applicant1GenerateContent } from '../../applicant1/details-other-proceedings/content'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; const labels = applicant1Content => ({ errors: { @@ -47,6 +48,7 @@ export const form: FormContent = { applicant2LegalProceedingUploadedFiles: { type: 'hidden', label: l => l.uploadFiles, + errorId: UPPY_FILE_INPUT_BUTTON_ID, labelHidden: true, value: (isObject(userCase.applicant2LegalProceedingUploadedFiles) @@ -65,6 +67,7 @@ export const form: FormContent = { type: 'checkboxes', label: l => l.unableToUploadEvidence, labelHidden: true, + errorId: UPPY_FILE_INPUT_BUTTON_ID, validator: (value, formData) => { const hasUploadedFiles = (formData.applicant2LegalProceedingUploadedFiles as unknown as string[])?.length && diff --git a/src/main/steps/respondent/details-other-proceedings/template.njk b/src/main/steps/respondent/details-other-proceedings/template.njk index 291afdb058..b74b4df817 100644 --- a/src/main/steps/respondent/details-other-proceedings/template.njk +++ b/src/main/steps/respondent/details-other-proceedings/template.njk @@ -11,7 +11,7 @@ errorList: [ { text: errors.applicant2LegalProceedingUploadedFiles.errorUploading, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorGeneric", class: "hidden" @@ -19,7 +19,7 @@ }, { text: errors.applicant2LegalProceedingUploadedFiles.fileSizeTooBig, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileSizeTooBig", class: "hidden" @@ -27,7 +27,7 @@ }, { text: errors.applicant2LegalProceedingUploadedFiles.fileWrongFormat, - href: "#upload", + href: elementSelectors.uploadButtonId, attributes: { id: "errorFileWrongFormat", class: "hidden" From bc901cb59bbc5ba2d1bae5b862e3ffc8bbfe42bc Mon Sep 17 00:00:00 2001 From: adamg-hmcts Date: Tue, 30 Jun 2026 15:18:09 +0100 Subject: [PATCH 2/4] Correct nested input and date error links --- src/main/app/form/Form.test.ts | 43 +++++++++++++++++++ src/main/app/form/Form.ts | 6 ++- src/main/assets/js/upload-manager/index.ts | 1 - .../date-from-certificate/content.ts | 1 + .../partner-date-of-birth/content.ts | 1 + .../dispense-service/last-date/content.ts | 1 + .../dispense-service/last-seen/content.ts | 1 + .../partner-date-of-birth/content.ts | 1 + 8 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/main/app/form/Form.test.ts b/src/main/app/form/Form.test.ts index a0fe69ba7b..4116ed8421 100644 --- a/src/main/app/form/Form.test.ts +++ b/src/main/app/form/Form.test.ts @@ -85,6 +85,49 @@ describe('Form', () => { ]); }); + test('Should use FormInput id as error focus id when provided', async () => { + const formWithInputId = new Form({ + applicant1Address1: { + id: 'address1', + type: 'text', + label: 'Address line 1', + validator: isFieldFilledIn, + }, + }); + + const errors = formWithInputId.getErrors({}); + + expect(errors).toStrictEqual([ + { + propertyName: 'applicant1Address1', + errorType: 'required', + focusId: 'address1', + }, + ]); + }); + + test('Should use FormInput errorId as focus id override when provided', async () => { + const formWithInputErrorId = new Form({ + applicant1UploadedFiles: { + id: 'applicant1UploadedFiles', + errorId: 'uploadGroup', + type: 'hidden', + label: 'Uploaded files', + validator: isFieldFilledIn, + }, + }); + + const errors = formWithInputErrorId.getErrors({}); + + expect(errors).toStrictEqual([ + { + propertyName: 'applicant1UploadedFiles', + errorType: 'required', + focusId: 'uploadGroup', + }, + ]); + }); + describe('subfield validation and parser', () => { const mockSubFieldForm: FormContent = { fields: { diff --git a/src/main/app/form/Form.ts b/src/main/app/form/Form.ts index a3ea607f5c..0c0e711396 100644 --- a/src/main/app/form/Form.ts +++ b/src/main/app/form/Form.ts @@ -62,7 +62,9 @@ export class Form { private getErrorsFromField(body: Partial, id: string, field: FormField): FormError[] { const errorType = field.validator && field.validator(body[id], body); - const focusId = isFormOptions(field) ? (field.values[0]?.id ?? field.id ?? id) : id; + const focusId = isFormOptions(field) + ? (field.values[0]?.errorId ?? field.values[0]?.id ?? field.errorId ?? field.id ?? id) + : (field.errorId ?? field.id ?? id); const errors: FormError[] = errorType ? [{ errorType, propertyName: id, focusId }] : []; errors.push(...this.validateGlobalInputRules(body, id, field as FormInput)); @@ -174,6 +176,7 @@ export type FormField = FormInput | FormOptions; export interface FormOptions { id?: string; + errorId?: string; type: string; label?: Label; labelHidden?: boolean; @@ -188,6 +191,7 @@ export interface FormOptions { export interface FormInput { id?: string; + errorId?: string; name?: string; label: Label; hint?: Label; diff --git a/src/main/assets/js/upload-manager/index.ts b/src/main/assets/js/upload-manager/index.ts index 61d9d69a28..8948ea6d9c 100644 --- a/src/main/assets/js/upload-manager/index.ts +++ b/src/main/assets/js/upload-manager/index.ts @@ -77,7 +77,6 @@ const initUploadManager = (): void => { }; const upload = qs('.upload'); - if (upload) { upload.classList.remove(hidden); initUploadManager(); diff --git a/src/main/steps/applicant1/date-from-certificate/content.ts b/src/main/steps/applicant1/date-from-certificate/content.ts index 814e2085fd..367961ab95 100644 --- a/src/main/steps/applicant1/date-from-certificate/content.ts +++ b/src/main/steps/applicant1/date-from-certificate/content.ts @@ -45,6 +45,7 @@ export const form: FormContent = { label: l => l.title, labelHidden: true, hint: l => l.hint, + errorId: 'relationshipDate-day', values: [ { label: language === SupportedLanguages.Cy ? 'Diwrnod' : 'Day', diff --git a/src/main/steps/applicant1/interim-applications/bailiff-service/partner-date-of-birth/content.ts b/src/main/steps/applicant1/interim-applications/bailiff-service/partner-date-of-birth/content.ts index c40eb17a83..e03b1438ea 100644 --- a/src/main/steps/applicant1/interim-applications/bailiff-service/partner-date-of-birth/content.ts +++ b/src/main/steps/applicant1/interim-applications/bailiff-service/partner-date-of-birth/content.ts @@ -78,6 +78,7 @@ export const form: FormContent = { label: l => l.yes, value: YesOrNo.YES, id: 'yes', + errorId: 'applicant1BailiffPartnersDateOfBirth-day', subFields: { applicant1BailiffPartnersDateOfBirth: { type: 'date', diff --git a/src/main/steps/applicant1/interim-applications/dispense-service/last-date/content.ts b/src/main/steps/applicant1/interim-applications/dispense-service/last-date/content.ts index 5ad35ee7a5..50ec12c523 100644 --- a/src/main/steps/applicant1/interim-applications/dispense-service/last-date/content.ts +++ b/src/main/steps/applicant1/interim-applications/dispense-service/last-date/content.ts @@ -83,6 +83,7 @@ export const form: FormContent = { label: l => l.lastLivedTogether, labelHidden: false, hint: l => l.hint, + errorId: 'applicant1DispenseLastLivedTogetherDate-day', values: [ { label: language === SupportedLanguages.Cy ? 'Diwrnod' : 'Day', diff --git a/src/main/steps/applicant1/interim-applications/dispense-service/last-seen/content.ts b/src/main/steps/applicant1/interim-applications/dispense-service/last-seen/content.ts index f4e4480fc0..261104d830 100644 --- a/src/main/steps/applicant1/interim-applications/dispense-service/last-seen/content.ts +++ b/src/main/steps/applicant1/interim-applications/dispense-service/last-seen/content.ts @@ -64,6 +64,7 @@ export const form: FormContent = { label: l => l.lastSeenDate, labelHidden: true, hint: l => `
${l.lastSeenDate}
`, + errorId: 'applicant1DispensePartnerLastSeenOrHeardOfDate-day', values: [ { label: language === SupportedLanguages.Cy ? 'Diwrnod' : 'Day', diff --git a/src/main/steps/applicant1/interim-applications/search-gov-records/partner-date-of-birth/content.ts b/src/main/steps/applicant1/interim-applications/search-gov-records/partner-date-of-birth/content.ts index bf5896b6d0..804cbad96a 100644 --- a/src/main/steps/applicant1/interim-applications/search-gov-records/partner-date-of-birth/content.ts +++ b/src/main/steps/applicant1/interim-applications/search-gov-records/partner-date-of-birth/content.ts @@ -82,6 +82,7 @@ export const form: FormContent = { label: l => l.enterPartnerDob, labelSize: 'normal', hint: l => l.enterPartnerDobHint, + errorId: 'applicant1SearchGovRecordsPartnerDateOfBirth-day', values: [ { label: language === SupportedLanguages.Cy ? 'Diwrnod' : 'Day', From 844598268618999e9b3856a3d675453f38d21dee Mon Sep 17 00:00:00 2001 From: adamg-hmcts Date: Tue, 30 Jun 2026 16:06:04 +0100 Subject: [PATCH 3/4] Linter corrections --- src/main/app/form/Form.ts | 9 ++++++--- src/main/assets/js/upload-manager/index.ts | 5 ++++- .../common/upload-evidence/content.ts | 2 +- .../dispense-service/upload-evidence/content.ts | 2 +- .../steps/applicant1/upload-your-documents/content.ts | 2 +- .../steps/applicant2/upload-your-documents/content.ts | 2 +- src/main/steps/common/common.content.ts | 4 ++-- .../respondent/details-other-proceedings/content.ts | 2 +- 8 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/main/app/form/Form.ts b/src/main/app/form/Form.ts index 0c0e711396..8ee981fd14 100644 --- a/src/main/app/form/Form.ts +++ b/src/main/app/form/Form.ts @@ -62,9 +62,12 @@ export class Form { private getErrorsFromField(body: Partial, id: string, field: FormField): FormError[] { const errorType = field.validator && field.validator(body[id], body); - const focusId = isFormOptions(field) - ? (field.values[0]?.errorId ?? field.values[0]?.id ?? field.errorId ?? field.id ?? id) - : (field.errorId ?? field.id ?? id); + + const isNestedInput = isFormOptions(field); + const parentId = field.errorId ?? field.id ?? id; + + const focusId = isNestedInput ? (field.values?.[0]?.errorId ?? field.values?.[0]?.id ?? parentId) : parentId; + const errors: FormError[] = errorType ? [{ errorType, propertyName: id, focusId }] : []; errors.push(...this.validateGlobalInputRules(body, id, field as FormInput)); diff --git a/src/main/assets/js/upload-manager/index.ts b/src/main/assets/js/upload-manager/index.ts index 8948ea6d9c..84e59ae714 100644 --- a/src/main/assets/js/upload-manager/index.ts +++ b/src/main/assets/js/upload-manager/index.ts @@ -4,6 +4,10 @@ import FileInput from '@uppy/file-input'; import ProgressBar from '@uppy/progress-bar'; import XHRUpload from '@uppy/xhr-upload'; +import { + UPPY_FILE_INPUT_BUTTON_CLASS, + UPPY_FILE_INPUT_BUTTON_ID, +} from '../../../app/document/DocumentManagementConstants'; import { SupportedLanguages } from '../../../modules/i18n'; import { DOCUMENT_MANAGER } from '../../../steps/urls'; import { getById, hidden, qs } from '../selectors'; @@ -11,7 +15,6 @@ import { getById, hidden, qs } from '../selectors'; import { FileUploadEvents } from './FileUploadEvents'; import { UploadedFiles } from './UploadedFiles'; import { updateFileList } from './updateFileList'; -import { UPPY_FILE_INPUT_BUTTON_CLASS, UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; import '@uppy/drop-target/src/style.scss'; import '@uppy/progress-bar/src/style.scss'; diff --git a/src/main/steps/applicant1/interim-applications/common/upload-evidence/content.ts b/src/main/steps/applicant1/interim-applications/common/upload-evidence/content.ts index 5d9d2ce433..32b0eb56f3 100644 --- a/src/main/steps/applicant1/interim-applications/common/upload-evidence/content.ts +++ b/src/main/steps/applicant1/interim-applications/common/upload-evidence/content.ts @@ -4,10 +4,10 @@ import { isObject } from 'lodash'; import { Checkbox } from '../../../../../app/case/case'; import { getFilename } from '../../../../../app/case/formatter/uploaded-files'; import { TranslationFn } from '../../../../../app/controller/GetController'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../../../app/document/DocumentManagementConstants'; import { FormContent, FormFieldsFn } from '../../../../../app/form/Form'; import { generateContent as uploadDocumentGenerateContent } from '../../../../applicant1/upload-your-documents/content'; import { CommonContent } from '../../../../common/common.content'; -import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../../../app/document/DocumentManagementConstants'; const en = ({ partner }: CommonContent, applicant1UploadDocumentContent) => ({ title: 'Upload your evidence', diff --git a/src/main/steps/applicant1/interim-applications/dispense-service/upload-evidence/content.ts b/src/main/steps/applicant1/interim-applications/dispense-service/upload-evidence/content.ts index 1fe9dd2516..8753851236 100644 --- a/src/main/steps/applicant1/interim-applications/dispense-service/upload-evidence/content.ts +++ b/src/main/steps/applicant1/interim-applications/dispense-service/upload-evidence/content.ts @@ -3,11 +3,11 @@ import { isObject } from 'lodash'; import { Checkbox } from '../../../../../app/case/case'; import { YesOrNo } from '../../../../../app/case/definition'; import { TranslationFn } from '../../../../../app/controller/GetController'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../../../app/document/DocumentManagementConstants'; import { FormContent, FormFieldsFn } from '../../../../../app/form/Form'; import { CommonContent } from '../../../../common/common.content'; import { getDispenseLogicalTests } from '../../../../dispenseServiceApplicationSequence'; import { generateContent as uploadEvidenceGenerateContent } from '../../common/upload-evidence/content'; -import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../../../app/document/DocumentManagementConstants'; const en = ({ partner }: CommonContent) => ({ statement: { diff --git a/src/main/steps/applicant1/upload-your-documents/content.ts b/src/main/steps/applicant1/upload-your-documents/content.ts index aa59761f24..37f72b1653 100644 --- a/src/main/steps/applicant1/upload-your-documents/content.ts +++ b/src/main/steps/applicant1/upload-your-documents/content.ts @@ -4,11 +4,11 @@ import { CaseWithId, Checkbox } from '../../../app/case/case'; import { DocumentType, YesOrNo } from '../../../app/case/definition'; import { getFilename } from '../../../app/case/formatter/uploaded-files'; import { TranslationFn } from '../../../app/controller/GetController'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; import { FormContent, FormFieldsFn } from '../../../app/form/Form'; import { atLeastOneFieldIsChecked } from '../../../app/form/validation'; import { CommonContent } from '../../common/common.content'; import { accessibleDetailsSpan } from '../../common/content.utils'; -import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; const en = ({ isDivorce, marriage, civilPartnership, partner, isJointApplication }: CommonContent) => { const union = isDivorce ? marriage : civilPartnership; diff --git a/src/main/steps/applicant2/upload-your-documents/content.ts b/src/main/steps/applicant2/upload-your-documents/content.ts index 99fa061bf2..d3845c7397 100644 --- a/src/main/steps/applicant2/upload-your-documents/content.ts +++ b/src/main/steps/applicant2/upload-your-documents/content.ts @@ -4,13 +4,13 @@ import { Checkbox } from '../../../app/case/case'; import { DocumentType } from '../../../app/case/definition'; import { getFilename } from '../../../app/case/formatter/uploaded-files'; import { TranslationFn } from '../../../app/controller/GetController'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; import { FormContent, FormFieldsFn } from '../../../app/form/Form'; import { atLeastOneFieldIsChecked } from '../../../app/form/validation'; import { form as applicant1Form, generateContent as applicant1GenerateContent, } from '../../applicant1/upload-your-documents/content'; -import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; const labels = applicant1Content => ({ errors: { diff --git a/src/main/steps/common/common.content.ts b/src/main/steps/common/common.content.ts index a0729b9ee9..2f701073b8 100644 --- a/src/main/steps/common/common.content.ts +++ b/src/main/steps/common/common.content.ts @@ -234,7 +234,7 @@ export const en = { }, elementSelectors: { uploadButtonId: `#${UPPY_FILE_INPUT_BUTTON_ID}`, - } + }, }; const cy: typeof en = { @@ -425,7 +425,7 @@ const cy: typeof en = { }, elementSelectors: { uploadButtonId: `#${UPPY_FILE_INPUT_BUTTON_ID}`, - } + }, }; export const generateCommonContent = ({ diff --git a/src/main/steps/respondent/details-other-proceedings/content.ts b/src/main/steps/respondent/details-other-proceedings/content.ts index aa0346c3bd..04e234738b 100644 --- a/src/main/steps/respondent/details-other-proceedings/content.ts +++ b/src/main/steps/respondent/details-other-proceedings/content.ts @@ -4,10 +4,10 @@ import { Checkbox } from '../../../app/case/case'; import { YesOrNo } from '../../../app/case/definition'; import { getFilename } from '../../../app/case/formatter/uploaded-files'; import { TranslationFn } from '../../../app/controller/GetController'; +import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; import { FormContent, FormFieldsFn } from '../../../app/form/Form'; import { isFieldFilledIn } from '../../../app/form/validation'; import { generateContent as applicant1GenerateContent } from '../../applicant1/details-other-proceedings/content'; -import { UPPY_FILE_INPUT_BUTTON_ID } from '../../../app/document/DocumentManagementConstants'; const labels = applicant1Content => ({ errors: { From b1969020162128d5fb7eb5f0e63ce31d52b5c436 Mon Sep 17 00:00:00 2001 From: adamg-hmcts Date: Tue, 30 Jun 2026 16:45:39 +0100 Subject: [PATCH 4/4] Correct Bailiff date of birth input config --- .../bailiff-service/partner-date-of-birth/content.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/steps/applicant1/interim-applications/bailiff-service/partner-date-of-birth/content.ts b/src/main/steps/applicant1/interim-applications/bailiff-service/partner-date-of-birth/content.ts index e03b1438ea..ad599021e2 100644 --- a/src/main/steps/applicant1/interim-applications/bailiff-service/partner-date-of-birth/content.ts +++ b/src/main/steps/applicant1/interim-applications/bailiff-service/partner-date-of-birth/content.ts @@ -78,13 +78,13 @@ export const form: FormContent = { label: l => l.yes, value: YesOrNo.YES, id: 'yes', - errorId: 'applicant1BailiffPartnersDateOfBirth-day', subFields: { applicant1BailiffPartnersDateOfBirth: { type: 'date', classes: 'govuk-date-input', label: l => l.enterDateOfBirthLabel, hint: l => l.enterDateOfBirthHint, + errorId: 'applicant1BailiffPartnersDateOfBirth-day', values: [ { label: language === SupportedLanguages.Cy ? 'Diwrnod' : 'Day',