Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## RELEASE NOTES

### Version 7.3.54-exui-3740
**EXUI-3740** Dynamic MultiSelect Box doesn't retain it's original data in Collection on Add new button click

### Version 7.3.54
**EXUI-4636** Revert CME-220 and EXUI-2111
**EXUI-4637** Revert EXUI-2645
Expand Down
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.3.54",
"version": "7.3.54-exui-3740",
"engines": {
"node": ">=20.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.3.54",
"version": "7.3.54-exui-3740",
"engines": {
"node": ">=20.19.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ describe('CaseEditComponent', () => {

formValueService = createSpyObj<FormValueService>('formValueService', [
'sanitise',
'sanitiseDynamicLists',
'clearNonCaseFields',
'removeNullLabels',
'removeEmptyDocuments',
Expand Down Expand Up @@ -1223,6 +1224,10 @@ describe('CaseEditComponent', () => {
});

expect(component.isSubmitting).toEqual(false);
expect(formValueService.sanitiseDynamicLists).toHaveBeenCalledWith(
component.eventTrigger.case_fields,
{ data: jasmine.any(Object) }
);
expect(formValueService.sanitise).toHaveBeenCalled();
});
});
Expand Down Expand Up @@ -1700,7 +1705,7 @@ describe('CaseEditComponent', () => {
{ provide: ConditionalShowRegistrarService, useValue: new ConditionalShowRegistrarService() },
{ provide: ValidPageListCaseFieldsService, useValue: new ValidPageListCaseFieldsService(new FieldsUtils()) },
{ provide: FormErrorService, useValue: jasmine.createSpyObj<FormErrorService>('FormErrorService', ['mapFieldErrors']) },
{ provide: FormValueService, useValue: jasmine.createSpyObj<FormValueService>('FormValueService', ['sanitise']) },
{ provide: FormValueService, useValue: jasmine.createSpyObj<FormValueService>('FormValueService', ['sanitise', 'sanitiseDynamicLists']) },
{ provide: LoadingService, useValue: jasmine.createSpyObj<LoadingService>('LoadingService', ['register','unregister']) },
{ provide: WorkAllocationService, useValue: jasmine.createSpyObj<WorkAllocationService>('WorkAllocationService', ['assignAndCompleteTask','completeTask']) },
{ provide: AlertService, useValue: jasmine.createSpyObj<AlertService>('AlertService', ['error','setPreserveAlerts']) },
Expand Down Expand Up @@ -1800,7 +1805,7 @@ describe('CaseEditComponent', () => {
wizard.pages = [];
formErrorService = createSpyObj<FormErrorService>('formErrorService', ['mapFieldErrors']);

formValueService = createSpyObj<FormValueService>('formValueService', ['sanitise']);
formValueService = createSpyObj<FormValueService>('formValueService', ['sanitise', 'sanitiseDynamicLists']);

const snapshotNoProfile = {
pathFromRoot: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,13 @@ export class CaseEditComponent implements OnInit, OnDestroy {
}

private generateCaseEventData({ eventTrigger, form }: CaseEditGenerateCaseEventData ): CaseEventData {
const formData = this.replaceHiddenFormValuesWithOriginalCaseData(
form.get('data') as FormGroup, eventTrigger.case_fields);
this.formValueService.sanitiseDynamicLists(eventTrigger.case_fields, { data: formData });

const caseEventData: CaseEventData = {
data: this.replaceEmptyComplexFieldValues(
this.formValueService.sanitise(
this.replaceHiddenFormValuesWithOriginalCaseData(
form.get('data') as FormGroup, eventTrigger.case_fields),
this.isCaseFlagSubmission)),
this.formValueService.sanitise(formData, this.isCaseFlagSubmission)),
event: form.value.event
} as CaseEventData;
this.formValueService.clearNonCaseFields(caseEventData.data, eventTrigger.case_fields);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DebugElement } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { FormArray, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatLegacyDialog as MatDialog, MatLegacyDialogRef as MatDialogRef } from '@angular/material/legacy-dialog';
import { By } from '@angular/platform-browser';
import { ScrollToService } from '@nicky-lenaers/ngx-scroll-to';
Expand Down Expand Up @@ -337,6 +337,32 @@ describe('WriteCollectionFieldComponent', () => {
const fields = de.queryAll($WRITE_FIELDS);
expect(fields.length).toBe(0);
});

it('should retain dynamic multiselect options for a new complex collection item', () => {
const orderListItems = [
{ code: 'order-1', label: 'Order 1' },
{ code: 'order-2', label: 'Order 2' }
];
caseField.field_type = {
type: 'Collection',
collection_field_type: {
type: 'Complex',
complex_fields: [{
id: 'orderList',
field_type: { type: 'DynamicMultiSelectList' },
list_items: orderListItems
} as CaseField]
}
} as FieldType;
component.formArray = new FormArray([]);

const newRecipient = component.buildCaseField({ value: null }, 1, true);
const orderListField = newRecipient.field_type.complex_fields.find((field) => field.id === 'orderList');

expect(orderListField.list_items).toEqual(orderListItems);
expect(orderListField.value).toBeUndefined();
expect(newRecipient.value).toBeNull();
});
});

describe('WriteCollectionFieldComponent CRUD impact', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,179 @@ describe('FieldsUtils', () => {

expect(callbackResponse).toEqual(expected);
});

it('should hydrate list_items for collection dynamic multiselects without copying selected values', () => {
const listItems = [
{code: '1', value: '1'},
{code: '2', value: '2'}
];
const callbackResponse = {
field_type: {
type: 'Collection',
collection_field_type: {
type: 'Complex',
complex_fields: [{
id: 'orderList',
field_type: { type: 'DynamicMultiSelectList' },
formatted_value: {}
}]
}
},
value: [{
value: {
orderList: {
list_items: listItems,
value: [{code: '2', value: '2'}]
}
}
}]
};

(FieldsUtils as any).setDynamicListDefinition(callbackResponse, callbackResponse.field_type, callbackResponse);

const orderListField = callbackResponse.field_type.collection_field_type.complex_fields[0] as any;
expect(orderListField.list_items).toEqual(listItems);
expect(orderListField.formatted_value).toEqual({ list_items: listItems });
expect(orderListField.value).toBeUndefined();
});

it('should hydrate list_items and selected values for complex dynamic multiselects', () => {
const listItems = [
{code: '1', value: '1'},
{code: '2', value: '2'}
];
const selectedValue = [{code: '2', value: '2'}];
const callbackResponse = {
field_type: {
type: 'Complex',
complex_fields: [{
id: 'orderList',
field_type: { type: 'DynamicMultiSelectList' },
formatted_value: {
label: 'Order list'
}
}]
},
value: {
orderList: {
list_items: listItems,
value: selectedValue
}
}
};

(FieldsUtils as any).setDynamicListDefinition(callbackResponse, callbackResponse.field_type, callbackResponse);

const orderListField = callbackResponse.field_type.complex_fields[0] as any;
expect(orderListField.list_items).toEqual(listItems);
expect(orderListField.value).toEqual(selectedValue);
expect(orderListField.formatted_value).toEqual({
label: 'Order list',
list_items: listItems,
value: selectedValue
});
});

it('should persist multiple selected values for complex dynamic multiselects', () => {
const listItems = [
{code: '1', value: '1'},
{code: '2', value: '2'},
{code: '3', value: '3'}
];
const selectedValue = [
{code: '1', value: '1'},
{code: '3', value: '3'}
];
const callbackResponse = {
field_type: {
type: 'Complex',
complex_fields: [{
id: 'orderList',
field_type: { type: 'DynamicMultiSelectList' },
formatted_value: {}
}]
},
value: {
orderList: {
list_items: listItems,
value: selectedValue
}
}
};

(FieldsUtils as any).setDynamicListDefinition(callbackResponse, callbackResponse.field_type, callbackResponse);

const orderListField = callbackResponse.field_type.complex_fields[0] as any;
expect(orderListField.value).toEqual(selectedValue);
expect(orderListField.formatted_value.value).toEqual(selectedValue);
});

it('should hydrate list_items from a later nested dynamic multiselect while using the first selected value', () => {
const listItems = [
{code: '1', value: '1'},
{code: '2', value: '2'}
];
const selectedValue = [{code: '1', value: '1'}];
const callbackResponse = {
field_type: {
type: 'Complex',
complex_fields: [{
id: 'orderList',
field_type: { type: 'DynamicMultiSelectList' },
formatted_value: {}
}]
},
value: {
recipients: [{
orderList: {
value: selectedValue
}
}, {
orderList: {
list_items: listItems,
value: [{code: '2', value: '2'}]
}
}]
}
};

(FieldsUtils as any).setDynamicListDefinition(callbackResponse, callbackResponse.field_type, callbackResponse);

const orderListField = callbackResponse.field_type.complex_fields[0] as any;
expect(orderListField.list_items).toEqual(listItems);
expect(orderListField.value).toEqual(selectedValue);
expect(orderListField.formatted_value).toEqual({
list_items: listItems,
value: selectedValue
});
});

it('should leave dynamic multiselects unchanged when matching data has no list_items or selected value', () => {
const callbackResponse = {
field_type: {
type: 'Complex',
complex_fields: [{
id: 'orderList',
field_type: { type: 'DynamicMultiSelectList' },
formatted_value: {
label: 'Order list'
}
}]
},
value: {
orderList: {}
}
};

(FieldsUtils as any).setDynamicListDefinition(callbackResponse, callbackResponse.field_type, callbackResponse);

const orderListField = callbackResponse.field_type.complex_fields[0] as any;
expect(orderListField.list_items).toBeUndefined();
expect(orderListField.value).toBeUndefined();
expect(orderListField.formatted_value).toEqual({
label: 'Order list'
});
});
});

describe('isFlagsCaseField() function test', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,11 @@ export class FieldsUtils {
caseFieldType.complex_fields.forEach(field => {
try {
const isDynamicField = FieldsUtils.SERVER_RESPONSE_FIELD_TYPE_DYNAMIC_LIST_TYPE.indexOf(field.field_type.type) !== -1;
const isDynamicMultiSelectField = field.field_type.type === 'DynamicMultiSelectList';

if (isDynamicField) {
if (isDynamicMultiSelectField) {
this.setDynamicMultiSelectListDefinition(field, rootCaseField);
} else if (isDynamicField) {
const dynamicListValue = this.getDynamicListValue(rootCaseField.value, field.id);
if (dynamicListValue) {
const list_items = dynamicListValue[0].list_items;
Expand Down Expand Up @@ -343,6 +346,31 @@ export class FieldsUtils {
}
}

private static setDynamicMultiSelectListDefinition(field: CaseField, rootCaseField: CaseField) {
const dynamicListValue = this.getDynamicListValue(rootCaseField.value, field.id);
if (dynamicListValue) {
const list_items = dynamicListValue.find(data => data?.list_items !== undefined)?.list_items;
if (list_items !== undefined) {
field.list_items = list_items;
field.formatted_value = {
...field.formatted_value,
list_items
};
}

if (rootCaseField.field_type.type !== FieldsUtils.SERVER_RESPONSE_FIELD_TYPE_COLLECTION) {
const value = dynamicListValue[0]?.value;
if (value !== undefined) {
field.value = value;
field.formatted_value = {
...field.formatted_value,
value
};
}
}
}
}

private static getDynamicListValue(jsonBlock: any, key: string) {
const data = jsonBlock ? this.getNestedFieldValues(jsonBlock, key, []) : [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,53 @@ describe('FieldTypeSanitiser', () => {
expect(editForm.data.dynamicList).toEqual(EXPECTED_VALUE_DYNAMIC_LIST);
});

it('should use collection row DynamicMultiSelectList options without leaking selections to new rows', () => {
const defaultItems = [{ code: 'default-1', label: 'Default 1' }, { code: 'default-2', label: 'Default 2' }];
const recipientOneItems = [{ code: 'recipient-1-order-1', label: 'Recipient 1 order 1' }];
const recipientTwoItems = [{ code: 'recipient-2-order-1', label: 'Recipient 2 order 1' }];
const collectionCaseFields = [{
id: 'stmtOfServiceAddRecipient',
value: [
{ id: 'recipient-1', value: { orderList: { list_items: recipientOneItems, value: [recipientOneItems[0]] } } },
{ id: 'recipient-2', value: { orderList: { list_items: recipientTwoItems, value: [recipientTwoItems[0]] } } }
],
field_type: {
type: 'Collection',
collection_field_type: {
type: 'Complex',
complex_fields: [{
id: 'orderList',
field_type: { type: 'DynamicMultiSelectList' },
display_context: 'OPTIONAL',
list_items: defaultItems
}]
}
}
}] as unknown as CaseField[];
const formData = {
stmtOfServiceAddRecipient: [
{ id: 'recipient-1', value: { orderList: [recipientOneItems[0]] } },
{ id: 'recipient-2', value: { orderList: [recipientTwoItems[0]] } },
{ id: 'recipient-3', value: { orderList: [defaultItems[1]] } }
]
};

new FieldTypeSanitiser().sanitiseLists(collectionCaseFields, formData);

expect(formData.stmtOfServiceAddRecipient[0].value.orderList as any).toEqual({
value: [recipientOneItems[0]],
list_items: recipientOneItems
});
expect(formData.stmtOfServiceAddRecipient[1].value.orderList as any).toEqual({
value: [recipientTwoItems[0]],
list_items: recipientTwoItems
});
expect(formData.stmtOfServiceAddRecipient[2].value.orderList as any).toEqual({
value: [defaultItems[1]],
list_items: defaultItems
});
});

describe('ensureDynamicMultiSelectListPopulated', () => {
let fieldTypeSanitiser: FieldTypeSanitiser;
let mockCaseFields: CaseField[];
Expand Down
Loading
Loading