From 1a910a086080478fc03375554a65d69fb4865449 Mon Sep 17 00:00:00 2001 From: eyelidlessness Date: Mon, 30 Aug 2021 14:46:51 -0700 Subject: [PATCH 1/2] Fix built JS app.js path in index.html --- src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index 1a7bc4e06..264d548c1 100644 --- a/src/index.html +++ b/src/index.html @@ -86,7 +86,7 @@ - + From 95b1db61a7990b9c67148d8a5bfe5d50529c79b4 Mon Sep 17 00:00:00 2001 From: eyelidlessness Date: Mon, 30 Aug 2021 15:49:06 -0700 Subject: [PATCH 2/2] [Fix] Ensure nested repeats are shown when relevance is true Fixes #804. --- src/js/relevant.js | 11 +- .../forms/core-804-nested-repeat-relevant.xml | 155 +++++++++++++++++ test/spec/repeat.spec.js | 164 +++++++++++++++++- 3 files changed, 326 insertions(+), 4 deletions(-) create mode 100644 test/forms/core-804-nested-repeat-relevant.xml diff --git a/src/js/relevant.js b/src/js/relevant.js index 971d9ceb2..50e51ef34 100644 --- a/src/js/relevant.js +++ b/src/js/relevant.js @@ -87,8 +87,15 @@ export default { * but currently has 0 repeats, the context will not be available. This same logic is applied in output.js. */ let context = p.path; - if ( getChild( node, `.or-repeat-info[data-name="${p.path}"]` ) && !getChild( node, `.or-repeat[name="${p.path}"]` ) ) { - context = null; + + const repeatInfo = getChild( node, `.or-repeat-info[data-name="${p.path}"]` ); + + if ( repeatInfo != null && !getChild( node, `.or-repeat[name="${p.path}"]` ) ) { + const count = this.form.repeats.updateViewInstancesFromModel( repeatInfo ); + + if ( count === 0 ) { + context = null; + } } /* diff --git a/test/forms/core-804-nested-repeat-relevant.xml b/test/forms/core-804-nested-repeat-relevant.xml new file mode 100644 index 000000000..fafd0cca9 --- /dev/null +++ b/test/forms/core-804-nested-repeat-relevant.xml @@ -0,0 +1,155 @@ + + + + enketo-repeat-bug + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + yes + + + no + + + + + + + + yes + + + no + + + + + + + + drawing_set + + + maintenance_schedule + + + other + + + + If no file available, go to next question to take a photo + + + + + + + + yes + + + no + + + + + + If available + + + conductivity + + + flow + + + other + + + + + + + + + + diff --git a/test/spec/repeat.spec.js b/test/spec/repeat.spec.js index 76275bef5..9b437d193 100644 --- a/test/spec/repeat.spec.js +++ b/test/spec/repeat.spec.js @@ -4,10 +4,31 @@ import forms from '../mock/forms'; import event from '../../src/js/event'; import dialog from '../../src/js/fake-dialog'; +/** + * @typedef {import('sinon').SinonSandbox} Sandbox + */ + +/** + * @typedef {import('../../src/js/form').Form} Form + */ + describe( 'repeat functionality', () => { + /** @type {Sandbox} */ + let sandbox; + + beforeEach( () => { + sandbox = sinon.createSandbox(); + + if ( !( 'off' in $.fx ) ) { + $.fx.off = undefined; + } - //turn jQuery animations off - $.fx.off = true; + sandbox.stub( $.fx, 'off' ).get( () => true ); + } ); + + afterEach( () => { + sandbox.restore(); + } ); describe( 'cloning', () => { beforeEach( () => { @@ -508,4 +529,143 @@ describe( 'repeat functionality', () => { } ); } ); + describe( 'relevance, nesting, groups', () => { + const hiddenClassName = 'disabled'; + + /** @type {Form} */ + let form; + + beforeEach( () => { + form = loadForm( 'core-804-nested-repeat-relevant.xml' ); + form.init(); + } ); + + it( 'includes a relevant nested repeat\'s first group when hidden', () => { + const parent = form.view.html.querySelector( 'section.or-branch.or-group[name="/data/field_asset_rpt"]' ); + const groupXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_rpt'; + const groupSection = parent.querySelector( `section[name="${groupXpath}"].or-group.or-branch` ); + const repeatXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_rpt'; + const repeat = groupSection.querySelector( `section.or-repeat[name="${repeatXpath}"]` ); + + expect( repeat ).not.to.be.null; + } ); + + it( 'shows a nested repeat when its group is relevant', () => { + const parent = form.view.html.querySelector( 'section.or-branch.or-group[name="/data/field_asset_rpt"]' ); + const checkboxXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_exists'; + const checkboxes = Array.from( parent.querySelectorAll( `input[data-name="${checkboxXpath}"]` ) ); + const yes = checkboxes.find( checkbox => checkbox.value === 'yes' ); + const groupXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_rpt'; + const groupSection = parent.querySelector( `section.or-group.or-branch[name="${groupXpath}"]` ); + + expect( groupSection.classList.contains( hiddenClassName ) ).to.equal( true ); + + yes.click(); + yes.dispatchEvent( event.Change() ); + + expect( groupSection.classList.contains( hiddenClassName ) ).to.equal( false ); + } ); + + it( 'includes a relevant nested repeat\'s first group when visible', () => { + const parent = form.view.html.querySelector( 'section.or-branch.or-group[name="/data/field_asset_rpt"]' ); + const groupXpath = '/data/field_asset_rpt/field_asset_rpt_grp/instrumentation_rpt'; + const groupSection = parent.querySelector( `section[name="${groupXpath}"].or-group.or-branch` ); + const repeatXpath = '/data/field_asset_rpt/field_asset_rpt_grp/instrumentation_rpt'; + const repeat = groupSection.querySelector( `section.or-repeat[name="${repeatXpath}"]` ); + + expect( repeat ).not.to.be.null; + } ); + + it( 'hides a nested repeat when its group is not relevant', () => { + const parent = form.view.html.querySelector( 'section.or-branch.or-group[name="/data/field_asset_rpt"]' ); + const checkboxXpath = '/data/field_asset_rpt/field_asset_rpt_grp/instrumentation_exists'; + const checkboxes = Array.from( parent.querySelectorAll( `input[data-name="${checkboxXpath}"]` ) ); + const yes = checkboxes.find( checkbox => checkbox.value === 'yes' ); + const no = checkboxes.find( checkbox => checkbox.value === 'no' ); + const groupXpath = '/data/field_asset_rpt/field_asset_rpt_grp/instrumentation_rpt'; + const groupSection = parent.querySelector( `section.or-group.or-branch[name="${groupXpath}"]` ); + + yes.click(); + yes.dispatchEvent( event.Change() ); + + expect( groupSection.classList.contains( hiddenClassName ) ).to.equal( false ); + + no.click(); + no.dispatchEvent( event.Change() ); + + expect( groupSection.classList.contains( hiddenClassName ) ).to.equal( true ); + } ); + + it( 'includes a relevant nested hidden repeat\'s first group when adding a new parent repeat', () => { + const parentRepeatXpath = '/data/field_asset_rpt'; + const parent = form.view.html.querySelector( `section.or-branch.or-group[name="${parentRepeatXpath}"]` ); + const addButton = parent.querySelector( `.or-repeat-info[data-name="${parentRepeatXpath}"] > button` ); + + addButton.click(); + + const newParent = form.view.html.querySelector( `section.or-repeat.clone[name="${parentRepeatXpath}"]` ); + const repeatXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_rpt'; + const newChild = newParent.querySelector( `section.or-repeat[name="${repeatXpath}"]` ); + + expect( newChild ).not.to.be.null; + } ); + + it( 'shows a nested repeat when its group is relevant in a new repeat', () => { + const parentRepeatXpath = '/data/field_asset_rpt'; + const parent = form.view.html.querySelector( `section.or-branch.or-group[name="${parentRepeatXpath}"]` ); + const addButton = parent.querySelector( `.or-repeat-info[data-name="${parentRepeatXpath}"] > button` ); + + addButton.click(); + + const newParent = form.view.html.querySelector( 'section.or-repeat.clone[name="/data/field_asset_rpt"]' ); + const checkboxXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_exists'; + const checkboxes = Array.from( newParent.querySelectorAll( `input[data-name="${checkboxXpath}"]` ) ); + const yes = checkboxes.find( checkbox => checkbox.value === 'yes' ); + const groupXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_rpt'; + const groupSection = newParent.querySelector( `section.or-group.or-branch[name="${groupXpath}"]` ); + + expect( groupSection.classList.contains( hiddenClassName ) ).to.equal( true ); + + yes.click(); + yes.dispatchEvent( event.Change() ); + + expect( groupSection.classList.contains( hiddenClassName ) ).to.equal( false ); + } ); + + it( 'includes a relevant nested visible repeat\'s first group when adding a new parent repeat', () => { + const parentRepeatXpath = '/data/field_asset_rpt'; + const parent = form.view.html.querySelector( `section.or-branch.or-group[name="${parentRepeatXpath}"]` ); + const addButton = parent.querySelector( `.or-repeat-info[data-name="${parentRepeatXpath}"] > button` ); + + addButton.click(); + + const newParent = form.view.html.querySelector( `section.or-repeat.clone[name="${parentRepeatXpath}"]` ); + const repeatXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_rpt'; + const newChild = newParent.querySelector( `section.or-repeat[name="${repeatXpath}"]` ); + + expect( newChild ).not.to.be.null; + } ); + + it( 'hides a nested repeat when its group is not relevant in a new repeat', () => { + const parentRepeatXpath = '/data/field_asset_rpt'; + const parent = form.view.html.querySelector( `section.or-branch.or-group[name="${parentRepeatXpath}"]` ); + const addButton = parent.querySelector( `.or-repeat-info[data-name="${parentRepeatXpath}"] > button` ); + + addButton.click(); + + const newParent = form.view.html.querySelector( 'section.or-repeat.clone[name="/data/field_asset_rpt"]' ); + const checkboxXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_exists'; + const checkboxes = Array.from( newParent.querySelectorAll( `input[data-name="${checkboxXpath}"]` ) ); + const no = checkboxes.find( checkbox => checkbox.value === 'no' ); + const groupXpath = '/data/field_asset_rpt/field_asset_rpt_grp/documentation_rpt'; + const groupSection = newParent.querySelector( `section.or-group.or-branch[name="${groupXpath}"]` ); + + expect( groupSection.classList.contains( hiddenClassName ) ).to.equal( true ); + + no.click(); + no.dispatchEvent( event.Change() ); + + expect( groupSection.classList.contains( hiddenClassName ) ).to.equal( true ); + } ); + } ); } );