From 31bafd718e3e2e853bb7c8410d82242fa874721b Mon Sep 17 00:00:00 2001 From: Darren Thornton <6361057+dthornton-hyl@users.noreply.github.com> Date: Fri, 6 Feb 2026 07:10:39 -0600 Subject: [PATCH] AAE-40604 Fix case when rest/variable are okay and dropdown is required and not selected (#11619) --- .../dropdown/dropdown-cloud.widget.spec.ts | 135 ++++++++++++++++++ .../widgets/dropdown/dropdown-cloud.widget.ts | 17 ++- 2 files changed, 148 insertions(+), 4 deletions(-) diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts index bfc3d492d9..5c16022547 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts @@ -303,6 +303,34 @@ describe('DropdownCloudWidgetComponent', () => { }); }); + describe('when options load successfully from restUrl', () => { + beforeEach(() => { + spyOn(formCloudService, 'getRestWidgetData').and.returnValue(of(fakeOptionList)); + widget.field.restUrl = 'https://fake-rest-url'; + widget.field.optionType = 'rest'; + widget.field.required = true; + widget.field.value = ''; + widget.field.isVisible = true; + }); + + it('should show required message for rest type when required and value is empty after touch', fakeAsync(() => { + widget.ngOnInit(); + fixture.detectChanges(); + tick(DROPDOWN_CLOUD_WIDGET_SET_VALUE_DEBOUNCE); + fixture.detectChanges(); + + expect(widget.isRestApiFailed).toBe(false); + expect(widget.dropdownControl.errors?.required).toBeTruthy(); + + widget.dropdownControl.markAsTouched(); + fixture.detectChanges(); + + const requiredErrorElement = fixture.debugElement.query(By.css('.adf-dropdown-required-message .adf-error-text')); + expect(requiredErrorElement).toBeTruthy(); + expect(requiredErrorElement.nativeElement.innerText).toEqual('FORM.FIELD.REQUIRED'); + })); + }); + it('should preselect dropdown widget value when Json (rest call) passed', async () => { widget.field.restUrl = 'https://fake-rest-url'; widget.field.optionType = 'rest'; @@ -1127,6 +1155,86 @@ describe('DropdownCloudWidgetComponent', () => { expect(widget.dropdownControl.value).toEqual({ id: 'testValueObj', name: 'testValueObjName' }); })); + it('should set dropdownControl value to null when field value is undefined', fakeAsync(() => { + widget.field = { + value: undefined, + options: [], + isVisible: true, + markAsValid: () => {} + } as FormFieldModel; + + fixture.detectChanges(); + widget['setFormControlValue'](); + + tick(DROPDOWN_CLOUD_WIDGET_SET_VALUE_DEBOUNCE); + + expect(widget.dropdownControl.value).toBeNull(); + })); + + it('should set dropdownControl value to null when field value is empty string', fakeAsync(() => { + widget.field = { + value: '', + options: [], + isVisible: true, + markAsValid: () => {} + } as FormFieldModel; + + fixture.detectChanges(); + widget['setFormControlValue'](); + + tick(DROPDOWN_CLOUD_WIDGET_SET_VALUE_DEBOUNCE); + + expect(widget.dropdownControl.value).toBeNull(); + })); + + it('should set dropdownControl value to null when field value is null', fakeAsync(() => { + widget.field = { + value: null, + options: [], + isVisible: true, + markAsValid: () => {} + } as FormFieldModel; + + fixture.detectChanges(); + widget['setFormControlValue'](); + + tick(DROPDOWN_CLOUD_WIDGET_SET_VALUE_DEBOUNCE); + + expect(widget.dropdownControl.value).toBeNull(); + })); + + it('should set dropdownControl value to null when field value is default/empty option object', fakeAsync(() => { + widget.field = { + value: { id: 'empty', name: 'Choose one...' }, + options: [], + isVisible: true, + markAsValid: () => {} + } as FormFieldModel; + + fixture.detectChanges(); + widget['setFormControlValue'](); + + tick(DROPDOWN_CLOUD_WIDGET_SET_VALUE_DEBOUNCE); + + expect(widget.dropdownControl.value).toBeNull(); + })); + + it('should set dropdownControl value to null when field value object has empty id', fakeAsync(() => { + widget.field = { + value: { id: '', name: 'label' }, + options: [], + isVisible: true, + markAsValid: () => {} + } as FormFieldModel; + + fixture.detectChanges(); + widget['setFormControlValue'](); + + tick(DROPDOWN_CLOUD_WIDGET_SET_VALUE_DEBOUNCE); + + expect(widget.dropdownControl.value).toBeNull(); + })); + it('should display options persisted from process variable', async () => { widget.field = getVariableDropdownWidget( 'variables.json-variable', @@ -1212,6 +1320,33 @@ describe('DropdownCloudWidgetComponent', () => { expect(variableFailedElement).toBeTruthy(); }); + it('should show required message for variable type when options load successfully and required and value is empty after touch', fakeAsync(() => { + widget.field = getVariableDropdownWidget( + 'variables.json-variable', + 'response.people.players', + 'playerId', + 'playerFullName', + mockProcessVariablesWithJson + ); + widget.field.required = true; + widget.field.value = ''; + widget.field.isVisible = true; + widget.ngOnInit(); + fixture.detectChanges(); + tick(DROPDOWN_CLOUD_WIDGET_SET_VALUE_DEBOUNCE); + fixture.detectChanges(); + + expect(widget.variableOptionsFailed).toBe(false); + expect(widget.dropdownControl.errors?.required).toBeTruthy(); + + widget.dropdownControl.markAsTouched(); + fixture.detectChanges(); + + const requiredErrorElement = fixture.debugElement.query(By.css('.adf-dropdown-required-message .adf-error-text')); + expect(requiredErrorElement).toBeTruthy(); + expect(requiredErrorElement.nativeElement.innerText).toEqual('FORM.FIELD.REQUIRED'); + })); + it('should return empty array and display error when id is incorrect', () => { widget.field = getVariableDropdownWidget( 'variables.json-variable', diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts index e1d22ce633..79a4282184 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts @@ -142,9 +142,14 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI if (Array.isArray(this.field.value)) { value = this.field?.value; } else if (this.field?.value && typeof this.field?.value === 'object') { - value = { id: this.field?.value.id, name: this.field?.value.name }; - } else if (this.field.value === null) { - value = this.field.value; + const obj = this.field.value; + if (obj.id === DEFAULT_OPTION.id || obj.id === '' || obj.id === undefined) { + value = null; + } else { + value = { id: obj.id, name: obj.name }; + } + } else if (this.field.value === null || this.field.value === undefined || this.field.value === '') { + value = null; } else { value = { id: this.field?.value, name: '' }; } @@ -519,7 +524,11 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI return event.field.type === FormFieldTypes.DROPDOWN; } - private setOptionValue(option: FormFieldOption | FormFieldOption[], field: FormFieldModel) { + private setOptionValue(option: FormFieldOption | FormFieldOption[] | null, field: FormFieldModel) { + if (option == null) { + field.value = undefined; + return; + } if (Array.isArray(option) || field.hasMultipleValues) { field.value = option; return;