diff --git a/blocks/test/fixtures/README.md b/blocks/test/fixtures/README.md new file mode 100644 index 00000000000000..b17018f85c88c2 --- /dev/null +++ b/blocks/test/fixtures/README.md @@ -0,0 +1,46 @@ +## Full post content test fixtures + +This directory contains sets of fixture files that are used to test the parsing +and serialization logic. + +Each test is made up of three fixture files: + +1. `fixture-name.html`: The initial post content. +2. `fixture-name.json`: The **expected** parsed representation of the block(s) + inside the post content, along with their attributes and any nested content. + The contents of this file are compared against the **actual** parsed block + object(s). +3. `fixture-name.serialized.html`: The **expected** result of calling + `serialize` on the parsed block object(s). The contents of this file are + compared against the **actual** re-serialized post content. This final step + simulates opening and re-saving a post. + +Every block is required to have at least one such set of fixture files to test +the parsing and serialization of that block. These fixtures must be named like +`core-blockname{-*,}.{html,json,serialized.html}`. For example, for the +`core/image` block, the following three fixture files must exist: + +1. `core-image.html` (or `core-image-specific-test-name.html`). Must contain a + `` block. +2. `core-image.json` (or `core-image-specific-test-name.html`). +3. `core-image.serialized.html` (or + `core-image-specific-test-name.serialized.html`). + +Ideally all important attributes and features of the block should be tested +this way. New contributions in the form of additional test cases are always +welcome - this is a great way for us to identify bugs and prevent them from +recurring in the future. + +When adding a new test, it's only necessary to create file (1) above, then +there is a command you can run to generate (2) and (3): + +```sh +GENERATE_MISSING_FIXTURES=y npm run test-unit -- --grep 'full post content fixture' +``` + +However, when using this command, please be sure to manually verify that the +contents of the `.json` and `.serialized.html` files are as expected. + +See the +[`full-content.js`](../full-content.js) +test file for the code that runs these tests. diff --git a/blocks/test/fixtures/core-button-center.html b/blocks/test/fixtures/core-button-center.html new file mode 100644 index 00000000000000..e19e0ed3fc4d6e --- /dev/null +++ b/blocks/test/fixtures/core-button-center.html @@ -0,0 +1,3 @@ + +
+ diff --git a/blocks/test/fixtures/core-button-center.json b/blocks/test/fixtures/core-button-center.json new file mode 100644 index 00000000000000..9cc92bc17c4b8a --- /dev/null +++ b/blocks/test/fixtures/core-button-center.json @@ -0,0 +1,13 @@ +[ + { + "uid": "_uid_0", + "blockType": "core/button", + "attributes": { + "align": "center", + "url": "https://github.com/WordPress/gutenberg", + "text": [ + "Help build Gutenberg" + ] + } + } +] diff --git a/blocks/test/fixtures/core-button-center.serialized.html b/blocks/test/fixtures/core-button-center.serialized.html new file mode 100644 index 00000000000000..c360f9e247a91c --- /dev/null +++ b/blocks/test/fixtures/core-button-center.serialized.html @@ -0,0 +1,4 @@ + + + + diff --git a/blocks/test/fixtures/core-embed-youtube-caption.html b/blocks/test/fixtures/core-embed-youtube-caption.html new file mode 100644 index 00000000000000..3e6f600b254d68 --- /dev/null +++ b/blocks/test/fixtures/core-embed-youtube-caption.html @@ -0,0 +1,3 @@ + +
+ 
+
+
diff --git a/blocks/test/fixtures/core-list-ul.html b/blocks/test/fixtures/core-list-ul.html
new file mode 100644
index 00000000000000..aef6bf669f2c98
--- /dev/null
+++ b/blocks/test/fixtures/core-list-ul.html
@@ -0,0 +1,3 @@
+
+Some preformatted text...+ diff --git a/blocks/test/fixtures/core-preformatted.json b/blocks/test/fixtures/core-preformatted.json new file mode 100644 index 00000000000000..083a138132eb07 --- /dev/null +++ b/blocks/test/fixtures/core-preformatted.json @@ -0,0 +1,20 @@ +[ + { + "uid": "_uid_0", + "blockType": "core/preformatted", + "attributes": { + "content": [ + "Some ", + { + "type": "em", + "children": "preformatted" + }, + " text...", + { + "type": "br" + }, + "And more!" + ] + } + } +] diff --git a/blocks/test/fixtures/core-preformatted.serialized.html b/blocks/test/fixtures/core-preformatted.serialized.html new file mode 100644 index 00000000000000..b119b914cf0e42 --- /dev/null +++ b/blocks/test/fixtures/core-preformatted.serialized.html @@ -0,0 +1,4 @@ + +
And more!
Some preformatted text...+ + diff --git a/blocks/test/fixtures/core-pullquote.html b/blocks/test/fixtures/core-pullquote.html new file mode 100644 index 00000000000000..027188bce2960d --- /dev/null +++ b/blocks/test/fixtures/core-pullquote.html @@ -0,0 +1,5 @@ + +
And more!
++ diff --git a/blocks/test/fixtures/core-pullquote.json b/blocks/test/fixtures/core-pullquote.json new file mode 100644 index 00000000000000..be58e7d4167f1f --- /dev/null +++ b/blocks/test/fixtures/core-pullquote.json @@ -0,0 +1,16 @@ +[ + { + "uid": "_uid_0", + "blockType": "core/pullquote", + "attributes": { + "value": [ + [ + "Testing pullquote block..." + ] + ], + "citation": [ + "...with a caption" + ] + } + } +] diff --git a/blocks/test/fixtures/core-pullquote.serialized.html b/blocks/test/fixtures/core-pullquote.serialized.html new file mode 100644 index 00000000000000..5a75e75e7bc2cc --- /dev/null +++ b/blocks/test/fixtures/core-pullquote.serialized.html @@ -0,0 +1,7 @@ + +Testing pullquote block...
+
++ + diff --git a/blocks/test/fixtures/core-quote-style-1.html b/blocks/test/fixtures/core-quote-style-1.html new file mode 100644 index 00000000000000..e8c9add2748c63 --- /dev/null +++ b/blocks/test/fixtures/core-quote-style-1.html @@ -0,0 +1,3 @@ + +Testing pullquote block...
+ +
+ diff --git a/blocks/test/fixtures/core-quote-style-1.json b/blocks/test/fixtures/core-quote-style-1.json new file mode 100644 index 00000000000000..b109c2c5deeb99 --- /dev/null +++ b/blocks/test/fixtures/core-quote-style-1.json @@ -0,0 +1,17 @@ +[ + { + "uid": "_uid_0", + "blockType": "core/quote", + "attributes": { + "style": "1", + "value": [ + [ + "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." + ] + ], + "citation": [ + "Matt Mullenweg, 2017" + ] + } + } +] diff --git a/blocks/test/fixtures/core-quote-style-1.serialized.html b/blocks/test/fixtures/core-quote-style-1.serialized.html new file mode 100644 index 00000000000000..e38001b88f29a9 --- /dev/null +++ b/blocks/test/fixtures/core-quote-style-1.serialized.html @@ -0,0 +1,7 @@ + +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.
++ + diff --git a/blocks/test/fixtures/core-quote-style-2.html b/blocks/test/fixtures/core-quote-style-2.html new file mode 100644 index 00000000000000..b8c255beee8fe7 --- /dev/null +++ b/blocks/test/fixtures/core-quote-style-2.html @@ -0,0 +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.
+ +
+ diff --git a/blocks/test/fixtures/core-quote-style-2.json b/blocks/test/fixtures/core-quote-style-2.json new file mode 100644 index 00000000000000..daf838c3e648d0 --- /dev/null +++ b/blocks/test/fixtures/core-quote-style-2.json @@ -0,0 +1,17 @@ +[ + { + "uid": "_uid_0", + "blockType": "core/quote", + "attributes": { + "style": "2", + "value": [ + [ + "There is no greater agony than bearing an untold story inside you." + ] + ], + "citation": [ + "Maya Angelou" + ] + } + } +] diff --git a/blocks/test/fixtures/core-quote-style-2.serialized.html b/blocks/test/fixtures/core-quote-style-2.serialized.html new file mode 100644 index 00000000000000..6d5b9988eb347c --- /dev/null +++ b/blocks/test/fixtures/core-quote-style-2.serialized.html @@ -0,0 +1,7 @@ + +There is no greater agony than bearing an untold story inside you.
++ + diff --git a/blocks/test/fixtures/core-separator.html b/blocks/test/fixtures/core-separator.html new file mode 100644 index 00000000000000..a241e07204f9aa --- /dev/null +++ b/blocks/test/fixtures/core-separator.html @@ -0,0 +1,3 @@ + +There is no greater agony than bearing an untold story inside you.
+ +
| Version | +Musician | +Date | +
|---|---|---|
| .70 | +No musician chosen. | +May 27, 2003 | +
| 1.0 | +Miles Davis | +January 3, 2004 | +
| Lots of versions skipped, see the full list | +… | +… | +
| 4.4 | +Clifford Brown | +December 8, 2015 | +
| 4.5 | +Coleman Hawkins | +April 12, 2016 | +
| 4.6 | +Pepper Adams | +August 16, 2016 | +
| 4.7 | +Sarah Vaughan | +December 6, 2016 | +
| Version | +Musician | +Date | +
|---|---|---|
| .70 | +No musician chosen. | +May 27, 2003 | +
| 1.0 | +Miles Davis | +January 3, 2004 | +
| Lots of versions skipped, see the full list | +… | +… | +
| 4.4 | +Clifford Brown | +December 8, 2015 | +
| 4.5 | +Coleman Hawkins | +April 12, 2016 | +
| 4.6 | +Pepper Adams | +August 16, 2016 | +
| 4.7 | +Sarah Vaughan | +December 6, 2016 | +
... like this one, which is separate from the above and right aligned.
+ diff --git a/blocks/test/fixtures/core-text-align-right.json b/blocks/test/fixtures/core-text-align-right.json new file mode 100644 index 00000000000000..c7fd3846c45857 --- /dev/null +++ b/blocks/test/fixtures/core-text-align-right.json @@ -0,0 +1,19 @@ +[ + { + "uid": "_uid_0", + "blockType": "core/text", + "attributes": { + "content": [ + { + "type": "p", + "attributes": { + "style": { + "textAlign": "right" + } + }, + "children": "... like this one, which is separate from the above and right aligned." + } + ] + } + } +] diff --git a/blocks/test/fixtures/core-text-align-right.serialized.html b/blocks/test/fixtures/core-text-align-right.serialized.html new file mode 100644 index 00000000000000..5a483bb238e29b --- /dev/null +++ b/blocks/test/fixtures/core-text-align-right.serialized.html @@ -0,0 +1,4 @@ + +... like this one, which is separate from the above and right aligned.
+ + diff --git a/blocks/test/fixtures/core-text-multi-paragraph.html b/blocks/test/fixtures/core-text-multi-paragraph.html new file mode 100644 index 00000000000000..1c48f39fac082e --- /dev/null +++ b/blocks/test/fixtures/core-text-multi-paragraph.html @@ -0,0 +1,4 @@ + +The goal of this new editor is to make adding rich content to WordPress simple and enjoyable. This whole post is composed of pieces of content—somewhat similar to LEGO bricks—that you can move around and interact with. Move your cursor around and you'll notice the different blocks light up with outlines and arrows. Press the arrows to reposition blocks quickly, without fearing about losing things in the process of copying and pasting.
+What you are reading now is a text block, the most basic block of all. A text block can have multiple paragraphs, if that's how you prefer to write your posts. But you can also split it by hitting enter twice. Once blocks are split they get their own controls to be moved freely around the post...
+ diff --git a/blocks/test/fixtures/core-text-multi-paragraph.json b/blocks/test/fixtures/core-text-multi-paragraph.json new file mode 100644 index 00000000000000..e1afda57342e00 --- /dev/null +++ b/blocks/test/fixtures/core-text-multi-paragraph.json @@ -0,0 +1,32 @@ +[ + { + "uid": "_uid_0", + "blockType": "core/text", + "attributes": { + "content": [ + { + "type": "p", + "children": [ + "The goal of this new editor is to make adding rich content to WordPress simple and enjoyable. This whole post is composed of ", + { + "type": "em", + "children": "pieces of content" + }, + "—somewhat similar to LEGO bricks—that you can move around and interact with. Move your cursor around and you'll notice the different blocks light up with outlines and arrows. Press the arrows to reposition blocks quickly, without fearing about losing things in the process of copying and pasting." + ] + }, + { + "type": "p", + "children": [ + "What you are reading now is a ", + { + "type": "strong", + "children": "text block" + }, + ", the most basic block of all. A text block can have multiple paragraphs, if that's how you prefer to write your posts. But you can also split it by hitting enter twice. Once blocks are split they get their own controls to be moved freely around the post..." + ] + } + ] + } + } +] diff --git a/blocks/test/fixtures/core-text-multi-paragraph.serialized.html b/blocks/test/fixtures/core-text-multi-paragraph.serialized.html new file mode 100644 index 00000000000000..e9ab7de13a1cbe --- /dev/null +++ b/blocks/test/fixtures/core-text-multi-paragraph.serialized.html @@ -0,0 +1,5 @@ + +The goal of this new editor is to make adding rich content to WordPress simple and enjoyable. This whole post is composed of pieces of content—somewhat similar to LEGO bricks—that you can move around and interact with. Move your cursor around and you'll notice the different blocks light up with outlines and arrows. Press the arrows to reposition blocks quickly, without fearing about losing things in the process of copying and pasting.
+What you are reading now is a text block, the most basic block of all. A text block can have multiple paragraphs, if that's how you prefer to write your posts. But you can also split it by hitting enter twice. Once blocks are split they get their own controls to be moved freely around the post...
+ + diff --git a/blocks/test/full-content.js b/blocks/test/full-content.js new file mode 100644 index 00000000000000..747d04a1d952c1 --- /dev/null +++ b/blocks/test/full-content.js @@ -0,0 +1,181 @@ +/** + * External dependencies + */ +import fs from 'fs'; +import path from 'path'; +import { uniq, isObject, omit, startsWith } from 'lodash'; +import { expect } from 'chai'; +import { format } from 'util'; + +/** + * Internal dependencies + */ +import { + // parseWithGrammar, + parseWithTinyMCE, +} from '../api/parser'; +import serialize from '../api/serializer'; +import { getBlocks } from '../api/registration'; + +const fixturesDir = path.join( __dirname, 'fixtures' ); + +// We expect 3 different types of files for each fixture: +// - fixture.html - original content +// - fixture.json - blocks structure +// - fixture.serialized.html - re-serialized content +// Get the "base" name for each fixture first. +const fileBasenames = uniq( + fs.readdirSync( fixturesDir ) + .filter( f => /(\.html|\.json)$/.test( f ) ) + .map( f => f.replace( /\..+$/, '' ) ) +); + +function readFixtureFile( filename ) { + try { + return fs.readFileSync( + path.join( fixturesDir, filename ), + 'utf8' + ); + } catch ( err ) { + return null; + } +} + +function writeFixtureFile( filename, content ) { + fs.writeFileSync( + path.join( fixturesDir, filename ), + content + ); +} + +function normalizeReactTree( element ) { + if ( Array.isArray( element ) ) { + return element.map( child => normalizeReactTree( child ) ); + } + + if ( isObject( element ) ) { + const toReturn = { + type: element.type, + }; + const attributes = omit( element.props, 'children' ); + if ( Object.keys( attributes ).length ) { + toReturn.attributes = attributes; + } + if ( element.props.children ) { + toReturn.children = normalizeReactTree( element.props.children ); + } + return toReturn; + } + + return element; +} + +function normalizeParsedBlocks( blocks ) { + return blocks.map( ( block, index ) => { + // Clone and remove React-instance-specific stuff; also, attribute + // values that equal `undefined` will be removed + block = JSON.parse( JSON.stringify( block ) ); + // Change unique UIDs to a predictable value + block.uid = '_uid_' + index; + // Walk each attribute and get a more concise representation of any + // React elements + for ( const k in block.attributes ) { + block.attributes[ k ] = normalizeReactTree( block.attributes[ k ] ); + } + return block; + } ); +} + +describe( 'full post content fixture', () => { + fileBasenames.forEach( f => { + it( f, () => { + const content = readFixtureFile( f + '.html' ); + + const blocksActual = parseWithTinyMCE( content ); + const blocksActualNormalized = normalizeParsedBlocks( blocksActual ); + let blocksExpectedString = readFixtureFile( f + '.json' ); + + if ( ! blocksExpectedString ) { + if ( process.env.GENERATE_MISSING_FIXTURES ) { + blocksExpectedString = JSON.stringify( + blocksActualNormalized, + null, + 4 + ) + '\n'; + writeFixtureFile( f + '.json', blocksExpectedString ); + } else { + throw new Error( + 'Missing fixture file: ' + f + '.json' + ); + } + } + + const blocksExpected = JSON.parse( blocksExpectedString ); + expect( blocksActualNormalized ).to.eql( blocksExpected ); + + const serializedActual = serialize( blocksActual ); + let serializedExpected = readFixtureFile( f + '.serialized.html' ); + + if ( ! serializedExpected ) { + if ( process.env.GENERATE_MISSING_FIXTURES ) { + serializedExpected = serializedActual; + writeFixtureFile( f + '.serialized.html', serializedExpected ); + } else { + throw new Error( + 'Missing fixture file: ' + f + '.serialized.html' + ); + } + } + + expect( serializedActual ).to.eql( serializedExpected ); + } ); + } ); + + it( 'should be present for each block', () => { + const errors = []; + + getBlocks().map( block => block.slug ).forEach( slug => { + const slugToFilename = slug.replace( /\//g, '-' ); + const foundFixtures = fileBasenames + .filter( basename => ( + basename === slugToFilename || + startsWith( basename, slugToFilename + '-' ) + ) ) + .map( basename => { + const filename = basename + '.html'; + return { + filename, + contents: readFixtureFile( filename ), + }; + } ) + .filter( fixture => fixture.contents !== null ); + + if ( ! foundFixtures.length ) { + errors.push( format( + 'Expected a fixture file called \'%s.html\' or \'%s-*.html\'.', + slugToFilename, + slugToFilename + ) ); + } + + foundFixtures.forEach( fixture => { + const delimiter = new RegExp( + ')' + ); + if ( ! delimiter.test( fixture.contents ) ) { + errors.push( format( + 'Expected fixture file \'%s\' to test the \'%s\' block.', + fixture.filename, + slug + ) ); + } + } ); + } ); + + if ( errors.length ) { + throw new Error( + 'Problem(s) with fixture files:\n\n' + errors.join( '\n' ) + ); + } + } ); +} ); diff --git a/webpack.config.js b/webpack.config.js index fd9feb954f5f9d..2847c01cb131a3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -96,6 +96,9 @@ switch ( process.env.NODE_ENV ) { case 'test': config.target = 'node'; + config.node = { + __dirname: true, + }; config.module.rules = [ ...[ 'i18n', 'element', 'blocks', 'editor' ].map( ( entry ) => ( { test: require.resolve( './' + entry + '/index.js' ), @@ -103,12 +106,16 @@ switch ( process.env.NODE_ENV ) { } ) ), ...config.module.rules, ]; + const testFiles = glob.sync( + './{' + Object.keys( config.entry ).sort() + '}/**/test/*.js' + ); config.entry = [ './i18n/index.js', './element/index.js', './blocks/index.js', './editor/index.js', - ...glob.sync( `./{${ Object.keys( config.entry ).join() }}/**/test/*.js` ), + ...testFiles.filter( f => /full-content\.js$/.test( f ) ), + ...testFiles.filter( f => ! /full-content\.js$/.test( f ) ), ]; config.externals = [ require( 'webpack-node-externals' )() ]; config.output = {