diff --git a/package.json b/package.json index d41e1e6f6f..be3ec6e914 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hmcts/ccd-case-ui-toolkit", - "version": "7.2.57", + "version": "7.2.57-exui-3582-rc1", "engines": { "node": ">=18.19.0" }, diff --git a/projects/ccd-case-ui-toolkit/package.json b/projects/ccd-case-ui-toolkit/package.json index 1080538943..3738ec5eb1 100644 --- a/projects/ccd-case-ui-toolkit/package.json +++ b/projects/ccd-case-ui-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@hmcts/ccd-case-ui-toolkit", - "version": "7.2.57", + "version": "7.2.57-exui-3582-rc1", "engines": { "node": ">=18.19.0" }, diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit-page/case-edit-page.component.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit-page/case-edit-page.component.ts index 5710951a0e..f4f4fae018 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit-page/case-edit-page.component.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit-page/case-edit-page.component.ts @@ -738,6 +738,12 @@ export class CaseEditPageComponent implements OnInit, AfterViewChecked, OnDestro this.formValueService.removeUnnecessaryFields(caseEventData.data, caseFields, clearEmpty, clearNonCase, fromPreviousPage, this.currentPage.case_fields); + // removeHiddenFields while are hidden in the UI + // Only remove hidden fields if editForm and its controls are available + if (this.editForm?.controls?.['data']?.['controls']) { + this.formValueService.removeHiddenField(caseEventData.data, caseFields, clearNonCase, this.editForm.controls['data']['controls']); + } + return caseEventData; } diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit/case-edit.component.spec.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit/case-edit.component.spec.ts index 0738945a91..d2cb8eb7e6 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit/case-edit.component.spec.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit/case-edit.component.spec.ts @@ -295,6 +295,7 @@ describe('CaseEditComponent', () => { mockabstractConfig = createSpyObj('AbstractAppConfig', ['logMessage']); spyOn(validPageListCaseFieldsService, 'deleteNonValidatedFields'); spyOn(validPageListCaseFieldsService, 'validPageListCaseFields'); + formValueService.removeHiddenField = jasmine.createSpy('removeHiddenField').and.callFake((...args: any[]) => {}); route = { queryParams: of({ Origin: 'viewDraft' }), diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit/case-edit.component.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit/case-edit.component.ts index e252a32205..671f82f30a 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit/case-edit.component.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/components/case-editor/case-edit/case-edit.component.ts @@ -330,6 +330,12 @@ export class CaseEditComponent implements OnInit, OnDestroy { this.formValueService.removeUnnecessaryFields(caseEventData.data, pageListCaseFields, true, true); } + // removeHiddenFields while are hidden in the UI + // Only remove hidden fields if editForm and its controls are available + if (form?.controls?.['data']?.['controls']) { + this.formValueService.removeHiddenField(caseEventData.data, pageListCaseFields, true, form.controls['data']['controls']); + } + caseEventData.event_token = eventTrigger.event_token; caseEventData.ignore_warning = this.ignoreWarning; if (this.confirmation) { diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-value.service.spec.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-value.service.spec.ts index 187f61a8d6..fdd71676f2 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-value.service.spec.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-value.service.spec.ts @@ -1075,5 +1075,220 @@ describe('FormValueService', () => { dollar: '$' }); }); + + describe('removeHiddenField', () => { + let formControls: any; + let caseFields: CaseField[]; + let data: any; + + beforeEach(() => { + formControls = {}; + caseFields = []; + data = {}; + }); + + it('should set field to null if hidden and not retain_hidden_value', () => { + data = { field1: 'value1', field2: 'value2' }; + caseFields = [ + { id: 'field1', display_context: 'MANDATORY', hidden: false, retain_hidden_value: false, field_type: { type: 'Text' } } as any, + { id: 'field2', display_context: 'MANDATORY', hidden: false, retain_hidden_value: false, field_type: { type: 'Text' } } as any + ]; + formControls = { + field1: { caseField: { hidden: true } }, + field2: { caseField: { hidden: false } } + }; + formValueService.removeHiddenField(data, caseFields, true, formControls); + expect(data.field1).toBeNull(); + expect(data.field2).toBe('value2'); + }); + + it('should not set field to null if retain_hidden_value is true', () => { + data = { field1: 'value1' }; + caseFields = [ + { id: 'field1', display_context: 'MANDATORY', hidden: false, retain_hidden_value: true, field_type: { type: 'Text' } } as any + ]; + formControls = { + field1: { caseField: { hidden: true } } + }; + formValueService.removeHiddenField(data, caseFields, true, formControls); + expect(data.field1).toBe('value1'); + }); + + it('should not touch readonly fields', () => { + spyOn(FormValueService as any, 'isReadOnly').and.returnValue(true); + data = { field1: 'value1' }; + caseFields = [ + { id: 'field1', display_context: 'READONLY', hidden: false, field_type: { type: 'Text' } } as any + ]; + formControls = { + field1: { caseField: { hidden: true } } + }; + formValueService.removeHiddenField(data, caseFields, true, formControls); + expect(data.field1).toBe('value1'); + }); + + it('should skip if formControls[field.id] is undefined', () => { + data = { field1: 'value1' }; + caseFields = [ + { id: 'field1', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } } as any + ]; + formControls = {}; + formValueService.removeHiddenField(data, caseFields, true, formControls); + expect(data.field1).toBe('value1'); + }); + + it('should skip if field is hidden', () => { + data = { field1: 'value1' }; + caseFields = [ + { id: 'field1', display_context: 'MANDATORY', hidden: true, field_type: { type: 'Text' } } as any + ]; + formControls = { + field1: { caseField: { hidden: true } } + }; + formValueService.removeHiddenField(data, caseFields, true, formControls); + expect(data.field1).toBe('value1'); + }); + + it('should recurse into complex fields', () => { + data = { + complex1: { + sub1: 'val1', + sub2: 'val2' + } + }; + caseFields = [ + { + id: 'complex1', + display_context: 'MANDATORY', + hidden: false, + field_type: { + type: 'Complex', + complex_fields: [ + { id: 'sub1', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } }, + { id: 'sub2', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } } + ] + } + } as any + ]; + formControls = { + complex1: { + caseField: { hidden: false }, + controls: { + sub1: { caseField: { hidden: true } }, + sub2: { caseField: { hidden: false } } + } + } + }; + formValueService.removeHiddenField(data, caseFields, true, formControls); + expect(data.complex1.sub1).toBeNull(); + expect(data.complex1.sub2).toBe('val2'); + }); + + it('should recurse into collection of complex fields', () => { + data = { + collection1: [ + { value: { sub1: 'a', sub2: 'b' } }, + { value: { sub1: 'c', sub2: 'd' } } + ] + }; + caseFields = [ + { + id: 'collection1', + display_context: 'MANDATORY', + hidden: false, + field_type: { + type: 'Collection', + collection_field_type: { + type: 'Complex', + complex_fields: [ + { id: 'sub1', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } }, + { id: 'sub2', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } } + ] + } + } + } as any + ]; + formControls = { + collection1: { + caseField: { hidden: false }, + controls: [ + { + controls: { + value: { + caseField: { + field_type: { + complex_fields: [ + { id: 'sub1', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } }, + { id: 'sub2', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } } + ] + } + }, + controls: { + sub1: { caseField: { hidden: true } }, + sub2: { caseField: { hidden: false } } + } + } + } + }, + { + controls: { + value: { + caseField: { + field_type: { + complex_fields: [ + { id: 'sub1', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } }, + { id: 'sub2', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } } + ] + } + }, + controls: { + sub1: { caseField: { hidden: false } }, + sub2: { caseField: { hidden: true } } + } + } + } + } + ] + } + }; + formValueService.removeHiddenField(data, caseFields, true, formControls); + expect(data.collection1[0].value.sub1).toBeNull(); + expect(data.collection1[0].value.sub2).toBe('b'); + expect(data.collection1[1].value.sub1).toBe('c'); + expect(data.collection1[1].value.sub2).toBeNull(); + }); + + it('should do nothing if clearNonCase is false', () => { + data = { field1: 'value1' }; + caseFields = [ + { id: 'field1', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } } as any + ]; + formControls = { + field1: { caseField: { hidden: true } } + }; + formValueService.removeHiddenField(data, caseFields, false, formControls); + expect(data.field1).toBe('value1'); + }); + + it('should skip if data is null', () => { + data = null; + caseFields = [ + { id: 'field1', display_context: 'MANDATORY', hidden: false, field_type: { type: 'Text' } } as any + ]; + formControls = { + field1: { caseField: { hidden: true } } + }; + formValueService.removeHiddenField(data, caseFields, true, formControls); + expect(data).toBeNull(); + }); + + it('should skip if caseFields is empty', () => { + data = { field1: 'value1' }; + caseFields = []; + formControls = {}; + formValueService.removeHiddenField(data, caseFields, true, formControls); + expect(data.field1).toBe('value1'); + }); + }); }); }); \ No newline at end of file diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-value.service.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-value.service.ts index 35ca3e59f1..b8a7488dc9 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-value.service.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-value.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; - +import { AbstractControl } from '@angular/forms'; import { CaseField, FieldTypeEnum } from '../../domain'; import { FieldsUtils } from '../fields'; import { FieldTypeSanitiser } from './field-type-sanitiser'; @@ -365,6 +365,64 @@ export class FormValueService { } } } + // exui-3582 When a form field becomes hidden based on user’s input in the event journey, + // its stored value must be cleared and it must not be submitted or persisted. + public removeHiddenField(data: object, caseFields: CaseField[], clearNonCase: boolean, formControls: { [key: string]: AbstractControl }): void { + if (clearNonCase && data && caseFields && caseFields.length > 0) { + for (const field of caseFields) { + if (!FormValueService.isLabel(field) && FormValueService.isReadOnly(field)) { + // Retain anything that is readonly and not a label. + continue; + } + // Check if formControls[field.id] exists before accessing its properties + const caseField = formControls[field.id] ? formControls[field.id]['caseField'] as CaseField : undefined; + if (caseField === undefined || field.hidden === true) { + continue; + } + + const hasValue = data.hasOwnProperty(field.id) && data[field.id] != null && + (typeof data[field.id] !== 'object' || Object.keys(data[field.id]).length > 0); + + if ( + caseField?.hidden === true && + field.display_context !== 'HIDDEN' && + field.display_context !== 'HIDDEN_TEMP' && + !field.retain_hidden_value && + field.id !== 'caseLinks' && + hasValue + ) { + data[field.id] = null; + continue; // If field is now hidden, skip checking its children + } + if (field.field_type) { + switch (field.field_type.type) { + case 'Complex': + const complexData = data[field.id] ?? data['value']; + if (complexData && formControls[field.id] && formControls[field.id]['controls']) { + this.removeHiddenField(complexData, field.field_type.complex_fields, clearNonCase, formControls[field.id]['controls']); + } + break; + case 'Collection': + const collection = data[field.id]; + if (collection && Array.isArray(collection) && field.field_type.collection_field_type.type === 'Complex') { + collection.forEach((item, index) => { + if (formControls[field.id] && formControls[field.id]['controls'] && formControls[field.id]['controls'][index]) { + const itemControls = formControls[field.id]?.['controls']?.[index]?.['controls']?.['value']; + const collectionData = item['value'] ?? item; + if (collectionData && itemControls?.['controls']) { + this.removeHiddenField(collectionData, field.field_type.collection_field_type.complex_fields, clearNonCase, itemControls['controls']); + } + } + }); + } + break; + default: + break; + } + } + } + } + } /** * Remove any empty collection fields where a value of greater than zero is specified in the field's {@link FieldType}