AAE-35981 Form button customization (#11047)

This commit is contained in:
Fabian Kindgen
2025-07-24 09:37:35 +02:00
committed by GitHub
parent 06a9ddc068
commit d7ec3a1b77
18 changed files with 516 additions and 56 deletions

View File

@@ -27,12 +27,28 @@ import { isOutcomeButtonVisible } from './helpers/buttons-visibility';
export abstract class FormBaseComponent {
protected _form: FormModel;
static SAVE_OUTCOME_ID: string = '$save';
static COMPLETE_OUTCOME_ID: string = '$complete';
static START_PROCESS_OUTCOME_ID: string = '$startProcess';
static CUSTOM_OUTCOME_ID: string = '$custom';
static COMPLETE_BUTTON_COLOR: ThemePalette = 'primary';
static COMPLETE_OUTCOME_NAME: string = 'COMPLETE';
/**
* @deprecated Use {@link FormModel.SAVE_OUTCOME} instead.
*/
static readonly SAVE_OUTCOME_ID: string = FormModel.SAVE_OUTCOME;
/**
* @deprecated Use {@link FormModel.COMPLETE_OUTCOME} instead.
*/
static readonly COMPLETE_OUTCOME_ID: string = FormModel.COMPLETE_OUTCOME;
/**
* @deprecated Use {@link FormModel.START_PROCESS_OUTCOME} instead.
*/
static readonly START_PROCESS_OUTCOME_ID: string = FormModel.START_PROCESS_OUTCOME;
static readonly CUSTOM_OUTCOME_ID: string = '$custom';
static readonly COMPLETE_BUTTON_COLOR: ThemePalette = 'primary';
/**
* @deprecated Use {@link FormOutcomeModel.COMPLETE_ACTION} instead.
*/
static readonly COMPLETE_OUTCOME_NAME: string = FormOutcomeModel.COMPLETE_ACTION;
/** Path of the folder where the metadata will be stored. */
@Input()
@@ -50,7 +66,7 @@ export abstract class FormBaseComponent {
@Input()
showCompleteButton: boolean = true;
/** If true then the `Complete` outcome button is shown but it will be disabled. */
/** If true then the `Complete` outcome button is shown, but it will be disabled. */
@Input()
disableCompleteButton: boolean = false;
@@ -138,7 +154,7 @@ export abstract class FormBaseComponent {
}
getColorForOutcome(outcomeName: string): ThemePalette {
return outcomeName === FormBaseComponent.COMPLETE_OUTCOME_NAME ? FormBaseComponent.COMPLETE_BUTTON_COLOR : null;
return outcomeName === FormOutcomeModel.COMPLETE_ACTION ? FormBaseComponent.COMPLETE_BUTTON_COLOR : null;
}
isOutcomeButtonEnabled(outcome?: FormOutcomeModel): boolean {
@@ -182,20 +198,20 @@ export abstract class FormBaseComponent {
}
if (outcome.isSystem) {
if (outcome.id === FormBaseComponent.SAVE_OUTCOME_ID) {
if (outcome.id === FormModel.SAVE_OUTCOME) {
this.disableSaveButton = true;
this.saveTaskForm();
return true;
}
if (outcome.id === FormBaseComponent.COMPLETE_OUTCOME_ID) {
if (outcome.id === FormModel.COMPLETE_OUTCOME) {
this.disableSaveButton = true;
this.disableCompleteButton = true;
this.completeTaskForm();
return true;
}
if (outcome.id === FormBaseComponent.START_PROCESS_OUTCOME_ID) {
if (outcome.id === FormModel.START_PROCESS_OUTCOME) {
this.completeTaskForm();
return true;
}

View File

@@ -21,9 +21,9 @@ import { FormWidgetModel } from './form-widget.model';
import { WidgetVisibilityModel } from '../../../models/widget-visibility.model';
export class FormOutcomeModel extends FormWidgetModel {
static SAVE_ACTION: string = 'SAVE'; // Activiti 'Save' action name
static COMPLETE_ACTION: string = 'COMPLETE'; // Activiti 'Complete' action name
static START_PROCESS_ACTION: string = 'START PROCESS'; // Activiti 'Start Process' action name
static readonly SAVE_ACTION: string = 'SAVE'; // Activiti 'Save' action name
static readonly COMPLETE_ACTION: string = 'COMPLETE'; // Activiti 'Complete' action name
static readonly START_PROCESS_ACTION: string = 'START PROCESS'; // Activiti 'Start Process' action name
isSystem: boolean = false;
isSelected: boolean = false;

View File

@@ -62,10 +62,10 @@ export interface FormRepresentationModel {
theme?: ThemeModel;
}
export class FormModel implements ProcessFormModel {
static UNSET_TASK_NAME: string = 'Nameless task';
static SAVE_OUTCOME: string = '$save';
static COMPLETE_OUTCOME: string = '$complete';
static START_PROCESS_OUTCOME: string = '$startProcess';
static readonly UNSET_TASK_NAME: string = 'Nameless task';
static readonly SAVE_OUTCOME: string = '$save';
static readonly COMPLETE_OUTCOME: string = '$complete';
static readonly START_PROCESS_OUTCOME: string = '$startProcess';
readonly id: string | number;
readonly name: string;
@@ -410,28 +410,29 @@ export class FormModel implements ProcessFormModel {
}
protected parseOutcomes() {
if (this.json.fields) {
if (!this.json.fields) return;
const saveOutcome = new FormOutcomeModel(this, {
id: FormModel.SAVE_OUTCOME,
name: 'SAVE',
name: FormOutcomeModel.SAVE_ACTION,
isSystem: true
});
const completeOutcome = new FormOutcomeModel(this, {
id: FormModel.COMPLETE_OUTCOME,
name: 'COMPLETE',
name: FormOutcomeModel.COMPLETE_ACTION,
isSystem: true
});
const startProcessOutcome = new FormOutcomeModel(this, {
id: FormModel.START_PROCESS_OUTCOME,
name: 'START PROCESS',
name: FormOutcomeModel.START_PROCESS_ACTION,
isSystem: true
});
const customOutcomes = (this.json.outcomes || []).map((obj) => new FormOutcomeModel(this, obj));
const customOutcomes = (this.json.outcomes ?? ([] as FormModel[])).map((formModel: FormModel) => new FormOutcomeModel(this, formModel));
this.outcomes = [saveOutcome].concat(customOutcomes.length > 0 ? customOutcomes : [completeOutcome, startProcessOutcome]);
}
}
addValuesNotPresent(valuesToSetIfNotPresent: FormValues) {
this.fieldsCache.forEach((field) => {

View File

@@ -81,7 +81,7 @@ export class FormService implements FormValidationService {
if (!json.fields) {
form.outcomes = [
new FormOutcomeModel(form, {
id: '$save',
id: FormModel.SAVE_OUTCOME,
name: FormOutcomeModel.SAVE_ACTION,
isSystem: true
})

View File

@@ -91,7 +91,7 @@
class="adf-cloud-form-custom-outcome-button"
(click)="onOutcomeClicked(outcome)"
>
{{ outcome.name | translate | uppercase }}
{{ getCustomOutcomeButtonText(outcome) || (outcome.name | translate | uppercase) }}
</button>
</ng-container>
</mat-card-actions>

View File

@@ -1492,6 +1492,54 @@ describe('FormCloudComponent', () => {
});
});
});
describe('Custom outcome button text for default outcomes', () => {
beforeEach(() => {
formComponent.form = formComponent.parseForm(emptyFormRepresentationJSON);
});
it('should display custom save button text when set', () => {
const customText = 'Custom Save Text';
formComponent.customSaveButtonText = customText;
fixture.detectChanges();
const buttonSelector = '#adf-form-save';
const outcomeButton = fixture.debugElement.query(By.css(buttonSelector));
expect(outcomeButton).toBeTruthy();
expect(outcomeButton.nativeElement.textContent.trim()).toBe(customText);
});
it('should display default save button text when not set', () => {
fixture.detectChanges();
const buttonSelector = '#adf-form-save';
const outcomeButton = fixture.debugElement.query(By.css(buttonSelector));
expect(outcomeButton).toBeTruthy();
expect(outcomeButton.nativeElement.textContent.trim()).toBe('SAVE');
});
it('should display custom complete button text when set', () => {
const customText = 'Custom Complete Text';
formComponent.customCompleteButtonText = customText;
fixture.detectChanges();
const buttonSelector = '#adf-form-complete';
const outcomeButton = fixture.debugElement.query(By.css(buttonSelector));
expect(outcomeButton).toBeTruthy();
expect(outcomeButton.nativeElement.textContent.trim()).toBe(customText);
});
it('should display default complete button text when not set', () => {
fixture.detectChanges();
const buttonSelector = '#adf-form-complete';
const outcomeButton = fixture.debugElement.query(By.css(buttonSelector));
expect(outcomeButton).toBeTruthy();
expect(outcomeButton.nativeElement.textContent.trim()).toBe('COMPLETE');
});
});
});
describe('Multilingual Form', () => {

View File

@@ -121,6 +121,20 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
@Input()
showCompleteButton = false;
/**
* Custom text for the `Save` button.
* If not provided, the default text will be used.
*/
@Input()
customSaveButtonText: string = '';
/**
* Custom text for the `Complete` button.
* If not provided, the default text will be used.
*/
@Input()
customCompleteButtonText: string = '';
/** Emitted when the form is submitted with the `Save` or custom outcomes. */
@Output()
formSaved = new EventEmitter<FormModel>();
@@ -298,6 +312,15 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
);
}
getCustomOutcomeButtonText(outcome: FormOutcomeModel): string {
if (outcome?.id === FormModel.SAVE_OUTCOME || outcome?.name === FormOutcomeModel.SAVE_ACTION) {
return this.customSaveButtonText;
} else if (outcome?.id === FormModel.COMPLETE_OUTCOME || outcome?.name === FormOutcomeModel.COMPLETE_ACTION) {
return this.customCompleteButtonText;
}
return '';
}
isAProcessTask(taskRepresentation: TaskDetailsCloudModel): boolean {
return taskRepresentation.processDefinitionId && taskRepresentation.processDefinitionDeploymentId !== 'null';
}
@@ -430,7 +453,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
* @returns list of form outcomes
*/
getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] {
return [new FormOutcomeModel(form, { id: '$save', name: FormOutcomeModel.SAVE_ACTION, isSystem: true })];
return [new FormOutcomeModel(form, { id: FormModel.SAVE_OUTCOME, name: FormOutcomeModel.SAVE_ACTION, isSystem: true })];
}
checkVisibility(field: FormFieldModel) {

View File

@@ -210,7 +210,7 @@ export class FormCloudService extends BaseCloudService implements FormCloudServi
*
* @param json JSON data to create the form
* @param data Values for the form's fields
* @param readOnly Toggles whether or not the form should be read-only
* @param readOnly Toggles whether the form should be read-only
* @returns Form created from the JSON specification
*/
parseForm(json: any, data?: TaskVariableCloud[], readOnly: boolean = false): FormModel {

View File

@@ -9,9 +9,12 @@
[readOnly]="isReadOnly()"
[showRefreshButton]="showRefreshButton"
[showValidationIcon]="showValidationIcon"
[showCompleteButton]="canCompleteTask()"
[showSaveButton]="canCompleteTask()"
[showCompleteButton]="showCompleteButton && canCompleteTask()"
[showSaveButton]="showSaveButton && canCompleteTask()"
[customSaveButtonText]="customSaveButtonText"
[customCompleteButtonText]="customCompleteButtonText"
[displayModeConfigurations]="displayModeConfigurations"
(formLoaded)="onFormLoaded($event)"
(formSaved)="onFormSaved($event)"
(formCompleted)="onFormCompleted($event)"
(formError)="onError($event)"
@@ -27,6 +30,7 @@
[canClaimTask]="canClaimTask()"
[canUnclaimTask]="canUnclaimTask()"
[showCancelButton]="showCancelButton"
[customCancelButtonText]="customCancelButtonText"
[taskId]="taskId"
(cancelClick)="onCancelClick()"
(claimTask)="onClaimTask()"

View File

@@ -33,6 +33,7 @@ import {
import { UserTaskCloudButtonsComponent } from '../user-task-cloud-buttons/user-task-cloud-buttons.component';
import { TaskFormCloudComponent } from './task-form-cloud.component';
import { FormCustomOutcomesComponent } from '../../../../form/components/form-cloud-custom-outcomes.component';
import { By } from '@angular/platform-browser';
const taskDetails: TaskDetailsCloudModel = {
appName: 'simple-app',
@@ -191,6 +192,90 @@ describe('TaskFormCloudComponent', () => {
const canUnclaimTask = component.canUnclaimTask();
expect(canUnclaimTask).toBe(false);
});
it('should pass showCompleteButton to adf-cloud-form when task can be completed', () => {
component.appName = 'app1';
component.taskId = 'task1';
component.showCompleteButton = true;
spyOn(component, 'canCompleteTask').and.returnValue(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.showCompleteButton).toBe(true);
});
it('should not pass showCompleteButton to adf-cloud-form when task cannot be completed', () => {
component.appName = 'app1';
component.taskId = 'task1';
component.showCompleteButton = true;
spyOn(component, 'canCompleteTask').and.returnValue(false);
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.showCompleteButton).toBe(false);
});
it('should pass showSaveButton to adf-cloud-form when task can be completed', () => {
component.appName = 'app1';
component.taskId = 'task1';
component.showSaveButton = true;
spyOn(component, 'canCompleteTask').and.returnValue(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.showSaveButton).toBe(true);
});
it('should not pass showSaveButton to adf-cloud-form when task cannot be completed', () => {
component.appName = 'app1';
component.taskId = 'task1';
component.showSaveButton = true;
spyOn(component, 'canCompleteTask').and.returnValue(false);
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.showSaveButton).toBe(false);
});
it('should pass customSaveButtonText to adf-cloud-form', () => {
const customText = 'Custom Save';
component.appName = 'app1';
component.taskId = 'task1';
component.customSaveButtonText = customText;
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.customSaveButtonText).toBe(customText);
});
it('should pass customCompleteButtonText to adf-cloud-form', () => {
const customText = 'Custom Complete';
component.appName = 'app1';
component.taskId = 'task1';
component.customCompleteButtonText = customText;
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.customCompleteButtonText).toBe(customText);
});
});
describe('Events', () => {
@@ -254,6 +339,25 @@ describe('TaskFormCloudComponent', () => {
expect(component.displayModeOff.emit).toHaveBeenCalledWith(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]);
});
it('should emit formLoaded when form is loaded', () => {
const mockForm = new FormModel();
spyOn(component.formLoaded, 'emit').and.stub();
component.onFormLoaded(mockForm);
expect(component.formLoaded.emit).toHaveBeenCalledOnceWith(mockForm);
});
it('should handle formLoaded event from adf-cloud-form and re-emit it', () => {
const mockForm = new FormModel();
spyOn(component.formLoaded, 'emit').and.stub();
// Trigger the formLoaded event from the child adf-cloud-form component
const cloudFormElement = fixture.debugElement.query((sel) => sel.name === 'adf-cloud-form');
cloudFormElement.triggerEventHandler('formLoaded', mockForm);
expect(component.formLoaded.emit).toHaveBeenCalledOnceWith(mockForm);
});
});
it('should call children cloud task form change display mode when changing the display mode', () => {

View File

@@ -72,6 +72,31 @@ export class TaskFormCloudComponent {
@Input()
showCompleteButton = true;
/** Toggle rendering of the `Save` button. */
@Input()
showSaveButton = true;
/**
* Custom text for the `Cancel` button.
* If not provided, the default text will be used.
*/
@Input()
customCancelButtonText: string = '';
/**
* Custom text for the `Complete` button.
* If not provided, the default text will be used.
*/
@Input()
customCompleteButtonText: string = '';
/**
* Custom text for the `Save` button.
* If not provided, the default text will be used.
*/
@Input()
customSaveButtonText: string = '';
/** Toggle readonly state of the task. */
@Input()
readOnly = false;
@@ -86,6 +111,10 @@ export class TaskFormCloudComponent {
@Input()
taskDetails: TaskDetailsCloudModel;
/** Emitted when the form is loaded or reloaded. */
@Output()
formLoaded = new EventEmitter<FormModel>();
/** Emitted when the form is saved. */
@Output()
formSaved = new EventEmitter<FormModel>();
@@ -224,4 +253,8 @@ export class TaskFormCloudComponent {
onDisplayModeOff(displayModeConfiguration: FormCloudDisplayModeConfiguration) {
this.displayModeOff.emit(displayModeConfiguration);
}
onFormLoaded(form: FormModel) {
this.formLoaded.emit(form);
}
}

View File

@@ -1,10 +1,5 @@
<button
*ngIf="showCancelButton"
mat-button
id="adf-cloud-cancel-task"
(click)="onCancelClick()"
>
{{'ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate}}
<button *ngIf="showCancelButton" mat-button id="adf-cloud-cancel-task" (click)="onCancelClick()">
{{ customCancelButtonText || ('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate) }}
</button>
<button
*ngIf="canClaimTask"

View File

@@ -71,6 +71,29 @@ describe('UserTaskCloudButtonsComponent', () => {
expect(cancelClickSpy).toHaveBeenCalled();
});
it('should display custom text when customCancelButtonText is provided', async () => {
const customText = 'Custom Cancel Text';
fixture.componentRef.setInput('showCancelButton', true);
fixture.componentRef.setInput('customCancelButtonText', customText);
fixture.detectChanges();
const cancelButton: MatButtonHarness = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' }));
const buttonText = await cancelButton.getText();
expect(buttonText.trim()).toBe(customText);
});
it('should display default text when customCancelButtonText is not provided', async () => {
fixture.componentRef.setInput('showCancelButton', true);
fixture.componentRef.setInput('customCancelButtonText', '');
fixture.detectChanges();
const cancelButton: MatButtonHarness = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' }));
const buttonText = await cancelButton.getText();
expect(buttonText.trim()).toBe('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL');
});
it('should show claim button', async () => {
let claimButton: MatButtonHarness = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '.adf-user-task-cloud-claim-btn' }));

View File

@@ -49,6 +49,13 @@ export class UserTaskCloudButtonsComponent {
@Input()
showCancelButton = true;
/**
* Custom text for the `Cancel` button.
* If not provided, the default text will be used.
*/
@Input()
customCancelButtonText: string = '';
/** Emitted when any error occurs. */
@Output() error = new EventEmitter<any>();

View File

@@ -12,9 +12,16 @@
[showTitle]="showTitle"
[taskId]="taskId"
[taskDetails]="taskDetails"
[showCancelButton]="showCancelButton"
[showSaveButton]="showSaveButton && canCompleteTask()"
[showCompleteButton]="showCompleteButton && canCompleteTask()"
[customCancelButtonText]="customCancelButtonText"
[customSaveButtonText]="customSaveButtonText"
[customCompleteButtonText]="customCompleteButtonText"
(cancelClick)="onCancelForm()"
(executeOutcome)="onExecuteOutcome($event)"
(error)="onError($event)"
(formLoaded)="onFormLoaded($event)"
(formSaved)="onFormSaved()"
(formContentClicked)="onFormContentClicked($event)"
(taskCompleted)="onCompleteTaskForm()"
@@ -37,7 +44,6 @@
[taskId]="taskId"
[showNextTaskCheckbox]="showNextTaskCheckbox && canCompleteTask()"
[isNextTaskCheckboxChecked]="isNextTaskCheckboxChecked"
(cancelTask)="onCancelClick()"
(claimTask)="onClaimTask()"
(error)="onError($event)"
@@ -79,7 +85,7 @@
color="primary"
id="adf-form-complete"
>
{{ 'ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.COMPLETE' | translate }}
{{ customCompleteButtonText || ('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.COMPLETE' | translate) }}
</button>
</mat-card-actions>
</mat-card>
@@ -98,6 +104,7 @@
[canClaimTask]="canClaimTask()"
[canUnclaimTask]="canUnclaimTask()"
[showCancelButton]="showCancelButton"
[customCancelButtonText]="customCancelButtonText"
[taskId]="taskId"
(cancelClick)="onCancelClick()"
(claimTask)="onClaimTask()"

View File

@@ -39,6 +39,8 @@ import {
} from '../../../models/task-details-cloud.model';
import { TaskFormCloudComponent } from '../task-form-cloud/task-form-cloud.component';
import { TaskCloudService } from '../../../services/task-cloud.service';
import { FormModel } from '../../../../../../../core';
import { UserTaskCloudButtonsComponent } from '../user-task-cloud-buttons/user-task-cloud-buttons.component';
const taskDetails: TaskDetailsCloudModel = {
appName: 'simple-app',
@@ -591,4 +593,168 @@ describe('UserTaskCloudComponent', () => {
prepareTestCase({ showNextTaskCheckbox: true, showCompleteButton: true, readOnly: false, canCompleteTask: true });
expect(isCheckboxShown()).toBeTrue();
});
describe('Input Properties - Button Controls', () => {
beforeEach(() => {
taskDetails.formKey = 'my-form';
component.taskDetails = { ...taskDetails };
component.getTaskType();
component.taskId = 'taskId';
component.appName = 'app';
});
describe('showSaveButton', () => {
it('should pass showSaveButton to task form when task type is Form', () => {
fixture.componentRef.setInput('showSaveButton', false);
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.showSaveButton).toBe(false);
});
it('should pass showSaveButton as true 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.showSaveButton).toBe(true);
});
it('should only show save button when canCompleteTask returns true', () => {
spyOn(component, 'canCompleteTask').and.returnValue(false);
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.showSaveButton).toBe(false);
});
});
describe('showCompleteButton', () => {
it('should pass showCompleteButton to task form when task type is Form', () => {
fixture.componentRef.setInput('showCompleteButton', false);
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.showCompleteButton).toBe(false);
});
it('should only show complete button when canCompleteTask returns true', () => {
spyOn(component, 'canCompleteTask').and.returnValue(false);
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.showCompleteButton).toBe(false);
});
it('should display custom complete button text in no form template', async () => {
component.taskDetails.formKey = '';
component.getTaskType();
const customText = 'Custom Complete Text';
fixture.componentRef.setInput('customCompleteButtonText', customText);
fixture.detectChanges();
const completeButton = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-form-complete' }));
expect(completeButton).not.toBeNull();
const buttonText = await completeButton.getText();
expect(buttonText).toBe(customText);
});
it('should display default complete button text when customCompleteButtonText is not provided', async () => {
component.taskDetails.formKey = '';
component.getTaskType();
const spy = spyOn(taskCloudService, 'canCompleteTask');
spy.and.returnValue(true);
fixture.detectChanges();
const completeButton = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-form-complete' }));
expect(completeButton).not.toBeNull();
const buttonText = await completeButton.getText();
expect(buttonText).toBe('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.COMPLETE');
});
});
describe('Custom button text', () => {
it('should pass customCancelButtonText to task form when task type is Form', () => {
const customText = 'Custom Cancel Text';
fixture.componentRef.setInput('customCancelButtonText', customText);
component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.customCancelButtonText).toBe(customText);
});
it('should pass customCancelButtonText to adf-cloud-user-task-cloud-buttons', () => {
component.taskDetails.formKey = '';
component.getTaskType();
const customText = 'Custom Cancel Text';
fixture.componentRef.setInput('customCancelButtonText', customText);
fixture.detectChanges();
const buttonsElement = fixture.debugElement.query(By.css('adf-cloud-user-task-cloud-buttons'));
const buttonsComponent = buttonsElement?.componentInstance as UserTaskCloudButtonsComponent;
expect(buttonsComponent.customCancelButtonText).toBe(customText);
});
it('should pass customSaveButtonText to task form when task type is Form', () => {
const customText = 'Custom Save Text';
fixture.componentRef.setInput('customSaveButtonText', customText);
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.customSaveButtonText).toBe(customText);
});
it('should pass customCompleteButtonText to task form when task type is Form', () => {
const customText = 'Custom Complete Text';
fixture.componentRef.setInput('customCompleteButtonText', customText);
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
expect(taskFormComponent.customCompleteButtonText).toBe(customText);
});
});
});
it('should emit formLoaded when task form emits formLoaded event', () => {
const mockForm = new FormModel();
taskDetails.formKey = 'my-form';
component.taskDetails = { ...taskDetails };
component.getTaskType();
component.taskId = 'taskId';
component.appName = 'app';
spyOn(component.formLoaded, 'emit');
component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
fixture.detectChanges();
const taskFormElement = fixture.debugElement.query(By.css('adf-cloud-task-form'));
const taskFormComponent = taskFormElement?.componentInstance as TaskFormCloudComponent;
taskFormComponent.formLoaded.emit(mockForm);
expect(component.formLoaded.emit).toHaveBeenCalledWith(mockForm);
});
});

View File

@@ -86,6 +86,31 @@ export class UserTaskCloudComponent implements OnInit, OnChanges {
@Input()
showCompleteButton = true;
/** Toggle rendering of the `Save` button. */
@Input()
showSaveButton = true;
/**
* Custom text for the `Cancel` button.
* If not provided, the default text will be used.
*/
@Input()
customCancelButtonText: string = '';
/**
* Custom text for the `Complete` button.
* If not provided, the default text will be used.
*/
@Input()
customCompleteButtonText: string = '';
/**
* Custom text for the `Save` button.
* If not provided, the default text will be used.
*/
@Input()
customSaveButtonText: string = '';
/** Toggle rendering of the `Open next task` checkbox (for screens only). */
@Input()
showNextTaskCheckbox = false;
@@ -129,6 +154,10 @@ export class UserTaskCloudComponent implements OnInit, OnChanges {
@Output()
formContentClicked: EventEmitter<ContentLinkModel> = new EventEmitter();
/** Emitted when the form is loaded or reloaded. */
@Output()
formLoaded = new EventEmitter<FormModel>();
/** Emitted when the form is saved. */
@Output()
formSaved = new EventEmitter<FormModel>();
@@ -248,6 +277,10 @@ export class UserTaskCloudComponent implements OnInit, OnChanges {
this.error.emit(data);
}
onFormLoaded(form: FormModel) {
this.formLoaded.emit(form);
}
onExecuteOutcome(outcome: FormOutcomeEvent): void {
this.executeOutcome.emit(outcome);
}

View File

@@ -323,7 +323,7 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnChange
* @returns list of form outcomes
*/
getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] {
return [new FormOutcomeModel(form, { id: '$save', name: FormOutcomeModel.SAVE_ACTION, isSystem: true })];
return [new FormOutcomeModel(form, { id: FormModel.SAVE_OUTCOME, name: FormOutcomeModel.SAVE_ACTION, isSystem: true })];
}
checkVisibility(field: FormFieldModel) {