From d9e1b0425489e28164e36ae6bad54e29bc4bce07 Mon Sep 17 00:00:00 2001 From: Blake Krammes <49688912+blakekrammes@users.noreply.github.com> Date: Mon, 25 Nov 2024 08:50:49 -0600 Subject: [PATCH 1/5] FIO-9350 Wizard: Show all form errors consistently after failed submission - Show all form errors on page without required fields after failed submission - Show all form errors on page with required fields after failed submission and after interacting with the page --- src/Wizard.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Wizard.js b/src/Wizard.js index f5bafbfea3..d41e8c70b3 100644 --- a/src/Wizard.js +++ b/src/Wizard.js @@ -690,7 +690,10 @@ export default class Wizard extends Webform { } this.redraw().then(() => { this.checkData(this.submission.data); - this.validateCurrentPage(); + const errors = this.submitted ? this.validate(this.localData, { dirty: true }) : this.validateCurrentPage(); + if (this.alert) { + this.showErrors(errors, true, true); + } }); return Promise.resolve(); } @@ -801,9 +804,11 @@ export default class Wizard extends Webform { }); } - // Validate the form, before go to the next page - const errors = this.validateCurrentPage({ dirty: true }); - if (errors.length === 0) { + // Validate the form before going to the next page + const currentPageErrors = this.validateCurrentPage({ dirty: true }); + const errors = this.submitted ? this.validate(this.localData, { dirty: true }) : currentPageErrors; + // allow going to the next page if the current page is valid, even if there are form level errors + if (currentPageErrors.length === 0) { this.checkData(this.submission.data); return this.beforePage(true).then(() => { return this.setPage(this.getNextPage()).then(() => { From 06ab5e0fa33b0a12de48cf8325ab30419c9333c9 Mon Sep 17 00:00:00 2001 From: Blake Krammes <49688912+blakekrammes@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:44:58 -0600 Subject: [PATCH 2/5] FIO-9350 Wizard: Persist form errors if submission failed - While showErrors worked as expected (defined in Webform), this.errors (get errors()) in Wizard was not persisting all the form errors after a failed submission --- src/Wizard.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Wizard.js b/src/Wizard.js index d41e8c70b3..678b9a6b8b 100644 --- a/src/Wizard.js +++ b/src/Wizard.js @@ -824,7 +824,7 @@ export default class Wizard extends Webform { else { this.currentPage.components.forEach((comp) => comp.setPristine(false)); this.scrollIntoView(this.element, true); - return Promise.reject(super.showErrors(errors, true)); + return Promise.reject(this.showErrors(errors, true)); } } @@ -1072,12 +1072,8 @@ export default class Wizard extends Webform { ); } - get errors() { - if (!this.isLastPage()) { - return this.currentPage.errors; - } - - return super.errors; + get errors() { + return !this.isLastPage() && !this.submitted ? this.currentPage.errors : super.errors; } focusOnComponent(key) { From 6380e8491638f84fa5a069e555e97501b568295e Mon Sep 17 00:00:00 2001 From: Blake Krammes <49688912+blakekrammes@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:36:02 -0600 Subject: [PATCH 3/5] FIO-9350 Wizard: Clean up dup helper fns in tests - Consolidate most link navigation to one global helper fn --- test/unit/Wizard.unit.js | 199 ++++++++++++--------------------------- 1 file changed, 59 insertions(+), 140 deletions(-) diff --git a/test/unit/Wizard.unit.js b/test/unit/Wizard.unit.js index 036f8085e2..01e41aa041 100644 --- a/test/unit/Wizard.unit.js +++ b/test/unit/Wizard.unit.js @@ -46,6 +46,13 @@ import { wait } from '../util'; // eslint-disable-next-line max-statements describe('Wizard tests', () => { + // helpers + const clickWizardBtn = (wizard, pathPart, clickError) => { + const btn = _.get(wizard.refs, clickError ? pathPart : `${wizard.wizardKey}-${pathPart}`); + const clickEvent = new Event('click'); + btn.dispatchEvent(clickEvent); + }; + it('Should recalculate values for components with "allow override" after wizard is canceled', function(done) { const formElement = document.createElement('div'); Formio.createForm(formElement, formsWithAllowOverride.wizard).then((form) => { @@ -331,18 +338,12 @@ describe('Wizard tests', () => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); }; - const clickWizardBtn = (pathPart, clickError) => { - const btn = _.get(wizard.refs, clickError ? pathPart : `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; - checkPage(0); assert.equal(wizard.pages.length, 4, 'Should have 4 pages'); assert.equal(wizard.allPages.length, 4, 'Should have 4 pages'); assert.equal(wizard.refs[`${wizard.wizardKey}-link`].length, 4, 'Should contain refs to breadcrumbs of parent and nested wizard'); - clickWizardBtn('link[3]'); + clickWizardBtn(wizard, 'link[3]'); setTimeout(() => { checkPage(3); @@ -363,11 +364,11 @@ describe('Wizard tests', () => { assert.equal(wizard.allPages.length, 5, 'Should show conditional page'); assert.equal(wizard.refs[`${wizard.wizardKey}-link`].length, 5, 'Should contain refs to breadcrumbs of visible conditional page'); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(4); - clickWizardBtn('previous'); + clickWizardBtn(wizard, 'previous'); setTimeout(() => { checkPage(3); @@ -411,7 +412,6 @@ describe('Wizard tests', () => { } parentWizard.components[0].components.push(requiredTextField); - const clickEvent = new Event('click'); wizard.setForm(parentWizard).then(() => { const nestedFormComp = wizard.getComponent('formNested'); @@ -428,12 +428,10 @@ describe('Wizard tests', () => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); }; checkPage(0); - const nestedWizardBreadcrumbBtn = _.get(wizard.refs, `${wizard.wizardKey}-link[2]`); - nestedWizardBreadcrumbBtn.dispatchEvent(clickEvent); + clickWizardBtn(wizard, 'link[2]'); setTimeout(() => { checkPage(2); - const nextBtn = _.get(wizard.refs, `${wizard.wizardKey}-next`); - nextBtn.dispatchEvent(clickEvent); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(2); const errors = wizard.errors; @@ -592,7 +590,6 @@ describe('Wizard tests', () => { const wizard = new Wizard(formElement); const nestedWizard = _.cloneDeep(formWithNestedWizardAndRequiredFields.childWizard); const parentWizard = _.cloneDeep(formWithNestedWizardAndRequiredFields.parentWizard); - const clickEvent = new Event('click'); wizard.setForm(parentWizard).then(() => { const formio = new Formio('http://test.localhost/test', {}); @@ -618,13 +615,10 @@ describe('Wizard tests', () => { setTimeout(() => { assert.equal(wizard.submission.data.textField, 'test'); - const nestedWizardBreadcrumbBtn = _.get(wizard.refs, `${wizard.wizardKey}-link[4]`); - nestedWizardBreadcrumbBtn.dispatchEvent(clickEvent); - + clickWizardBtn(wizard, 'link[4]'); setTimeout(() => { checkPage(4); - _.get(wizard.refs, `${wizard.wizardKey}-submit`).dispatchEvent(clickEvent); - + clickWizardBtn(wizard, 'submit'); setTimeout(() => { assert.equal(wizard.errors.length, 2); done(); @@ -645,11 +639,6 @@ describe('Wizard tests', () => { const form = _.cloneDeep(wizardTestForm.form); wizard.setForm(form, ).then(() => { - const clickWizardBtn = (pathPart, clickError) => { - const btn = _.get(wizard.refs, clickError ? pathPart : `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); @@ -674,12 +663,12 @@ describe('Wizard tests', () => { setTimeout(() => { checkPage(0); checkValues(); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(1); checkValues(); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(2); @@ -700,11 +689,6 @@ describe('Wizard tests', () => { const form = _.cloneDeep(wizardWithPrefixComps.form); wizard.setForm(form).then(() => { - const clickWizardBtn = (pathPart, clickError) => { - const btn = _.get(wizard.refs, clickError ? pathPart : `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); @@ -730,7 +714,7 @@ describe('Wizard tests', () => { setTimeout(() => { checkPage(0); checkValues(); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(1); @@ -749,12 +733,6 @@ describe('Wizard tests', () => { }); wizard.setForm(wizardWithComponentsWithSameApi).then(() => { - const clickWizardBtn = (pathPart) => { - const [btnKey] = Object.keys(wizard.refs).filter((key) => key.indexOf(pathPart) !== -1); - const btn = _.get(wizard.refs, btnKey); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); @@ -766,7 +744,7 @@ describe('Wizard tests', () => { setTimeout(() => { checkPage(1); - clickWizardBtn('submit'); + clickWizardBtn(wizard, 'submit'); setTimeout(() => { assert.equal(wizard.refs.errorRef.length, 1, 'Should have an error'); @@ -815,20 +793,14 @@ describe('Wizard tests', () => { nestedFormComp.createSubForm(); setTimeout(() => { - const clickWizardBtn = (pathPart) => { - const btn = _.get(wizard.refs, `${wizard.wizardKey}-${pathPart}`); - btn.dispatchEvent(clickEvent); - }; - const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); }; - clickWizardBtn('link[4]'); - + clickWizardBtn(wizard, 'link[4]'); setTimeout(() => { checkPage(4); - clickWizardBtn('submit'); + clickWizardBtn(wizard, 'submit'); setTimeout(() => { assert.equal(wizard.refs.errorRef.length, 4, 'Should have errors'); wizard.refs.errorRef[0].dispatchEvent(clickEvent); @@ -870,7 +842,6 @@ describe('Wizard tests', () => { type: 'textfield', input: true }) - const clickEvent = new Event('click'); wizard.setForm(parentWizardForm).then(() => { const nestedFormComp = wizard.getComponent('formNested'); @@ -884,16 +855,12 @@ describe('Wizard tests', () => { nestedFormComp.createSubForm(); setTimeout(() => { - const clickWizardBtn = (pathPart) => { - const btn = _.get(wizard.refs, `${wizard.wizardKey}-${pathPart}`); - btn.dispatchEvent(clickEvent); - }; - - clickWizardBtn('link[1]'); + + clickWizardBtn(wizard, 'link[1]'); setTimeout(() => { assert.equal(wizard.page, 1, `Should open wizard page 2`); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { assert.equal(wizard.errors.length, 1); @@ -946,11 +913,6 @@ describe('Wizard tests', () => { }); wizard.setForm(form).then(() => { - const clickWizardBtn = (pathPart, clickError) => { - const btn = _.get(wizard.refs, clickError ? pathPart : `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); @@ -958,7 +920,7 @@ describe('Wizard tests', () => { checkPage(0); wizard.getComponent('textField').setValue('tooltip'); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(1); @@ -966,7 +928,7 @@ describe('Wizard tests', () => { assert.equal(!!wizard.refs[`${wizard.wizardKey}-tooltip`][0], true, 'Should render tooltip icon'); wizard.getComponent('checkbox').setValue(true); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(2); @@ -992,28 +954,23 @@ describe('Wizard tests', () => { }); wizard.setForm(form).then(() => { - const clickWizardBtn = (pathPart, clickError) => { - const btn = _.get(wizard.refs, clickError ? pathPart : `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); }; checkPage(0); wizard.getComponent('textField').setValue('page3'); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(2); wizard.getComponent('select').setValue('value1'); - clickWizardBtn('previous'); + clickWizardBtn(wizard, 'previous'); setTimeout(() => { checkPage(1); wizard.getComponent('checkbox').setValue(true); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(0); @@ -1064,11 +1021,6 @@ describe('Wizard tests', () => { }); wizard.setForm(form).then(() => { - const clickWizardBtn = (pathPart, clickError) => { - const btn = _.get(wizard.refs, clickError ? pathPart : `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); @@ -1084,12 +1036,12 @@ describe('Wizard tests', () => { setTimeout(() => { checkPage(0); checkBreadcrumb(); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(1); checkBreadcrumb(); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(2); @@ -1111,22 +1063,17 @@ describe('Wizard tests', () => { }); wizard.setForm(form).then(() => { - const clickWizardBtn = (pathPart, clickError) => { - const btn = _.get(wizard.refs, clickError ? pathPart : `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); }; checkPage(0); - clickWizardBtn('link[1]'); + clickWizardBtn(wizard, 'link[1]'); setTimeout(() => { checkPage(0); - clickWizardBtn('link[2]'); + clickWizardBtn(wizard, 'link[2]'); setTimeout(() => { checkPage(0); @@ -1134,11 +1081,11 @@ describe('Wizard tests', () => { setTimeout(() => { checkPage(0); - clickWizardBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(1); - clickWizardBtn('link[0]'); + clickWizardBtn(wizard, 'link[0]'); setTimeout(() => { checkPage(1); @@ -1378,11 +1325,6 @@ describe('Wizard tests', () => { const wizard = new Wizard(formElement); wizard.setForm(wizardTestForm.form).then(() => { - const clickWizardBtn = (pathPart, clickError) => { - const btn = _.get(wizard.refs, clickError ? pathPart : `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); @@ -1399,20 +1341,20 @@ describe('Wizard tests', () => { }; checkPage(0); - clickWizardBtn('link[2]'); + clickWizardBtn(wizard, 'link[2]'); setTimeout(() => { checkPage(2); assert.equal(wizard.visibleErrors.length, 0, 'Should not have validation errors'); - clickWizardBtn('submit'); + clickWizardBtn(wizard, 'submit'); setTimeout(() => { assert.equal(wizard.visibleErrors.length, 3, 'Should have validation errors'); assert.equal(wizard.refs.errorRef.length, wizard.errors.length, 'Should show alert with validation errors'); assert.equal(!!wizard.element.querySelector('.alert-danger'), true, 'Should have alert with validation errors'); checkInvalidComp('select', true); - clickWizardBtn('errorRef[0]', true); + clickWizardBtn(wizard, 'errorRef[0]', true); setTimeout(() => { checkPage(0); @@ -1420,7 +1362,7 @@ describe('Wizard tests', () => { assert.equal(wizard.visibleErrors.length, 3, 'Should have page validation error'); assert.equal(wizard.refs.errorRef.length, 3, 'Should keep alert with validation errors'); checkInvalidComp('textField', true); - clickWizardBtn('errorRef[1]', true); + clickWizardBtn(wizard, 'errorRef[1]', true); setTimeout(() => { checkPage(1); @@ -1434,7 +1376,7 @@ describe('Wizard tests', () => { checkPage(1); assert.equal(wizard.visibleErrors.length, 2, 'Should not have page validation error'); assert.equal(wizard.refs.errorRef.length, 2, 'Should keep alert with validation errors'); - clickWizardBtn('errorRef[1]', true); + clickWizardBtn(wizard, 'errorRef[1]', true); setTimeout(() => { checkPage(2); @@ -1446,7 +1388,7 @@ describe('Wizard tests', () => { setTimeout(() => { assert.equal(wizard.visibleErrors.length, 1, 'Should have wizard validation error'); assert.equal(wizard.refs.errorRef.length, 1, 'Should keep alert with validation errors'); - clickWizardBtn('errorRef[0]', true); + clickWizardBtn(wizard, 'errorRef[0]', true); setTimeout(() => { checkPage(0); @@ -1458,10 +1400,10 @@ describe('Wizard tests', () => { setTimeout(() => { assert.equal(wizard.visibleErrors.length, 0, 'Should not have page validation error'); assert.equal(!!wizard.element.querySelector('.alert-danger'), false, 'Should not have alert with validation errors'); - clickWizardBtn('link[2]'); + clickWizardBtn(wizard, 'link[2]'); setTimeout(() => { - clickWizardBtn('submit'); + clickWizardBtn(wizard, 'submit'); setTimeout(() => { assert.equal(wizard.submission.state, 'submitted', 'Should submit the form'); done(); @@ -1485,17 +1427,12 @@ describe('Wizard tests', () => { const wizard = new Wizard(formElement); wizard.setForm(wizardTestForm.form).then(() => { - const clickNavigationBtn = (pathPart) => { - const btn = _.get(wizard.refs, `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; const checkPage = (pageNumber) => { assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); }; checkPage(0); - clickNavigationBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(0); @@ -1505,11 +1442,11 @@ describe('Wizard tests', () => { wizard.getComponent('textField').setValue('valid'); - clickNavigationBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(1); - clickNavigationBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(1); @@ -1517,7 +1454,7 @@ describe('Wizard tests', () => { assert.equal(wizard.errors.length, 1, 'Should have validation error'); assert.equal(wizard.refs.errorRef.length, wizard.errors.length, 'Should show alert with validation error'); - clickNavigationBtn('previous'); + clickWizardBtn(wizard, 'previous'); setTimeout(() => { checkPage(0); @@ -1525,25 +1462,25 @@ describe('Wizard tests', () => { assert.equal(wizard.errors.length, 0, 'Should not have validation error'); assert.equal(!!wizard.refs.errorRef, false, 'Should not have alert with validation error'); - clickNavigationBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(1); assert.equal(wizard.errors.length, 1, 'Should have validation error'); wizard.getComponent('checkbox').setValue(true); - clickNavigationBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(() => { checkPage(2); assert.equal(wizard.errors.length, 1, 'Should have validation error'); assert.equal(wizard.visibleErrors.length, 0, 'Should not have visible validation error'); - clickNavigationBtn('link[0]'); + clickWizardBtn(wizard, 'link[0]'); setTimeout(() => { checkPage(0); assert.equal(wizard.errors.length, 0, 'Should not have validation error'); - clickNavigationBtn('link[2]'); + clickWizardBtn(wizard, 'link[2]'); setTimeout(() => { checkPage(2); @@ -1593,9 +1530,7 @@ describe('Wizard tests', () => { const numberValue = formHTMLMode.element.querySelector('[ref="value"]').textContent; assert.equal(+numberValue, wizardForHtmlModeTest.submission.data.number); - const nextPageBtn = formHTMLMode.refs[`${formHTMLMode.wizardKey}-next`]; - const clickEvent = new Event('click'); - nextPageBtn.dispatchEvent(clickEvent); + clickWizardBtn(formHTMLMode, 'next'); setTimeout(() => { const textValue = formHTMLMode.element.querySelector('[ref="value"]').textContent; @@ -1638,7 +1573,6 @@ it('Should show tooltip for wizard pages', function(done) { const formWithHiddenPage = new Wizard(formElement); formWithHiddenPage.setForm(wizardWithHiddenPanel).then(() => { - const clickEvent = new Event('click'); const inputEvent = new Event('input'); assert.equal(formWithHiddenPage.pages.length, 2); @@ -1651,14 +1585,12 @@ it('Should show tooltip for wizard pages', function(done) { setTimeout(() => { assert.equal(formWithHiddenPage.element.querySelector('[name="data[number]"]').value, 555); - const nextPageBtn = formWithHiddenPage.refs[`${formWithHiddenPage.wizardKey}-next`]; - nextPageBtn.dispatchEvent(clickEvent); + clickWizardBtn(formWithHiddenPage, 'next'); setTimeout(() => { assert.equal(formWithHiddenPage.page, 1); - const prevPageBtn = formWithHiddenPage.refs[`${formWithHiddenPage.wizardKey}-previous`]; - prevPageBtn.dispatchEvent(clickEvent); + clickWizardBtn(formWithHiddenPage, 'previous'); setTimeout(() => { assert.equal(formWithHiddenPage.page, 0); @@ -1742,10 +1674,7 @@ it('Should show tooltip for wizard pages', function(done) { assert.equal(wizardWithCustomConditionalPage.components.length, 2); assert.deepEqual(wizardWithCustomConditionalPage.data, submissionData); - const clickEvent = new Event('click'); - const secondPageBtn = wizardWithCustomConditionalPage.refs[`${wizardWithCustomConditionalPage.wizardKey}-link`][1]; - - secondPageBtn.dispatchEvent(clickEvent); + clickWizardBtn(wizardWithCustomConditionalPage, 'link[1]'); setTimeout(() => { assert.equal(wizardWithCustomConditionalPage.page, 1); @@ -2287,15 +2216,10 @@ it('Should show tooltip for wizard pages', function(done) { it('Should retain previous checkbox checked property when navigating to another page (checked)', (done) => { const wizard = new Wizard(document.createElement('div')); wizard.setForm(WizardWithCheckboxes).then(()=> { - const clickNavigationBtn = (pathPart) => { - const btn = _.get(wizard.refs, `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; wizard.element.querySelector('input').click(); - clickNavigationBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(()=>{ - clickNavigationBtn('previous'); + clickWizardBtn(wizard, 'previous'); setTimeout(()=>{ assert.equal(wizard.element.querySelector('input').checked, true); done(); @@ -2306,17 +2230,12 @@ it('Should show tooltip for wizard pages', function(done) { it('Should retain previous checkbox checked property when navigating to another page (unchecked)', (done) => { const wizard = new Wizard(document.createElement('div')); - wizard.setForm(WizardWithCheckboxes).then(()=> { - const clickNavigationBtn = (pathPart) => { - const btn = _.get(wizard.refs, `${wizard.wizardKey}-${pathPart}`); - const clickEvent = new Event('click'); - btn.dispatchEvent(clickEvent); - }; + wizard.setForm(WizardWithCheckboxes).then(()=> { wizard.element.querySelector('input').click(); wizard.element.querySelector('input').click(); - clickNavigationBtn('next'); + clickWizardBtn(wizard, 'next'); setTimeout(()=>{ - clickNavigationBtn('previous'); + clickWizardBtn(wizard, 'previous'); setTimeout(()=>{ assert.equal(wizard.element.querySelector('input').checked, false); done(); From 34cf9a2be7e7c689a3b3c9297a60fe1bcb2d167c Mon Sep 17 00:00:00 2001 From: Blake Krammes <49688912+blakekrammes@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:01:26 -0600 Subject: [PATCH 4/5] FIO-9350 Wizard: Add test for maintaining form errors after failed submit - Show form-level errors after failed submission even when the current page has no errors --- test/forms/simpleWizardWithRequiredFields.js | 140 +++++++++++++++++++ test/unit/Wizard.unit.js | 34 +++++ 2 files changed, 174 insertions(+) create mode 100644 test/forms/simpleWizardWithRequiredFields.js diff --git a/test/forms/simpleWizardWithRequiredFields.js b/test/forms/simpleWizardWithRequiredFields.js new file mode 100644 index 0000000000..6791f19dcd --- /dev/null +++ b/test/forms/simpleWizardWithRequiredFields.js @@ -0,0 +1,140 @@ +export default { + "_id": "6740b7686f3a02cd736b5750", + "title": "form123", + "name": "form123", + "path": "form123", + "type": "form", + "display": "wizard", + "tags": [], + "access": [ + { + "type": "read_all", + "roles": [ + "6740b7686f3a02cd736b56f3", + "6740b7686f3a02cd736b56f7", + "6740b7686f3a02cd736b56fb", + "6740b7686f3a02cd736b56ff", + "6740b7686f3a02cd736b5703", + "6740b7686f3a02cd736b5707", + "6740b7686f3a02cd736b570b", + "6740b7686f3a02cd736b570f", + "6740b7686f3a02cd736b5713", + "6740b7686f3a02cd736b5717", + "6740b7686f3a02cd736b571b", + "6740b7686f3a02cd736b571f", + "6740b7686f3a02cd736b5723", + "6740b7686f3a02cd736b5727", + "6740b7686f3a02cd736b572b", + "6740b7686f3a02cd736b572f", + "6740b7686f3a02cd736b5733", + "6740b7686f3a02cd736b5737", + "6740b7686f3a02cd736b573b", + "6740b7686f3a02cd736b573f", + "6740b7686f3a02cd736b5743", + "6740b7686f3a02cd736b5747", + "6740b7686f3a02cd736b574b" + ] + } + ], + "submissionAccess": [], + "owner": null, + "components": [ + { + "title": "Page 1", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": false, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page1", + "type": "panel", + "label": "Page 1", + "components": [ + { + "label": "Text Field", + "applyMaskOn": "change", + "tableView": true, + "validate": { + "required": true + }, + "key": "textField", + "type": "textfield", + "input": true + } + ], + "input": false, + "tableView": false + }, + { + "title": "Page 2", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page2", + "type": "panel", + "label": "Page 2", + "components": [ + { + "label": "Number", + "applyMaskOn": "change", + "mask": false, + "tableView": false, + "delimiter": false, + "requireDecimal": false, + "inputFormat": "plain", + "truncateMultipleSpaces": false, + "key": "number", + "type": "number", + "input": true + } + ], + "input": false, + "tableView": false + }, + { + "title": "Page 3", + "label": "Page 3", + "type": "panel", + "key": "page3", + "components": [ + { + "label": "Text Field", + "applyMaskOn": "change", + "tableView": true, + "validate": { + "required": true + }, + "validateWhenHidden": false, + "key": "textField1", + "type": "textfield", + "input": true + } + ], + "input": false, + "tableView": false + } + ], + "pdfComponents": [], + "settings": {}, + "properties": {}, + "machineName": "authoring-bsajzvvuohccvoq:form123", + "project": "6740b7686f3a02cd736b56e9", + "controller": "", + "revisions": "", + "submissionRevisions": "", + "_vid": 0, + "created": "2024-11-22T16:55:04.926Z", + "modified": "2024-11-22T16:55:04.928Z" +}; \ No newline at end of file diff --git a/test/unit/Wizard.unit.js b/test/unit/Wizard.unit.js index 01e41aa041..411249ad17 100644 --- a/test/unit/Wizard.unit.js +++ b/test/unit/Wizard.unit.js @@ -42,6 +42,7 @@ import formsWithAllowOverride from '../forms/formsWithAllowOverrideComps'; import WizardWithCheckboxes from '../forms/wizardWithCheckboxes'; import WizardWithRequiredFields from '../forms/wizardWithRequiredFields'; import formWithNestedWizardAndRequiredFields from '../forms/formWithNestedWizardAndRequiredFields'; +import simpleWizardWithRequiredFields from '../forms/simpleWizardWithRequiredFields'; import { wait } from '../util'; // eslint-disable-next-line max-statements @@ -630,6 +631,39 @@ describe('Wizard tests', () => { .catch((err) => done(err)); }) + it('Should show form-level errors after failed submission even when the current page has no errors', async () => { + const formElement = document.createElement('div'); + const wizard = new Wizard(formElement); + await wizard.setForm(simpleWizardWithRequiredFields); + // link[2] is the button for page 3 + clickWizardBtn(wizard, 'link[2]'); + await wait(200); + // need to click on page 3 and submit the form to see all the errors + // you can't submit the form without first navigating to page 3 + wizard.submit(); + await wait(400); + const getRequiredFieldErrors = () => + wizard.errors.filter(error => error.message === 'Text Field is required' && + ['Page 1', 'Page 3'].includes(error.component.parent.title)); + + assert.equal(getRequiredFieldErrors().length, 2); + // navigate to page 2 + clickWizardBtn(wizard, 'link[1]'); + await wait(200); + // the form-level errors should still be the same + assert.equal(getRequiredFieldErrors().length, 2); + // navigate to page 1 + clickWizardBtn(wizard, 'link[0]'); + const page1Input = wizard.element.querySelector('input[name="data[textField]"]'); + const inputEvent = new Event('input'); + page1Input.value = '1'; + page1Input.dispatchEvent(inputEvent); + await wait(200); + // maintain form-level error after supplying value for one required field + assert.equal(getRequiredFieldErrors().length, 1); + assert.equal(getRequiredFieldErrors()[0].component.parent.title, 'Page 3'); + }); + it('Should render values in HTML render mode', function(done) { const formElement = document.createElement('div'); const wizard = new Wizard(formElement, { From 103bd98989e36d35de83c0ec9000e46ab353ebde Mon Sep 17 00:00:00 2001 From: Blake Krammes <49688912+blakekrammes@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:27:40 -0600 Subject: [PATCH 5/5] FIO-9350 Wizard: Simplify async flow for flaky test --- test/unit/Wizard.unit.js | 118 ++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 65 deletions(-) diff --git a/test/unit/Wizard.unit.js b/test/unit/Wizard.unit.js index 411249ad17..8b1d8dc8bd 100644 --- a/test/unit/Wizard.unit.js +++ b/test/unit/Wizard.unit.js @@ -317,80 +317,68 @@ describe('Wizard tests', () => { .catch((err) => done(err)); }); - it('Should show conditional page inside nested wizard', function(done) { + it('Should show conditional page inside nested wizard', async () => { const formElement = document.createElement('div'); const wizard = new Wizard(formElement); const nestedWizard = _.cloneDeep(wizardTestForm.form); nestedWizard.components[2].conditional = { show: true, when: 'checkbox', eq: 'true' }; - wizard.setForm(formWithNestedWizard).then(() => { - const nestedFormComp = wizard.getComponent('formNested'); - - nestedFormComp.loadSubForm = ()=> { - nestedFormComp.formObj = nestedWizard; - nestedFormComp.subFormLoading = false; - return new Promise((resolve) => resolve(nestedWizard)); - }; - - nestedFormComp.createSubForm(); - - setTimeout(() => { - const checkPage = (pageNumber) => { - assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); - }; - - checkPage(0); - assert.equal(wizard.pages.length, 4, 'Should have 4 pages'); - assert.equal(wizard.allPages.length, 4, 'Should have 4 pages'); - assert.equal(wizard.refs[`${wizard.wizardKey}-link`].length, 4, 'Should contain refs to breadcrumbs of parent and nested wizard'); - - clickWizardBtn(wizard, 'link[3]'); - - setTimeout(() => { - checkPage(3); - - assert.deepEqual(!!wizard.refs[`${wizard.wizardKey}-submit`], true, 'Should hav submit btn on the last page'); - wizard.getComponent('checkbox').setValue(true); - - setTimeout(() => { - checkPage(3); - - assert.deepEqual(!!wizard.refs[`${wizard.wizardKey}-submit`], true, 'Should have submit btn on the last page'); - wizard.getComponent('checkbox').setValue(true); - - setTimeout(() => { - checkPage(3); - assert.deepEqual(!!wizard.refs[`${wizard.wizardKey}-submit`], false, 'Should not have submit btn '); - assert.equal(wizard.pages.length, 5, 'Should show conditional page'); - assert.equal(wizard.allPages.length, 5, 'Should show conditional page'); - assert.equal(wizard.refs[`${wizard.wizardKey}-link`].length, 5, 'Should contain refs to breadcrumbs of visible conditional page'); - - clickWizardBtn(wizard, 'next'); + await wizard.setForm(formWithNestedWizard); + + const nestedFormComp = wizard.getComponent('formNested'); - setTimeout(() => { - checkPage(4); - clickWizardBtn(wizard, 'previous'); + nestedFormComp.loadSubForm = () => { + nestedFormComp.formObj = nestedWizard; + nestedFormComp.subFormLoading = false; + return new Promise((resolve) => resolve(nestedWizard)); + }; - setTimeout(() => { - checkPage(3); - wizard.getComponent('checkbox').setValue(false); + nestedFormComp.createSubForm(); + await wait(300); - setTimeout(() => { - assert.equal(wizard.pages.length, 4, 'Should hide conditional page'); - assert.equal(wizard.allPages.length, 4, 'Should hide conditional page'); - assert.equal(wizard.refs[`${wizard.wizardKey}-link`].length, 4, 'Should contain refs to breadcrumbs of visible pages'); - assert.deepEqual(!!wizard.refs[`${wizard.wizardKey}-submit`], true, 'Should have submit btn on the last page'); + const checkPage = (pageNumber) => { + assert.equal(wizard.page, pageNumber, `Should open wizard page ${pageNumber + 1}`); + }; + + checkPage(0); + assert.equal(wizard.pages.length, 4, 'Should have 4 pages'); + assert.equal(wizard.allPages.length, 4, 'Should have 4 pages'); + assert.equal(wizard.refs[`${wizard.wizardKey}-link`].length, 4, 'Should contain refs to breadcrumbs of parent and nested wizard'); - done(); - }, 500); - }, 300); - }, 300); - }, 500); - }, 300); - }, 300); - }, 300); - }) - .catch((err) => done(err)); + clickWizardBtn(wizard, 'link[3]'); + await wait(300); + + checkPage(3); + assert.deepEqual(!!wizard.refs[`${wizard.wizardKey}-submit`], true, 'Should hav submit btn on the last page'); + wizard.getComponent('checkbox').setValue(true); + await wait(300); + + checkPage(3); + assert.deepEqual(!!wizard.refs[`${wizard.wizardKey}-submit`], true, 'Should have submit btn on the last page'); + wizard.getComponent('checkbox').setValue(true); + await wait(500); + + checkPage(3); + assert.deepEqual(!!wizard.refs[`${wizard.wizardKey}-submit`], false, 'Should not have submit btn '); + assert.equal(wizard.pages.length, 5, 'Should show conditional page'); + assert.equal(wizard.allPages.length, 5, 'Should show conditional page'); + assert.equal(wizard.refs[`${wizard.wizardKey}-link`].length, 5, 'Should contain refs to breadcrumbs of visible conditional page'); + + clickWizardBtn(wizard, 'next'); + await wait(300); + + checkPage(4); + clickWizardBtn(wizard, 'previous'); + await wait(300); + + checkPage(3); + wizard.getComponent('checkbox').setValue(false); + await wait(500); + + assert.equal(wizard.pages.length, 4, 'Should hide conditional page'); + assert.equal(wizard.allPages.length, 4, 'Should hide conditional page'); + assert.equal(wizard.refs[`${wizard.wizardKey}-link`].length, 4, 'Should contain refs to breadcrumbs of visible pages'); + assert.deepEqual(!!wizard.refs[`${wizard.wizardKey}-submit`], true, 'Should have submit btn on the last page'); }).timeout(6000); it('Should trigger validation of nested wizard before going to the next page', function (done) {