From 8423336c790ddef640563fe109657bf57d1f6af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 14 May 2025 12:26:00 +0300 Subject: [PATCH 01/19] ci: bump node versions --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 551c65c..27f8274 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: ['18.18.2', '20.5.1'] + node-version: ['18.18.2', '20.5.1'] steps: - uses: actions/checkout@v3 - uses: volta-cli/action@v4 From 7e13433c13635991741ced981e96bf7d62e272c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 14 May 2025 13:00:20 +0300 Subject: [PATCH 02/19] wip done() --- test/spec/xform.spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/spec/xform.spec.js b/test/spec/xform.spec.js index 297a257..f04f106 100644 --- a/test/spec/xform.spec.js +++ b/test/spec/xform.spec.js @@ -12,9 +12,10 @@ describe( 'XForm', () => { describe( 'that is valid', () => { const xf = loadXForm( 'model-only.xml' ); - it( 'returns duration', async() => { + it( 'returns duration', async(done) => { const result = await validator.validate( xf ); - expect( result.duration ).to.be.above( 0 ); + expect(result.duration).to.be.above(0); + done() } ); it( 'returns no errors and no warnings', async() => { From 4ab5ae76e990fb8299e95e475dfc63ca6d6056c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 11 Jun 2025 19:20:53 +0300 Subject: [PATCH 03/19] wip: increase timeout --- package.json | 2 +- test/spec/xform.spec.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c36cfd7..fbc44f8 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/validator.js", "bin": "./validate", "scripts": { - "test": "mocha test/spec/*.spec.js --exit --timeout 9000 && npm run style-check", + "test": "mocha test/spec/*.spec.js --exit --timeout 19000 && npm run style-check", "build-docs": "rimraf docs && ./node_modules/.bin/jsdoc -c jsdoc.config.js", "prepare": "rollup --config && rollup --config rollup.utils.config.mjs", "style-fix": "eslint *.js src/**/*.js test/**/*.js --fix", diff --git a/test/spec/xform.spec.js b/test/spec/xform.spec.js index f04f106..297a257 100644 --- a/test/spec/xform.spec.js +++ b/test/spec/xform.spec.js @@ -12,10 +12,9 @@ describe( 'XForm', () => { describe( 'that is valid', () => { const xf = loadXForm( 'model-only.xml' ); - it( 'returns duration', async(done) => { + it( 'returns duration', async() => { const result = await validator.validate( xf ); - expect(result.duration).to.be.above(0); - done() + expect( result.duration ).to.be.above( 0 ); } ); it( 'returns no errors and no warnings', async() => { From d88a728b95e2cf317f4b20220d9fb531fb88254e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 11 Jun 2025 19:29:32 +0300 Subject: [PATCH 04/19] wip: bail as well --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fbc44f8..67daf53 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/validator.js", "bin": "./validate", "scripts": { - "test": "mocha test/spec/*.spec.js --exit --timeout 19000 && npm run style-check", + "test": "mocha test/spec/*.spec.js --exit --timeout 19000 --bail && npm run style-check", "build-docs": "rimraf docs && ./node_modules/.bin/jsdoc -c jsdoc.config.js", "prepare": "rollup --config && rollup --config rollup.utils.config.mjs", "style-fix": "eslint *.js src/**/*.js test/**/*.js --fix", From 0a4961e918d397b80b9b80262ea4d9fe9b240a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 11 Jun 2025 19:48:25 +0300 Subject: [PATCH 05/19] wip: try before apporach --- test/spec/xform.spec.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/spec/xform.spec.js b/test/spec/xform.spec.js index 297a257..b84fcf9 100644 --- a/test/spec/xform.spec.js +++ b/test/spec/xform.spec.js @@ -1,6 +1,6 @@ const XForm = require( '../../src/xform' ).XForm; const validator = require( '../../src/validator' ); -const expect = require( 'chai' ).expect; +const {expect} = require( 'chai' ); const fs = require( 'fs' ); const path = require( 'path' ); @@ -8,20 +8,20 @@ const loadXForm = filename => fs.readFileSync( path.join( process.cwd(), 'test/x const arrContains = ( arr, reg ) => arr.some( item => item.search( reg ) !== -1 ); +const beforeAllReturn = (fn, timeout) => { + const box = { current: null } + before(async () => box.current = await fn(), timeout) + return box +} +const beforeValidateLoadXForm = (path, timeout) => beforeAllReturn(() => validator.validate(loadXForm(path)), timeout) + describe( 'XForm', () => { describe( 'that is valid', () => { - const xf = loadXForm( 'model-only.xml' ); - it( 'returns duration', async() => { - const result = await validator.validate( xf ); - expect( result.duration ).to.be.above( 0 ); - } ); - - it( 'returns no errors and no warnings', async() => { - const result = await validator.validate( xf ); - expect( result.errors.length ).to.equal( 0 ); - expect( result.warnings.length ).to.equal( 0 ); - } ); + const result = beforeValidateLoadXForm('model-only.xml', 1000) + it( 'returns no errors and no warnings', async() => expect( result.current.errors.length ).to.equal( 0 )) + it( 'returns no warnings', async() => expect( result.current.warnings.length ).to.equal( 0 )) + it( 'returns duration', async() => expect( result.current.duration ).to.be.above( 0 )) } ); describe( 'that is invalid', () => { From 209e0b39fc5e3816d7611a8c1c519d59d8add1a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Thu, 12 Jun 2025 10:52:03 +0300 Subject: [PATCH 06/19] wip: increase timeout --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 67daf53..8ef62d6 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/validator.js", "bin": "./validate", "scripts": { - "test": "mocha test/spec/*.spec.js --exit --timeout 19000 --bail && npm run style-check", + "test": "mocha test/spec/*.spec.js --exit --timeout 59000 --bail && npm run style-check", "build-docs": "rimraf docs && ./node_modules/.bin/jsdoc -c jsdoc.config.js", "prepare": "rollup --config && rollup --config rollup.utils.config.mjs", "style-fix": "eslint *.js src/**/*.js test/**/*.js --fix", @@ -69,5 +69,6 @@ "volta": { "node": "20.5.1", "yarn": "1.22.19" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } \ No newline at end of file From 0c556345a81397823cb098e8750dba76f7be97ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 18 Jun 2025 15:11:52 +0300 Subject: [PATCH 07/19] wip: spray console.logs --- src/validator.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/validator.js b/src/validator.js index 66e716d..7a523b7 100644 --- a/src/validator.js +++ b/src/validator.js @@ -26,15 +26,19 @@ const { version } = require( '../package' ); * @property {boolean} openclinica - Run validator in OpenClinica mode. */ +let incCounter = 0 +const inc = () => incCounter++ + /** * The validate function. Relies heavily on the {@link XForm} class. * * @static * @param {string} xformStr - XForm content. * @param {ValidationOptions} [options] - Validation options. - * @return {ValidateResult} validation results. + * @return {Promise} validation results. */ const validate = async( xformStr, options = {} ) => { + console.log(`validate a`) const start = Date.now(); let warnings = []; let errors = []; @@ -46,12 +50,14 @@ const validate = async( xformStr, options = {} ) => { } catch ( e ) { errors.push( e ); } + console.log(`validate b`) if ( !xform ){ const duration = Date.now() - start; return Promise.resolve( { warnings, errors, version, duration } ); } + console.log(`validate b`) result = xform.checkStructure(); warnings = warnings.concat( result.warnings ); @@ -70,6 +76,7 @@ const validate = async( xformStr, options = {} ) => { warnings = warnings.concat( result.warnings ); errors = errors.concat( result.errors ); } + console.log(`validate d`) try{ await xform.parseModel(); @@ -77,10 +84,12 @@ const validate = async( xformStr, options = {} ) => { let ers = Array.isArray( e ) ? e : [ e ]; errors = errors.concat( ers ); } + console.log(`validate e`) // Check logic for( const el of xform.binds.concat( xform.setvalues ) ){ + console.log(`validate inner f ${String(el).slice(0, 99)}`) const type = el.nodeName.toLowerCase(); const props = type === 'bind' ? { path: 'nodeset', logic: [ 'calculate', 'constraint', 'relevant', 'required', 'readonly' ] } : { path: 'ref', logic: [ 'value' ] }; const path = el.getAttribute( props.path ); @@ -91,25 +100,31 @@ const validate = async( xformStr, options = {} ) => { continue; } + console.log(`validate inner g`) const nodeName = xform._nodeName( path ); // Note: using enketoEvaluate here, would be much slower const nodeExists = await xform.nodeExists( path ); + console.log(`validate inner h`) if ( !nodeExists ) { errors.push( `Found ${type} for "${nodeName}" that does not exist in the model.` ); continue; } + console.log(`validate inner h`) for ( const logicName of props.logic ){ + console.log(`validate inner logicName=${logicName}`) const logicExpr = el.getAttribute( logicName ); const calculation = logicName === 'calculate'; if ( logicExpr ) { let friendlyLogicName = logicName[ 0 ].toUpperCase() + logicName.substring( 1 ); if ( calculation ){ + console.log(`validate inner logic i`) friendlyLogicName = 'Calculation'; } else if ( type === 'setvalue' ){ + console.log(`validate inner logic j`) const event = el.getAttribute( 'event' ); if ( !event ){ errors.push( 'Found ${type} without event attribute.' ); @@ -117,6 +132,7 @@ const validate = async( xformStr, options = {} ) => { } friendlyLogicName = event.split( ' ' ).includes( 'xforms-value-changed' ) ? 'Triggered calculation' : 'Dynamic default'; } else { + console.log(`validate inner logic k`) // e.g. the results for accidentally writing "ues" instead of "yes", putting an appearance in a logic column, etc // and accidentally writing 'true' or 'false' or 'yes' or 'no' in the constraint or relevant column in XLSForm if ( likelyNonSyntaxError( logicExpr ) @@ -126,9 +142,11 @@ const validate = async( xformStr, options = {} ) => { } try { + console.log(`validate inner logic l`) await xform.enketoEvaluate( logicExpr, ( calculation ? 'string' : 'boolean' ), path ); } catch( e ){ + console.log(`validate inner logic m`) errors.push( `${friendlyLogicName} formula for "${nodeName}": ${e}` ); } // TODO: check for cyclic dependencies within single expression and between calculations, e.g. triangular calculation dependencies @@ -137,7 +155,9 @@ const validate = async( xformStr, options = {} ) => { } const duration = Date.now() - start; + console.log(`validate y`) await xform.exit(); + console.log(`validate z`) return { warnings, errors, version, duration }; }; From 8e1bc7a9754f9948bc6a4e1c76dde28e0ed34019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 18 Jun 2025 15:29:16 +0300 Subject: [PATCH 08/19] wip: check if that's overload --- test/spec/xform.spec.js | 469 +--------------------------------------- 1 file changed, 10 insertions(+), 459 deletions(-) diff --git a/test/spec/xform.spec.js b/test/spec/xform.spec.js index b84fcf9..12c9be3 100644 --- a/test/spec/xform.spec.js +++ b/test/spec/xform.spec.js @@ -18,472 +18,23 @@ const beforeValidateLoadXForm = (path, timeout) => beforeAllReturn(() => validat describe( 'XForm', () => { describe( 'that is valid', () => { - const result = beforeValidateLoadXForm('model-only.xml', 1000) - it( 'returns no errors and no warnings', async() => expect( result.current.errors.length ).to.equal( 0 )) - it( 'returns no warnings', async() => expect( result.current.warnings.length ).to.equal( 0 )) - it( 'returns duration', async() => expect( result.current.duration ).to.be.above( 0 )) + const validation = beforeValidateLoadXForm('model-only.xml') + it( 'returns no errors and no warnings', () => expect( validation.current.errors.length ).to.equal( 0 )) + it( 'returns no warnings', () => expect( validation.current.warnings.length ).to.equal( 0 )) + it( 'returns duration', () => expect( validation.current.duration ).to.be.above( 0 )) } ); describe( 'that is invalid', () => { - const xf = loadXForm( 'missing-closing-tag.xml' ); - it( 'returns duration', async() => { - const result = await validator.validate( xf ); - expect( result.duration ).to.be.above( 0 ); - } ); + const validation = beforeValidateLoadXForm('missing-closing-tag.xml') + it( 'returns duration', () => expect( validation.current.duration ).to.be.above( 0 )); } ); describe( 'with bind that has no matching primary instance node (b)', () => { - const xf = loadXForm( 'bind-not-binding.xml' ); + const validation = beforeValidateLoadXForm('bind-not-binding.xml') - it( 'should return an error', async() => { - const result = await validator.validate( xf ); - expect( result.warnings.length ).to.equal( 0 ); - expect( result.errors.length ).to.equal( 1 ); - expect( result.errors[0] ).to.include( 'not exist' ); - } ); + it( 'should return no warnings', () => expect( validation.current.warnings.length ).to.equal( 0 ) ); + it( 'should return one error', () => expect( validation.current.errors.length ).to.equal( 1 ) ); + it( 'should return an error "not exist"', () => expect( validation.current.errors[0] ).to.include( 'not exist' ) ); } ); - describe( 'with bind that has no matching primary instance node (instanceID)', () => { - const xf = loadXForm( 'missing-instanceID.xml' ); - it( 'should return a error', async() => { - const result = await validator.validate( xf ); - expect( result.errors.length ).to.equal( 1 ); - expect( result.errors[0] ).to.include( 'instanceID' ); - } ); - } ); - - describe( 'with bind that has no nodeset', () => { - const xf = loadXForm( 'bind-without-nodeset.xml' ); - it( 'should return an error', async() => { - const result = await validator.validate( xf ); - expect( result.errors.length ).to.equal( 1 ); - expect( result.errors[0] ).to.include( 'without a nodeset attribute' ); - } ); - } ); - - describe( 'with external instance', () => { - const xf = loadXForm( 'external-instance.xml' ); - it( 'should not return an error because the instance is empty', async() => { - const result = await validator.validate( xf ); - expect( result.errors.length ).to.equal( 0 ); - } ); - } ); - - describe( 'with basic XForm structural errors', () => { - const validation1 = validator.validate( loadXForm( 'structure-1.xml' ) ); - const validation2 = validator.validate( loadXForm( 'structure-2.xml' ) ); - const validation3 = validator.validate( loadXForm( 'structure-3.xml' ) ); - const validation4 = validator.validate( loadXForm( 'structure-4.xml' ) ); - - it( 'should return a root nodename error', async() => { - const result1 = await validation1; - expect( arrContains( result1.errors, /root.*html/i ) ).to.equal( true ); - } ); - it( 'should return a root namespace error', async() => { - const result1 = await validation1; - expect( arrContains( result1.errors, /root.*namespace/i ) ).to.equal( true ); - } ); - - it( 'should return a head not found error', async() => { - const result1 = await validation1; - expect( arrContains( result1.errors, /head/i ) ).to.equal( true ); - } ); - it( 'should return a head namespace error', async() => { - const result2 = await validation2; - expect( arrContains( result2.errors, /head.*namespace/i ) ).to.equal( true ); - } ); - it( 'should return a body not found error', async() => { - const result2 = await validation2; - expect( arrContains( result2.errors, /body/i ) ).to.equal( true ); - } ); - it( 'should return a body namespace error', async() => { - const result1 = await validation1; - expect( arrContains( result1.errors, /body.*namespace/i ) ).to.equal( true ); - } ); - it( 'should return a model not found error', async() => { - const result2 = await validation2; - expect( arrContains( result2.errors, /model/i ) ).to.equal( true ); - } ); - it( 'should return a model namespace error', async() => { - const result3 = await validation3; - expect( arrContains( result3.errors, /model.*namespace/i ) ).to.equal( true ); - } ); - it( 'should return a primary instance not found error', async() => { - const result3 = await validation3; - expect( arrContains( result3.errors, /primary instance.*found/i ) ).to.equal( true ); - } ); - it( 'should return a primary instance has too many children error', async() => { - const result4 = await validation4; - expect( arrContains( result4.errors, /primary instance.*more than 1 child/i ) ).to.equal( true ); - } ); - it( 'should return a missing id attribute error', async() => { - const result4 = await validation4; - expect( arrContains( result4.errors, /data root.*no id attribute/i ) ).to.equal( true ); - } ); - } ); - - describe( 'with errors in relevant, constraint, calculate and required expressions', () => { - const validation = validator.validate( loadXForm( 'xpath-fails.xml' ) ); - - it( 'should be detected', async() => { - const result = await validation; - expect( result.errors.length ).to.equal( 7 ); - expect( arrContains( result.errors, /Calculation formula for "calc1"/i ) ).to.equal( true ); - expect( arrContains( result.errors, /Relevant formula for "calc1"/i ) ).to.equal( true ); - expect( arrContains( result.errors, /Calculation formula for "calc11"/i ) ).to.equal( true ); - expect( arrContains( result.errors, /Relevant formula for "calc11"/i ) ).to.equal( true ); - expect( arrContains( result.errors, /Constraint formula for "cond1"/i ) ).to.equal( true ); - expect( arrContains( result.errors, /Required formula for "cond1"/i ) ).to.equal( true ); - expect( arrContains( result.errors, /Calculation formula for "instanceID"/i ) ).to.equal( true ); - } ); - - } ); - - describe( 'with errors in setvalue expressions and attributes', () => { - const validation = validator.validate( loadXForm( 'setvalue-fails.xml' ) ); - - it( 'should be detected', async() => { - const result = await validation; - expect( result.errors.length ).to.equal( 5 ); - expect( arrContains( result.errors, /setvalue without a ref attribute/i ) ).to.equal( true ); - expect( arrContains( result.errors, /setvalue for "age_chang" that does not exist in the model/i ) ).to.equal( true ); - expect( arrContains( result.errors, /default formula for "b"/i ) ).to.equal( true ); - expect( arrContains( result.errors, /calculation formula for "my_age_changed"/i ) ).to.equal( true ); - expect( arrContains( result.errors, /default formula for "age"/i ) ).to.equal( true ); - } ); - - } ); - - describe( 'with calculations on a form control that are not set to readonly', () => { - const validation = validator.validate( loadXForm( 'calculation-not-readonly.xml' ) ); - - it( 'returns errors', async() => { - const result = await validation; - expect( arrContains( result.errors, /"a" has a calculation that is not set to readonly/i ) ).to.equal( true ); - } ); - } ); - - describe( 'validated with custom OpenClinica rules', () => { - - describe( 'forms with special clinicaldata extensions', () => { - const validation = validator.validate( loadXForm( 'openclinica-clinicaldata.xml' ), { - openclinica: true - } ); - - it( 'returns errors', async() => { - const result = await validation; - expect( result.errors.length ).to.equal( 6 ); - } ); - - it( 'returns errors for calculations without form control that refer to external ' + - 'clinicaldata instance but do not have the oc:external="clinicaldata" bind', async() => { - const result = await validation; - expect( arrContains( result.errors, /"invalid1" .* to external clinicaldata without the required "external" attribute/i ) ).to.equal( true ); - expect( arrContains( result.errors, /"invalid2" .* to external clinicaldata without the required "external" attribute/i ) ).to.equal( true ); - expect( arrContains( result.errors, /"invalid3" .* to external clinicaldata without the required "external" attribute/i ) ).to.equal( true ); - } ); - - it( 'returns errors for binds with oc:external="clinicaldata" that do not ' + - 'do not have a calculation that refers to instance(\'clinicaldata\')', async() => { - const result = await validation; - expect( arrContains( result.errors, /"invalid4" .* not .* calculation referring to instance\("clinicaldata"\)/i ) ).to.equal( true ); - expect( arrContains( result.errors, /"invalid5" .* not .* calculation referring to instance\("clinicaldata"\)/i ) ).to.equal( true ); - expect( arrContains( result.errors, /"invalid6" .* not .* calculation referring to instance\("clinicaldata"\)/i ) ).to.equal( true ); - } ); - - } ); - - describe( 'forms with the special signature extensions ', ()=>{ - const validation1 = validator.validate( loadXForm( 'openclinica-external-signature-invalid.xml' ), { - openclinica: true - } ); - const validation2 = validator.validate( loadXForm( 'openclinica-external-signature-valid.xml' ), { - openclinica: true - } ); - - it( 'passes without errors and warnings when defined correctly', async()=>{ - const result = await validation2; - expect( result.warnings.length ).to.equal( 0 ); - expect( result.errors.length ).to.equal( 0 ); - } ); - - it( 'returns errors when defined incorrectly', async()=>{ - const result = await validation1; - expect( result.warnings.length ).to.equal( 0 ); - expect( result.errors.length ).to.equal( 11 ); - expect ( arrContains( result.errors, /Signature .* choice name set to "1"/ ) ).to.equal( true ); - expect ( arrContains( result.errors, /only include one signature item/ ) ).to.equal( true ); - expect ( arrContains( result.errors, /Signature .* must be of type "select_multiple" with one option/ ) ).to.equal( true ); - } ); - } ); - - describe( 'forms with special multiple constraints extensions', () => { - const validation = validator.validate( loadXForm( 'openclinica-multiple-constraints-fails.xml' ), { - openclinica: true - } ); - - it( 'returns errors', async() => { - const result = await validation; - expect( result.errors.length ).to.equal( 9 ); - expect( arrContains( result.errors, /unsupported oc:constraint .+ "something"/ ) ).to.equal( true ); - expect( arrContains( result.errors, /unsupported oc:constraint22 .+ "something"/ ) ).to.equal( true ); - expect( arrContains( result.errors, /unsupported oc:constraintMsg .+ "something"/ ) ).to.equal( true ); - expect( arrContains( result.errors, /unsupported oc:constraint21Msg .+ "something"/ ) ).to.equal( true ); - expect( arrContains( result.errors, /unsupported oc:constraintABCMsg .+ "something"/ ) ).to.equal( true ); - expect( arrContains( result.errors, /matching oc:constraint1Msg .+ "something"/ ) ).to.equal( true ); - expect( arrContains( result.errors, /matching oc:constraint2Msg .+ "something"/ ) ).to.equal( true ); - expect( arrContains( result.errors, /matching oc:constraint18 .+ "something"/ ) ).to.equal( true ); - expect( arrContains( result.errors, /matching oc:constraint20Msg .+ "something"/ ) ).to.equal( true ); - } ); - } ); - - describe( 'forms using the special last-saved instance', () => { - const validation = validator.validate( loadXForm( 'last-saved.xml' ), { - openclinica: true - } ); - - it( 'returns an error', async() => { - const result = await validation; - expect( result.errors.length ).to.equal( 1 ); - expect( arrContains( result.errors, /last-saved\s+not supported/ ) ); - } ); - } ); - - } ); - - // This test is to confirm the opposite behavior of the behavior in OpenClinica mode to ensure that behavior is isolated. - describe( 'forms using the special last-saved instance', () => { - const validation = validator.validate( loadXForm( 'last-saved.xml' ) ); - - it( 'does not return an error', async() => { - const result = await validation; - expect( result.errors.length ).to.equal( 0 ); - } ); - } ); - - describe( 'with incorrect appearance usage', () => { - const xf = loadXForm( 'appearances.xml' ); - const validation = validator.validate( xf ); - const validationOc = validator.validate( xf, { - openclinica: true - } ); - const WARNINGS = 14; - const ERRORS = 1; - - it( 'returns warnings', async() => { - const result = await validation; - - expect( result.warnings.length ).to.equal( WARNINGS ); - expect( arrContains( result.warnings, /"minimal" for "b"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"compact-2" for "b"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"maximal" for question "c"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"hide-input" for "d"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"search" for question "d" .+ deprecated.+"autocomplete"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"compact" for "e"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"compact-19" for question "f"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"numbers" for question "g"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"no-ticks" for question "g"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"maps" for question "h"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"signature" for "h"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"pulldown" for question "i"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"horizontal-compact" for question "k" .+ deprecated.+"columns-pack"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"field-list" for "two"/i ) ).to.equal( true ); - } ); - - it( 'returns 1 error', async() => { - const result = await validation; - expect( result.errors.length ).to.equal( ERRORS ); - expect( arrContains( result.errors, /"search" for question "l"/i ) ).to.equal( true ); - } ); - - it( 'returns 1 error with --oc flag', async() => { - const resultOc = await validationOc; - expect( resultOc.errors.length ).to.equal( ERRORS ); - expect( arrContains( resultOc.errors, /"search" for question "l"/i ) ).to.equal( true ); - } ); - - it( 'returns warnings with --oc flag too', async() => { - const resultOc = await validationOc; - //expect( arrContains( result.warnings, /deprecated/ ) ).to.equal( false ); - expect( resultOc.warnings.length ).to.equal( WARNINGS ); - } ); - - it( 'including the special case "horizontal" return warnings', async() => { - const result = await validator.validate( loadXForm( 'appearance-horizontal.xml' ) ); - - expect( result.warnings.length ).to.equal( 4 ); - expect( arrContains( result.warnings, /"horizontal" for question "d" .+ deprecated.+"columns"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"horizontal" for question "f" .+ deprecated.+"columns"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"horizontal" for "i".+not valid.+type odkkkkkk:rank/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"horizontal" for "one".+not valid.+type group/i ) ).to.equal( true ); - } ); - - it( 'for custom analog-scale widgets', async() => { - const result = await validator.validate( loadXForm( 'openclinica-analog-scale.xml' ) ); - expect( result.warnings.length ).to.equal( 2 ); - expect( arrContains( result.warnings, /"show-scale" for question "d" .+ combination .+no-ticks/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"show-scale" for question "e" .+ combination .+horizontal/i ) ).to.equal( true ); - } ); - - } ); - - describe( 'with repeats with incorrect w-values for Grid Theme forms', () => { - const xf = loadXForm( 'appearances-repeat.xml' ); - const validation = validator.validate( xf ); - const WARNINGS = 3; - - it( 'returns warnings', async() => { - const result = await validation; - - expect( result.warnings.length ).to.equal( WARNINGS ); - expect( arrContains( result.warnings, /"w3" for "rep3"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"w1" for "rep2"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /"w2" for "rep1"/i ) ).to.equal( true ); - } ); - } ); - - describe( 'with likely user errors that are not actually XPath syntax errors', () => { - const xf = loadXForm( 'user-ues.xml' ); - const validation = validator.validate( xf ); - - it( 'returns warnings', async() => { - const result = await validation; - - expect( result.warnings.length ).to.equal( 12 ); - expect( arrContains( result.warnings, /Constraint .+ "ues"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Relevant .+ "ues"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Required .+ "ues"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Readonly .+ "ues"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Constraint .+ "w6"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Relevant .+ "w6"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Required .+ "w6"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Readonly .+ "w6"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Constraint .+ "true\(\)"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Constraint .+ "false\(\)"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Relevant .+ "true\(\)"/i ) ).to.equal( true ); - expect( arrContains( result.warnings, /Relevant .+ "false\(\)"/i ) ).to.equal( true ); - } ); - } ); - - describe( 'with unsupported external app launching syntax', () => { - const xf = loadXForm( 'external-app.xml' ); - const validation = validator.validate( xf ); - - it( 'returns warnings', async() => { - const result = await validation; - - expect( result.errors.length ).to.equal( 2 ); - expect( arrContains( result.errors, /"ex:" to launch an external app for question "counter"/i ) ).to.equal( true ); - expect( arrContains( result.errors, /"intent" attribute to launch an external app/i ) ).to.equal( true ); - } ); - - } ); - - describe( 'with missing