AAE-50868 Fix some visibility conditions scenarios with form variables (#12206)

* AAE-50868 Fix some visibility conditions senarios with form variables

* form 0 check updates

* copilot review suggestions
This commit is contained in:
Darren Thornton
2026-09-02 11:34:41 -05:00
committed by GitHub
parent b773e2b49d
commit 6bb7ec3b7d
8 changed files with 370 additions and 22 deletions
@@ -2575,3 +2575,126 @@ describe('FormCloudComponent — runtime state preservation on data refresh', ()
});
});
});
describe('FormCloudComponent — form variable visibility on data refresh', () => {
let fixture: ComponentFixture<FormCloudComponent>;
let formComponent: FormCloudComponent;
let visibilityService: WidgetVisibilityService;
/** field is hidden while person_type is Requestor */
const personTypeFormJson = {
id: 'person-type-form',
name: 'Person Type Form',
variables: [{ id: 'person-type-var', name: 'person_type', value: null }],
fields: [
{
fieldType: 'ContainerRepresentation',
id: 'container1',
name: 'Container',
type: 'container',
tab: null,
numberOfColumns: 1,
fields: {
1: [
{
fieldType: 'FormFieldRepresentation',
id: 'conditionalField',
name: 'Conditional Field',
type: 'multiline-text',
value: null,
required: false,
readOnly: false,
visibilityCondition: {
leftType: 'variable',
leftValue: 'person_type',
operator: '!=',
rightType: 'value',
rightValue: 'Requestor',
nextConditionOperator: '',
nextCondition: null
},
params: { existingColspan: 1, maxColspan: 1 }
}
]
}
}
]
};
const requestorVariables = () => [new TaskVariableCloud({ name: 'variables.person_type', value: 'Requestor' })];
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NoopTranslateModule, NoopAuthModule, FormCloudComponent],
providers: [
{ provide: VersionCompatibilityService, useValue: {} },
{ provide: FormRenderingService, useClass: CloudFormRenderingService }
]
});
const apiService = TestBed.inject(AlfrescoApiService);
spyOn(apiService, 'getInstance').and.returnValue(mockOauth2Auth);
visibilityService = TestBed.inject(WidgetVisibilityService);
visibilityService.cleanProcessVariable();
fixture = TestBed.createComponent(FormCloudComponent);
formComponent = fixture.componentInstance;
formComponent.formCloudRepresentationJSON = new FormCloudRepresentation(JSON.parse(JSON.stringify(personTypeFormJson)));
formComponent.formCloudRepresentationJSON.processVariables = requestorVariables();
formComponent.data = requestorVariables();
formComponent.form = formComponent.parseForm(formComponent.formCloudRepresentationJSON);
visibilityService.refreshVisibility(formComponent.form, formComponent.data);
fixture.detectChanges();
});
it('should keep the field hidden when the refreshed data omits the variable', () => {
expect(formComponent.form.getFieldById('conditionalField').isVisible).toBeFalse();
const partialData = [new TaskVariableCloud({ name: 'processOutput', value: 'result' })];
const change = new SimpleChange(formComponent.data, partialData, false);
formComponent.data = partialData;
formComponent.ngOnChanges({ data: change });
expect(formComponent.form.getFieldById('conditionalField').isVisible).toBeFalse();
});
it('should show the field when the refreshed data changes the variable', () => {
const approverData = [new TaskVariableCloud({ name: 'variables.person_type', value: 'Approver' })];
const change = new SimpleChange(formComponent.data, approverData, false);
formComponent.data = approverData;
formComponent.ngOnChanges({ data: change });
expect(formComponent.form.getFieldById('conditionalField').isVisible).toBeTrue();
});
it('should keep a variable changed by a form rule when the refreshed data omits it', () => {
formComponent.form.changeVariableValue('person-type-var', 'Approver');
const partialData = [new TaskVariableCloud({ name: 'processOutput', value: 'result' })];
const change = new SimpleChange(formComponent.data, partialData, false);
formComponent.data = partialData;
formComponent.ngOnChanges({ data: change });
expect(formComponent.form.getFieldById('conditionalField').isVisible).toBeTrue();
});
it('should keep the latest received variable value across a following partial refresh', () => {
const approverData = [new TaskVariableCloud({ name: 'variables.person_type', value: 'Approver' })];
formComponent.data = approverData;
formComponent.ngOnChanges({ data: new SimpleChange(requestorVariables(), approverData, false) });
expect(formComponent.form.getFieldById('conditionalField').isVisible).toBeTrue();
const partialData = [new TaskVariableCloud({ name: 'processOutput', value: 'result' })];
formComponent.data = partialData;
formComponent.ngOnChanges({ data: new SimpleChange(approverData, partialData, false) });
expect(formComponent.form.getFieldById('conditionalField').isVisible).toBeTrue();
});
});
@@ -601,6 +601,8 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
private refreshFormData(previousData: TaskVariableCloud[] = []) {
const snapshot = this.snapshotRuntimeState();
this.mergeProcessVariables(this.data ?? []);
this.form = this.parseForm(this.formCloudRepresentationJSON);
if (!this.form) {
return;
@@ -610,13 +612,36 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
this.restoreRuntimeState(this.form, snapshot, changedFieldIds);
this.setCheckParentVisibilityForValidationOnFields();
this.visibilityService.refreshVisibility(this.form);
this.visibilityService.refreshVisibility(this.form, this.data);
this.form.validateForm();
this.onFormLoaded(this.form);
this.formService.formRulesEvent.next(new FormRulesEvent('dataRefreshed', new FormEvent(this.form)));
this.onFormDataRefreshed(this.form);
}
/**
* Keeps the process variables on the stored representation up to date with the latest data, so that a
* variable omitted by a later partial refresh still resolves to the most recent value received.
*
* @param updates Variables received on the latest data refresh
*/
private mergeProcessVariables(updates: TaskVariableCloud[]): void {
if (!this.formCloudRepresentationJSON) {
return;
}
const existing: TaskVariableCloud[] = this.formCloudRepresentationJSON.processVariables ?? [];
const byName = new Map<string, TaskVariableCloud>();
for (const variable of [...existing, ...updates]) {
if (variable?.name) {
byName.set(variable.name, variable);
}
}
this.formCloudRepresentationJSON.processVariables = Array.from(byName.values());
}
private snapshotRuntimeState(): Map<string, FormFieldRuntimeState> {
const snapshot = new Map<string, FormFieldRuntimeState>();
if (!this.form) {