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 new file mode 100644 index 0000000000..af00dcb7ac --- /dev/null +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.spec.ts @@ -0,0 +1,131 @@ +import {inject, TestBed, waitForAsync} from '@angular/core/testing'; + +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { + DYNAMIC_FORM_CONTROL_TYPE_ARRAY, + DYNAMIC_FORM_CONTROL_TYPE_GROUP, + DynamicFormControlEvent, DynamicFormControlRelation, DynamicFormValidationService, + DynamicFormControlMatcher, DynamicFormRelationService, + DynamicInputModel, MATCH_VISIBLE, OR_OPERATOR, HIDDEN_MATCHER, DYNAMIC_MATCHERS +} from '@ng-dynamic-forms/core'; + + + +import { + mockInputWithTypeBindModel, MockRelationModel, mockDcTypeInputModel +} from '../../../mocks/form-models.mock'; +import {DsDynamicTypeBindRelationService} from './ds-dynamic-type-bind-relation.service'; +import {FormFieldMetadataValueObject} from "../models/form-field-metadata-value.model"; +import {FormControl, NG_ASYNC_VALIDATORS, NG_VALIDATORS, ReactiveFormsModule} from "@angular/forms"; +import {FormBuilderService} from "../form-builder.service"; +import {getMockFormBuilderService} from "../../../mocks/form-builder-service.mock"; +import {DsDynamicFormComponent} from "./ds-dynamic-form.component"; +import {DsDynamicInputModel} from "./models/ds-dynamic-input.model"; +import {FieldParser} from "../parsers/field-parser"; + +describe('DSDynamicTypeBindRelationService test suite', () => { + let service: DsDynamicTypeBindRelationService; + let dynamicFormRelationService: DynamicFormRelationService; + let dynamicFormControlMatchers: DynamicFormControlMatcher[]; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule], + providers: [ + //{ provide: FormBuilderService, useValue: getMockFormBuilderService() }, + { provide: FormBuilderService, useValue: getMockFormBuilderService() }, + { provide: DsDynamicTypeBindRelationService, useClass: DsDynamicTypeBindRelationService }, + { provide: DynamicFormRelationService }, + ] + }).compileComponents().then(); + }); + + beforeEach(inject([DsDynamicTypeBindRelationService, DynamicFormRelationService], + (relationService: DsDynamicTypeBindRelationService, + formRelationService: DynamicFormRelationService + ) => { + service = relationService; + dynamicFormRelationService = formRelationService; + dynamicFormControlMatchers = []; + })); + + describe('Test getTypeBindValue method', () => { + it('Should get type bind "boundType" from the given metadata object value', () => { + const mockMetadataValueObject: FormFieldMetadataValueObject = new FormFieldMetadataValueObject( + 'boundType', null, null, 'Bound Type' + ); + const bindType = service.getTypeBindValue(mockMetadataValueObject); + expect(bindType).toBe('boundType'); + }); + it('Should get type authority key "bound-auth-key" from the given metadata object value', () => { + const mockMetadataValueObject: FormFieldMetadataValueObject = new FormFieldMetadataValueObject( + 'boundType', null, 'bound-auth-key', 'Bound Type' + ); + const bindType = service.getTypeBindValue(mockMetadataValueObject); + console.dir(bindType); + expect(bindType).toBe('bound-auth-key'); + }); + it('Should get passed string returned directly as string passed instead of metadata', () => { + const bindType = service.getTypeBindValue('rawString'); + expect(bindType).toBe('rawString'); + }); + it('Should get "undefined" returned directly as no object given', () => { + const bindType = service.getTypeBindValue(undefined); + expect(bindType).toBeUndefined(); + }); + }); + + describe('Test getRelatedFormModel method', () => { + it('Should get 0 related form models for simple type bind mock data', () => { + const testModel = mockInputWithTypeBindModel; + const relatedModels = service.getRelatedFormModel(testModel); + expect(relatedModels).toHaveSize(0); + }); + it('Should get 1 related form models for mock relation model data', () => { + const testModel = MockRelationModel; + testModel.typeBindRelations = getTypeBindRelations(['boundType']); + const relatedModels = service.getRelatedFormModel(testModel); + expect(relatedModels).toHaveSize(1); + }); + }); + + describe('Test matchesCondition method', () => { + it('Should receive one subscription to dc.type type binding"', () => { + const testModel = MockRelationModel; + //testModel.typeBindRelations = getTypeBindRelations(['boundType']); + const relatedModels = service.getRelatedFormModel(testModel); + const dcTypeControl = new FormControl(); + dcTypeControl.setValue('boundType'); + expect(service.subscribeRelations(testModel, dcTypeControl)).toHaveSize(1); + }); + + it('TEST MTACHe"', () => { + const testModel = MockRelationModel; + testModel.typeBindRelations = getTypeBindRelations(['boundType']); + const relatedModels = service.getRelatedFormModel(testModel); + const dcTypeControl = new FormControl(); + dcTypeControl.setValue('boundType'); + testModel.typeBindRelations[0].when[0].value = 'asdfaf'; + const relation = dynamicFormRelationService.findRelationByMatcher((testModel as any).typeBindRelations, HIDDEN_MATCHER); + // console.dir(relation); + }); + }); + + + +}); + +function getTypeBindRelations(configuredTypeBindValues: string[]): DynamicFormControlRelation[] { + const bindValues = []; + configuredTypeBindValues.forEach((value) => { + bindValues.push({ + id: 'dc.type', + value: value + }); + }); + return [{ + match: MATCH_VISIBLE, + operator: OR_OPERATOR, + when: bindValues + }]; +} diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.ts index 3ec6909c07..959952e6a6 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.ts @@ -15,10 +15,11 @@ import { OR_OPERATOR } from '@ng-dynamic-forms/core'; -import { isNotUndefined, isUndefined } from '../../../empty.util'; +import { isNotUndefined, isUndefined, hasNoValue, hasValue } from '../../../empty.util'; import { FormBuilderService } from '../form-builder.service'; import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './ds-dynamic-form-constants'; +import {DsDynamicInputModel} from "./models/ds-dynamic-input.model"; /** * Service to manage type binding for submission input fields @@ -39,11 +40,12 @@ export class DsDynamicTypeBindRelationService { * @param bindModelValue * @private */ - private static getTypeBindValue(bindModelValue: string | FormFieldMetadataValueObject): string { + public getTypeBindValue(bindModelValue: string | FormFieldMetadataValueObject): string { let value; - if (isUndefined(bindModelValue) || typeof bindModelValue === 'string') { + if (hasNoValue(bindModelValue) || typeof bindModelValue === 'string') { value = bindModelValue; - } else if (bindModelValue.hasAuthority()) { + } else if (bindModelValue instanceof FormFieldMetadataValueObject + && bindModelValue.hasAuthority()) { value = bindModelValue.authority; } else { value = bindModelValue.value; @@ -110,9 +112,9 @@ export class DsDynamicTypeBindRelationService { // be used, or where the entry doesn't have .value but is a string itself, etc) // If values isn't an array, make it a single element array with the looked-up type bind value. if (Array.isArray(bindModelValue)) { - values = [...bindModelValue.map((entry) => DsDynamicTypeBindRelationService.getTypeBindValue(entry))]; + values = [...bindModelValue.map((entry) => this.getTypeBindValue(entry))]; } else { - values = [DsDynamicTypeBindRelationService.getTypeBindValue(bindModelValue)]; + values = [this.getTypeBindValue(bindModelValue)]; } // If bind model evaluates to 'true' (is not undefined, is not null, is not false etc, @@ -186,10 +188,12 @@ export class DsDynamicTypeBindRelationService { const relatedModels = this.getRelatedFormModel(model); const subscriptions: Subscription[] = []; + console.dir(relatedModels); + Object.values(relatedModels).forEach((relatedModel: any) => { - if (isNotUndefined(relatedModel)) { - const initValue = (isUndefined(relatedModel.value) || typeof relatedModel.value === 'string') ? relatedModel.value : + if (hasValue(relatedModel)) { + const initValue = (hasNoValue(relatedModel.value) || typeof relatedModel.value === 'string') ? relatedModel.value : (Array.isArray(relatedModel.value) ? relatedModel.value : relatedModel.value.value); const valueChanges = relatedModel.valueChanges.pipe( @@ -200,20 +204,22 @@ export class DsDynamicTypeBindRelationService { // I still don't fully understand what is happening here, or the triggers in various form usage that // cause which / what to fire change events, why the matcher has onChange() instead of a field value or // form model, etc. - subscriptions.push(valueChanges.subscribe(() => { - // Iterate each matcher - this.dynamicMatchers.forEach((matcher) => { + if (hasValue(this.dynamicMatchers) || true) { + subscriptions.push(valueChanges.subscribe(() => { + // Iterate each matcher + this.dynamicMatchers.forEach((matcher) => { - // Find the relation - const relation = this.dynamicFormRelationService.findRelationByMatcher((model as any).typeBindRelations, matcher); + // Find the relation + const relation = this.dynamicFormRelationService.findRelationByMatcher((model as any).typeBindRelations, matcher); - // If the relation is defined, get matchesCondition result and pass it to the onChange event listener - if (relation !== undefined) { - const hasMatch = this.matchesCondition(relation, matcher); - matcher.onChange(hasMatch, model, control, this.injector); - } - }); - })); + // If the relation is defined, get matchesCondition result and pass it to the onChange event listener + if (relation !== undefined) { + const hasMatch = this.matchesCondition(relation, matcher); + matcher.onChange(hasMatch, model, control, this.injector); + } + }); + })); + } } }); diff --git a/src/app/shared/mocks/form-builder-service.mock.ts b/src/app/shared/mocks/form-builder-service.mock.ts index e37df20e13..025d5bd6ba 100644 --- a/src/app/shared/mocks/form-builder-service.mock.ts +++ b/src/app/shared/mocks/form-builder-service.mock.ts @@ -1,7 +1,30 @@ import { FormBuilderService } from '../form/builder/form-builder.service'; import { FormControl, FormGroup } from '@angular/forms'; +import {DynamicFormControlModel, DynamicInputModel} from "@ng-dynamic-forms/core"; +import {DsDynamicInputModel} from "../form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model"; export function getMockFormBuilderService(): FormBuilderService { + + const inputWithTypeBindConfig = { + name: 'testWithTypeBind', + id: 'testWithTypeBind', + readOnly: false, + disabled: false, + repeatable: false, + value: { + value: 'testWithTypeBind', + display: 'testWithTypeBind' + }, + submissionId: '1234', + metadataFields: [], + hasSelectableMetadata: false, + typeBindRelations: [ + {match: 'VISIBLE', operator: 'OR', when: [{'id': 'dc.type', 'value': 'boundType'}]} + ] + }; + + const thing = new DsDynamicInputModel(inputWithTypeBindConfig); + return jasmine.createSpyObj('FormBuilderService', { modelFromConfiguration: [], createFormGroup: new FormGroup({}), @@ -17,8 +40,26 @@ export function getMockFormBuilderService(): FormBuilderService { isQualdropGroup: false, isModelInCustomGroup: true, isRelationGroup: true, - hasArrayGroupValue: true - + hasArrayGroupValue: true, + getTypeBindModel: new DsDynamicInputModel({ + name: 'testWithTypeBind', + id: 'testWithTypeBind', + readOnly: false, + disabled: false, + repeatable: false, + value: { + value: 'testWithTypeBind', + display: 'testWithTypeBind', + authority: 'bound-auth-key' + }, + submissionId: '1234', + metadataFields: [], + hasSelectableMetadata: false, + typeBindRelations: [ + {match: 'VISIBLE', operator: 'OR', when: [{'id': 'dc.type', 'value': 'boundType'}]} + ] + } + ) }); } diff --git a/src/app/shared/mocks/form-models.mock.ts b/src/app/shared/mocks/form-models.mock.ts index 100f98caff..3529f9e81b 100644 --- a/src/app/shared/mocks/form-models.mock.ts +++ b/src/app/shared/mocks/form-models.mock.ts @@ -306,3 +306,57 @@ export const mockFileFormEditRowGroupModel = new DynamicRowGroupModel({ id: 'mockRowGroupModel', group: [mockFileFormEditInputModel] }); + +// Mock configuration and model for an input with type binding +export const inputWithTypeBindConfig = { + name: 'testWithTypeBind', + id: 'testWithTypeBind', + readOnly: false, + disabled: false, + repeatable: false, + value: { + value: 'testWithTypeBind', + display: 'testWithTypeBind', + authority: 'bound-auth-key' + }, + submissionId: '1234', + metadataFields: [], + hasSelectableMetadata: false, + getTypeBindModel: new DsDynamicInputModel({ + name: 'testWithTypeBind', + id: 'testWithTypeBind', + readOnly: false, + disabled: false, + repeatable: false, + value: { + value: 'testWithTypeBind', + display: 'testWithTypeBind', + authority: 'bound-auth-key' + }, + submissionId: '1234', + metadataFields: [], + hasSelectableMetadata: false, + typeBindRelations: [ + {match: 'VISIBLE', operator: 'OR', when: [{'id': 'dc.type', 'value': 'boundType'}]} + ] + } + ) +}; + +export const mockInputWithTypeBindModel = new DsDynamicInputModel(inputWithAuthorityValueConfig); + +export const dcTypeInputConfig = { + name: 'dc.type', + id: 'dc_type', + readOnly: false, + disabled: false, + repeatable: false, + submissionId: '1234', + metadataFields: [], + hasSelectableMetadata: false, + value: { + value: 'boundType' + } +}; + +export const mockDcTypeInputModel = new DsDynamicInputModel(dcTypeInputConfig);