From 7a938894893687b8207e3c79d59d39b5e12d1742 Mon Sep 17 00:00:00 2001 From: Blake Krammes <49688912+blakekrammes@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:39:54 -0600 Subject: [PATCH 1/3] FIO-9241 Wizard: Rename hasExtraPages getter - The getter had more nuanced logic in the past, but now only checks if the wizard form has sub/nested wizards --- src/Wizard.js | 10 +++++----- src/components/_classes/nested/NestedComponent.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Wizard.js b/src/Wizard.js index 35c800f497..43cfd829cc 100644 --- a/src/Wizard.js +++ b/src/Wizard.js @@ -60,14 +60,14 @@ export default class Wizard extends Webform { getPages(args = {}) { const { all = false } = args; - const pages = this.hasExtraPages ? this.components : this.pages; + const pages = this.hasSubWizards ? this.components : this.pages; const filteredPages = pages .filter(all ? _.identity : (p, index) => this._seenPages.includes(index)); return filteredPages; } - get hasExtraPages() { + get hasSubWizards() { return !_.isEmpty(this.subWizards); } @@ -676,7 +676,7 @@ export default class Wizard extends Webform { this.getNextPage(); let parentNum = num; - if (this.hasExtraPages) { + if (this.hasSubWizards) { const pageFromPages = this.pages[num]; const pageFromComponents = this.components[num]; if (!pageFromComponents || pageFromPages?.id !== pageFromComponents.id) { @@ -1009,7 +1009,7 @@ export default class Wizard extends Webform { let currentPanels; let panels; const currentNextPage = this.currentNextPage; - if (this.hasExtraPages) { + if (this.hasSubWizards) { currentPanels = this.pages.map(page => page.component.key); this.establishPages(); panels = this.pages.map(page => page.component.key); @@ -1074,7 +1074,7 @@ export default class Wizard extends Webform { } showErrors(errors, triggerEvent) { - if (this.hasExtraPages) { + if (this.hasSubWizards) { this.subWizards.forEach((subWizard) => { if(Array.isArray(subWizard.errors)) { errors = [...errors, ...subWizard.errors] diff --git a/src/components/_classes/nested/NestedComponent.js b/src/components/_classes/nested/NestedComponent.js index e429f71ff8..ad31fcab7d 100644 --- a/src/components/_classes/nested/NestedComponent.js +++ b/src/components/_classes/nested/NestedComponent.js @@ -765,7 +765,7 @@ export default class NestedComponent extends Field { validationProcessor({ scope, data, row, instance, component }, flags) { const { dirty } = flags; - if (this.root.hasExtraPages && this.page !== this.root.page) { + if (this.root.hasSubWizards && this.page !== this.root.page) { instance = this.childComponentsMap?.hasOwnProperty(component.path) ? this.childComponentsMap[component.path] : this.getComponent(component.path); From efabe1811772aae35520aa8d61445dd7d146bc50 Mon Sep 17 00:00:00 2001 From: Blake Krammes <49688912+blakekrammes@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:48:45 -0600 Subject: [PATCH 2/3] FIO-9241 Wizard: Fix bug in render with dup keys - If you have multiple/sibling nested wizards and conditional logic controlling the visibility of a nested wizard page, it can set the currentPanel to the wrong page on render (i.e. both forms have a 'page1' key. This is fixed by using a unique id to set the currentPanel. --- src/Wizard.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Wizard.js b/src/Wizard.js index 43cfd829cc..b052b0c100 100644 --- a/src/Wizard.js +++ b/src/Wizard.js @@ -216,9 +216,9 @@ export default class Wizard extends Webform { render() { const ctx = this.renderContext; - if (this.component.key) { - ctx.panels.map(panel => { - if (panel.key === this.component.key) { + if (this.component.id) { + ctx.panels.forEach(panel => { + if (panel.id === this.component.id) { this.currentPanel = panel; ctx.wizardPageTooltip = this.getFormattedTooltip(panel.tooltip); } From d415fcc78ea94503d2604f64ebc65d5567f6e7c9 Mon Sep 17 00:00:00 2001 From: Blake Krammes <49688912+blakekrammes@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:58:08 -0600 Subject: [PATCH 3/3] FIO-9241 Wizard: Set page after conditional eval in nested wizard - this.setPage wasn't being called after conditional logic was evaluated on the page of a nested wizard form having a sibling nested wizard form, causing the main wizard page index not to update to the correct page - Clean up onChange logic - this.currentPanels was only being referenced in onChange() and didn't seem like a necessary piece of state in addition to this.pages --- src/Wizard.js | 27 +- .../wizardWithNestedWizardsAdvConditional.js | 465 ++++++++++++++++++ test/unit/Wizard.unit.js | 27 + 3 files changed, 502 insertions(+), 17 deletions(-) create mode 100644 test/forms/wizardWithNestedWizardsAdvConditional.js diff --git a/src/Wizard.js b/src/Wizard.js index b052b0c100..f0f853d8ae 100644 --- a/src/Wizard.js +++ b/src/Wizard.js @@ -37,7 +37,6 @@ export default class Wizard extends Webform { this.originalComponents = []; this.page = 0; this.currentPanel = null; - this.currentPanels = null; this.currentNextPage = 0; this._seenPages = [0]; this.subWizards = []; @@ -1006,24 +1005,18 @@ export default class Wizard extends Webform { } // If the pages change, need to redraw the header. - let currentPanels; - let panels; + const currentPanels = this.pages; + // calling this.establishPages() updates/mutates this.pages to be the current pages + this.establishPages(); + const newPanels = this.pages; const currentNextPage = this.currentNextPage; - if (this.hasSubWizards) { - currentPanels = this.pages.map(page => page.component.key); - this.establishPages(); - panels = this.pages.map(page => page.component.key); - } - else { - currentPanels = this.currentPanels || this.pages.map(page => page.component.key); - panels = this.establishPages().map(panel => panel.key); - this.currentPanels = panels; - if (this.currentPanel?.key && this.currentPanels?.length) { - this.setPage(this.currentPanels.findIndex(panel => panel === this.currentPanel.key)); - } + const panelsUpdated = !_.isEqual(newPanels, currentPanels); + + if (this.currentPanel?.id && this.pages.length && (!this.hasSubWizards || (this.hasSubWizards && panelsUpdated))) { + const newIndex = this.pages.findIndex(page => page.id === this.currentPanel.id); + if (newIndex !== -1) this.setPage(newIndex); } - - if (!_.isEqual(panels, currentPanels) || (flags && flags.fromSubmission)) { + if (panelsUpdated || (flags && flags.fromSubmission)) { this.redrawHeader(); } diff --git a/test/forms/wizardWithNestedWizardsAdvConditional.js b/test/forms/wizardWithNestedWizardsAdvConditional.js new file mode 100644 index 0000000000..c50e11751b --- /dev/null +++ b/test/forms/wizardWithNestedWizardsAdvConditional.js @@ -0,0 +1,465 @@ +export default { + "_id": "67379d374d2df086c78c93d0", + "title": "WIZARD", + "name": "wizard", + "path": "wizard", + "type": "form", + "display": "wizard", + "tags": [], + "access": [ + { + "type": "read_all", + "roles": [ + "67379d374d2df086c78c93ae", + "67379d374d2df086c78c93b2", + "67379d374d2df086c78c93b6" + ] + } + ], + "submissionAccess": [], + "owner": null, + "components": [ + { + "title": "A", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page1", + "type": "panel", + "label": "Page 1", + "components": [ + { + "_id": "67379d374d2df086c78c93bb", + "title": "CHILD A", + "name": "childA", + "path": "childa", + "type": "form", + "display": "wizard", + "tags": [], + "access": [ + { + "type": "read_all", + "roles": [ + "67379d374d2df086c78c93ae", + "67379d374d2df086c78c93b2", + "67379d374d2df086c78c93b6" + ] + } + ], + "submissionAccess": [], + "owner": null, + "components": [ + { + "title": "A1", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page1", + "type": "panel", + "label": "Page 1", + "components": [ + { + "label": "A1", + "applyMaskOn": "change", + "tableView": true, + "validateWhenHidden": false, + "key": "a", + "type": "textfield", + "input": true + } + ], + "input": false, + "tableView": false + }, + { + "title": "A2", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page2", + "customConditional": "var b2comp = instance.root.root.getComponent('b2');\nshow = !(b2comp ? b2comp.getValue() === 'hide' : false);", + "type": "panel", + "label": "Page 2", + "input": false, + "tableView": false, + "components": [ + { + "label": "A2", + "applyMaskOn": "change", + "tableView": true, + "validateWhenHidden": false, + "key": "a2", + "type": "textfield", + "input": true + } + ] + }, + { + "title": "A3", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page3", + "type": "panel", + "label": "Page 3", + "input": false, + "tableView": false, + "components": [ + { + "label": "A3", + "applyMaskOn": "change", + "tableView": true, + "validateWhenHidden": false, + "key": "a3", + "type": "textfield", + "input": true + } + ] + } + ], + "pdfComponents": [], + "settings": {}, + "properties": {}, + "machineName": "authoring-qwwqrlcmffdcymd:childA", + "project": "67379d374d2df086c78c93a4", + "controller": "", + "revisions": "", + "submissionRevisions": "", + "_vid": 0, + "created": "2024-11-15T19:12:55.899Z", + "modified": "2024-11-15T19:12:55.901Z" + } + ], + "input": false, + "tableView": false + }, + { + "title": "B", + "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": [ + { + "_id": "67379d374d2df086c78c93c2", + "title": "CHILD B", + "name": "childB", + "path": "childb", + "type": "form", + "display": "wizard", + "tags": [], + "access": [ + { + "type": "read_all", + "roles": [ + "67379d374d2df086c78c93ae", + "67379d374d2df086c78c93b2", + "67379d374d2df086c78c93b6" + ] + } + ], + "submissionAccess": [], + "owner": null, + "components": [ + { + "title": "B1", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page1", + "type": "panel", + "label": "Page 1", + "components": [ + { + "label": "B1", + "applyMaskOn": "change", + "tableView": true, + "validateWhenHidden": false, + "key": "b", + "type": "textfield", + "input": true + } + ], + "input": false, + "tableView": false + }, + { + "title": "B2", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page2", + "type": "panel", + "label": "Page 2", + "input": false, + "tableView": false, + "components": [ + { + "label": "B2", + "applyMaskOn": "change", + "tableView": true, + "validateWhenHidden": false, + "key": "b2", + "type": "textfield", + "input": true + } + ] + }, + { + "title": "B3", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page3", + "type": "panel", + "label": "Page 3", + "input": false, + "tableView": false, + "components": [ + { + "label": "B3", + "applyMaskOn": "change", + "tableView": true, + "validate": { + "required": true + }, + "validateWhenHidden": false, + "key": "b3", + "type": "textfield", + "input": true + } + ] + } + ], + "pdfComponents": [], + "settings": {}, + "properties": {}, + "machineName": "authoring-qwwqrlcmffdcymd:childB", + "project": "67379d374d2df086c78c93a4", + "controller": "", + "revisions": "", + "submissionRevisions": "", + "_vid": 0, + "created": "2024-11-15T19:12:55.909Z", + "modified": "2024-11-15T19:12:55.911Z" + } + ], + "input": false, + "tableView": false + }, + { + "title": "C", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page3", + "type": "panel", + "label": "Page 3", + "components": [ + { + "_id": "67379d374d2df086c78c93c9", + "title": "CHILD C", + "name": "childC", + "path": "childc", + "type": "form", + "display": "wizard", + "tags": [], + "access": [ + { + "type": "read_all", + "roles": [ + "67379d374d2df086c78c93ae", + "67379d374d2df086c78c93b2", + "67379d374d2df086c78c93b6" + ] + } + ], + "submissionAccess": [], + "owner": null, + "components": [ + { + "title": "C1", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page1", + "type": "panel", + "label": "Page 1", + "components": [ + { + "label": "C1", + "applyMaskOn": "change", + "tableView": true, + "validateWhenHidden": false, + "key": "c1", + "type": "textfield", + "input": true + } + ], + "input": false, + "tableView": false + }, + { + "title": "C2", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page2", + "type": "panel", + "label": "Page 2", + "input": false, + "tableView": false, + "components": [ + { + "label": "C2", + "applyMaskOn": "change", + "tableView": true, + "validateWhenHidden": false, + "key": "c2", + "type": "textfield", + "input": true + } + ] + }, + { + "title": "C3", + "breadcrumbClickable": true, + "buttonSettings": { + "previous": true, + "cancel": true, + "next": true + }, + "navigateOnEnter": false, + "saveOnEnter": false, + "scrollToTop": false, + "collapsible": false, + "key": "page3", + "type": "panel", + "label": "Page 3", + "input": false, + "tableView": false, + "components": [ + { + "label": "C3", + "applyMaskOn": "change", + "tableView": true, + "validateWhenHidden": false, + "key": "c3", + "type": "textfield", + "input": true + } + ] + } + ], + "pdfComponents": [], + "settings": {}, + "properties": {}, + "machineName": "authoring-qwwqrlcmffdcymd:childC", + "project": "67379d374d2df086c78c93a4", + "controller": "", + "revisions": "", + "submissionRevisions": "", + "_vid": 0, + "created": "2024-11-15T19:12:55.919Z", + "modified": "2024-11-15T19:12:55.921Z" + } + ], + "input": false, + "tableView": false + } + ], + "pdfComponents": [], + "settings": { + + }, + "properties": { + + }, + "machineName": "authoring-qwwqrlcmffdcymd:wizard", + "project": "67379d374d2df086c78c93a4", + "controller": "", + "revisions": "", + "submissionRevisions": "", + "_vid": 0, + "created": "2024-11-15T19:12:55.928Z", + "modified": "2024-11-15T19:12:55.930Z" +} \ No newline at end of file diff --git a/test/unit/Wizard.unit.js b/test/unit/Wizard.unit.js index 62eab3c671..a481647a26 100644 --- a/test/unit/Wizard.unit.js +++ b/test/unit/Wizard.unit.js @@ -32,6 +32,7 @@ import wizardWithPanel from '../forms/wizardWithPanel'; import wizardWithWizard from '../forms/wizardWithWizard'; import simpleTwoPagesWizard from '../forms/simpleTwoPagesWizard'; import wizardWithNestedWizardInEditGrid from '../forms/wizardWithNestedWizardInEditGrid'; +import wizardWithNestedWizardsAdvConditional from '../forms/wizardWithNestedWizardsAdvConditional'; import wizardNavigateOrSaveOnEnter from '../forms/wizardNavigateOrSaveOnEnter'; import nestedConditionalWizard from '../forms/nestedConditionalWizard'; import wizardWithPrefixComps from '../forms/wizardWithPrefixComps'; @@ -42,6 +43,7 @@ import formsWithAllowOverride from '../forms/formsWithAllowOverrideComps'; import WizardWithCheckboxes from '../forms/wizardWithCheckboxes'; import WizardWithRequiredFields from '../forms/wizardWithRequiredFields'; import formWithNestedWizardAndRequiredFields from '../forms/formWithNestedWizardAndRequiredFields'; +import { wait } from '../util'; // eslint-disable-next-line max-statements describe('Wizard tests', () => { @@ -1880,6 +1882,31 @@ it('Should show tooltip for wizard pages', function(done) { }) .catch(done); }); + + it('Should set correct page index after hiding a conditionally hidden page in sibling nested wizard.', async () => { + const formElement = document.createElement('div'); + wizardForm = new Wizard(formElement); + await wizardForm.setForm(wizardWithNestedWizardsAdvConditional); + // navigate forward 4 pages to B2 + // A1 -> A2 -> A3 -> B1 -> B2 + for (let i = 0; i < 4; i++) { + wizardForm.nextPage(); + await wait(200); + } + const getPageTitles = () => wizardForm.pages.map(p => p.component.title); + assert.equal(wizardForm.page, 4); + assert.equal(wizardForm.currentPanel.title, 'B2'); + assert(getPageTitles().includes('A2')); + const b2Input = wizardForm.element.querySelector('input[name="data[b2]"]'); + const inputEvent = new Event('input'); + b2Input.value = 'hide'; + b2Input.dispatchEvent(inputEvent); + await wait(400); + assert(!getPageTitles().includes('A2'), 'A2 is hidden when B2 has a value of "hide"'); + assert.equal(wizardForm.page, 3, 'A2 is removed from the pages array and the page number/index must be decremented to maintain B2 as the current page'); + assert.equal(wizardForm.currentPanel.title, 'B2'); + }); + // BUG - uncomment once fixed (ticket FIO-6043) // it('Should render all pages as a part of wizard pagination', (done) => { // const formElement = document.createElement('div');