Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion projects/ccd-case-ui-toolkit/package.json
Original file line number Diff line number Diff line change
@@ -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"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ describe('CaseEditComponent', () => {
mockabstractConfig = createSpyObj<AbstractAppConfig>('AbstractAppConfig', ['logMessage']);
spyOn(validPageListCaseFieldsService, 'deleteNonValidatedFields');
spyOn(validPageListCaseFieldsService, 'validPageListCaseFields');
formValueService.removeHiddenField = jasmine.createSpy('removeHiddenField').and.callFake((...args: any[]) => {});

route = {
queryParams: of({ Origin: 'viewDraft' }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { CaseField, FieldTypeEnum } from '../../domain';
import { FieldsUtils } from '../fields';
import { FieldTypeSanitiser } from './field-type-sanitiser';
import { AbstractControl } from '@angular/forms';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor comment but direct imports should be below external imports ideally


@Injectable()
export class FormValueService {
Expand Down Expand Up @@ -365,6 +366,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}
Expand Down