From 36c9def4ac5256421fba16543dd0b86b5d19e372 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 10 Apr 2019 16:14:09 -0400 Subject: [PATCH] Blocks: Add enum validation to browser block parser (#14810) * Blocks: Add enum validation to browser block parser * Block API: Remove array type in isValidByEnum JSDoc * Block API: Fix typo in test description expectation --- packages/blocks/src/api/parser.js | 38 +++++++++-- packages/blocks/src/api/test/parser.js | 94 ++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 4 deletions(-) diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index 5b05f0fe93af6..2132f6fa45f4f 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -116,6 +116,36 @@ export function isOfTypes( value, types ) { return types.some( ( type ) => isOfType( value, type ) ); } +/** + * Returns true if value is valid per the given block attribute schema type + * definition, or false otherwise. + * + * @link https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.1 + * + * @param {*} value Value to test. + * @param {?(Array|string)} type Block attribute schema type. + * + * @return {boolean} Whether value is valid. + */ +export function isValidByType( value, type ) { + return type === undefined || isOfTypes( value, castArray( type ) ); +} + +/** + * Returns true if value is valid per the given block attribute schema enum + * definition, or false otherwise. + * + * @link https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.2 + * + * @param {*} value Value to test. + * @param {?Array} enumSet Block attribute schema enum. + * + * @return {boolean} Whether value is valid. + */ +export function isValidByEnum( value, enumSet ) { + return ! Array.isArray( enumSet ) || enumSet.includes( value ); +} + /** * Returns true if the given attribute schema describes a value which may be * an ambiguous string. @@ -242,7 +272,7 @@ export function parseWithAttributeSchema( innerHTML, attributeSchema ) { * @return {*} Attribute value. */ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, commentAttributes ) { - const { type } = attributeSchema; + const { type, enum: enumSet } = attributeSchema; let value; switch ( attributeSchema.source ) { @@ -262,9 +292,9 @@ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, com break; } - if ( type !== undefined && ! isOfTypes( value, castArray( type ) ) ) { - // Reject the value if it is not valid of type. Reverting to the - // undefined value ensures the default is restored, if applicable. + if ( ! isValidByType( value, type ) || ! isValidByEnum( value, enumSet ) ) { + // Reject the value if it is not valid. Reverting to the undefined + // value ensures the default is respected, if applicable. value = undefined; } diff --git a/packages/blocks/src/api/test/parser.js b/packages/blocks/src/api/test/parser.js index 02469c6e3f569..f6c1569a42043 100644 --- a/packages/blocks/src/api/test/parser.js +++ b/packages/blocks/src/api/test/parser.js @@ -18,6 +18,8 @@ import { toBooleanAttributeMatcher, isOfType, isOfTypes, + isValidByType, + isValidByEnum, } from '../parser'; import { registerBlockType, @@ -180,6 +182,42 @@ describe( 'block parser', () => { } ); } ); + describe( 'isValidByType', () => { + it( 'returns true if type undefined', () => { + expect( isValidByType( null ) ).toBe( true ); + } ); + + it( 'returns false if value is not one of types array', () => { + expect( isValidByType( null, [ 'string' ] ) ).toBe( false ); + } ); + + it( 'returns true if value is one of types array', () => { + expect( isValidByType( null, [ 'string', 'null' ] ) ).toBe( true ); + } ); + + it( 'returns false if value is not of type string', () => { + expect( isValidByType( null, 'string' ) ).toBe( false ); + } ); + + it( 'returns true if value is type string', () => { + expect( isValidByType( null, 'null' ) ).toBe( true ); + } ); + } ); + + describe( 'isValidByEnum', () => { + it( 'returns true if enum set undefined', () => { + expect( isValidByEnum( 2 ) ).toBe( true ); + } ); + + it( 'returns false if value is not of enum set', () => { + expect( isValidByEnum( 2, [ 1, 3 ] ) ).toBe( false ); + } ); + + it( 'returns true if value is of enum set', () => { + expect( isValidByEnum( 2, [ 1, 2, 3 ] ) ).toBe( true ); + } ); + } ); + describe( 'parseWithAttributeSchema', () => { it( 'should return the matcher’s attribute value', () => { const value = parseWithAttributeSchema( @@ -260,6 +298,62 @@ describe( 'block parser', () => { expect( value ).toBe( 10 ); } ); + it( 'should reject type-invalid value, with default', () => { + const value = getBlockAttribute( + 'number', + { + type: 'string', + default: 5, + }, + '', + { number: 10 } + ); + + expect( value ).toBe( 5 ); + } ); + + it( 'should reject type-invalid value, without default', () => { + const value = getBlockAttribute( + 'number', + { + type: 'string', + }, + '', + { number: 10 } + ); + + expect( value ).toBe( undefined ); + } ); + + it( 'should reject enum-invalid value, with default', () => { + const value = getBlockAttribute( + 'number', + { + type: 'number', + enum: [ 4, 5, 6 ], + default: 5, + }, + '', + { number: 10 } + ); + + expect( value ).toBe( 5 ); + } ); + + it( 'should reject enum-invalid value, without default', () => { + const value = getBlockAttribute( + 'number', + { + type: 'number', + enum: [ 4, 5, 6 ], + }, + '', + { number: 10 } + ); + + expect( value ).toBe( undefined ); + } ); + it( "should return the matcher's attribute value", () => { const value = getBlockAttribute( 'content',