diff --git a/blocks/api/parser.js b/blocks/api/parser.js index 94a1dfe92f4f03..82add30a463e8f 100644 --- a/blocks/api/parser.js +++ b/blocks/api/parser.js @@ -2,7 +2,7 @@ * External dependencies */ import { parse as hpqParse } from 'hpq'; -import { mapValues, find, omit } from 'lodash'; +import { mapValues, omit } from 'lodash'; /** * Internal dependencies @@ -129,6 +129,32 @@ export function getBlockAttributes( blockType, innerHTML, attributes ) { return blockAttributes; } +/** + * Attempt to parse the innerHTML using using a supplied `deprecated` definition. + * + * @param {?Object} blockType Block type + * @param {string} innerHTML Raw block content + * @param {?Object} attributes Known block attributes (from delimiters) + * @return {Object} Block attributes + */ +export function getAttributesFromDeprecatedVersion( blockType, innerHTML, attributes ) { + if ( ! blockType.deprecated ) { + return; + } + + for ( let i = 0; i < blockType.deprecated.length; i++ ) { + const deprecatedBlockType = { + ...omit( blockType, [ 'attributes', 'save', 'supports' ] ), // Parsing/Serialization properties + ...blockType.deprecated[ i ], + }; + const deprecatedBlockAttributes = getBlockAttributes( deprecatedBlockType, innerHTML, attributes ); + const isValid = isValidBlock( innerHTML, deprecatedBlockType, deprecatedBlockAttributes ); + if ( isValid ) { + return deprecatedBlockAttributes; + } + } +} + /** * Creates a block with fallback to the unknown type handler. * @@ -181,23 +207,14 @@ export function createBlockWithFallback( name, innerHTML, attributes ) { // as invalid, or future serialization attempt results in an error block.originalContent = innerHTML; - // When a block is invalid, attempt to validate again using a supplied `deprecated` definition. + // When a block is invalid, attempt to parse it using a supplied `deprecated` definition. // This allows blocks to modify their attribute and markup structure without invalidating // content written in previous formats. - if ( ! block.isValid && blockType.deprecated ) { - let attributesParsedWithDeprecatedVersion; - const hasValidOlderVersion = find( blockType.deprecated, ( oldBlockType ) => { - const deprecatedBlockType = { - ...omit( blockType, [ 'attributes', 'save', 'supports' ] ), // Parsing/Serialization properties - ...oldBlockType, - }; - const deprecatedBlockAttributes = getBlockAttributes( deprecatedBlockType, innerHTML, attributes ); - const isValid = isValidBlock( innerHTML, deprecatedBlockType, deprecatedBlockAttributes ); - attributesParsedWithDeprecatedVersion = isValid ? deprecatedBlockAttributes : undefined; - return isValid; - } ); - - if ( hasValidOlderVersion ) { + if ( ! block.isValid ) { + const attributesParsedWithDeprecatedVersion = getAttributesFromDeprecatedVersion( + blockType, innerHTML, attributes + ); + if ( attributesParsedWithDeprecatedVersion ) { block.isValid = true; block.attributes = attributesParsedWithDeprecatedVersion; } diff --git a/blocks/api/test/parser.js b/blocks/api/test/parser.js index 8ef32c002891d5..8c2205b18f14f4 100644 --- a/blocks/api/test/parser.js +++ b/blocks/api/test/parser.js @@ -6,6 +6,7 @@ import { getBlockAttributes, asType, createBlockWithFallback, + getAttributesFromDeprecatedVersion, default as parse, } from '../parser'; import { @@ -15,6 +16,15 @@ import { setUnknownTypeHandlerName, } from '../registration'; +const expectFailingBlockValidation = () => { + /* eslint-disable no-console */ + expect( console.error ).toHaveBeenCalled(); + expect( console.warn ).toHaveBeenCalled(); + console.warn.mockClear(); + console.error.mockClear(); + /* eslint-enable no-console */ +}; + describe( 'block parser', () => { const defaultBlockSettings = { attributes: { @@ -164,6 +174,62 @@ describe( 'block parser', () => { } ); } ); + describe( 'getAttributesFromDeprecatedVersion', () => { + it( 'should return undefined if the block has no deprecated versions', () => { + const attributes = getAttributesFromDeprecatedVersion( + defaultBlockSettings, + 'Bananas', + {}, + ); + expect( attributes ).toBeUndefined(); + } ); + + it( 'should return undefined if no valid deprecated version found', () => { + const attributes = getAttributesFromDeprecatedVersion( + { + name: 'core/test-block', + ...defaultBlockSettings, + deprecated: [ + { + save() { + return 'nothing'; + }, + }, + ], + }, + 'Bananas', + {}, + ); + expect( attributes ).toBeUndefined(); + expectFailingBlockValidation(); + } ); + + it( 'should return the attributes parsed by the deprecated version', () => { + const attributes = getAttributesFromDeprecatedVersion( + { + name: 'core/test-block', + ...defaultBlockSettings, + save: ( props ) =>
{ props.attributes.fruit }
, + deprecated: [ + { + attributes: { + fruit: { + type: 'string', + source: 'text', + selector: 'span', + }, + }, + save: ( props ) => { props.attributes.fruit }, + }, + ], + }, + 'Bananas', + {}, + ); + expect( attributes ).toEqual( { fruit: 'Bananas' } ); + } ); + } ); + describe( 'createBlockWithFallback', () => { it( 'should create the requested block if it exists', () => { registerBlockType( 'core/test-block', defaultBlockSettings ); @@ -245,12 +311,7 @@ describe( 'block parser', () => { expect( block.name ).toEqual( 'core/test-block' ); expect( block.attributes ).toEqual( { fruit: 'Bananas' } ); expect( block.isValid ).toBe( true ); - /* eslint-disable no-console */ - expect( console.error ).toHaveBeenCalled(); - expect( console.warn ).toHaveBeenCalled(); - console.warn.mockClear(); - console.error.mockClear(); - /* eslint-enable no-console */ + expectFailingBlockValidation(); } ); } );