mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-44435 Fix incorrect Start process button on user task forms (#11837)
This commit is contained in:
@@ -89,7 +89,7 @@ The template defined inside `empty-form` will be shown when no form definition i
|
||||
| path | `string` | | Path of the folder where the metadata will be stored. |
|
||||
| processInstanceId | `string` | | ProcessInstanceId id to fetch corresponding form and values. |
|
||||
| readOnly | `boolean` | false | Toggle readonly state of the form. Forces all form widgets to render as readonly if enabled. |
|
||||
| showCompleteButton | `boolean` | true | Toggle rendering of the `Complete` outcome button. |
|
||||
| showCompleteButton | `boolean` | false | Toggle rendering of the `Complete` outcome button. |
|
||||
| showRefreshButton | `boolean` | true | Toggle rendering of the `Refresh` button. |
|
||||
| showSaveButton | `boolean` | true | Toggle rendering of the `Save` outcome button. |
|
||||
| showTitle | `boolean` | true | Toggle rendering of the form title. |
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* 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 { FormOutcomeModel } from '../widgets';
|
||||
import { isOutcomeButtonVisible } from './buttons-visibility';
|
||||
|
||||
describe('isOutcomeButtonVisible', () => {
|
||||
const defaultProps = {
|
||||
isFormReadOnly: false,
|
||||
showCompleteButton: true,
|
||||
showSaveButton: true
|
||||
};
|
||||
|
||||
const outcome = (overrides: Partial<FormOutcomeModel>): FormOutcomeModel => ({ name: '', isSelected: false, ...overrides }) as FormOutcomeModel;
|
||||
|
||||
it('should return false when outcome has no name', () => {
|
||||
expect(isOutcomeButtonVisible(outcome({ name: '' }), defaultProps)).toBe(false);
|
||||
});
|
||||
|
||||
it('should respect showCompleteButton for COMPLETE action', () => {
|
||||
const o = outcome({ name: FormOutcomeModel.COMPLETE_ACTION });
|
||||
expect(isOutcomeButtonVisible(o, { ...defaultProps, showCompleteButton: false })).toBe(false);
|
||||
expect(isOutcomeButtonVisible(o, { ...defaultProps, showCompleteButton: true })).toBe(true);
|
||||
});
|
||||
|
||||
it('should always hide START_PROCESS action regardless of readOnly state', () => {
|
||||
const o = outcome({ name: FormOutcomeModel.START_PROCESS_ACTION });
|
||||
expect(isOutcomeButtonVisible(o, { ...defaultProps, isFormReadOnly: false })).toBe(false);
|
||||
expect(isOutcomeButtonVisible(o, { ...defaultProps, isFormReadOnly: true })).toBe(false);
|
||||
});
|
||||
|
||||
it('should show only selected outcome when form is read-only', () => {
|
||||
const selected = outcome({ name: 'custom-1', isSelected: true });
|
||||
const notSelected = outcome({ name: 'custom-2', isSelected: false });
|
||||
expect(isOutcomeButtonVisible(selected, { ...defaultProps, isFormReadOnly: true })).toBe(true);
|
||||
expect(isOutcomeButtonVisible(notSelected, { ...defaultProps, isFormReadOnly: true })).toBe(false);
|
||||
});
|
||||
|
||||
it('should respect showSaveButton for SAVE action on writable forms', () => {
|
||||
const o = outcome({ name: FormOutcomeModel.SAVE_ACTION });
|
||||
expect(isOutcomeButtonVisible(o, { ...defaultProps, showSaveButton: true })).toBe(true);
|
||||
expect(isOutcomeButtonVisible(o, { ...defaultProps, showSaveButton: false })).toBe(false);
|
||||
});
|
||||
|
||||
it('should show custom outcomes on writable forms', () => {
|
||||
const o = outcome({ name: 'custom-outcome' });
|
||||
expect(isOutcomeButtonVisible(o, defaultProps)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -30,15 +30,15 @@ export const isOutcomeButtonVisible = (outcome: FormOutcomeModel, props: IsOutco
|
||||
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
|
||||
return showCompleteButton;
|
||||
}
|
||||
if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
|
||||
return false;
|
||||
}
|
||||
if (isFormReadOnly) {
|
||||
return outcome.isSelected;
|
||||
}
|
||||
if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
|
||||
return showSaveButton;
|
||||
}
|
||||
if (outcome.name === FormOutcomeModel.START_PROCESS_ACTION) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -121,20 +121,17 @@
|
||||
}
|
||||
<div class="adf-cloud-form-outcome-buttons">
|
||||
<ng-content select="adf-cloud-form-custom-outcomes" />
|
||||
@for (outcome of form.outcomes; track outcome.name) {
|
||||
@if (outcome.isVisible) {
|
||||
<button
|
||||
[id]="'adf-form-' + outcome.name | formatSpace"
|
||||
[color]="getColorForOutcome(outcome.name)"
|
||||
mat-button
|
||||
[disabled]="!isOutcomeButtonEnabled(outcome)"
|
||||
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
|
||||
class="adf-cloud-form-custom-outcome-button"
|
||||
(click)="onOutcomeClicked(outcome)"
|
||||
>
|
||||
{{ getCustomOutcomeButtonText(outcome) || (outcome.name | translate | uppercase) }}
|
||||
</button>
|
||||
}
|
||||
@for (outcome of visibleOutcomes; track outcome.name) {
|
||||
<button
|
||||
[id]="'adf-form-' + outcome.name | formatSpace"
|
||||
[color]="getColorForOutcome(outcome.name)"
|
||||
mat-button
|
||||
[disabled]="!isOutcomeButtonEnabled(outcome)"
|
||||
class="adf-cloud-form-custom-outcome-button"
|
||||
(click)="onOutcomeClicked(outcome)"
|
||||
>
|
||||
{{ getCustomOutcomeButtonText(outcome) || (outcome.name | translate | uppercase) }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</mat-card-actions>
|
||||
|
||||
@@ -1210,6 +1210,33 @@ describe('FormCloudComponent', () => {
|
||||
expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not include START_PROCESS outcome when form is loaded for a task', () => {
|
||||
formComponent.taskId = 'mock-task-id';
|
||||
formComponent.appName = 'mock-app';
|
||||
|
||||
const form = formComponent.parseForm(cloudFormMock);
|
||||
|
||||
const startProcessOutcome = form.outcomes.find((outcome) => outcome.id === FormModel.START_PROCESS_OUTCOME);
|
||||
expect(startProcessOutcome).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should populate visibleOutcomes when the form is set', () => {
|
||||
formComponent.showCompleteButton = true;
|
||||
formComponent.form = new FormModel(cloudFormMock);
|
||||
|
||||
expect(formComponent.visibleOutcomes.length).toBeGreaterThan(0);
|
||||
expect(formComponent.visibleOutcomes.every((outcome) => outcome.name !== FormOutcomeModel.START_PROCESS_ACTION)).toBe(true);
|
||||
});
|
||||
|
||||
it('should clear visibleOutcomes when the form is cleared', () => {
|
||||
formComponent.showCompleteButton = true;
|
||||
formComponent.form = new FormModel(cloudFormMock);
|
||||
expect(formComponent.visibleOutcomes.length).toBeGreaterThan(0);
|
||||
|
||||
formComponent.form = null;
|
||||
expect(formComponent.visibleOutcomes).toEqual([]);
|
||||
});
|
||||
|
||||
it('should raise [executeOutcome] event for formService', async () => {
|
||||
spyOn(formComponent.executeOutcome, 'emit');
|
||||
|
||||
@@ -1656,6 +1683,7 @@ describe('FormCloudComponent', () => {
|
||||
|
||||
describe('Custom outcome button text for default outcomes', () => {
|
||||
beforeEach(() => {
|
||||
formComponent.showCompleteButton = true;
|
||||
formComponent.form = formComponent.parseForm(emptyFormRepresentationJSON);
|
||||
});
|
||||
|
||||
|
||||
@@ -175,6 +175,19 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
||||
formCloudRepresentationJSON: any;
|
||||
fieldValidators: FormFieldValidator[] = [];
|
||||
|
||||
/** Pre-computed list of outcome buttons to render, filtered by visibility rules. */
|
||||
visibleOutcomes: FormOutcomeModel[] = [];
|
||||
|
||||
override get form(): FormModel {
|
||||
return super.form;
|
||||
}
|
||||
|
||||
@Input()
|
||||
override set form(form: FormModel) {
|
||||
super.form = form;
|
||||
this.recomputeVisibleOutcomes();
|
||||
}
|
||||
|
||||
readonly id: string;
|
||||
displayMode: string;
|
||||
displayConfiguration: FormCloudDisplayModeConfiguration = DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0];
|
||||
@@ -268,6 +281,13 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
||||
this.disableSaveButton = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.formService.formRulesEvent
|
||||
.pipe(
|
||||
filter((event) => event?.type === 'fieldValueChanged' && event.form?.id === this.form?.id),
|
||||
takeUntilDestroyed()
|
||||
)
|
||||
.subscribe(() => this.recomputeVisibleOutcomes());
|
||||
}
|
||||
|
||||
@HostListener('keydown', ['$event'])
|
||||
@@ -318,6 +338,10 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
||||
this.setCheckParentVisibilityForValidationOnFields();
|
||||
this.form.validateForm();
|
||||
}
|
||||
|
||||
if (changes['readOnly'] || changes['showCompleteButton'] || changes['showSaveButton']) {
|
||||
this.recomputeVisibleOutcomes();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -511,9 +535,11 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
||||
});
|
||||
|
||||
const form = new FormModel(formCloudRepresentationJSON, formValues, this.readOnly, this.formService, undefined, this.fieldValidators);
|
||||
if (!form) {
|
||||
form.outcomes = this.getFormDefinitionOutcomes(form);
|
||||
|
||||
if (this.taskId) {
|
||||
form.outcomes = (form.outcomes ?? []).filter((outcome) => outcome.id !== FormModel.START_PROCESS_OUTCOME);
|
||||
}
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
@@ -533,6 +559,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
||||
checkVisibility(field: FormFieldModel) {
|
||||
if (field?.form) {
|
||||
this.visibilityService.refreshVisibility(field.form);
|
||||
this.recomputeVisibleOutcomes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +572,16 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
||||
}
|
||||
}
|
||||
|
||||
private recomputeVisibleOutcomes(): void {
|
||||
const outcomes = this.form?.outcomes;
|
||||
if (!outcomes) {
|
||||
this.visibleOutcomes = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this.visibleOutcomes = outcomes.filter((outcome) => outcome.isVisible && this.isOutcomeButtonVisible(outcome, this.form.readOnly));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent visibility check flag on all form fields.
|
||||
* When enabled, fields inside hidden groups/sections will skip validation.
|
||||
|
||||
Reference in New Issue
Block a user