From f9d80198f39d1209528a2602366559ff51af70f4 Mon Sep 17 00:00:00 2001 From: zepumph Date: Thu, 19 Sep 2019 16:24:14 -0800 Subject: [PATCH] add arrayElementType to validator, https://github.com/phetsims/axon/issues/195 --- js/ValidatorDef.js | 82 +++++++++++++++++++++++++++++++++++------ js/ValidatorDefTests.js | 61 ++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 12 deletions(-) diff --git a/js/ValidatorDef.js b/js/ValidatorDef.js index 396c4b26..162d0859 100644 --- a/js/ValidatorDef.js +++ b/js/ValidatorDef.js @@ -69,7 +69,13 @@ define( require => { // isValidValue: function( value ) { return Util.isInteger( value ) && value >= 0; } 'isValidValue', - // {function(PhetioType is a + // This option takes the same types as are supported with `valueType`. This option is to specify the type of the + // elements of an array. For this option to valid, `valueType` must be not also be provided. It is assumed that + // valueType is `Array`. + 'arrayElementType', + + // {function(new: ObjectIO)} - A TypeIO used to specify the public typeing for PhET-iO. Each TypeIO must have a + // `validator` key specified that can be used for validation. See ObjectIO for an example. 'phetioType' /************************************** @@ -106,6 +112,7 @@ define( require => { } if ( !( validator.hasOwnProperty( 'isValidValue' ) || validator.hasOwnProperty( 'valueType' ) || + validator.hasOwnProperty( 'arrayElementType' ) || validator.hasOwnProperty( 'validValues' ) || validator.hasOwnProperty( 'phetioType' ) ) ) { assert && options.assertions && assert( false, @@ -113,19 +120,17 @@ define( require => { return false; } - if ( validator.hasOwnProperty( 'valueType' ) ) { - const valueType = validator.valueType; - if ( Array.isArray( valueType ) ) { + if ( validator.hasOwnProperty( 'valueType' ) && !validateValueOrElementType( validator.valueType, options ) ) { + return false; + } - // If every valueType in the list is not valid, then return false, pass options through verbatum. - if ( !_.every( valueType.map( typeInArray => ValidatorDef.validateValueType( typeInArray, options ) ) ) ) { - return false; - } + if ( validator.hasOwnProperty( 'arrayElementType' ) ) { + if ( validator.hasOwnProperty( 'valueType' ) ) { + assert && options.assertions && assert( false, 'valueType is redundant with arrayElementType. valueType is Array.' ); + return false; } - else if ( valueType ) { - if ( !ValidatorDef.validateValueType( valueType, options ) ) { - return false; - } + if ( !validateValueOrElementType( validator.arrayElementType, options ) ) { + return false; } } @@ -259,6 +264,37 @@ define( require => { } } } + + if ( validator.hasOwnProperty( 'arrayElementType' ) ) { + const arrayElementType = validator.arrayElementType; + + // If using arrayElementType, then the value should be an array. No need for assertions, because nested + // isValueValid will assert out if asserting. + if ( !ValidatorDef.isValueValid( value, { valueType: Array }, options ) ) { + return false; + } + + // every element in the array should pass + if ( !_.every( value.map( arrayElement => { + + // if the type is an array, then handle it like we did for valueType, with _.some + if ( Array.isArray( arrayElementType ) ) { + if ( !_.some( arrayElementType.map( typeInArray => ValidatorDef.isValueValidValueType( arrayElement, typeInArray, ASSERTIONS_FALSE ) ) ) ) { + assert && options.assertions && assert( false, `array element not valid for any arrayElementType in ${arrayElementType}, value: ${arrayElement}` ); + return false; + } + return true; + } + else { + + // if not an array, then just check the array element + return ValidatorDef.isValueValidValueType( arrayElement, validator.arrayElementType, options ) + } + } ) ) ) { + return false; // if every element didn't pass, then return false + } + } + if ( validator.hasOwnProperty( 'validValues' ) && validator.validValues.indexOf( value ) === -1 ) { assert && options.assertions && assert( false, `value not in validValues: ${value}` ); return false; @@ -310,6 +346,28 @@ define( require => { } }; + /** + * Validate a type that can be a type, or an array of multiple types. + * @param {*} type - see valueType documentation + * @param {Object} options - see isValidValidator + * @returns {boolean} + */ + const validateValueOrElementType = ( type, options ) => { + if ( Array.isArray( type ) ) { + + // If every type in the list is not valid, then return false, pass options through verbatum. + if ( !_.every( type.map( typeInArray => ValidatorDef.validateValueType( typeInArray, options ) ) ) ) { + return false; + } + } + else if ( type ) { + if ( !ValidatorDef.validateValueType( type, options ) ) { + return false; + } + } + return true; + }; + ValidatorDef.VALIDATOR_KEYS = VALIDATOR_KEYS; return axon.register( 'ValidatorDef', ValidatorDef ); diff --git a/js/ValidatorDefTests.js b/js/ValidatorDefTests.js index 1201dfa8..9dd52ca1 100644 --- a/js/ValidatorDefTests.js +++ b/js/ValidatorDefTests.js @@ -139,4 +139,65 @@ define( require => { assert.ok( ValidatorDef.isValueValid( new Emitter(), { phetioType: EmitterIO( [] ) } ), 'emitter is valid' ); } ); + + QUnit.test( 'Test arrayElementType', assert => { + + + window.assert && assert.throws( () => ValidatorDef.validateValidator( { + valueType: Array, + arrayElementType: null + } ), 'arrayElementType expected should not have valueType' ); + + assert.ok( ValidatorDef.isValidValidator( { arrayElementType: 'number' } ), 'good valueType' ); + assert.ok( !ValidatorDef.isValidValidator( { arrayElementTypes: 'number' } ), 'no validator keys supplied' ); + assert.ok( !ValidatorDef.isValidValidator( { arrayElementTypes: 4 } ), 'no validator keys supplied' ); + assert.ok( !ValidatorDef.isValidValidator( { arrayElementType: 'blaradysharady' } ), 'invalid valueType string' ); + + assert.ok( ValidatorDef.isValidValidator( { arrayElementType: null } ), 'null is valid' ); + assert.ok( ValidatorDef.isValidValidator( { arrayElementType: [ 'number', null ] } ), 'array of null and number is valid' ); + assert.ok( ValidatorDef.isValidValidator( { arrayElementType: [ 'number', null, Node ] } ), 'array of null and number is valid' ); + assert.ok( !ValidatorDef.isValidValidator( { arrayElementType: [ 'numberf', null, Node ] } ), 'numberf is not a valid arrayElementType' ); + assert.ok( ValidatorDef.isValueValid( [ 1, 2, 3, 4, 5 ], { arrayElementType: [ 'number' ] } ), 'number array ok' ); + assert.ok( !ValidatorDef.isValueValid( [ 1, 2, 3, 4, 5, null ], { arrayElementType: [ 'number' ] } ), 'number array bad with null' ); + assert.ok( ValidatorDef.isValueValid( [ 1, 2, 3, 4, 5, null ], { arrayElementType: [ 'number', null ] } ), 'number array ok with null' ); + assert.ok( ValidatorDef.isValueValid( [ 1, 'fdsaf', 3, 4, 5, null ], { arrayElementType: [ 'number', 'string', null ] } ), 'number and string array ok with null' ); + assert.ok( !ValidatorDef.isValueValid( [ 1, 'fdsaf', 3, 4, 5, null ], { arrayElementType: [ 'string', null ] } ), 'number and string array ok with null' ); + assert.ok( ValidatorDef.isValueValid( [ [], [], [], [] ], { arrayElementType: [ Array ] } ), 'array array' ); + + window.assert && assert.throws( () => { + ValidatorDef.isValueValid( undefined, { arrayElementType: [ 'number', 'string' ], }, ASSERTIONS_TRUE ); + }, 'undefined is not a valid arrayElementType' ); + + window.assert && assert.throws( () => { + ValidatorDef.isValueValid( undefined, { arrayElementType: [ 7 ] }, ASSERTIONS_TRUE ); + }, '7 is not a valid arrayElementType' ); + + window.assert && assert.throws( () => { + ValidatorDef.isValueValid( undefined, { arrayElementType: [ 'number', {} ] }, ASSERTIONS_TRUE ); + }, 'Object literal is not a valid arrayElementType' ); + + window.assert && assert.throws( () => { + ValidatorDef.isValueValid( [ 'sting here, what up' ], { arrayElementType: [ 'number' ] }, ASSERTIONS_TRUE ); + }, 'sstring is not a valid arrayElementType' ); + + window.assert && assert.throws( () => { + ValidatorDef.isValueValid( [ 'sting here, what up' ], { arrayElementType: [ 'number' ] }, ASSERTIONS_TRUE ); + }, 'sstring is not a valid arrayElementType' ); + + window.assert && assert.throws( () => { + ValidatorDef.isValueValid( [ 5 ], { arrayElementType: [ 'string' ] }, ASSERTIONS_TRUE ); + }, 'sstring is not a valid arrayElementType' ); + + window.assert && assert.throws( () => { + ValidatorDef.isValueValid( [ null, 3, 4, 5, undefined ], { arrayElementType: [ 'number', null ] }, ASSERTIONS_TRUE ); + }, 'sstring is not a valid arrayElementType' ); + + window.assert && assert.throws( () => { + ValidatorDef.isValueValid( undefined, { arrayElementType: [ 7 ] }, ASSERTIONS_TRUE ); + }, '7 is not a valid arrayElementType' ); + + window.assert && assert.throws( () => { + ValidatorDef.isValueValid( undefined, { arrayElementType: [ 'number', {} ] }, ASSERTIONS_TRUE ); + }, 'Object literal is not a valid arrayElementType' ); + } ); } ); \ No newline at end of file