diff --git a/src/Webform.js b/src/Webform.js index 316f0e07e7..5ee7a85efb 100644 --- a/src/Webform.js +++ b/src/Webform.js @@ -1133,7 +1133,6 @@ export default class Webform extends NestedDataComponent { } errors = errors.concat(this.customErrors); - errors = errors.concat(this.serverErrors || []); if (!errors.length) { this.setAlert(false); @@ -1276,7 +1275,7 @@ export default class Webform extends NestedDataComponent { this.submitting = false; this.setPristine(false); - this.emit('submitError', error); + this.emit('submitError', error || this.errors); // Allow for silent cancellations (no error message, no submit button error state) if (error && error.silent) { @@ -1284,13 +1283,8 @@ export default class Webform extends NestedDataComponent { return false; } - let errors; - if (this.submitted) { - errors = this.showErrors(); - } - else { - errors = this.showErrors(error, true); - } + const errors = this.showErrors(error, true); + if (this.root && this.root.alert) { this.scrollIntoView(this.root.alert); } @@ -1323,7 +1317,9 @@ export default class Webform extends NestedDataComponent { value.isValid = this.checkData(value.data, flags); this.loading = false; if (this.submitted) { - this.showErrors(); + // show server errors while they are not cleaned/fixed + const nonComponentServerErrors = _.filter(this.serverErrors || [], err => !err.component && !err.path); + this.showErrors(nonComponentServerErrors.length ? nonComponentServerErrors : null); } // See if we need to save the draft of the form. diff --git a/src/Webform.unit.js b/src/Webform.unit.js index 4c0d582f39..7851ebd83c 100644 --- a/src/Webform.unit.js +++ b/src/Webform.unit.js @@ -74,6 +74,7 @@ import formWithCheckboxRadioType from '../test/forms/formWithCheckboxRadioType'; import formWithFormController from '../test/forms/formWithFormController'; import calculateValueOnServerForEditGrid from '../test/forms/calculateValueOnServerForEditGrid'; import formsWithAllowOverride from '../test/forms/formsWithAllowOverrideComps'; +import formWithValidation from '../test/forms/formWithValidation'; global.requestAnimationFrame = (cb) => cb(); global.cancelAnimationFrame = () => {}; @@ -86,6 +87,87 @@ if (_.has(Formio, 'Components.setComponents')) { describe('Webform tests', function() { this.retries(3); + it('Should fire error and submitError events with args on attempt to submit invalid form', function(done) { + const formElement = document.createElement('div'); + const form = new Webform(formElement); + + form.setForm(formWithValidation).then(() => { + let errorEvents = 0; + let submitErrorEvents = 0; + form.on('error', (arg) => { + assert.equal(!!arg, true, 'Error event should have argument'); + errorEvents = errorEvents + 1; + }); + + form.on('submitError', (arg) => { + assert.equal(!!arg, true, 'submitError event should have argument'); + submitErrorEvents = submitErrorEvents + 1; + }); + + const clickEvent = new Event('click'); + const submitBtn = form.element.querySelector('[name="data[submit]"]'); + submitBtn.dispatchEvent(clickEvent); + + setTimeout(() => { + assert.equal(form.errors.length, 1); + assert.equal(errorEvents, 1); + assert.equal(submitErrorEvents, 1); + done(); + }, 300); + }).catch((err) => done(err)); + }); + + it('Should keep non-component server errors visible after changes in the form', (done) => { + const element = document.createElement('div'); + const form = fastCloneDeep(formWithValidation); + form.components[0].validate = {}; + + const originalMakeRequest = Formio.makeRequest; + const errorText = 'Server error'; + Formio.makeRequest = function() { + return new Promise((res, rej) => { + setTimeout(() => { + console.log(8888); + rej(errorText); + }, 50); + }); + }; + + Formio.createForm(element, form) + .then(instance => { + instance.formio = new Formio('http://localhost:3000/test'); + assert.equal(instance.errors.length, 0); + assert.equal(!!(instance.serverErrors && instance.serverErrors.length), false); + assert.equal(!!(instance.refs.errorRef && instance.refs.errorRef.length), false); + + const clickEvent = new Event('click'); + const submitBtn = instance.element.querySelector('[name="data[submit]"]'); + submitBtn.dispatchEvent(clickEvent); + + setTimeout(() => { + assert.equal(instance.errors.length, 0); + assert.equal(instance.serverErrors.length, 1); + assert.equal(instance.refs.errorRef.length, 1); + assert.equal(instance.refs.errorRef[0].textContent.trim(), errorText); + + const inputEvent = new Event('input'); + const textField = instance.element.querySelector('input[name="data[name]"]'); + textField.value = 'test'; + textField.dispatchEvent(inputEvent); + + setTimeout(() => { + assert.equal(instance.errors.length, 0); + assert.equal(instance.serverErrors.length, 1); + assert.equal(instance.refs.errorRef.length, 1); + assert.equal(instance.refs.errorRef[0].textContent.trim(), errorText); + Formio.makeRequest = originalMakeRequest; + done(); + }, 400); + }, 400); + }) + .catch(done); + }); + it('Should execute form controller', function(done) { Formio.createForm(formWithFormController).then((form) => { setTimeout(() => { diff --git a/test/forms/formWithValidation.js b/test/forms/formWithValidation.js new file mode 100644 index 0000000000..0574e5ef71 --- /dev/null +++ b/test/forms/formWithValidation.js @@ -0,0 +1,26 @@ +export default { + components: [ + { + label: 'Name', + applyMaskOn: 'change', + tableView: true, + validate: { + required: true, + minLength: 2, + maxLength: 5, + }, + key: 'name', + type: 'textfield', + input: true, + }, + { + type: 'button', + showValidations: false, + label: 'Submit', + key: 'submit', + input: true, + tableView: false, + }, + ], +}; + \ No newline at end of file