From a02a8a4ad96df6601ebc306065c28abeac6ec1f4 Mon Sep 17 00:00:00 2001 From: Pablo Martinez Garcia Date: Thu, 14 Apr 2022 11:59:12 +0200 Subject: [PATCH] [AAE-8086] Propagate form events (#7572) * [AAE-8086] Propagate events * [AAE-8086] Add form rules manager to the form renderer * [AAE-8086] Extensibility improvements * [AAE-8086] Fix wrong import * [AAE-8087] Add form actions * [AAE-8086] Initialize form rules manager on form renderer component changes * [AAE-8087] Fix form actions * [AAE-8087] Fix unit tests for field visibility * trigger travis --- .../form-field/form-field.component.spec.ts | 9 +- .../form-field/form-field.component.ts | 2 +- .../form-renderer.component.spec.ts | 35 +++++-- .../components/form-renderer.component.ts | 24 ++++- .../widgets/core/form.model.spec.ts | 55 ++++++++++- .../components/widgets/core/form.model.ts | 41 ++++++++ .../widgets/widget.component.spec.ts | 19 +++- .../components/widgets/widget.component.ts | 3 + lib/core/form/events/form-rules.event.ts | 32 +++++++ lib/core/form/events/index.ts | 1 + lib/core/form/models/form-rules.model.spec.ts | 93 +++++++++++++++++++ lib/core/form/models/form-rules.model.ts | 81 ++++++++++++++++ lib/core/form/public-api.ts | 2 + lib/core/form/services/form.service.spec.ts | 73 ++++++++++++++- lib/core/form/services/form.service.ts | 9 ++ .../src/lib/form/mocks/cloud-form.mock.ts | 1 + .../form/form.component.visibility.spec.ts | 14 +-- .../task-form/task-form.component.spec.ts | 4 +- 18 files changed, 464 insertions(+), 34 deletions(-) create mode 100644 lib/core/form/events/form-rules.event.ts create mode 100644 lib/core/form/models/form-rules.model.spec.ts create mode 100644 lib/core/form/models/form-rules.model.ts diff --git a/lib/core/form/components/form-field/form-field.component.spec.ts b/lib/core/form/components/form-field/form-field.component.spec.ts index 0409c6a27a..147d7aa3e9 100644 --- a/lib/core/form/components/form-field/form-field.component.spec.ts +++ b/lib/core/form/components/form-field/form-field.component.spec.ts @@ -113,7 +113,8 @@ describe('FormFieldComponent', () => { component.field.isVisible = false; fixture.detectChanges(); fixture.whenStable().then(() => { - expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').hidden).toBeTruthy(); + const debugElement = fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').style.visibility; + expect(debugElement).toEqual('hidden'); done(); }); }); @@ -128,7 +129,7 @@ describe('FormFieldComponent', () => { fixture.detectChanges(); fixture.whenStable().then(() => { - expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').hidden).toBeFalsy(); + expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').style.visibility).toEqual('visible'); done(); }); }); @@ -141,10 +142,10 @@ describe('FormFieldComponent', () => { component.field = field; fixture.detectChanges(); - expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').hidden).toBeFalsy(); + expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').style.visibility).toEqual('visible'); component.field.isVisible = false; fixture.detectChanges(); - expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').hidden).toBeTruthy(); + expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').style.visibility).toEqual('hidden'); }); it('[C213878] - Should fields be correctly rendered when filled with process variables', async () => { diff --git a/lib/core/form/components/form-field/form-field.component.ts b/lib/core/form/components/form-field/form-field.component.ts index 49ef7bc62b..27a0530e24 100644 --- a/lib/core/form/components/form-field/form-field.component.ts +++ b/lib/core/form/components/form-field/form-field.component.ts @@ -40,7 +40,7 @@ declare const adf: any; selector: 'adf-form-field', template: `
diff --git a/lib/core/form/components/form-renderer.component.spec.ts b/lib/core/form/components/form-renderer.component.spec.ts index 57766a4748..a9d195b38c 100644 --- a/lib/core/form/components/form-renderer.component.spec.ts +++ b/lib/core/form/components/form-renderer.component.spec.ts @@ -43,6 +43,7 @@ import { FormRenderingService } from '../services/form-rendering.service'; import { TextWidgetComponent } from './widgets'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; +import { FormRulesManager } from '../models/form-rules.model'; const typeIntoInput = (targetInput: HTMLInputElement, message: string) => { expect(targetInput).toBeTruthy('Expected input to set to be valid and not null'); @@ -57,12 +58,12 @@ const typeIntoDate = (targetInput: DebugElement, date: { srcElement: { value: st const expectElementToBeHidden = (targetElement: HTMLElement): void => { expect(targetElement).toBeTruthy(); - expect(targetElement.hidden).toBe(true, `${targetElement.id} should be hidden but it is not`); + expect(targetElement.style.visibility).toBe('hidden', `${targetElement.id} should be hidden but it is not`); }; const expectElementToBeVisible = (targetElement: HTMLElement): void => { expect(targetElement).toBeTruthy(); - expect(targetElement.hidden).toBe(false, `${targetElement.id} should be visibile but it is not`); + expect(targetElement.style.visibility).not.toBe('hidden', `${targetElement.id} should be visibile but it is not`); }; const expectInputElementValueIs = (targetElement: HTMLInputElement, value: string): void => { @@ -70,22 +71,23 @@ const expectInputElementValueIs = (targetElement: HTMLInputElement, value: strin expect(targetElement.value).toBe(value, `invalid value for ${targetElement.name}`); }; -const expectElementToBeInvalid = (fieldId: string, fixture: ComponentFixture): void => { +const expectElementToBeInvalid = (fieldId: string, fixture: ComponentFixture>): void => { const invalidElementContainer = fixture.nativeElement.querySelector(`#field-${fieldId}-container .adf-invalid`); expect(invalidElementContainer).toBeTruthy(); }; -const expectElementToBeValid = (fieldId: string, fixture: ComponentFixture): void => { +const expectElementToBeValid = (fieldId: string, fixture: ComponentFixture>): void => { const invalidElementContainer = fixture.nativeElement.querySelector(`#field-${fieldId}-container .adf-invalid`); expect(invalidElementContainer).toBeFalsy(); }; describe('Form Renderer Component', () => { - let formRendererComponent: FormRendererComponent; - let fixture: ComponentFixture; + let formRendererComponent: FormRendererComponent; + let fixture: ComponentFixture>; let formService: FormService; let formRenderingService: FormRenderingService; + let rulesManager: FormRulesManager; setupTestBed({ imports: [ @@ -100,6 +102,7 @@ describe('Form Renderer Component', () => { formRendererComponent = fixture.componentInstance; formService = TestBed.inject(FormService); formRenderingService = TestBed.inject(FormRenderingService); + rulesManager = fixture.debugElement.injector.get(FormRulesManager); }); afterEach(() => { @@ -659,4 +662,24 @@ describe('Form Renderer Component', () => { }); }); + + describe('Form rules', () => { + it('should call the Form Rules Manager init on component changes', () => { + spyOn(rulesManager, 'initialize'); + const formModel = formService.parseForm(customWidgetFormWithVisibility.formRepresentation.formDefinition); + + formRendererComponent.formDefinition = formModel; + formRendererComponent.ngOnChanges(); + + expect(rulesManager.initialize).toHaveBeenCalledWith(formModel); + }); + + it('should call the Form Rules Manager destroy on component destruction', () => { + spyOn(rulesManager, 'destroy'); + + formRendererComponent.ngOnDestroy(); + + expect(rulesManager.destroy).toHaveBeenCalled(); + }); + }); }); diff --git a/lib/core/form/components/form-renderer.component.ts b/lib/core/form/components/form-renderer.component.ts index 1deb4323fe..3b8ecddca3 100644 --- a/lib/core/form/components/form-renderer.component.ts +++ b/lib/core/form/components/form-renderer.component.ts @@ -15,16 +15,24 @@ * limitations under the License. */ -import { Component, ViewEncapsulation, Input } from '@angular/core'; +import { Component, ViewEncapsulation, Input, OnDestroy, Injector, OnChanges } from '@angular/core'; +import { FormRulesManager, formRulesManagerFactory } from '../models/form-rules.model'; import { FormModel } from './widgets/core/form.model'; @Component({ selector: 'adf-form-renderer', templateUrl: './form-renderer.component.html', styleUrls: ['./form-renderer.component.scss'], - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.None, + providers: [ + { + provide: FormRulesManager, + useFactory: formRulesManagerFactory, + deps: [Injector] + } + ] }) -export class FormRendererComponent { +export class FormRendererComponent implements OnChanges, OnDestroy { /** Toggle debug options. */ @Input() @@ -35,4 +43,14 @@ export class FormRendererComponent { debugMode: boolean; + constructor(private formRulesManager: FormRulesManager) { } + + ngOnChanges(): void { + this.formRulesManager.initialize(this.formDefinition); + } + + ngOnDestroy() { + this.formRulesManager.destroy(); + } + } diff --git a/lib/core/form/components/widgets/core/form.model.spec.ts b/lib/core/form/components/widgets/core/form.model.spec.ts index 617db89bff..44be53d7b5 100644 --- a/lib/core/form/components/widgets/core/form.model.spec.ts +++ b/lib/core/form/components/widgets/core/form.model.spec.ts @@ -24,7 +24,7 @@ import { FormFieldModel } from './form-field.model'; import { FormOutcomeModel } from './form-outcome.model'; import { FormModel } from './form.model'; import { TabModel } from './tab.model'; -import { fakeMetadataForm, fakeViewerForm } from 'process-services-cloud/src/lib/form/mocks/cloud-form.mock'; +import { cloudFormMock, fakeMetadataForm, fakeViewerForm } from 'process-services-cloud/src/lib/form/mocks/cloud-form.mock'; import { Node } from '@alfresco/js-api'; import { UploadWidgetContentLinkModel } from './upload-widget-content-link.model'; import { AlfrescoApiService } from '../../../../services'; @@ -607,8 +607,8 @@ describe('FormModel', () => { expect(form.values['pfx_property_one']).toBe('testValue'); expect(form.values['pfx_property_two']).toBe(true); - expect(form.values['pfx_property_three']).toEqual({ id: 'opt_1', name: 'Option 1'}); - expect(form.values['pfx_property_four']).toEqual({ id: 'option_2', name: 'Option: 2'}); + expect(form.values['pfx_property_three']).toEqual({ id: 'opt_1', name: 'Option 1' }); + expect(form.values['pfx_property_four']).toEqual({ id: 'option_2', name: 'Option: 2' }); expect(form.values['pfx_property_five']).toEqual('green'); expect(form.values['pfx_property_six']).toEqual('text-value'); expect(form.values['pfx_property_seven']).toBeNull(); @@ -649,4 +649,53 @@ describe('FormModel', () => { expect(form.values['cmfb85b2a7295ba41209750bca176ccaf9a']).toBeNull(); }); }); + + describe('Form actions', () => { + + let form: FormModel; + let fieldId: string; + let field: FormFieldModel; + + beforeEach(() => { + form = new FormModel(cloudFormMock); + fieldId = 'text1'; + field = form.getFieldById(fieldId); + }); + + it('should change field visibility', () => { + const originalValue = field.isVisible; + + form.changeFieldVisibility(fieldId, !originalValue); + + expect(field.isVisible).toEqual(!originalValue); + }); + + it('should change field disabled', () => { + const originalValue = field.readOnly; + + form.changeFieldDisabled(fieldId, !originalValue); + + expect(field.readOnly).toEqual(!originalValue); + }); + + it('should change field required', () => { + const originalValue = field.required; + + form.changeFieldRequired(fieldId, !originalValue); + + expect(field.required).toEqual(!originalValue); + }); + + it('should change field value', () => { + form.changeFieldValue(fieldId, 'newValue'); + + expect(field.value).toEqual('newValue'); + }); + + it('should change variable value', () => { + form.changeVariableValue('FormVarStrId', 'newValue'); + + expect(form.getFormVariable('FormVarStrId').value).toEqual('newValue'); + }); + }); }); diff --git a/lib/core/form/components/widgets/core/form.model.ts b/lib/core/form/components/widgets/core/form.model.ts index 4f63c5dc80..13ed152a43 100644 --- a/lib/core/form/components/widgets/core/form.model.ts +++ b/lib/core/form/components/widgets/core/form.model.ts @@ -33,6 +33,7 @@ import { FormFieldTemplates } from './form-field-templates'; import { UploadWidgetContentLinkModel } from './upload-widget-content-link.model'; import { FormValidationService } from '../../../services/form-validation-service.interface'; import { ProcessFormModel } from './process-form-model.interface'; +import { WidgetTypeEnum, WidgetVisibilityModel } from '../../../models/widget-visibility.model'; export interface FormRepresentationModel { [key: string]: any; @@ -419,4 +420,44 @@ export class FormModel implements ProcessFormModel { viewer.value = viewer.parseValue(viewer.json); }); } + + changeFieldVisibility(fieldId: string, visibility: boolean): void { + const visibilityRule: WidgetVisibilityModel = new WidgetVisibilityModel(); + + const field = this.getFieldById(fieldId); + if (!!field) { + visibilityRule.operator = visibility ? 'empty' : '!empty'; + visibilityRule.leftType = WidgetTypeEnum.field; + field.visibilityCondition = visibilityRule; + field.isVisible = false; + } + } + + changeFieldDisabled(fieldId: string, disabled: boolean): void { + const field = this.getFieldById(fieldId); + if (!!field) { + field.readOnly = this.readOnly || disabled; + } + } + + changeFieldRequired(fieldId: string, required: boolean): void { + const field = this.getFieldById(fieldId); + if (!!field) { + field.required = required; + } + } + + changeFieldValue(fieldId: string, value: any): void { + const field = this.getFieldById(fieldId); + if (!!field) { + field.value = value; + } + } + + changeVariableValue(variableId: string, value: any): void { + const variable = this.getFormVariable(variableId); + if (!!variable) { + variable.value = value; + } + } } diff --git a/lib/core/form/components/widgets/widget.component.spec.ts b/lib/core/form/components/widgets/widget.component.spec.ts index 6f61ff386e..6104246a33 100644 --- a/lib/core/form/components/widgets/widget.component.spec.ts +++ b/lib/core/form/components/widgets/widget.component.spec.ts @@ -54,7 +54,16 @@ describe('WidgetComponent', () => { element.click(); }); - }); + + it('should click event be redirect on the form rules event service', (done) => { + widget.formService.formRulesEvent.subscribe((event) => { + expect(event.type).toEqual('click'); + done(); + }); + + element.click(); + }); + }); it('should check field', () => { expect(widget.hasField()).toBeFalsy(); @@ -64,7 +73,7 @@ describe('WidgetComponent', () => { it('should send an event after view init', (done) => { const fakeForm = new FormModel(); - const fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'}); + const fakeField = new FormFieldModel(fakeForm, { id: 'fakeField', value: 'fakeValue' }); widget.field = fakeField; widget.fieldChanged.subscribe((field) => { @@ -79,7 +88,7 @@ describe('WidgetComponent', () => { it('should send an event when a field is changed', (done) => { const fakeForm = new FormModel(); - const fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'}); + const fakeField = new FormFieldModel(fakeForm, { id: 'fakeField', value: 'fakeValue' }); widget.fieldChanged.subscribe((field) => { expect(field).not.toBe(null); expect(field.id).toBe('fakeField'); @@ -96,10 +105,10 @@ describe('WidgetComponent', () => { widget.field = new FormFieldModel(null); expect(widget.isRequired()).toBeFalsy(); - widget.field = new FormFieldModel(null, {required: false}); + widget.field = new FormFieldModel(null, { required: false }); expect(widget.isRequired()).toBeFalsy(); - widget.field = new FormFieldModel(null, {required: true}); + widget.field = new FormFieldModel(null, { required: true }); expect(widget.isRequired()).toBeTruthy(); }); }); diff --git a/lib/core/form/components/widgets/widget.component.ts b/lib/core/form/components/widgets/widget.component.ts index 53dcd838ed..d2dbbdc6cd 100644 --- a/lib/core/form/components/widgets/widget.component.ts +++ b/lib/core/form/components/widgets/widget.component.ts @@ -18,6 +18,8 @@ /* eslint-disable @angular-eslint/component-selector */ import { AfterViewInit, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; +import { FormFieldEvent } from '../../events/form-field.event'; +import { FormRulesEvent } from '../../events/form-rules.event'; import { FormService } from './../../services/form.service'; import { FormFieldModel } from './core/index'; @@ -106,6 +108,7 @@ export class WidgetComponent implements AfterViewInit { event(event: Event): void { this.formService.formEvents.next(event); + this.formService.formRulesEvent.next(new FormRulesEvent(event?.type, new FormFieldEvent(this.field?.form, this.field), event)); } markAsTouched() { diff --git a/lib/core/form/events/form-rules.event.ts b/lib/core/form/events/form-rules.event.ts new file mode 100644 index 0000000000..0cba4f64af --- /dev/null +++ b/lib/core/form/events/form-rules.event.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FormFieldEvent } from './form-field.event'; +import { FormEvent } from './form.event'; + +export class FormRulesEvent extends FormFieldEvent { + + readonly type: string; + readonly event: Event; + + constructor(type: string, formEvent: FormEvent, event?: Event) { + super(formEvent.form, formEvent['field']); + this.type = type; + this.event = event; + } + +} diff --git a/lib/core/form/events/index.ts b/lib/core/form/events/index.ts index 0194d7b9fe..ba2617e5d4 100644 --- a/lib/core/form/events/index.ts +++ b/lib/core/form/events/index.ts @@ -21,3 +21,4 @@ export * from './form-field.event'; export * from './validate-form-field.event'; export * from './validate-form.event'; export * from './validate-dynamic-table-row.event'; +export * from './form-rules.event'; diff --git a/lib/core/form/models/form-rules.model.spec.ts b/lib/core/form/models/form-rules.model.spec.ts new file mode 100644 index 0000000000..a89f14322b --- /dev/null +++ b/lib/core/form/models/form-rules.model.spec.ts @@ -0,0 +1,93 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { setupTestBed } from '../../testing/setup-test-bed'; +import { FormBaseModule } from '../form-base.module'; +import { CoreTestingModule } from '../../testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { ByPassFormRuleManager, FormRulesManager, formRulesManagerFactory, FORM_RULES_MANAGER } from '../models/form-rules.model'; +import { Injector } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +class CustomRuleManager extends FormRulesManager { + protected getRules() { + return null; + } + protected handleRuleEvent(): void { + return; + } + +} + +describe('Form Rules', () => { + + let injector: Injector; + const customRuleManager = new CustomRuleManager(null); + + describe('Injection token provided', () => { + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + CoreTestingModule, + FormBaseModule + ], + providers: [ + { + provide: FORM_RULES_MANAGER, + useValue: customRuleManager + } + ] + }); + + beforeEach(() => { + injector = TestBed.inject(Injector); + }); + + it('factory function should not return bypass service', () => { + spyOn(customRuleManager, 'destroy'); + spyOn(customRuleManager, 'initialize'); + const rulesManager = formRulesManagerFactory(injector); + + expect(rulesManager instanceof CustomRuleManager).toBeTruthy(); + + rulesManager.destroy(); + expect(customRuleManager.destroy).toHaveBeenCalled(); + rulesManager.initialize(null); + expect(customRuleManager.initialize).toHaveBeenCalled(); + }); + }); + + describe('Injection token not provided', () => { + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + CoreTestingModule, + FormBaseModule + ] + }); + + beforeEach(() => { + injector = TestBed.inject(Injector); + }); + + it('factory function should return bypass service', () => { + const rulesManager = formRulesManagerFactory(injector); + + expect(rulesManager instanceof ByPassFormRuleManager).toBeTruthy(); + }); + }); +}); diff --git a/lib/core/form/models/form-rules.model.ts b/lib/core/form/models/form-rules.model.ts new file mode 100644 index 0000000000..32f652405d --- /dev/null +++ b/lib/core/form/models/form-rules.model.ts @@ -0,0 +1,81 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InjectionToken, Injector } from '@angular/core'; +import { Subject } from 'rxjs'; +import { filter, takeUntil } from 'rxjs/operators'; +import { FormRulesEvent } from '../events/form-rules.event'; +import { FormModel, FormService } from '../public-api'; + +export const FORM_RULES_MANAGER = new InjectionToken>('form.rule.manager'); + +export function formRulesManagerFactory(injector: Injector): FormRulesManager { + try { + return injector.get(FORM_RULES_MANAGER); + } catch { + return new ByPassFormRuleManager(null); + } +} + +export abstract class FormRulesManager { + constructor(private formService: FormService) { } + + protected formModel: FormModel; + private onDestroy$ = new Subject(); + private initialized = false; + + initialize(formModel: FormModel) { + if (this.initialized) { + this.destroy(); + this.onDestroy$ = new Subject(); + } + + this.formModel = formModel; + const rules = this.getRules(); + + if (!!rules) { + this.formService.formRulesEvent + .pipe( + filter(event => !!event.form.id && event.form.id === formModel?.id), + takeUntil(this.onDestroy$) + ).subscribe(event => { + this.handleRuleEvent(event, rules); + }); + } + + this.initialized = true; + } + + protected abstract getRules(): T; + protected abstract handleRuleEvent(event: FormRulesEvent, rules: T): void; + + destroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } +} + +export class ByPassFormRuleManager extends FormRulesManager { + + protected getRules(): T { + return null; + } + + protected handleRuleEvent(): void { + return; + } +} diff --git a/lib/core/form/public-api.ts b/lib/core/form/public-api.ts index 39fd7dadb1..4f67b97067 100644 --- a/lib/core/form/public-api.ts +++ b/lib/core/form/public-api.ts @@ -37,3 +37,5 @@ export * from './services/widget-visibility.service'; export * from './events/index'; export * from './form-base.module'; + +export * from './models/form-rules.model'; diff --git a/lib/core/form/services/form.service.spec.ts b/lib/core/form/services/form.service.spec.ts index 22619ce32b..0998eb32f8 100644 --- a/lib/core/form/services/form.service.spec.ts +++ b/lib/core/form/services/form.service.spec.ts @@ -21,6 +21,9 @@ import { FormService } from './form.service'; import { setupTestBed } from '../../testing/setup-test-bed'; import { CoreTestingModule } from '../../testing/core.testing.module'; import { TranslateModule } from '@ngx-translate/core'; +import { FormEvent, ValidateDynamicTableRowEvent, ValidateFormEvent, ValidateFormFieldEvent } from '../events'; +import { take } from 'rxjs/operators'; +import { FormModel } from '../components/widgets/core/form.model'; declare let jasmine: any; @@ -88,7 +91,7 @@ describe('Form service', () => { it('should fetch and parse process definitions', (done) => { service.getProcessDefinitions().subscribe(() => { expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/process-definitions')).toBeTruthy(); - expect( [ { id: '1' }, { id: '2' } ]).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data); + expect([{ id: '1' }, { id: '2' }]).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data); done(); }); @@ -102,7 +105,7 @@ describe('Form service', () => { it('should fetch and parse tasks', (done) => { service.getTasks().subscribe(() => { expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/query')).toBeTruthy(); - expect( [ { id: '1' }, { id: '2' } ]).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data); + expect([{ id: '1' }, { id: '2' }]).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data); done(); }); @@ -408,5 +411,69 @@ describe('Form service', () => { done(); }); }); - }); + }); + + describe('Form rules', () => { + const event = new FormEvent('mock'); + + it('should emit the formLoaded in the formRulesEvent observable', async(done) => { + service.formRulesEvent.pipe(take(1)).subscribe(formRuleEvent => { + expect(formRuleEvent.event).toBeFalsy(); + expect(formRuleEvent.field).toBeFalsy(); + expect(formRuleEvent.form).toEqual('mock'); + expect(formRuleEvent.type).toEqual('formLoaded'); + done(); + }); + + service.formLoaded.next(event); + }); + + it('should emit the formDataRefreshed in the formRulesEvent observable', async(done) => { + service.formRulesEvent.pipe(take(1)).subscribe(formRuleEvent => { + expect(formRuleEvent.event).toBeFalsy(); + expect(formRuleEvent.field).toBeFalsy(); + expect(formRuleEvent.form).toEqual('mock'); + expect(formRuleEvent.type).toEqual('formDataRefreshed'); + done(); + }); + + service.formDataRefreshed.next(event); + }); + + it('should emit the formValidated in the formRulesEvent observable', async(done) => { + service.formRulesEvent.pipe(take(1)).subscribe(formRuleEvent => { + expect(formRuleEvent.event).toBeFalsy(); + expect(formRuleEvent.field).toBeFalsy(); + expect(formRuleEvent.form).toEqual('mock'); + expect(formRuleEvent.type).toEqual('formValidated'); + done(); + }); + + service.validateForm.next(new ValidateFormEvent('mock')); + }); + + it('should emit the fieldValidated in the formRulesEvent observable', async(done) => { + service.formRulesEvent.pipe(take(1)).subscribe(formRuleEvent => { + expect(formRuleEvent.event).toBeFalsy(); + expect(formRuleEvent.field).toBeFalsy(); + expect(formRuleEvent.form).toEqual('mock'); + expect(formRuleEvent.type).toEqual('fieldValidated'); + done(); + }); + + service.validateFormField.next(new ValidateFormFieldEvent('mock', null)); + }); + + it('should emit the fieldDynamicTableRowValidated in the formRulesEvent observable', async(done) => { + service.formRulesEvent.pipe(take(1)).subscribe(formRuleEvent => { + expect(formRuleEvent.event).toBeFalsy(); + expect(formRuleEvent.field).toBeFalsy(); + expect(formRuleEvent.form).toEqual('mock'); + expect(formRuleEvent.type).toEqual('fieldDynamicTableRowValidated'); + done(); + }); + + service.validateDynamicTableRow.next(new ValidateDynamicTableRowEvent('mock' as unknown as FormModel, null, null, null)); + }); + }); }); diff --git a/lib/core/form/services/form.service.ts b/lib/core/form/services/form.service.ts index 21be23cc9f..49fd879763 100644 --- a/lib/core/form/services/form.service.ts +++ b/lib/core/form/services/form.service.ts @@ -49,6 +49,7 @@ import { ValidateFormEvent } from '../events/validate-form.event'; import { ValidateFormFieldEvent } from '../events/validate-form-field.event'; import { ValidateDynamicTableRowEvent } from '../events/validate-dynamic-table-row.event'; import { FormValidationService } from './form-validation-service.interface'; +import { FormRulesEvent } from '../events/form-rules.event'; @Injectable({ providedIn: 'root' @@ -130,9 +131,17 @@ export class FormService implements FormValidationService { updateFormValuesRequested = new Subject(); + formRulesEvent = new Subject(); + constructor(private ecmModelService: EcmModelService, private apiService: AlfrescoApiService, protected logService: LogService) { + + this.formLoaded.subscribe(event => this.formRulesEvent.next(new FormRulesEvent('formLoaded', event))); + this.formDataRefreshed.subscribe(event => this.formRulesEvent.next(new FormRulesEvent('formDataRefreshed', event))); + this.validateForm.subscribe(event => this.formRulesEvent.next(new FormRulesEvent('formValidated', event))); + this.validateFormField.subscribe(event => this.formRulesEvent.next(new FormRulesEvent('fieldValidated', event))); + this.validateDynamicTableRow.subscribe(event => this.formRulesEvent.next(new FormRulesEvent('fieldDynamicTableRowValidated', event))); } /** diff --git a/lib/process-services-cloud/src/lib/form/mocks/cloud-form.mock.ts b/lib/process-services-cloud/src/lib/form/mocks/cloud-form.mock.ts index 011325d895..4c2525efed 100644 --- a/lib/process-services-cloud/src/lib/form/mocks/cloud-form.mock.ts +++ b/lib/process-services-cloud/src/lib/form/mocks/cloud-form.mock.ts @@ -649,6 +649,7 @@ export const cloudFormMock = { metadata: {}, variables: [ { + id: 'FormVarStrId', name: 'FormVarStr', type: 'string', value: '' diff --git a/lib/process-services/src/lib/form/form.component.visibility.spec.ts b/lib/process-services/src/lib/form/form.component.visibility.spec.ts index 0fdef59724..ff31c4fe99 100644 --- a/lib/process-services/src/lib/form/form.component.visibility.spec.ts +++ b/lib/process-services/src/lib/form/form.component.visibility.spec.ts @@ -158,12 +158,12 @@ describe('FormComponent UI and visibility', () => { fixture.detectChanges(); const firstEl = fixture.debugElement.query(By.css('#field-country-container')); - expect(firstEl.nativeElement.hidden).toBeTruthy(); + expect(firstEl.nativeElement.style.visibility).toBe('hidden'); const secondEl = fixture.debugElement.query(By.css('#name')); expect(secondEl).not.toBeNull(); expect(secondEl).toBeDefined(); - expect(fixture.nativeElement.querySelector('#field-name-container').hidden).toBeFalsy(); + expect(fixture.nativeElement.querySelector('#field-name-container').style.visibility).not.toBe('hidden'); }); it('should hide the field based on the previous one', () => { @@ -177,10 +177,10 @@ describe('FormComponent UI and visibility', () => { const firstEl = fixture.debugElement.query(By.css('#name')); expect(firstEl).not.toBeNull(); expect(firstEl).toBeDefined(); - expect(fixture.nativeElement.querySelector('#field-name-container').hidden).toBeFalsy(); + expect(fixture.nativeElement.querySelector('#field-name-container').style.visibility).not.toBe('hidden'); const secondEl = fixture.debugElement.query(By.css('#field-country-container')); - expect(secondEl.nativeElement.hidden).toBeTruthy(); + expect(secondEl.nativeElement.style.visibility).toBe('hidden'); }); it('should show the hidden field when the visibility condition change to true', () => { @@ -192,10 +192,10 @@ describe('FormComponent UI and visibility', () => { fixture.detectChanges(); let firstEl = fixture.debugElement.query(By.css('#field-country-container')); - expect(firstEl.nativeElement.hidden).toBeTruthy(); + expect(firstEl.nativeElement.style.visibility).toBe('hidden'); const secondEl = fixture.debugElement.query(By.css('#field-name-container')); - expect(secondEl.nativeElement.hidden).toBeFalsy(); + expect(secondEl.nativeElement.style.visibility).not.toBe('hidden'); const inputElement = fixture.nativeElement.querySelector('#name'); inputElement.value = 'italy'; @@ -203,7 +203,7 @@ describe('FormComponent UI and visibility', () => { fixture.detectChanges(); firstEl = fixture.debugElement.query(By.css('#field-country-container')); - expect(firstEl.nativeElement.hidden).toBeFalsy(); + expect(firstEl.nativeElement.style.visibility).not.toBe('hidden'); }); }); diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts index f9d5db0928..7395753b85 100644 --- a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts +++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts @@ -513,9 +513,9 @@ describe('TaskFormComponent', () => { fixture.detectChanges(); await fixture.whenStable(); const inputThreeContainer = fixture.nativeElement.querySelector('#field-text3-container'); - expect(inputThreeContainer.hidden).toBe(true); + expect(inputThreeContainer.style.visibility).toBe('hidden'); const completeOutcomeButton: HTMLButtonElement = fixture.nativeElement.querySelector('#adf-form-complete'); - expect(completeOutcomeButton.hidden).toBe(false); + expect(completeOutcomeButton.style.visibility).not.toBe('hidden'); completeOutcomeButton.click(); fixture.detectChanges(); });