[AAE-40028] Added group/section validation skipping to cloud forms for workspace (#11466)

This commit is contained in:
Alex Molodyh
2025-12-16 20:02:07 -08:00
committed by GitHub
parent 8c1118984d
commit fe39b3c1bd
15 changed files with 287 additions and 2 deletions
@@ -461,6 +461,138 @@ describe('FormCloudComponent', () => {
expect(formComponent.getFormById).not.toHaveBeenCalled();
});
describe('enableParentVisibilityCheck', () => {
let formModel: FormModel;
let field1: FormFieldModel;
let field2: FormFieldModel;
beforeEach(() => {
const formJson = {
id: 'test-form',
name: 'Test Form',
fields: [
{
id: 'group1',
type: FormFieldTypes.GROUP,
numberOfColumns: 1,
fields: {
1: [
{ id: 'field1', type: FormFieldTypes.TEXT, required: true },
{ id: 'field2', type: FormFieldTypes.TEXT, required: true }
]
}
}
]
};
formModel = new FormModel(formJson);
formComponent.form = formModel;
field1 = formModel.getFieldById('field1');
field2 = formModel.getFieldById('field2');
});
it('should set flags on form and fields when enableParentVisibilityCheck changes from false to true', () => {
formComponent.enableParentVisibilityCheck = false;
expect(formModel.enableParentVisibilityCheck).toBe(false);
expect(field1.checkParentVisibilityForValidation).toBe(false);
expect(field2.checkParentVisibilityForValidation).toBe(false);
const change = new SimpleChange(false, true, false);
formComponent.enableParentVisibilityCheck = true;
spyOn(formModel, 'validateForm').and.stub();
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
expect(formModel.enableParentVisibilityCheck).toBe(true);
expect(field1.checkParentVisibilityForValidation).toBe(true);
expect(field2.checkParentVisibilityForValidation).toBe(true);
expect(formModel.validateForm).toHaveBeenCalled();
});
it('should not set flags when enableParentVisibilityCheck changes but form is null', () => {
formComponent.form = null;
formComponent.enableParentVisibilityCheck = false;
const change = new SimpleChange(false, true, false);
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
expect(formComponent.form).toBeNull();
});
it('should reset flags when enableParentVisibilityCheck changes from true to false', () => {
formModel.enableParentVisibilityCheck = true;
field1.checkParentVisibilityForValidation = true;
field2.checkParentVisibilityForValidation = true;
formComponent.enableParentVisibilityCheck = false;
const change = new SimpleChange(true, false, false);
spyOn(formModel, 'validateForm').and.stub();
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
expect(formModel.enableParentVisibilityCheck).toBe(false);
expect(field1.checkParentVisibilityForValidation).toBe(false);
expect(field2.checkParentVisibilityForValidation).toBe(false);
expect(formModel.validateForm).toHaveBeenCalled();
});
it('should set flags on all fields including nested fields', () => {
const formJsonWithNestedFields = {
id: 'test-form',
name: 'Test Form',
fields: [
{
id: 'group1',
type: FormFieldTypes.GROUP,
numberOfColumns: 1,
fields: {
1: [{ id: 'field1', type: FormFieldTypes.TEXT }]
}
},
{
id: 'section1',
type: FormFieldTypes.SECTION,
numberOfColumns: 1,
fields: {
1: [
{
id: 'group2',
type: FormFieldTypes.GROUP,
numberOfColumns: 1,
fields: {
1: [{ id: 'field2', type: FormFieldTypes.TEXT }]
}
}
]
}
},
{ id: 'field3', type: FormFieldTypes.TEXT }
]
};
const nestedFormModel = new FormModel(formJsonWithNestedFields);
formComponent.form = nestedFormModel;
formComponent.enableParentVisibilityCheck = true;
const change = new SimpleChange(false, true, false);
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
const allFields = nestedFormModel.getFormFields();
allFields.forEach((field) => {
expect(field.checkParentVisibilityForValidation).toBe(true);
});
expect(nestedFormModel.enableParentVisibilityCheck).toBe(true);
});
it('should re-validate form after setting flags', () => {
formComponent.enableParentVisibilityCheck = true;
spyOn(formModel, 'validateForm').and.stub();
const change = new SimpleChange(false, true, false);
formComponent.ngOnChanges({ enableParentVisibilityCheck: change });
expect(formModel.validateForm).toHaveBeenCalled();
});
});
it('should complete form on custom outcome click', () => {
const formModel = new FormModel();
const outcomeName = 'Custom Action';
@@ -135,6 +135,13 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
@Input()
customCompleteButtonText: string = '';
/**
* Toggle to enable parent visibility check for validation.
* When enabled, fields inside hidden groups/sections will skip validation.
*/
@Input()
enableParentVisibilityCheck: boolean = false;
/** Emitted when the form is submitted with the `Save` or custom outcomes. */
@Output()
formSaved = new EventEmitter<FormModel>();
@@ -254,6 +261,12 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
this.onFormLoaded(this.form);
return;
}
const enableParentVisibilityCheckChange = changes['enableParentVisibilityCheck'];
if (enableParentVisibilityCheckChange && this.form) {
this.setCheckParentVisibilityForValidationOnFields();
this.form.validateForm();
}
}
ngOnInit(): void {
@@ -338,8 +351,9 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
const parsedForm = this.parseForm(this.formCloudRepresentationJSON);
this.visibilityService.refreshVisibility(parsedForm, this.data);
parsedForm.validateForm();
this.form = parsedForm;
this.setCheckParentVisibilityForValidationOnFields();
parsedForm.validateForm();
this.form.nodeId = '-my-';
this.onFormLoaded(this.form);
resolve(this.form);
@@ -369,8 +383,9 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
this.formCloudRepresentationJSON.processVariables = this.data || [];
const parsedForm = this.parseForm(form);
this.visibilityService.refreshVisibility(parsedForm);
parsedForm?.validateForm();
this.form = parsedForm;
this.setCheckParentVisibilityForValidationOnFields();
parsedForm?.validateForm();
this.form.nodeId = '-my-';
this.onFormLoaded(this.form);
},
@@ -467,11 +482,29 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
private refreshFormData() {
this.form = this.parseForm(this.formCloudRepresentationJSON);
if (this.form) {
this.setCheckParentVisibilityForValidationOnFields();
this.onFormLoaded(this.form);
this.onFormDataRefreshed(this.form);
}
}
/**
* Sets the parent visibility check flag on all form fields.
* When enabled, fields inside hidden groups/sections will skip validation.
* When disabled, resets flags to false to revert to default behavior.
*/
private setCheckParentVisibilityForValidationOnFields(): void {
if (!this.form) {
return;
}
this.form.enableParentVisibilityCheck = this.enableParentVisibilityCheck;
const allFields = this.form.getFormFields();
allFields.forEach((field) => {
field.checkParentVisibilityForValidation = this.enableParentVisibilityCheck;
});
}
protected onFormLoaded(form: FormModel) {
if (form) {
this.displayModeConfigurations = this.displayModeService.getDisplayModeConfigurations(this.displayModeConfigurations);
@@ -96,6 +96,7 @@
[data]="resolvedValues"
[formId]="processDefinitionCurrent.formKey"
[displayModeConfigurations]="displayModeConfigurations"
[enableParentVisibilityCheck]="enableParentVisibilityCheck"
[showSaveButton]="showSaveButton"
[showCompleteButton]="showCompleteButton"
[showRefreshButton]="false"
@@ -21,6 +21,7 @@ import { FormModel, FormOutcomeEvent, FormOutcomeModel } from '@alfresco/adf-cor
import { of, throwError } from 'rxjs';
import { StartProcessCloudService } from '../services/start-process-cloud.service';
import { FormCloudService } from '../../../form/services/form-cloud.service';
import { FormCloudComponent } from '../../../form/components/form-cloud.component';
import { StartProcessCloudComponent } from './start-process-cloud.component';
import {
fakeProcessDefinitions,
@@ -629,6 +630,51 @@ describe('StartProcessCloudComponent', () => {
expect(processForm).not.toBeNull();
});
it('should pass enableParentVisibilityCheck to adf-cloud-form', async () => {
getProcessDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[2]]));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
component.enableParentVisibilityCheck = true;
const change = new SimpleChange('myApp', 'myApp1', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
await selectOptionByName('processwithform');
component.processDefinitionName = fakeProcessDefinitions[2].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name);
fixture.detectChanges();
await fixture.whenStable();
const cloudFormElement = fixture.debugElement.query(By.css('adf-cloud-form'));
expect(cloudFormElement).toBeDefined();
const cloudFormComponent = cloudFormElement.componentInstance as FormCloudComponent;
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.enableParentVisibilityCheck).toBe(true);
});
it('should pass enableParentVisibilityCheck as false by default to adf-cloud-form', async () => {
getProcessDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[2]]));
formDefinitionSpy.and.returnValue(of(fakeStartForm));
const change = new SimpleChange('myApp', 'myApp1', true);
component.ngOnChanges({ appName: change });
fixture.detectChanges();
await fixture.whenStable();
await selectOptionByName('processwithform');
component.processDefinitionName = fakeProcessDefinitions[2].name;
component.setProcessDefinitionOnForm(fakeProcessDefinitions[2].name);
fixture.detectChanges();
await fixture.whenStable();
const cloudFormElement = fixture.debugElement.query(By.css('adf-cloud-form'));
expect(cloudFormElement).toBeDefined();
const cloudFormComponent = cloudFormElement.componentInstance as FormCloudComponent;
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.enableParentVisibilityCheck).toBe(false);
});
it('should not select automatically any processDefinition if the app contain multiple process and does not have any processDefinition as input', async () => {
getProcessDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions));
component.appName = 'myApp';
@@ -142,6 +142,13 @@ export class StartProcessCloudComponent implements OnChanges, OnInit {
@Input()
displayModeConfigurations: FormCloudDisplayModeConfiguration[];
/**
* Toggle to enable parent visibility check for validation.
* When enabled, fields inside hidden groups/sections will skip validation.
*/
@Input()
enableParentVisibilityCheck: boolean = false;
/** Emitted when the process is successfully started. */
@Output()
success = new EventEmitter<ProcessInstanceCloud>();
@@ -14,6 +14,7 @@
[customSaveButtonText]="customSaveButtonText"
[customCompleteButtonText]="customCompleteButtonText"
[displayModeConfigurations]="displayModeConfigurations"
[enableParentVisibilityCheck]="enableParentVisibilityCheck"
(formLoaded)="onFormLoaded($event)"
(formSaved)="onFormSaved($event)"
(formCompleted)="onFormCompleted($event)"
@@ -276,6 +276,31 @@ describe('TaskFormCloudComponent', () => {
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.customCompleteButtonText).toBe(customText);
});
it('should pass enableParentVisibilityCheck to adf-cloud-form', () => {
component.appName = 'app1';
component.taskId = 'task1';
component.enableParentVisibilityCheck = true;
fixture.detectChanges();
const cloudFormElement = fixture.debugElement.query(By.css('adf-cloud-form'));
expect(cloudFormElement).toBeDefined();
const cloudFormComponent = cloudFormElement.componentInstance as FormCloudComponent;
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.enableParentVisibilityCheck).toBe(true);
});
it('should pass enableParentVisibilityCheck as false by default to adf-cloud-form', () => {
component.appName = 'app1';
component.taskId = 'task1';
fixture.detectChanges();
const cloudFormElement = fixture.debugElement.query(By.css('adf-cloud-form'));
expect(cloudFormElement).toBeDefined();
const cloudFormComponent = cloudFormElement.componentInstance as FormCloudComponent;
expect(cloudFormComponent).toBeDefined();
expect(cloudFormComponent.enableParentVisibilityCheck).toBe(false);
});
});
describe('Events', () => {
@@ -107,6 +107,13 @@ export class TaskFormCloudComponent {
@Input()
displayModeConfigurations: FormCloudDisplayModeConfiguration[];
/**
* Toggle to enable parent visibility check for validation.
* When enabled, fields inside hidden groups/sections will skip validation.
*/
@Input()
enableParentVisibilityCheck: boolean = false;
/** Task details. */
@Input()
taskDetails: TaskDetailsCloudModel;
@@ -8,6 +8,7 @@
[candidateUsers]="candidateUsers"
[candidateGroups]="candidateGroups"
[displayModeConfigurations]="displayModeConfigurations"
[enableParentVisibilityCheck]="enableParentVisibilityCheck"
[showValidationIcon]="showValidationIcon"
[showTitle]="showTitle"
[taskId]="taskId"
@@ -736,6 +736,25 @@ describe('UserTaskCloudComponent', () => {
expect(taskFormComponent.customCompleteButtonText).toBe(customText);
});
it('should pass enableParentVisibilityCheck to task form when task type is Form', () => {
fixture.componentRef.setInput('enableParentVisibilityCheck', true);
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.enableParentVisibilityCheck).toBe(true);
});
it('should pass enableParentVisibilityCheck as false by default to task form', () => {
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.enableParentVisibilityCheck).toBe(false);
});
});
});
@@ -67,6 +67,13 @@ export class UserTaskCloudComponent implements OnInit, OnChanges {
@Input()
displayModeConfigurations: FormCloudDisplayModeConfiguration[];
/**
* Toggle to enable parent visibility check for validation.
* When enabled, fields inside hidden groups/sections will skip validation.
*/
@Input()
enableParentVisibilityCheck: boolean = false;
/** Toggle readonly state of the task. */
@Input()
readOnly = false;