diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts index 7311d19e949..bd1e81f55a7 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts @@ -86,7 +86,7 @@ describe('DSDynamicTypeBindRelationService test suite', () => { it('Should not push undefined bind models', () => { const testModel = mockInputWithTypeBindModel; testModel.typeBindRelations = getTypeBindRelations(['boundType']); - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.returnValue(undefined); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.returnValue(undefined); const relatedModels = service.getRelatedFormModel(testModel); @@ -143,7 +143,7 @@ describe('DSDynamicTypeBindRelationService test suite', () => { opposingMatch: HIDDEN_MATCHER.match, onChange: jasmine.createSpy('onChange') }; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.returnValue(undefined); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.returnValue(undefined); const hasMatch = service.matchesCondition(relation, visibleMatcher); @@ -159,7 +159,7 @@ describe('DSDynamicTypeBindRelationService test suite', () => { opposingMatch: MATCH_VISIBLE, onChange: jasmine.createSpy('onChange') }; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.returnValue(undefined); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.returnValue(undefined); const hasMatch = service.matchesCondition(relation, hiddenMatcher); @@ -185,8 +185,8 @@ describe('DSDynamicTypeBindRelationService test suite', () => { onChange: jasmine.createSpy('onChange') }; (service as any).dynamicMatchers = [visibleMatcher]; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.callFake(() => bindModelAvailable ? bindModel : undefined); - spyOn((service as any).formBuilderService, 'getTypeBindModelUpdates').and.returnValue(bindModelUpdates$.asObservable()); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.callFake(() => bindModelAvailable ? bindModel : undefined); + ((service as any).formBuilderService.getTypeBindModelUpdates as jasmine.Spy).and.returnValue(bindModelUpdates$.asObservable()); const subscriptions = service.subscribeRelations(testModel, dcTypeControl); expect(subscriptions.length).toBe(1); @@ -220,8 +220,8 @@ describe('DSDynamicTypeBindRelationService test suite', () => { onChange: jasmine.createSpy('onChange') }; (service as any).dynamicMatchers = [hiddenMatcher]; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.callFake(() => bindModelAvailable ? bindModel : undefined); - spyOn((service as any).formBuilderService, 'getTypeBindModelUpdates').and.returnValue(bindModelUpdates$.asObservable()); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.callFake(() => bindModelAvailable ? bindModel : undefined); + ((service as any).formBuilderService.getTypeBindModelUpdates as jasmine.Spy).and.returnValue(bindModelUpdates$.asObservable()); const subscriptions = service.subscribeRelations(testModel, dcTypeControl); expect(subscriptions.length).toBe(1); @@ -254,8 +254,8 @@ describe('DSDynamicTypeBindRelationService test suite', () => { onChange: jasmine.createSpy('onChange') }; (service as any).dynamicMatchers = [visibleMatcher]; - spyOn((service as any).formBuilderService, 'getTypeBindModel').and.callFake(() => bindModelAvailable ? bindModel : undefined); - spyOn((service as any).formBuilderService, 'getTypeBindModelUpdates').and.returnValue(bindModelUpdates$.asObservable()); + ((service as any).formBuilderService.getTypeBindModel as jasmine.Spy).and.callFake(() => bindModelAvailable ? bindModel : undefined); + ((service as any).formBuilderService.getTypeBindModelUpdates as jasmine.Spy).and.returnValue(bindModelUpdates$.asObservable()); const subscriptions = service.subscribeRelations(testModel, dcTypeControl); expect(visibleMatcher.onChange).toHaveBeenCalledWith(false, testModel, dcTypeControl, jasmine.anything()); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.spec.ts index 116e4a0f463..3df5cedc06a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.spec.ts @@ -5,6 +5,7 @@ import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync, } fro import { By } from '@angular/platform-browser'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { DynamicFormLayoutService, DynamicFormsCoreModule, DynamicFormValidationService } from '@ng-dynamic-forms/core'; import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap'; @@ -15,6 +16,7 @@ import { VocabularyServiceStub } from '../../../../../testing/vocabulary-service import { DsDynamicScrollableDropdownComponent } from './dynamic-scrollable-dropdown.component'; import { DynamicScrollableDropdownModel } from './dynamic-scrollable-dropdown.model'; import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model'; +import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model'; import { createTestComponent, hasClass } from '../../../../../testing/utils.test'; import { mockDynamicFormLayoutService, @@ -209,6 +211,50 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => { expect(scrollableDropdownComp.optionsList).toEqual(vocabularyServiceStub.getList()); expect(scrollableDropdownComp.model.value).toEqual(modelValue); }); + + it('should fall back to the underlying value when display is empty on value change', () => { + const valueWithEmptyDisplay = { display: '', value: 'Corpus' }; + scrollableDropdownComp.setCurrentValue(valueWithEmptyDisplay); + + let currentValue; + scrollableDropdownComp.currentValue.subscribe((v) => currentValue = v); + + expect(currentValue).toBe('Corpus'); + }); + + it('should fall back to the underlying value when display is empty on init', () => { + // Build the value bypassing the FormFieldMetadataValueObject constructor (which would + // otherwise apply its own `display || value`), so the init branch genuinely receives an + // empty display and exercises the fallback in setCurrentValue. + const emptyDisplayValue = Object.assign( + new FormFieldMetadataValueObject(), + { display: '', value: 'Corpus' } + ); + spyOn(scrollableDropdownComp, 'getInitValueFromModel').and.returnValue(observableOf(emptyDisplayValue)); + scrollableDropdownComp.setCurrentValue(emptyDisplayValue, true); + + let currentValue; + scrollableDropdownComp.currentValue.subscribe((v) => currentValue = v); + + expect(currentValue).toBe('Corpus'); + }); + + // Regression for ufal/clarin-dspace#1377: after a dc.type change the section + // reloads and pushes a new value with an empty display through the form control. + // The rendered input must not blank out. + it('should keep the rendered value when a value with empty display arrives via valueChanges', fakeAsync(() => { + const reloadedValue = Object.assign( + new FormFieldMetadataValueObject(), + { display: '', value: 'Corpus', authority: 'corpus-auth' } + ); + + scrollableDropdownComp.group.get(scrollableDropdownComp.model.id).setValue(reloadedValue); + tick(); + scrollableDropdownFixture.detectChanges(); + + const input = scrollableDropdownFixture.debugElement.query(By.css('input.form-control')).nativeElement; + expect(input.value).toBe('Corpus'); + })); }); }); }); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts index b284fa4fc5e..4c5f3069b31 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts @@ -276,7 +276,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom if (init && !this.useFindAllService) { result = this.getInitValueFromModel().pipe( - map((formValue: FormFieldMetadataValueObject) => formValue.display) + map((formValue: FormFieldMetadataValueObject) => formValue.display || formValue.value) ); } else { if (isEmpty(value)) { @@ -286,7 +286,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom } else if (this.useFindAllService) { result = observableOf(value[this.model.displayKey]); } else { - result = observableOf(value.display); + result = observableOf(value.display || value.value); } }