AAE-49862 Fix Form Rule Does Not Update Hidden Status of Outcomes on First Run (#12168)

This commit is contained in:
Darren Thornton
2026-08-19 10:17:52 -05:00
committed by GitHub
parent 70db1e383f
commit f915c813f0
6 changed files with 80 additions and 9 deletions
@@ -16,6 +16,7 @@
*/ */
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { FormEvent } from '../events/form.event';
import { FormFieldEvent } from '../events/form-field.event'; import { FormFieldEvent } from '../events/form-field.event';
import { FormRulesEvent } from '../events/form-rules.event'; import { FormRulesEvent } from '../events/form-rules.event';
import { ValidateFormFieldEvent } from '../events/validate-form-field.event'; import { ValidateFormFieldEvent } from '../events/validate-form-field.event';
@@ -26,4 +27,5 @@ export interface FormValidationService {
validateForm: Subject<ValidateFormEvent>; validateForm: Subject<ValidateFormEvent>;
validateFormField: Subject<ValidateFormFieldEvent>; validateFormField: Subject<ValidateFormFieldEvent>;
formRulesEvent?: Subject<FormRulesEvent>; formRulesEvent?: Subject<FormRulesEvent>;
formVisibilityRefreshed?: Subject<FormEvent>;
} }
@@ -62,6 +62,12 @@ export class FormService implements FormValidationService {
formRulesEvent = new Subject<FormRulesEvent>(); formRulesEvent = new Subject<FormRulesEvent>();
/**
* 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<FormEvent>();
constructor() { constructor() {
const injectedFieldValidators = inject(FORM_SERVICE_FIELD_VALIDATORS_TOKEN, { optional: true }); const injectedFieldValidators = inject(FORM_SERVICE_FIELD_VALIDATORS_TOKEN, { optional: true });
@@ -19,6 +19,7 @@ import { TestBed } from '@angular/core/testing';
import { ContainerModel, FormFieldModel, FormFieldTypes, FormModel, TabModel } from '../components/widgets/core'; import { ContainerModel, FormFieldModel, FormFieldTypes, FormModel, TabModel } from '../components/widgets/core';
import { WidgetVisibilityModel } from '../models/widget-visibility.model'; import { WidgetVisibilityModel } from '../models/widget-visibility.model';
import { WidgetVisibilityService } from './widget-visibility.service'; import { WidgetVisibilityService } from './widget-visibility.service';
import { FormService } from './form.service';
import { import {
fakeFormJson, fakeFormJson,
formTest, formTest,
@@ -50,6 +51,30 @@ describe('WidgetVisibilityService', () => {
service = TestBed.inject(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', () => { describe('should be able to evaluate next condition operations', () => {
it('using == and return true', () => { it('using == and return true', () => {
const resultsArray = evaluateConditions( const resultsArray = evaluateConditions(
@@ -15,16 +15,20 @@
* limitations under the License. * 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 { FormFieldModel, FormModel, TabModel, ContainerModel, FormOutcomeModel } from '../components/widgets/core';
import { FormEvent } from '../events/form.event';
import { TaskProcessVariableModel } from '../models/task-process-variable.model'; import { TaskProcessVariableModel } from '../models/task-process-variable.model';
import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model'; import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model';
import { format, isValid, parse } from 'date-fns'; import { format, isValid, parse } from 'date-fns';
import { FormService } from './form.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class WidgetVisibilityService { export class WidgetVisibilityService {
private readonly formService = inject(FormService);
private processVarList: TaskProcessVariableModel[]; private processVarList: TaskProcessVariableModel[];
private form: FormModel; private form: FormModel;
@@ -45,6 +49,8 @@ export class WidgetVisibilityService {
} }
form.getFormFields().map((field) => this.refreshEntityVisibility(field)); form.getFormFields().map((field) => this.refreshEntityVisibility(field));
this.formService.formVisibilityRefreshed.next(new FormEvent(form));
} }
} }
@@ -32,6 +32,8 @@ import {
provideTranslations, provideTranslations,
AuthModule, AuthModule,
FormFieldEvent, FormFieldEvent,
FormEvent,
FormRulesEvent,
NoopTranslateModule, NoopTranslateModule,
NoopAuthModule, NoopAuthModule,
FORM_FIELD_VALIDATORS FORM_FIELD_VALIDATORS
@@ -1299,6 +1301,38 @@ describe('FormCloudComponent', () => {
expect(formComponent.visibleOutcomes).toEqual([]); 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 () => { it('should raise [executeOutcome] event for formService', async () => {
spyOn(formComponent.executeOutcome, 'emit'); spyOn(formComponent.executeOutcome, 'emit');
@@ -30,7 +30,7 @@ import {
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from '@angular/core'; } 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 { filter, map, switchMap } from 'rxjs/operators';
import { import {
ConfirmDialogComponent, ConfirmDialogComponent,
@@ -306,11 +306,11 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
} }
}); });
this.formService.formRulesEvent merge(
.pipe( this.formService.formVisibilityRefreshed.pipe(filter((event) => event.form?.id === this.form?.id)),
filter((event) => event?.type === 'fieldValueChanged' && event.form?.id === this.form?.id), this.formService.formRulesEvent.pipe(filter((event) => event?.type === 'fieldValueChanged' && event.form?.id === this.form?.id))
takeUntilDestroyed()
) )
.pipe(takeUntilDestroyed())
.subscribe(() => this.recomputeVisibleOutcomes()); .subscribe(() => this.recomputeVisibleOutcomes());
} }
@@ -595,7 +595,6 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
checkVisibility(field: FormFieldModel) { checkVisibility(field: FormFieldModel) {
if (field?.form) { if (field?.form) {
this.visibilityService.refreshVisibility(field.form); this.visibilityService.refreshVisibility(field.form);
this.recomputeVisibleOutcomes();
} }
} }
@@ -613,7 +612,6 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
this.setCheckParentVisibilityForValidationOnFields(); this.setCheckParentVisibilityForValidationOnFields();
this.visibilityService.refreshVisibility(this.form); this.visibilityService.refreshVisibility(this.form);
this.form.validateForm(); this.form.validateForm();
this.recomputeVisibleOutcomes();
this.onFormLoaded(this.form); this.onFormLoaded(this.form);
this.formService.formRulesEvent.next(new FormRulesEvent('dataRefreshed', new FormEvent(this.form))); this.formService.formRulesEvent.next(new FormRulesEvent('dataRefreshed', new FormEvent(this.form)));
this.onFormDataRefreshed(this.form); this.onFormDataRefreshed(this.form);