Skip to content

Commit

Permalink
Framework: Use custom serializer for texturize compatibility (#5897)
Browse files Browse the repository at this point in the history
* Blocks: Improve validation to ensure decoded equivalence

* Block API: Allow boolean attribute equivalence by presence

* Block API: Update and dedupe attribute set

* Framework: Create custom React serializer

* Element: Render explicitly empty attribute values

* Element: Render RawHTML dangerously from serializer
  • Loading branch information
aduth authored Apr 3, 2018
1 parent c5d419c commit fd08045
Show file tree
Hide file tree
Showing 14 changed files with 1,144 additions and 50 deletions.
45 changes: 43 additions & 2 deletions blocks/api/test/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,17 @@ describe( 'validation', () => {
expect( isEqual ).toBe( false );
} );
} );

describe( 'boolean attributes', () => {
it( 'returns true if both present', () => {
const isEqual = isEqualAttributesOfName.controls(
'true',
''
);

expect( isEqual ).toBe( true );
} );
} );
} );

describe( 'isEqualTagAttributePairs()', () => {
Expand All @@ -211,10 +222,12 @@ describe( 'validation', () => {
[
[ 'class', 'b a c' ],
[ 'style', 'color: red; background-image: url( "https://wordpress.org/img.png" );' ],
[ 'controls', '' ],
],
[
[ 'class', 'c a b' ],
[ 'style', 'background-image: url( "https://wordpress.org/img.png" ); color: red;' ],
[ 'controls', 'true' ],
]
);

Expand Down Expand Up @@ -316,8 +329,8 @@ describe( 'validation', () => {

it( 'should return true for effectively equivalent html', () => {
const isEquivalent = isEquivalentHTML(
'<div>Hello<span class="b a" id="foo"> World!</ span> </div>',
'<div >Hello\n<span id="foo" class="a b">World!</span></div>'
'<div>&quot; Hello<span class="b a" id="foo"> World!</ span> "</div>',
'<div >" Hello\n<span id="foo" class="a b">World!</span>"</div>'
);

expect( isEquivalent ).toBe( true );
Expand Down Expand Up @@ -399,6 +412,34 @@ describe( 'validation', () => {
expect( console ).toHaveWarned();
expect( isEquivalent ).toBe( false );
} );

it( 'should return false when difference of boolean attribute', () => {
const isEquivalent = isEquivalentHTML(
'<video controls></video>',
'<video></video>'
);

expect( console ).toHaveWarned();
expect( isEquivalent ).toBe( false );
} );

it( 'should return true when same boolean attribute', () => {
const isEquivalent = isEquivalentHTML(
'<video controls></video>',
'<video controls></video>'
);

expect( isEquivalent ).toBe( true );
} );

it( 'should return true when effectively same boolean attribute', () => {
const isEquivalent = isEquivalentHTML(
'<video controls></video>',
'<video controls=""></video>'
);

expect( isEquivalent ).toBe( true );
} );
} );

describe( 'isValidBlock()', () => {
Expand Down
29 changes: 17 additions & 12 deletions blocks/api/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import { tokenize } from 'simple-html-tokenizer';
import { xor, fromPairs, isEqual, includes } from 'lodash';
import { xor, fromPairs, isEqual, includes, stubTrue } from 'lodash';

/**
* Internal dependencies
Expand Down Expand Up @@ -37,9 +37,11 @@ const REGEXP_STYLE_URL_TYPE = /^url\s*\(['"\s]*(.*?)['"\s]*\)$/;
* See: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes
* Extracted from: https://html.spec.whatwg.org/multipage/indices.html#attributes-3
*
* [ ...document.querySelectorAll( '#attributes-1 > tbody > tr' ) ]
* Object.keys( [ ...document.querySelectorAll( '#attributes-1 > tbody > tr' ) ]
* .filter( ( tr ) => tr.lastChild.textContent.indexOf( 'Boolean attribute' ) !== -1 )
* .map( ( tr ) => tr.firstChild.textContent.trim() )
* .reduce( ( result, tr ) => Object.assign( result, {
* [ tr.firstChild.textContent.trim() ]: true
* } ), {} ) ).sort();
*
* @type {Array}
*/
Expand All @@ -65,7 +67,6 @@ const BOOLEAN_ATTRIBUTES = [
'nomodule',
'novalidate',
'open',
'open',
'playsinline',
'readonly',
'required',
Expand All @@ -82,35 +83,36 @@ const BOOLEAN_ATTRIBUTES = [
* See: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#enumerated-attribute
* Extracted from: https://html.spec.whatwg.org/multipage/indices.html#attributes-3
*
* [ ...document.querySelectorAll( '#attributes-1 > tbody > tr' ) ]
* filter( ( tr ) => /("(.+?)";?\s*)+/.test( tr.lastChild.textContent ) )
* .map( ( tr ) => tr.firstChild.textContent.trim() )
* Object.keys( [ ...document.querySelectorAll( '#attributes-1 > tbody > tr' ) ]
* .filter( ( tr ) => /^("(.+?)";?\s*)+/.test( tr.lastChild.textContent.trim() ) )
* .reduce( ( result, tr ) => Object.assign( result, {
* [ tr.firstChild.textContent.trim() ]: true
* } ), {} ) ).sort();
*
* @type {Array}
*/
const ENUMERATED_ATTRIBUTES = [
'autocapitalize',
'autocomplete',
'charset',
'contenteditable',
'crossorigin',
'dir',
'decoding',
'dir',
'draggable',
'enctype',
'formenctype',
'formmethod',
'http-equiv',
'inputmode',
'kind',
'method',
'preload',
'sandbox',
'scope',
'shape',
'spellcheck',
'step',
'translate',
'type',
'type',
'workertype',
'wrap',
];

Expand Down Expand Up @@ -281,6 +283,9 @@ export const isEqualAttributesOfName = {
style: ( actual, expected ) => {
return isEqual( ...[ actual, expected ].map( getStyleProperties ) );
},
// For each boolean attribute, mere presence of attribute in both is enough
// to assume equivalence.
...fromPairs( BOOLEAN_ATTRIBUTES.map( ( attribute ) => [ attribute, stubTrue ] ) ),
};

/**
Expand Down
2 changes: 1 addition & 1 deletion blocks/test/fixtures/core__audio.serialized.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<!-- wp:audio {"align":"right"} -->
<figure class="wp-block-audio alignright"><audio controls="" src="https://media.simplecast.com/episodes/audio/80564/draft-podcast-51-livePublish2.mp3"></audio></figure>
<figure class="wp-block-audio alignright"><audio controls src="https://media.simplecast.com/episodes/audio/80564/draft-podcast-51-livePublish2.mp3"></audio></figure>
<!-- /wp:audio -->
2 changes: 1 addition & 1 deletion blocks/test/fixtures/core__code.serialized.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- wp:code -->
<pre class="wp-block-code"><code>export default function MyButton() {
return &lt;Button&gt;Click Me!&lt;/Button&gt;;
return &lt;Button>Click Me!&lt;/Button>;
}</code></pre>
<!-- /wp:code -->
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- wp:image {"align":"center"} -->
<figure class="wp-block-image aligncenter"><img src="https://cldup.com/YLYhpou2oq.jpg" alt="" />
<figcaption>Give it a try. Press the &quot;really wide&quot; button on the image toolbar.</figcaption>
<figcaption>Give it a try. Press the "really wide" button on the image toolbar.</figcaption>
</figure>
<!-- /wp:image -->
2 changes: 1 addition & 1 deletion blocks/test/fixtures/core__video.serialized.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<!-- wp:video -->
<figure class="wp-block-video"><video controls="" src="https://awesome-fake.video/file.mp4"></video></figure>
<figure class="wp-block-video"><video controls src="https://awesome-fake.video/file.mp4"></video></figure>
<!-- /wp:video -->
15 changes: 6 additions & 9 deletions element/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/
import { createContext, createElement, Component, cloneElement, Children, Fragment } from 'react';
import { render, findDOMNode, createPortal, unmountComponentAtNode } from 'react-dom';
import { renderToStaticMarkup } from 'react-dom/server';
import {
camelCase,
flowRight,
Expand All @@ -12,6 +11,11 @@ import {
isEmpty,
} from 'lodash';

/**
* Internal dependencies
*/
import serialize from './serialize';

/**
* Returns a new element of given type. Type can be either a string tag name or
* another function which itself returns an element.
Expand Down Expand Up @@ -97,14 +101,7 @@ export { createPortal };
*
* @return {string} HTML.
*/
export function renderToString( element ) {
let rendered = renderToStaticMarkup( element );

// Drop raw HTML wrappers (support dangerous inner HTML without wrapper)
rendered = rendered.replace( /<\/?wp-raw-html>/g, '' );

return rendered;
}
export { serialize as renderToString };

/**
* Concatenate two or more React children objects.
Expand Down
Loading

0 comments on commit fd08045

Please sign in to comment.