From 5ccc183cae637f08b39a15b028d350ef8aeed6ba Mon Sep 17 00:00:00 2001 From: Blake Krammes Date: Wed, 20 Nov 2024 12:39:54 -0600 Subject: [PATCH] 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 * 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 * 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 | 39 +- .../_classes/nested/NestedComponent.js | 2 +- .../wizardWithNestedWizardsAdvConditional.js | 465 ++++++++++++++++++ test/unit/Wizard.unit.js | 26 + 4 files changed, 508 insertions(+), 24 deletions(-) create mode 100644 test/forms/wizardWithNestedWizardsAdvConditional.js diff --git a/src/Wizard.js b/src/Wizard.js index ce541143ae..5038406268 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 = []; @@ -60,14 +59,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); } @@ -216,9 +215,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); } @@ -676,7 +675,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) { @@ -1018,24 +1017,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.hasExtraPages) { - currentPanels = this.pages.map(page => page.component.key); - this.establishPages(); - panels = this.pages.map(page => page.component.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); } - 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)); - } - } - - if (!_.isEqual(panels, currentPanels) || (flags && flags.fromSubmission)) { + if (panelsUpdated || (flags && flags.fromSubmission)) { this.redrawHeader(); } diff --git a/src/components/_classes/nested/NestedComponent.js b/src/components/_classes/nested/NestedComponent.js index 39d7cc3058..fc1b1c2467 100644 --- a/src/components/_classes/nested/NestedComponent.js +++ b/src/components/_classes/nested/NestedComponent.js @@ -737,7 +737,7 @@ export default class NestedComponent extends Field { validationProcessor({ scope, data, row, instance, paths }, flags) { const { dirty } = flags; - if (this.root.hasExtraPages && this.page !== this.root.page) { + if (this.root.hasSubWizards && this.page !== this.root.page) { instance = this.componentsMap?.hasOwnProperty(paths.dataPath) ? this.componentsMap[paths.dataPath] : this.getComponent(paths.dataPath); 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 62516edf5a..e923701c61 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'; @@ -2105,6 +2106,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');