Skip to content

Commit

Permalink
Merge pull request #319 from WordPress/update/block-api
Browse files Browse the repository at this point in the history
Implement createBlockElement for basic block rendering
  • Loading branch information
aduth authored Mar 23, 2017
2 parents c18d4f7 + 5ef8198 commit e20441a
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 43 deletions.
3 changes: 2 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
} ]
],
"plugins": [
"transform-runtime"
"transform-runtime",
"transform-object-rest-spread"
],
"env": {
"test": {
Expand Down
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"root": true,
"parser": "babel-eslint",
"extends": "wordpress",
"env": {
"browser": true,
Expand Down
22 changes: 14 additions & 8 deletions bootstrap-test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
/**
* External dependencies
*/
import chai from 'chai';
import dirtyChai from 'dirty-chai';
import sinonChai from 'sinon-chai';
// Chai plugins
require( 'chai' )
.use( require( 'dirty-chai' ) )
.use( require( 'sinon-chai' ) );

chai.use( dirtyChai );
chai.use( sinonChai );
// Fake DOM
global.document = require( 'jsdom' ).jsdom( '', {
features: {
FetchExternalResources: false,
ProcessExternalResources: false,
SkipExternalResources: true
}
} );
global.window = document.defaultView;
global.navigator = window.navigator;
4 changes: 2 additions & 2 deletions modules/blocks/components/editable/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default function Editable() {

export default function Editable( { value } ) {
return wp.element.createElement( 'p', null, value );
}
36 changes: 36 additions & 0 deletions modules/blocks/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
/* eslint-disable no-console */

/**
* External dependencies
*/
import * as query from 'hpq';

export { query };
export { default as Editable } from './components/editable';
export { parse } from './post.pegjs';

Expand Down Expand Up @@ -79,3 +85,33 @@ export function getBlockSettings( slug ) {
export function getBlocks() {
return Object.values( blocks );
}

/**
* Returns the element of a registered block node given a context and its
* parsed metadata.
*
* @param {Object} blockNode Parsed block node
* @param {String} context Render context ("edit", "save")
* @return {?WPElement} Block element, or undefined if type unknown
*/
export function createBlockElement( blockNode, context = 'edit' ) {
const { blockType, rawContent } = blockNode;

// Verify block is of known type
const block = getBlockSettings( blockType );
if ( ! block ) {
return;
}

// Merge attributes from parse with block implementation
let { attrs } = blockNode;
if ( 'function' === typeof block.attributes ) {
attrs = { ...attrs, ...block.attributes( rawContent ) };
} else if ( block.attributes ) {
attrs = { ...attrs, ...query.parse( rawContent, block.attributes ) };
}

if ( 'function' === typeof block[ context ] ) {
return block[ context ]( attrs );
}
}
76 changes: 71 additions & 5 deletions modules/blocks/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import sinon from 'sinon';
*/
import * as blocks from '../';

describe( 'blocks API', () => {
describe( 'blocks', () => {
// Reset block state before each test.
beforeEach( () => {
blocks.getBlocks().forEach( block => {
Expand All @@ -24,7 +24,7 @@ describe( 'blocks API', () => {
console.error.restore();
} );

describe( 'registerBlock', () => {
describe( 'registerBlock()', () => {
it( 'should reject numbers', () => {
const block = blocks.registerBlock( 999 );
expect( console.error ).to.have.been.calledWith( 'Block slugs must be strings.' );
Expand Down Expand Up @@ -67,7 +67,7 @@ describe( 'blocks API', () => {
} );
} );

describe( 'unregisterBlock', () => {
describe( 'unregisterBlock()', () => {
it( 'should fail if a block is not registered', () => {
const oldBlock = blocks.unregisterBlock( 'core/test-block' );
expect( console.error ).to.have.been.calledWith( 'Block "core/test-block" is not registered.' );
Expand All @@ -86,7 +86,7 @@ describe( 'blocks API', () => {
} );
} );

describe( 'getBlockSettings', () => {
describe( 'getBlockSettings()', () => {
it( 'should return { slug } for blocks with no settings', () => {
blocks.registerBlock( 'core/test-block' );
expect( blocks.getBlockSettings( 'core/test-block' ) ).to.eql( {
Expand All @@ -104,7 +104,7 @@ describe( 'blocks API', () => {
} );
} );

describe( 'getBlocks', () => {
describe( 'getBlocks()', () => {
it( 'should return an empty array at first', () => {
expect( blocks.getBlocks() ).to.eql( [] );
} );
Expand All @@ -119,4 +119,70 @@ describe( 'blocks API', () => {
] );
} );
} );

describe( 'createBlockElement()', () => {
it( 'should return undefined if block is not registered', () => {
const element = blocks.createBlockElement( {
blockType: 'core/test-block',
attrs: {},
rawContent: 'Ribs'
}, 'edit' );

expect( element ).to.be.undefined();
} );

it( 'should return undefined if render context is not valid', () => {
blocks.registerBlock( 'core/test-block', {} );

const element = blocks.createBlockElement( {
blockType: 'core/test-block',
attrs: {},
rawContent: 'Ribs'
}, 'edit' );

expect( element ).to.be.undefined();
} );

it( 'should merge attributes from function implementation', () => {
blocks.registerBlock( 'core/test-block', {
attributes: function( rawContent ) {
return {
content: rawContent + ' & Chicken'
};
},
edit: function( attributes ) {
return attributes.content;
}
} );

const element = blocks.createBlockElement( {
blockType: 'core/test-block',
attrs: {},
rawContent: 'Ribs'
}, 'edit' );

expect( element ).to.equal( 'Ribs & Chicken' );
} );

it( 'should merge attributes from query object implementation', () => {
const { text } = blocks.query;

blocks.registerBlock( 'core/test-block', {
attributes: {
emphasis: text( 'strong' )
},
edit: function( attributes ) {
return attributes.emphasis;
}
} );

const element = blocks.createBlockElement( {
blockType: 'core/test-block',
attrs: {},
rawContent: '<span>Ribs <strong>& Chicken</strong></span>'
}, 'edit' );

expect( element ).to.equal( '& Chicken' );
} );
} );
} );
15 changes: 11 additions & 4 deletions modules/editor/blocks/text-block/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
const { query, html } = wp.blocks.query;

wp.blocks.registerBlock( 'wp/text', {
edit( state, onChange ) {
attributes: {
value: query( 'p', html() )
},

edit( attributes, onChange ) {
return wp.element.createElement( wp.blocks.Editable, {
value: state.value,
value: attributes.value,
onChange: ( value ) => onChange( { value } )
} );
},
save( state ) {
return wp.element.createElement( 'p', null, state.value );

save( attributes ) {
return wp.element.createElement( 'p', null, attributes.value );
}
} );
9 changes: 8 additions & 1 deletion modules/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ export default class Editor {
const blocks = wp.blocks.parse( settings.content );
console.log( blocks ); // eslint-disable-line no-console

document.getElementById( id ).innerHTML = settings.content;
if ( ! blocks.length ) {
return;
}

wp.element.render(
wp.blocks.createBlockElement( blocks[ 1 ] ),
document.getElementById( id )
);
}
}
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"editor"
],
"scripts": {
"test-unit": "cross-env NODE_ENV=test webpack && mocha build",
"test-unit": "cross-env NODE_ENV=test webpack && mocha build --require bootstrap-test.js",
"build": "cross-env NODE_ENV=production webpack",
"lint": "eslint modules",
"dev": "webpack --watch",
Expand All @@ -20,7 +20,9 @@
"devDependencies": {
"autoprefixer": "^6.7.7",
"babel-core": "^6.24.0",
"babel-eslint": "^7.2.0",
"babel-loader": "^6.4.1",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-latest": "^6.24.0",
"chai": "^3.5.0",
Expand All @@ -30,6 +32,7 @@
"eslint-config-wordpress": "^1.1.0",
"extract-text-webpack-plugin": "^2.1.0",
"glob": "^7.1.1",
"jsdom": "^9.12.0",
"mocha": "^3.2.0",
"node-sass": "^4.5.0",
"pegjs": "^0.10.0",
Expand All @@ -40,6 +43,10 @@
"sinon": "^2.1.0",
"sinon-chai": "^2.9.0",
"style-loader": "^0.14.1",
"webpack": "^2.2.1"
"webpack": "^2.2.1",
"webpack-node-externals": "^1.5.4"
},
"dependencies": {
"hpq": "^1.1.0"
}
}
32 changes: 16 additions & 16 deletions post-content.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
window.content = [
'<!-- wp:heading -->',
'<!-- wp:wp/heading -->',
'<h1>1.0 Is The Loneliest Number</h1>',
'<!-- /wp:heading -->',
'<!-- /wp:wp/heading -->',

'<!-- wp:text -->',
'<!-- wp:wp/text -->',
'<p>I imagine prior to the launch of the iPod, or the iPhone, there were teams saying the same thing: the copy + paste guys are <em>so close</em> to being ready and we know Walt Mossberg is going to ding us for this so let\'s just not ship to the manufacturers in China for just a few more weeks… The Apple teams were probably embarrassed. But <strong>if you\'re not embarrassed when you ship your first version you waited too long</strong>.</p>',
'<!-- /wp:text -->',
'<!-- /wp:wp/text -->',

'<!-- wp:image -->',
'<!-- wp:wp/image -->',
'<figure><img src="https://cldup.com/Bc9YxmqFnJ.jpg" /></figure>',
'<!-- /wp:image -->',
'<!-- /wp:wp/image -->',

'<!-- wp:text -->',
'<!-- wp:wp/text -->',
'<p>A beautiful thing about Apple is how quickly they obsolete their own products. I imagine this also makes the discipline of getting things out there easier. Like I mentioned before, the longer it’s been since the last release the more pressure there is, but if you know that if your bit of code doesn’t make this version but there’s the +0.1 coming out in 6 weeks, then it’s not that bad. It’s like flights from San Francisco to LA, if you miss one you know there’s another one an hour later so it’s not a big deal. Amazon has done a fantastic job of this with the Kindle as well, with a new model every year.</p>',
'<!-- /wp:text -->',
'<!-- /wp:wp/text -->',

'<!-- wp:quote -->',
'<!-- wp:wp/quote -->',
'<blockquote><p>Real artists ship.</p><footer><p><a href="http://www.folklore.org/StoryView.py?story=Real_Artists_Ship.txt">Steve Jobs, 1983</a></p></footer></blockquote>',
'<!-- /wp:quote -->',
'<!-- /wp:wp/quote -->',

'<!-- wp:image -->',
'<!-- wp:wp/image -->',
'<figure><img src="https://cldup.com/vuGcj2VB8M.jpg" /><figcaption>Beautiful landscape</figcaption></figure>',
'<!-- /wp:image -->',
'<!-- /wp:wp/image -->',

'<!-- wp:text -->',
'<!-- wp:wp/text -->',
'<p>By shipping early and often you have the unique competitive advantage of hearing from real people what they think of your work, which in best case helps you anticipate market direction, and in worst case gives you a few people rooting for you that you can email when your team pivots to a new idea. Nothing can recreate the crucible of real usage.</p>',
'<!-- /wp:text -->',
'<!-- /wp:wp/text -->',

'<!-- wp:embed url:https://www.youtube.com/watch?v=Nl6U7UotA-M -->',
'<!-- wp:wp/embed url:https://www.youtube.com/watch?v=Nl6U7UotA-M -->',
'<iframe width="560" height="315" src="//www.youtube.com/embed/Nl6U7UotA-M" frameborder="0" allowfullscreen></iframe>',
'<!-- /wp:embed -->'
'<!-- /wp:wp/embed -->'
].join( '' );
7 changes: 3 additions & 4 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,9 @@ switch ( process.env.NODE_ENV ) {
break;

case 'test':
config.entry = [
'./bootstrap-test.js',
...glob.sync( BASE_PATH + '/**/test/*.js' )
];
config.target = 'node';
config.entry = glob.sync( BASE_PATH + '/**/test/*.js' );
config.externals = [ require( 'webpack-node-externals' )() ];
config.output = {
filename: 'build/test.js',
path: __dirname
Expand Down

0 comments on commit e20441a

Please sign in to comment.