From 06fe513edb5b08d4d72cd3f94fa34c063dd4af5e Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 27 Nov 2017 10:43:36 +0100 Subject: [PATCH 1/4] Framework: Handle and upgrade deprecated blocks --- blocks/api/parser.js | 22 ++++- blocks/api/test/parser.js | 41 +++++++++ blocks/library/quote/index.js | 85 +++++++++++++------ .../test/fixtures/core__quote__style-1.html | 2 +- .../test/fixtures/core__quote__style-1.json | 2 +- .../fixtures/core__quote__style-1.parsed.json | 2 +- .../core__quote__style-1.serialized.html | 4 +- .../test/fixtures/core__quote__style-2.html | 2 +- .../test/fixtures/core__quote__style-2.json | 2 +- .../fixtures/core__quote__style-2.parsed.json | 2 +- .../core__quote__style-2.serialized.html | 4 +- phpunit/fixtures/long-content.html | 2 +- post-content.js | 2 +- 13 files changed, 131 insertions(+), 41 deletions(-) diff --git a/blocks/api/parser.js b/blocks/api/parser.js index ed55b8a7c1a2ba..5fb86020d00965 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 } from 'lodash'; +import { mapValues, find, omit } from 'lodash'; /** * Internal dependencies @@ -181,6 +181,26 @@ export function createBlockWithFallback( name, innerHTML, attributes ) { // as invalid, or future serialization attempt results in an error block.originalContent = innerHTML; + // If the block is invalid, try to find an older compatible version + if ( ! block.isValid && blockType.deprecatedVersions ) { + let attributesParsedWithDeprecatedVersion; + const hasValidOlderVersion = find( blockType.deprecatedVersions, ( 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 ) { + block.isValid = true; + block.attributes = attributesParsedWithDeprecatedVersion; + } + } + return block; } } diff --git a/blocks/api/test/parser.js b/blocks/api/test/parser.js index 5ed001bb49940b..5dc1f226b8ef03 100644 --- a/blocks/api/test/parser.js +++ b/blocks/api/test/parser.js @@ -211,6 +211,47 @@ describe( 'block parser', () => { const block = createBlockWithFallback( 'core/test-block', '' ); expect( block ).toBeUndefined(); } ); + + it( 'should fallback to an older version of the block if the current one is invalid', () => { + registerBlockType( 'core/test-block', { + ...defaultBlockSettings, + attributes: { + fruit: { + type: 'string', + source: 'text', + selector: 'div', + }, + }, + save: ( { attributes } ) =>
{attributes.fruit}
, + deprecatedVersions: [ + { + attributes: { + fruit: { + type: 'string', + source: 'text', + selector: 'span', + }, + }, + save: ( { attributes } ) => {attributes.fruit}, + }, + ], + } ); + + const block = createBlockWithFallback( + 'core/test-block', + 'Bananas', + { fruit: 'Bananas' } + ); + 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 */ + } ); } ); describe( 'parse()', () => { diff --git a/blocks/library/quote/index.js b/blocks/library/quote/index.js index fbcb26caa8a5e9..f50ceb1f8951ff 100644 --- a/blocks/library/quote/index.js +++ b/blocks/library/quote/index.js @@ -26,36 +26,38 @@ const fromEditableValue = value => value.map( ( subValue ) => ( { children: subValue, } ) ); +const blockAttributes = { + value: { + type: 'array', + source: 'query', + selector: 'blockquote > p', + query: { + children: { + source: 'node', + }, + }, + default: [], + }, + citation: { + type: 'array', + source: 'children', + selector: 'cite', + }, + align: { + type: 'string', + }, + style: { + type: 'number', + default: 1, + }, +}; + registerBlockType( 'core/quote', { title: __( 'Quote' ), icon: 'format-quote', category: 'common', - attributes: { - value: { - type: 'array', - source: 'query', - selector: 'blockquote > p', - query: { - children: { - source: 'node', - }, - }, - default: [], - }, - citation: { - type: 'array', - source: 'children', - selector: 'footer', - }, - align: { - type: 'string', - }, - style: { - type: 'number', - default: 1, - }, - }, + attributes: blockAttributes, transforms: { from: [ @@ -227,9 +229,40 @@ registerBlockType( 'core/quote', {

{ paragraph.children && paragraph.children.props.children }

) ) } { citation && citation.length > 0 && ( - + { citation } ) } ); }, + + deprecatedVersions: [ + { + attributes: { + ...blockAttributes, + citation: { + type: 'array', + source: 'children', + selector: 'footer', + }, + }, + + save( { attributes } ) { + const { align, value, citation, style } = attributes; + + return ( +
+ { value.map( ( paragraph, i ) => ( +

{ paragraph.children && paragraph.children.props.children }

+ ) ) } + { citation && citation.length > 0 && ( +
{ citation }
+ ) } +
+ ); + }, + }, + ], } ); diff --git a/blocks/test/fixtures/core__quote__style-1.html b/blocks/test/fixtures/core__quote__style-1.html index 9d25475fbd32cb..d004ff822ed179 100644 --- a/blocks/test/fixtures/core__quote__style-1.html +++ b/blocks/test/fixtures/core__quote__style-1.html @@ -1,3 +1,3 @@ -

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
+

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
diff --git a/blocks/test/fixtures/core__quote__style-1.json b/blocks/test/fixtures/core__quote__style-1.json index c4587aa2988038..3955e7a68359c5 100644 --- a/blocks/test/fixtures/core__quote__style-1.json +++ b/blocks/test/fixtures/core__quote__style-1.json @@ -23,6 +23,6 @@ ], "style": 1 }, - "originalContent": "

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
" + "originalContent": "

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
" } ] diff --git a/blocks/test/fixtures/core__quote__style-1.parsed.json b/blocks/test/fixtures/core__quote__style-1.parsed.json index f92bde0d087b5a..5e6780b29b5fbb 100644 --- a/blocks/test/fixtures/core__quote__style-1.parsed.json +++ b/blocks/test/fixtures/core__quote__style-1.parsed.json @@ -5,7 +5,7 @@ "style": "1" }, "innerBlocks": [], - "innerHTML": "\n

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
\n" + "innerHTML": "\n

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
\n" }, { "attrs": {}, diff --git a/blocks/test/fixtures/core__quote__style-1.serialized.html b/blocks/test/fixtures/core__quote__style-1.serialized.html index 6eb5a4e20fcec1..bda101357ff126 100644 --- a/blocks/test/fixtures/core__quote__style-1.serialized.html +++ b/blocks/test/fixtures/core__quote__style-1.serialized.html @@ -1,6 +1,4 @@
-

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

-
Matt Mullenweg, 2017
-
+

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017 diff --git a/blocks/test/fixtures/core__quote__style-2.html b/blocks/test/fixtures/core__quote__style-2.html index 66562ac020fb85..fb29b5cec5fb7f 100644 --- a/blocks/test/fixtures/core__quote__style-2.html +++ b/blocks/test/fixtures/core__quote__style-2.html @@ -1,3 +1,3 @@ -

There is no greater agony than bearing an untold story inside you.

Maya Angelou
+

There is no greater agony than bearing an untold story inside you.

Maya Angelou
diff --git a/blocks/test/fixtures/core__quote__style-2.json b/blocks/test/fixtures/core__quote__style-2.json index e5edab1803ea81..fd8f3b0e6425f0 100644 --- a/blocks/test/fixtures/core__quote__style-2.json +++ b/blocks/test/fixtures/core__quote__style-2.json @@ -23,6 +23,6 @@ ], "style": 2 }, - "originalContent": "

There is no greater agony than bearing an untold story inside you.

Maya Angelou
" + "originalContent": "

There is no greater agony than bearing an untold story inside you.

Maya Angelou
" } ] diff --git a/blocks/test/fixtures/core__quote__style-2.parsed.json b/blocks/test/fixtures/core__quote__style-2.parsed.json index 74901e41031c50..be323c894a95f2 100644 --- a/blocks/test/fixtures/core__quote__style-2.parsed.json +++ b/blocks/test/fixtures/core__quote__style-2.parsed.json @@ -5,7 +5,7 @@ "style": "2" }, "innerBlocks": [], - "innerHTML": "\n

There is no greater agony than bearing an untold story inside you.

Maya Angelou
\n" + "innerHTML": "\n

There is no greater agony than bearing an untold story inside you.

Maya Angelou
\n" }, { "attrs": {}, diff --git a/blocks/test/fixtures/core__quote__style-2.serialized.html b/blocks/test/fixtures/core__quote__style-2.serialized.html index 3c0d0a9b325eb4..1cadb4d22a93e3 100644 --- a/blocks/test/fixtures/core__quote__style-2.serialized.html +++ b/blocks/test/fixtures/core__quote__style-2.serialized.html @@ -1,6 +1,4 @@
-

There is no greater agony than bearing an untold story inside you.

-
Maya Angelou
-
+

There is no greater agony than bearing an untold story inside you.

Maya Angelou diff --git a/phpunit/fixtures/long-content.html b/phpunit/fixtures/long-content.html index 7f4a0f2a4f04cd..4008a79305151e 100644 --- a/phpunit/fixtures/long-content.html +++ b/phpunit/fixtures/long-content.html @@ -56,7 +56,7 @@

Visual Editing

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

-
Matt Mullenweg, 2017
+Matt Mullenweg, 2017
diff --git a/post-content.js b/post-content.js index 925edad8f1ec0f..1302dcdbcd6ee4 100644 --- a/post-content.js +++ b/post-content.js @@ -72,7 +72,7 @@ window._wpGutenbergPost.content = { '', '', - '

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
', + '

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
', '', '', From 3863e50481efd3ffa0063d58fc80d01e63771ebb Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 27 Nov 2017 11:57:28 +0100 Subject: [PATCH 2/4] Framework: Rename "deprecatedVersions" to "deprecated" --- blocks/api/parser.js | 8 +++++--- blocks/api/test/parser.js | 2 +- blocks/library/quote/index.js | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/blocks/api/parser.js b/blocks/api/parser.js index 5fb86020d00965..94a1dfe92f4f03 100644 --- a/blocks/api/parser.js +++ b/blocks/api/parser.js @@ -181,10 +181,12 @@ export function createBlockWithFallback( name, innerHTML, attributes ) { // as invalid, or future serialization attempt results in an error block.originalContent = innerHTML; - // If the block is invalid, try to find an older compatible version - if ( ! block.isValid && blockType.deprecatedVersions ) { + // When a block is invalid, attempt to validate again 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.deprecatedVersions, ( oldBlockType ) => { + const hasValidOlderVersion = find( blockType.deprecated, ( oldBlockType ) => { const deprecatedBlockType = { ...omit( blockType, [ 'attributes', 'save', 'supports' ] ), // Parsing/Serialization properties ...oldBlockType, diff --git a/blocks/api/test/parser.js b/blocks/api/test/parser.js index 5dc1f226b8ef03..8ef32c002891d5 100644 --- a/blocks/api/test/parser.js +++ b/blocks/api/test/parser.js @@ -223,7 +223,7 @@ describe( 'block parser', () => { }, }, save: ( { attributes } ) =>
{attributes.fruit}
, - deprecatedVersions: [ + deprecated: [ { attributes: { fruit: { diff --git a/blocks/library/quote/index.js b/blocks/library/quote/index.js index f50ceb1f8951ff..12b4c830360f82 100644 --- a/blocks/library/quote/index.js +++ b/blocks/library/quote/index.js @@ -235,7 +235,7 @@ registerBlockType( 'core/quote', { ); }, - deprecatedVersions: [ + deprecated: [ { attributes: { ...blockAttributes, From 3f94460460520657d4f8aedb9e644546fce2754c Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 27 Nov 2017 14:28:21 +0100 Subject: [PATCH 3/4] Quote Block: Drop the blocks-quote-style-* styles in favor of a is-large modifier --- blocks/library/quote/editor.scss | 2 +- blocks/library/quote/index.js | 6 ++++-- blocks/library/quote/style.scss | 2 +- blocks/test/fixtures/core__quote__style-1.html | 2 +- blocks/test/fixtures/core__quote__style-1.json | 2 +- blocks/test/fixtures/core__quote__style-1.parsed.json | 2 +- blocks/test/fixtures/core__quote__style-1.serialized.html | 2 +- blocks/test/fixtures/core__quote__style-2.html | 2 +- blocks/test/fixtures/core__quote__style-2.json | 2 +- blocks/test/fixtures/core__quote__style-2.parsed.json | 2 +- blocks/test/fixtures/core__quote__style-2.serialized.html | 2 +- post-content.js | 2 +- 12 files changed, 15 insertions(+), 13 deletions(-) diff --git a/blocks/library/quote/editor.scss b/blocks/library/quote/editor.scss index 3ae1aec392e10f..ae2516556baf92 100644 --- a/blocks/library/quote/editor.scss +++ b/blocks/library/quote/editor.scss @@ -1,4 +1,4 @@ -.wp-block-quote.blocks-quote-style-1 { +.wp-block-quote:not(.is-large) { border-left: 4px solid $black; padding-left: 1em; } diff --git a/blocks/library/quote/index.js b/blocks/library/quote/index.js index 12b4c830360f82..3b7a6a10618499 100644 --- a/blocks/library/quote/index.js +++ b/blocks/library/quote/index.js @@ -2,6 +2,7 @@ * External dependencies */ import { isString, get } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies @@ -154,6 +155,7 @@ registerBlockType( 'core/quote', { edit( { attributes, setAttributes, focus, setFocus, mergeBlocks, className } ) { const { align, value, citation, style } = attributes; const focusedEditable = focus ? focus.editable || 'value' : null; + const containerClassname = classnames( className, style === 2 ? 'is-large' : '' ); return [ focus && ( @@ -183,7 +185,7 @@ registerBlockType( 'core/quote', { ),
{ value.map( ( paragraph, i ) => ( diff --git a/blocks/library/quote/style.scss b/blocks/library/quote/style.scss index 40cff9dfd3b693..ecdeb7380d5c29 100644 --- a/blocks/library/quote/style.scss +++ b/blocks/library/quote/style.scss @@ -10,7 +10,7 @@ font-style: normal; } - &.blocks-quote-style-2 { + &.is-large { padding: 0 1em; p { font-size: 24px; diff --git a/blocks/test/fixtures/core__quote__style-1.html b/blocks/test/fixtures/core__quote__style-1.html index d004ff822ed179..50f330921b5a34 100644 --- a/blocks/test/fixtures/core__quote__style-1.html +++ b/blocks/test/fixtures/core__quote__style-1.html @@ -1,3 +1,3 @@ -

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
+

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
diff --git a/blocks/test/fixtures/core__quote__style-1.json b/blocks/test/fixtures/core__quote__style-1.json index 3955e7a68359c5..bea152f89f32c6 100644 --- a/blocks/test/fixtures/core__quote__style-1.json +++ b/blocks/test/fixtures/core__quote__style-1.json @@ -23,6 +23,6 @@ ], "style": 1 }, - "originalContent": "

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
" + "originalContent": "

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
" } ] diff --git a/blocks/test/fixtures/core__quote__style-1.parsed.json b/blocks/test/fixtures/core__quote__style-1.parsed.json index 5e6780b29b5fbb..f09e5b0b9d384d 100644 --- a/blocks/test/fixtures/core__quote__style-1.parsed.json +++ b/blocks/test/fixtures/core__quote__style-1.parsed.json @@ -5,7 +5,7 @@ "style": "1" }, "innerBlocks": [], - "innerHTML": "\n

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
\n" + "innerHTML": "\n

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
\n" }, { "attrs": {}, diff --git a/blocks/test/fixtures/core__quote__style-1.serialized.html b/blocks/test/fixtures/core__quote__style-1.serialized.html index bda101357ff126..ad7516a48ae532 100644 --- a/blocks/test/fixtures/core__quote__style-1.serialized.html +++ b/blocks/test/fixtures/core__quote__style-1.serialized.html @@ -1,4 +1,4 @@ -
+

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
diff --git a/blocks/test/fixtures/core__quote__style-2.html b/blocks/test/fixtures/core__quote__style-2.html index fb29b5cec5fb7f..544a6062c1d802 100644 --- a/blocks/test/fixtures/core__quote__style-2.html +++ b/blocks/test/fixtures/core__quote__style-2.html @@ -1,3 +1,3 @@ -

There is no greater agony than bearing an untold story inside you.

Maya Angelou
+

There is no greater agony than bearing an untold story inside you.

Maya Angelou
diff --git a/blocks/test/fixtures/core__quote__style-2.json b/blocks/test/fixtures/core__quote__style-2.json index fd8f3b0e6425f0..ea48f03aef42fe 100644 --- a/blocks/test/fixtures/core__quote__style-2.json +++ b/blocks/test/fixtures/core__quote__style-2.json @@ -23,6 +23,6 @@ ], "style": 2 }, - "originalContent": "

There is no greater agony than bearing an untold story inside you.

Maya Angelou
" + "originalContent": "

There is no greater agony than bearing an untold story inside you.

Maya Angelou
" } ] diff --git a/blocks/test/fixtures/core__quote__style-2.parsed.json b/blocks/test/fixtures/core__quote__style-2.parsed.json index be323c894a95f2..5af57f9cc0706d 100644 --- a/blocks/test/fixtures/core__quote__style-2.parsed.json +++ b/blocks/test/fixtures/core__quote__style-2.parsed.json @@ -5,7 +5,7 @@ "style": "2" }, "innerBlocks": [], - "innerHTML": "\n

There is no greater agony than bearing an untold story inside you.

Maya Angelou
\n" + "innerHTML": "\n

There is no greater agony than bearing an untold story inside you.

Maya Angelou
\n" }, { "attrs": {}, diff --git a/blocks/test/fixtures/core__quote__style-2.serialized.html b/blocks/test/fixtures/core__quote__style-2.serialized.html index 1cadb4d22a93e3..f1256d9f8952ea 100644 --- a/blocks/test/fixtures/core__quote__style-2.serialized.html +++ b/blocks/test/fixtures/core__quote__style-2.serialized.html @@ -1,4 +1,4 @@ -
+

There is no greater agony than bearing an untold story inside you.

Maya Angelou
diff --git a/post-content.js b/post-content.js index 1302dcdbcd6ee4..e993898bd94dd0 100644 --- a/post-content.js +++ b/post-content.js @@ -72,7 +72,7 @@ window._wpGutenbergPost.content = { '', '', - '

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
', + '

The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.

Matt Mullenweg, 2017
', '', '', From bd6f99cb0ae5578408df8f79305c2d786a298e68 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 28 Nov 2017 08:52:09 +0100 Subject: [PATCH 4/4] Parser: Extract the deprecated versions handler to a separate function --- blocks/api/parser.js | 49 +++++++++++++++++-------- blocks/api/test/parser.js | 77 +++++++++++++++++++++++++++++++++++---- 2 files changed, 102 insertions(+), 24 deletions(-) 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..0ef4a373566931 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 ); @@ -222,7 +288,7 @@ describe( 'block parser', () => { selector: 'div', }, }, - save: ( { attributes } ) =>
{attributes.fruit}
, + save: ( { attributes } ) =>
{ attributes.fruit }
, deprecated: [ { attributes: { @@ -232,7 +298,7 @@ describe( 'block parser', () => { selector: 'span', }, }, - save: ( { attributes } ) => {attributes.fruit}, + save: ( { attributes } ) => { attributes.fruit }, }, ], } ); @@ -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(); } ); } );