[ADF-4441] Save button should be disabled when form fields are invalid (#4817)

* [ADF-4441] Save button should be disabled when form inputs are invalid, Refactored disable custom outcome buttons unit tests

* [ADF-4441] add missing space

* [ADF-4441] Refactor failing unit tests

* [ADF-4441] Added documentation for disableSaveButton property
This commit is contained in:
arditdomi 2019-06-10 12:01:58 +01:00 committed by Eugenio Romano
parent 3e1092d7bf
commit 4ea656e537
5 changed files with 156 additions and 33 deletions

View File

@ -78,6 +78,7 @@ The template defined inside `empty-form` will be shown when no form definition i
| appName | `string` | | App id to fetch corresponding form and values. | | appName | `string` | | App id to fetch corresponding form and values. |
| data | [`TaskVariableCloud`](../../../lib/process-services-cloud/src/lib/form/models/task-variable-cloud.model.ts)`[]` | | Custom form values map to be used with the rendered form. | | data | [`TaskVariableCloud`](../../../lib/process-services-cloud/src/lib/form/models/task-variable-cloud.model.ts)`[]` | | Custom form values map to be used with the rendered form. |
| disableCompleteButton | `boolean` | false | If true then the `Complete` outcome button is shown but it will be disabled. | | disableCompleteButton | `boolean` | false | If true then the `Complete` outcome button is shown but it will be disabled. |
| disableSaveButton | `boolean` | false | If true then the `Save` outcome button is shown but it will be disabled. |
| disableStartProcessButton | `boolean` | false | If true then the `Start Process` outcome button is shown but it will be disabled. | | disableStartProcessButton | `boolean` | false | If true then the `Start Process` outcome button is shown but it will be disabled. |
| fieldValidators | [`FormFieldValidator`](../../../lib/core/form/components/widgets/core/form-field-validator.ts)`[]` | \[] | Contains a list of form field validator instances. | | fieldValidators | [`FormFieldValidator`](../../../lib/core/form/components/widgets/core/form-field-validator.ts)`[]` | \[] | Contains a list of form field validator instances. |
| form | [`FormCloud`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts) | | Underlying [form model](../../../lib/core/form/components/widgets/core/form.model.ts) instance. | | form | [`FormCloud`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts) | | Underlying [form model](../../../lib/core/form/components/widgets/core/form.model.ts) instance. |

View File

@ -56,6 +56,7 @@ Any content in the body of `<adf-form>` will be shown when no form definition is
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| data | [`FormValues`](../../../lib/core/form/components/widgets/core/form-values.ts) | | Custom form values map to be used with the rendered form. | | data | [`FormValues`](../../../lib/core/form/components/widgets/core/form-values.ts) | | Custom form values map to be used with the rendered form. |
| disableCompleteButton | `boolean` | false | If true then the `Complete` outcome button is shown but it will be disabled. | | disableCompleteButton | `boolean` | false | If true then the `Complete` outcome button is shown but it will be disabled. |
| disableSaveButton | `boolean` | false | If true then the `Save` outcome button is shown but it will be disabled. |
| disableStartProcessButton | `boolean` | false | If true then the `Start Process` outcome button is shown but it will be disabled. | | disableStartProcessButton | `boolean` | false | If true then the `Start Process` outcome button is shown but it will be disabled. |
| fieldValidators | [`FormFieldValidator`](../../../lib/core/form/components/widgets/core/form-field-validator.ts)`[]` | \[] | Contains a list of form field validator instances. | | fieldValidators | [`FormFieldValidator`](../../../lib/core/form/components/widgets/core/form-field-validator.ts)`[]` | \[] | Contains a list of form field validator instances. |
| form | [`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts) | | Underlying [form model](../../../lib/core/form/components/widgets/core/form.model.ts) instance. | | form | [`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts) | | Underlying [form model](../../../lib/core/form/components/widgets/core/form.model.ts) instance. |

View File

@ -48,6 +48,10 @@ export abstract class FormBaseComponent {
@Input() @Input()
disableCompleteButton: boolean = false; disableCompleteButton: boolean = false;
/** If true then the `Save` outcome button is shown but it will be disabled. */
@Input()
disableSaveButton: boolean = false;
/** If true then the `Start Process` outcome button is shown but it will be disabled. */ /** If true then the `Start Process` outcome button is shown but it will be disabled. */
@Input() @Input()
disableStartProcessButton: boolean = false; disableStartProcessButton: boolean = false;
@ -116,9 +120,8 @@ export abstract class FormBaseComponent {
} }
if (outcome) { if (outcome) {
// Make 'Save' button always available
if (outcome.name === FormOutcomeModel.SAVE_ACTION) { if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
return true; return this.disableSaveButton ? false : this.form.isValid;
} }
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) { if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
return this.disableCompleteButton ? false : this.form.isValid; return this.disableCompleteButton ? false : this.form.isValid;

View File

@ -689,25 +689,48 @@ describe('FormCloudComponent', () => {
}); });
it('should disable complete outcome button when disableCompleteButton is true', () => { it('should disable complete outcome button when disableCompleteButton is true', () => {
const formModel = new FormCloud(); const formModel = new FormCloud(cloudFormMock);
formComponent.form = formModel; formComponent.form = formModel;
formComponent.disableCompleteButton = true; formComponent.disableCompleteButton = true;
expect(formModel.isValid).toBeTruthy(); expect(formModel.isValid).toBeTruthy();
expect(formComponent.form.hasOutcomes()).toBe(true);
const completeOutcome = formComponent.form.outcomes.find((outcome) => outcome.name === FormOutcomeModel.COMPLETE_ACTION); const completeOutcome = formComponent.form.outcomes.find((outcome) => outcome.name === FormOutcomeModel.COMPLETE_ACTION);
expect(formComponent.isOutcomeButtonEnabled(completeOutcome)).toBeFalsy(); expect(formComponent.isOutcomeButtonEnabled(completeOutcome)).toBeFalsy();
formComponent.disableCompleteButton = false;
expect(formComponent.isOutcomeButtonEnabled(completeOutcome)).toBeTruthy();
});
it('should disable save outcome button when disableSaveButton is true', () => {
const formModel = new FormCloud(cloudFormMock);
formComponent.form = formModel;
formComponent.disableSaveButton = true;
expect(formModel.isValid).toBeTruthy();
expect(formComponent.form.hasOutcomes()).toBe(true);
const saveOutcome = formComponent.form.outcomes.find((outcome) => outcome.name === FormOutcomeModel.SAVE_ACTION);
expect(formComponent.isOutcomeButtonEnabled(saveOutcome)).toBeFalsy();
formComponent.disableSaveButton = false;
expect(formComponent.isOutcomeButtonEnabled(saveOutcome)).toBeTruthy();
}); });
it('should disable start process outcome button when disableStartProcessButton is true', () => { it('should disable start process outcome button when disableStartProcessButton is true', () => {
const formModel = new FormCloud(); const formModel = new FormCloud(cloudFormMock);
formComponent.form = formModel; formComponent.form = formModel;
formComponent.disableStartProcessButton = true; formComponent.disableStartProcessButton = true;
expect(formModel.isValid).toBeTruthy(); expect(formModel.isValid).toBeTruthy();
expect(formComponent.form.hasOutcomes()).toBe(true);
const startProcessOutcome = formComponent.form.outcomes.find((outcome) => outcome.name === FormOutcomeModel.START_PROCESS_ACTION); const startProcessOutcome = formComponent.form.outcomes.find((outcome) => outcome.name === FormOutcomeModel.START_PROCESS_ACTION);
expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeFalsy(); expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeFalsy();
formComponent.disableStartProcessButton = false;
expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeTruthy();
}); });
it('should raise [executeOutcome] event for formService', (done) => { it('should raise [executeOutcome] event for formService', (done) => {

View File

@ -586,7 +586,7 @@ describe('FormComponent', () => {
expect(formService.completeTaskForm).not.toHaveBeenCalled(); expect(formService.completeTaskForm).not.toHaveBeenCalled();
}); });
it('should complete form form and raise corresponding event', () => { it('should complete form and raise corresponding event', () => {
spyOn(formService, 'completeTaskForm').and.callFake(() => { spyOn(formService, 'completeTaskForm').and.callFake(() => {
return new Observable((observer) => { return new Observable((observer) => {
observer.next(); observer.next();
@ -709,7 +709,7 @@ describe('FormComponent', () => {
expect(formComponent.data).toBe(metadata); expect(formComponent.data).toBe(metadata);
}); });
it('should disable outcome buttons for readonly form', () => { it('should disable custom outcome buttons for readonly form', () => {
const formModel = new FormModel(); const formModel = new FormModel();
formModel.readOnly = true; formModel.readOnly = true;
formComponent.form = formModel; formComponent.form = formModel;
@ -727,31 +727,6 @@ describe('FormComponent', () => {
expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy(); expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy();
}); });
it('should always enable save outcome for writeable form', () => {
const formModel = new FormModel();
const field = new FormFieldModel(formModel, {
type: 'text',
value: null,
required: true
});
const containerModel = new ContainerModel(field);
formModel.fields.push(containerModel);
formComponent.form = formModel;
formModel.onFormFieldChanged(field);
expect(formModel.isValid).toBeFalsy();
const outcome = new FormOutcomeModel(new FormModel(), {
id: FormComponent.SAVE_OUTCOME_ID,
name: FormOutcomeModel.SAVE_ACTION
});
formComponent.readOnly = true;
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy();
});
it('should disable outcome buttons for invalid form', () => { it('should disable outcome buttons for invalid form', () => {
const formModel = new FormModel(); const formModel = new FormModel();
const field = new FormFieldModel(formModel, { const field = new FormFieldModel(formModel, {
@ -780,19 +755,139 @@ describe('FormComponent', () => {
formComponent.form = formModel; formComponent.form = formModel;
formComponent.disableCompleteButton = true; formComponent.disableCompleteButton = true;
const field = new FormFieldModel(formModel, {
type: 'text',
value: 'text',
required: true
});
const containerModel = new ContainerModel(field);
formModel.fields.push(containerModel);
formComponent.form = formModel;
formModel.onFormFieldChanged(field);
expect(formModel.isValid).toBeTruthy();
const completeOutcome = new FormOutcomeModel(new FormModel(), {
id: FormComponent.COMPLETE_OUTCOME_ID,
name: FormOutcomeModel.COMPLETE_ACTION
});
expect(formModel.isValid).toBeTruthy(); expect(formModel.isValid).toBeTruthy();
const completeOutcome = formComponent.form.outcomes.find((outcome) => outcome.name === FormOutcomeModel.COMPLETE_ACTION);
expect(formComponent.isOutcomeButtonEnabled(completeOutcome)).toBeFalsy(); expect(formComponent.isOutcomeButtonEnabled(completeOutcome)).toBeFalsy();
}); });
it('should disable save outcome button when form is valid and readOnly', () => {
const formModel = new FormModel();
const field = new FormFieldModel(formModel, {
type: 'text',
value: 'text',
required: true
});
const containerModel = new ContainerModel(field);
formModel.fields.push(containerModel);
formComponent.form = formModel;
formModel.onFormFieldChanged(field);
expect(formModel.isValid).toBeTruthy();
const saveOutcome = new FormOutcomeModel(new FormModel(), {
id: FormComponent.SAVE_OUTCOME_ID,
name: FormOutcomeModel.SAVE_ACTION
});
formComponent.form.readOnly = true;
expect(formComponent.isOutcomeButtonEnabled(saveOutcome)).toBeFalsy();
});
it('should enable save outcome button when form is valid and not readOnly', () => {
const formModel = new FormModel();
const field = new FormFieldModel(formModel, {
type: 'text',
value: 'text',
required: true
});
const containerModel = new ContainerModel(field);
formModel.fields.push(containerModel);
formComponent.form = formModel;
formModel.onFormFieldChanged(field);
expect(formModel.isValid).toBeTruthy();
const saveOutcome = new FormOutcomeModel(new FormModel(), {
id: FormComponent.SAVE_OUTCOME_ID,
name: FormOutcomeModel.SAVE_ACTION
});
formComponent.form.readOnly = false;
expect(formComponent.isOutcomeButtonEnabled(saveOutcome)).toBeTruthy();
});
it('should disable save outcome button when disableSaveButton is true', () => {
const formModel = new FormModel();
formComponent.form = formModel;
formComponent.disableSaveButton = true;
const saveOutcome = new FormOutcomeModel(new FormModel(), {
id: FormComponent.SAVE_OUTCOME_ID,
name: FormOutcomeModel.SAVE_ACTION
});
expect(formComponent.isOutcomeButtonEnabled(saveOutcome)).toBeFalsy();
});
it('should disable save outcome button when the form is invalid', () => {
const formModel = new FormModel();
formComponent.form = formModel;
const field = new FormFieldModel(formModel, {
type: 'text',
value: null,
required: true
});
const containerModel = new ContainerModel(field);
formModel.fields.push(containerModel);
formComponent.form = formModel;
formModel.onFormFieldChanged(field);
expect(formModel.isValid).toBeFalsy();
const saveOutcome = new FormOutcomeModel(new FormModel(), {
id: FormComponent.SAVE_OUTCOME_ID,
name: FormOutcomeModel.SAVE_ACTION
});
expect(formComponent.isOutcomeButtonEnabled(saveOutcome)).toBeFalsy();
});
it('should disable start process outcome button when disableStartProcessButton is true', () => { it('should disable start process outcome button when disableStartProcessButton is true', () => {
const formModel = new FormModel(); const formModel = new FormModel();
formComponent.form = formModel; formComponent.form = formModel;
formComponent.disableStartProcessButton = true; formComponent.disableStartProcessButton = true;
const field = new FormFieldModel(formModel, {
type: 'text',
value: 'text',
required: true
});
const containerModel = new ContainerModel(field);
formModel.fields.push(containerModel);
formComponent.form = formModel;
formModel.onFormFieldChanged(field);
expect(formModel.isValid).toBeTruthy(); expect(formModel.isValid).toBeTruthy();
const startProcessOutcome = formComponent.form.outcomes.find((outcome) => outcome.name === FormOutcomeModel.START_PROCESS_ACTION);
const startProcessOutcome = new FormOutcomeModel(new FormModel(), {
id: FormComponent.START_PROCESS_OUTCOME_ID,
name: FormOutcomeModel.START_PROCESS_ACTION
});
expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeFalsy(); expect(formComponent.isOutcomeButtonEnabled(startProcessOutcome)).toBeFalsy();
}); });