From f915c813f0bc45958f5251398345735b4e43dd96 Mon Sep 17 00:00:00 2001 From: Darren Thornton <6361057+dthornton-hyl@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:17:52 -0500 Subject: [PATCH] AAE-49862 Fix Form Rule Does Not Update Hidden Status of Outcomes on First Run (#12168) --- .../form-validation-service.interface.ts | 2 ++ .../src/lib/form/services/form.service.ts | 6 ++++ .../widget-visibility.service.spec.ts | 25 ++++++++++++++ .../services/widget-visibility.service.ts | 8 ++++- .../components/form-cloud.component.spec.ts | 34 +++++++++++++++++++ .../form/components/form-cloud.component.ts | 14 ++++---- 6 files changed, 80 insertions(+), 9 deletions(-) diff --git a/lib/core/src/lib/form/services/form-validation-service.interface.ts b/lib/core/src/lib/form/services/form-validation-service.interface.ts index 08b78bd6b5..7a5c5d6e7d 100644 --- a/lib/core/src/lib/form/services/form-validation-service.interface.ts +++ b/lib/core/src/lib/form/services/form-validation-service.interface.ts @@ -16,6 +16,7 @@ */ import { Subject } from 'rxjs'; +import { FormEvent } from '../events/form.event'; import { FormFieldEvent } from '../events/form-field.event'; import { FormRulesEvent } from '../events/form-rules.event'; import { ValidateFormFieldEvent } from '../events/validate-form-field.event'; @@ -26,4 +27,5 @@ export interface FormValidationService { validateForm: Subject; validateFormField: Subject; formRulesEvent?: Subject; + formVisibilityRefreshed?: Subject; } diff --git a/lib/core/src/lib/form/services/form.service.ts b/lib/core/src/lib/form/services/form.service.ts index be9091917a..131430c972 100644 --- a/lib/core/src/lib/form/services/form.service.ts +++ b/lib/core/src/lib/form/services/form.service.ts @@ -62,6 +62,12 @@ export class FormService implements FormValidationService { formRulesEvent = new Subject(); + /** + * Emitted after form field/outcome visibility has been re-evaluated via WidgetVisibilityService.refreshVisibility. + * Internal ADF form-rendering event — not part of the FormValidationService contract. + */ + formVisibilityRefreshed = new Subject(); + constructor() { const injectedFieldValidators = inject(FORM_SERVICE_FIELD_VALIDATORS_TOKEN, { optional: true }); diff --git a/lib/core/src/lib/form/services/widget-visibility.service.spec.ts b/lib/core/src/lib/form/services/widget-visibility.service.spec.ts index 796a01afb9..a75f9fa768 100644 --- a/lib/core/src/lib/form/services/widget-visibility.service.spec.ts +++ b/lib/core/src/lib/form/services/widget-visibility.service.spec.ts @@ -19,6 +19,7 @@ import { TestBed } from '@angular/core/testing'; import { ContainerModel, FormFieldModel, FormFieldTypes, FormModel, TabModel } from '../components/widgets/core'; import { WidgetVisibilityModel } from '../models/widget-visibility.model'; import { WidgetVisibilityService } from './widget-visibility.service'; +import { FormService } from './form.service'; import { fakeFormJson, formTest, @@ -50,6 +51,30 @@ describe('WidgetVisibilityService', () => { service = TestBed.inject(WidgetVisibilityService); }); + it('should emit formVisibilityRefreshed when visibility is refreshed', () => { + const formService = TestBed.inject(FormService); + let emittedForm: FormModel | undefined; + + formService.formVisibilityRefreshed.subscribe((event) => { + emittedForm = event.form; + }); + + service.refreshVisibility(stubFormWithFields); + + expect(emittedForm).toBe(stubFormWithFields); + }); + + it('should not emit formVisibilityRefreshed when form is null', () => { + const formService = TestBed.inject(FormService); + let emitCount = 0; + + formService.formVisibilityRefreshed.subscribe(() => emitCount++); + + service.refreshVisibility(null); + + expect(emitCount).toBe(0); + }); + describe('should be able to evaluate next condition operations', () => { it('using == and return true', () => { const resultsArray = evaluateConditions( diff --git a/lib/core/src/lib/form/services/widget-visibility.service.ts b/lib/core/src/lib/form/services/widget-visibility.service.ts index 3e717afa2f..a7d1ea039d 100644 --- a/lib/core/src/lib/form/services/widget-visibility.service.ts +++ b/lib/core/src/lib/form/services/widget-visibility.service.ts @@ -15,16 +15,20 @@ * limitations under the License. */ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { FormFieldModel, FormModel, TabModel, ContainerModel, FormOutcomeModel } from '../components/widgets/core'; +import { FormEvent } from '../events/form.event'; import { TaskProcessVariableModel } from '../models/task-process-variable.model'; import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model'; import { format, isValid, parse } from 'date-fns'; +import { FormService } from './form.service'; @Injectable({ providedIn: 'root' }) export class WidgetVisibilityService { + private readonly formService = inject(FormService); + private processVarList: TaskProcessVariableModel[]; private form: FormModel; @@ -45,6 +49,8 @@ export class WidgetVisibilityService { } form.getFormFields().map((field) => this.refreshEntityVisibility(field)); + + this.formService.formVisibilityRefreshed.next(new FormEvent(form)); } } diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts index 5ef60cf0b2..221f4dedb5 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.spec.ts @@ -32,6 +32,8 @@ import { provideTranslations, AuthModule, FormFieldEvent, + FormEvent, + FormRulesEvent, NoopTranslateModule, NoopAuthModule, FORM_FIELD_VALIDATORS @@ -1299,6 +1301,38 @@ describe('FormCloudComponent', () => { expect(formComponent.visibleOutcomes).toEqual([]); }); + it('should recompute visibleOutcomes when form visibility is refreshed', () => { + formComponent.showCompleteButton = true; + const formModel = new FormModel(cloudFormMock); + formComponent.form = formModel; + + expect(formComponent.visibleOutcomes.length).toBeGreaterThan(0); + + formModel.outcomes.forEach((outcome) => { + outcome.isVisible = false; + }); + + TestBed.inject(FormService).formVisibilityRefreshed.next(new FormEvent(formModel)); + + expect(formComponent.visibleOutcomes).toEqual([]); + }); + + it('should recompute visibleOutcomes when fieldValueChanged rule event fires', () => { + formComponent.showCompleteButton = true; + const formModel = new FormModel(cloudFormMock); + formComponent.form = formModel; + + expect(formComponent.visibleOutcomes.length).toBeGreaterThan(0); + + formModel.outcomes.forEach((outcome) => { + outcome.isVisible = false; + }); + + TestBed.inject(FormService).formRulesEvent.next(new FormRulesEvent('fieldValueChanged', new FormEvent(formModel))); + + expect(formComponent.visibleOutcomes).toEqual([]); + }); + it('should raise [executeOutcome] event for formService', async () => { spyOn(formComponent.executeOutcome, 'emit'); diff --git a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts index fd2a681e99..c2ab42da40 100644 --- a/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/form/components/form-cloud.component.ts @@ -30,7 +30,7 @@ import { SimpleChanges, ViewChild } from '@angular/core'; -import { forkJoin, isObservable, Observable, of, Subscription } from 'rxjs'; +import { forkJoin, isObservable, merge, Observable, of, Subscription } from 'rxjs'; import { filter, map, switchMap } from 'rxjs/operators'; import { ConfirmDialogComponent, @@ -306,11 +306,11 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, } }); - this.formService.formRulesEvent - .pipe( - filter((event) => event?.type === 'fieldValueChanged' && event.form?.id === this.form?.id), - takeUntilDestroyed() - ) + merge( + this.formService.formVisibilityRefreshed.pipe(filter((event) => event.form?.id === this.form?.id)), + this.formService.formRulesEvent.pipe(filter((event) => event?.type === 'fieldValueChanged' && event.form?.id === this.form?.id)) + ) + .pipe(takeUntilDestroyed()) .subscribe(() => this.recomputeVisibleOutcomes()); } @@ -595,7 +595,6 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, checkVisibility(field: FormFieldModel) { if (field?.form) { this.visibilityService.refreshVisibility(field.form); - this.recomputeVisibleOutcomes(); } } @@ -613,7 +612,6 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges, this.setCheckParentVisibilityForValidationOnFields(); this.visibilityService.refreshVisibility(this.form); this.form.validateForm(); - this.recomputeVisibleOutcomes(); this.onFormLoaded(this.form); this.formService.formRulesEvent.next(new FormRulesEvent('dataRefreshed', new FormEvent(this.form))); this.onFormDataRefreshed(this.form);