Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

enketo-validate

Package Overview
Dependencies
Maintainers
1
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

enketo-validate - npm Package Compare versions

Comparing version 2.0.1 to 2.1.0

test/xform/language-issues.xml

7

CHANGELOG.md

@@ -5,2 +5,9 @@ ## Change Log

[2.1.0] - 2023-02-03
--------------------------
##### Added
- Check for missing value elements in choice options.
- Check that only one of OpenClinica's _external signature_ questions are present.
- Check that value of OpenClinica's _external signature_ question is "1".
[2.0.1] - 2022-11-07

@@ -7,0 +14,0 @@ --------------------------

6

package.json
{
"name": "enketo-validate",
"version": "2.0.1",
"version": "2.1.0",
"description": "An XForm validator around Enketo's form engine",

@@ -31,4 +31,4 @@ "main": "src/validator.js",

"dependencies": {
"commander": "^9.4.1",
"enketo-transformer": "^2.1.6",
"commander": "^10.0.0",
"enketo-transformer": "^2.1.7",
"enketo-xpath-extensions-oc": "git+https://github.com/OpenClinica/enketo-xpath-extensions-oc.git#727803c",

@@ -35,0 +35,0 @@ "jsdom": "^20.0.2",

@@ -247,3 +247,3 @@ const utils = require( '../build/utils-cjs-bundle' );

} );
*/
*/

@@ -363,13 +363,21 @@ return page.evaluateHandle( ( modelStr, externalArr, ocExtensions ) => {

// These are the elements we expect to have a label though we're going slightly beyond spec requirement here.
this.formControls.concat( this.items )
.forEach( control => {
// The selector ":scope > label" fails with namespaced elements such as odk:rank
// TODO: after https://github.com/XLSForm/pyxform/issues/439 has been implemented remove "|| el.nodeName === 'hint'".
if ( ![ ...control.childNodes ].some( el => el.nodeName === 'label' || el.nodeName === 'hint' ) ) {
const type = control.nodeName === 'item' || control.nodeName === 'itemset' ? 'Select option for question' : 'Question';
const nodeName = this._nodeName( control,'ref' ) || this._nodeName( control.parentElement, 'ref' ) || '?';
errors.push( `${type} "${nodeName}" has no label.` );
}
} );
this.formControls.forEach( control => {
// The selector ":scope > label" fails with namespaced elements such as odk:rank
if ( ![ ...control.childNodes ].some( el => el.nodeName === 'label' ) ) {
const nodeName = this._nodeName( control,'ref' ) || '?';
errors.push( `Question "${nodeName}" has no label.` );
}
} );
this.items.forEach( item => {
if ( ![ ...item.childNodes ].some( el => el.nodeName === 'label' ) ){
const nodeName = this._nodeName( item.parentElement, 'ref' ) || '?';
errors.push( `Select option for question "${nodeName}" has no label.` );
}
if ( ![ ...item.childNodes ].some( el => el.nodeName === 'value' ) ){
const nodeName = this._nodeName( item.parentElement, 'ref' ) || '?';
errors.push( `Select option for question "${nodeName}" has no value.` );
}
} );
let modelEl;

@@ -650,5 +658,11 @@ if ( headEl ) {

this.binds
.filter( bind => bind.getAttributeNS( this.NAMESPACES.oc, 'external' ) === 'signature' )
.filter( bind => {
const externalSignatureQuestions = this.binds
.filter( bind => bind.getAttributeNS( this.NAMESPACES.oc, 'external' ) === 'signature' );
if ( externalSignatureQuestions.length > 1 ){
errors.push( 'Consent forms can only include one signature item.' );
}
externalSignatureQuestions
.forEach( bind => {
const path = bind.getAttribute( 'nodeset' );

@@ -658,8 +672,11 @@ const select = this.doc.querySelector( `select[ref="${path}"]` );

const options = select ? select.querySelectorAll( 'item' ) : [];
const valueEl = options[0] ? options[0].querySelector( 'value' ) : null;
return !select || options.length !== 1 ||
( appearanceVal && appearanceVal.trim().split( ' ' ).includes( 'minimal' ) );
} )
.map( bind => this._nodeName( bind ) )
.forEach( () => errors.push( 'Signature items must be of type "select_multiple" with one option.' ) );
if( !select || options.length !== 1 ||
( appearanceVal && appearanceVal.trim().split( ' ' ).includes( 'minimal' ) ) ){
errors.push( 'Signature items must be of type "select_multiple" with one option.' );
} else if ( valueEl && valueEl.textContent !== '1' ){
errors.push( 'Signature items must have choice name set to "1"' );
}
} );

@@ -666,0 +683,0 @@ this.binds

@@ -18,2 +18,8 @@ const XForm = require( '../../src/xform' ).XForm;

} );
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 );
} );
} );

@@ -154,3 +160,3 @@

it( 'outputs errors', async() => {
it( 'returns errors', async() => {
const result = await validation;

@@ -168,3 +174,3 @@ expect( arrContains( result.errors, /"a" has a calculation that is not set to readonly/i ) ).to.equal( true );

it( 'outputs errors', async() => {
it( 'returns errors', async() => {
const result = await validation;

@@ -174,3 +180,3 @@ expect( result.errors.length ).to.equal( 6 );

it( 'outputs errors for calculations without form control that refer to external ' +
it( 'returns errors for calculations without form control that refer to external ' +
'clinicaldata instance but do not have the oc:external="clinicaldata" bind', async() => {

@@ -183,3 +189,3 @@ const result = await validation;

it( 'outputs errors for binds with oc:external="clinicaldata" that do not ' +
it( 'returns errors for binds with oc:external="clinicaldata" that do not ' +
'do not have a calculation that refers to instance(\'clinicaldata\')', async() => {

@@ -194,13 +200,24 @@ const result = await validation;

describe( 'forms with the special signature extensions', ()=>{
const validation = validator.validate( loadXForm( 'openclinica-external-signature.xml' ), {
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( 'outputs warnings for non-checkbox questions or questions with more than 1 checkbox', async()=>{
const result = await validation;
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( 9 );
expect( result.errors.every( error => error.includes( 'Signature' ) ) ).to.equal( true );
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 );
} );
} );

@@ -213,3 +230,3 @@

it( 'outputs errors', async() => {
it( 'returns errors', async() => {
const result = await validation;

@@ -234,3 +251,3 @@ expect( result.errors.length ).to.equal( 9 );

it( 'outputs an error', async() => {
it( 'returns an error', async() => {
const result = await validation;

@@ -248,3 +265,3 @@ expect( result.errors.length ).to.equal( 1 );

it( 'does not output an error', async() => {
it( 'does not return an error', async() => {
const result = await validation;

@@ -264,3 +281,3 @@ expect( result.errors.length ).to.equal( 0 );

it( 'outputs warnings', async() => {
it( 'returns warnings', async() => {
const result = await validation;

@@ -285,3 +302,3 @@

it( 'outputs 1 error', async() => {
it( 'returns 1 error', async() => {
const result = await validation;

@@ -292,3 +309,3 @@ expect( result.errors.length ).to.equal( ERRORS );

it( 'outputs 1 error with --oc flag', async() => {
it( 'returns 1 error with --oc flag', async() => {
const resultOc = await validationOc;

@@ -299,3 +316,3 @@ expect( resultOc.errors.length ).to.equal( ERRORS );

it( 'outputs warnings with --oc flag too', async() => {
it( 'returns warnings with --oc flag too', async() => {
const resultOc = await validationOc;

@@ -306,3 +323,3 @@ //expect( arrContains( result.warnings, /deprecated/ ) ).to.equal( false );

it( 'including the special case "horizontal" output warnings', async() => {
it( 'including the special case "horizontal" return warnings', async() => {
const result = await validator.validate( loadXForm( 'appearance-horizontal.xml' ) );

@@ -331,3 +348,3 @@

it( 'outputs warnings', async() => {
it( 'returns warnings', async() => {
const result = await validation;

@@ -346,3 +363,3 @@

it( 'outputs warnings', async() => {
it( 'returns warnings', async() => {
const result = await validation;

@@ -370,3 +387,3 @@

it( 'outputs warnings', async() => {
it( 'returns warnings', async() => {
const result = await validation;

@@ -383,3 +400,3 @@

it( 'outputs errors', async() => {
it( 'returns errors', async() => {
const result = await validator.validate( loadXForm( 'missing-labels.xml' ) );

@@ -396,3 +413,3 @@ const ISSUES = 6;

it( 'does not output errors for setvalue actions without a label', async() => {
it( 'does not return errors for setvalue actions without a label', async() => {
const result = await validator.validate( loadXForm( 'setvalue.xml' ) );

@@ -404,5 +421,20 @@ expect( result.errors.length ).to.equal( 0 );

describe( 'with missing <value> elements', () => {
it( 'returns errors', async() => {
const result = await validator.validate( loadXForm( 'missing-values.xml' ) );
expect( result.errors.length ).to.equal( 1 );
expect( arrContains( result.errors, /option for question "k" has no value/i ) ).to.equal( true );
} );
it( 'does not return errors for setvalue actions without a label', async() => {
const result = await validator.validate( loadXForm( 'setvalue.xml' ) );
expect( result.errors.length ).to.equal( 0 );
} );
} );
describe( 'with duplicate nodenames', () => {
it( 'outputs warnings', async() => {
it( 'returns warnings', async() => {
const result = await validator.validate( loadXForm( 'duplicate-nodename.xml' ) );

@@ -419,3 +451,3 @@ expect( result.warnings.length ).to.equal( 2 );

it( 'outputs warnings', async() => {
it( 'returns warnings', async() => {
const result = await validator.validate( loadXForm( 'nodename-underscore.xml' ) );

@@ -430,3 +462,3 @@ expect( result.warnings.length ).to.equal( 0 );

it( 'outputs warnings', async() => {
it( 'returns warnings', async() => {
const result = await validator.validate( loadXForm( 'nested-repeats.xml' ) );

@@ -442,3 +474,3 @@ expect( result.warnings.length ).to.equal( 2 );

it( 'outputs errors for disallowed self-referencing', async() => {
it( 'returns errors for disallowed self-referencing', async() => {
// Unit tests are in xpath.spec.js

@@ -445,0 +477,0 @@ const result = await validator.validate( loadXForm( 'self-reference.xml' ) );

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc