Skip to content

Commit

Permalink
Plugin: Implement block registering API (#289)
Browse files Browse the repository at this point in the history
* Remove namespace from PHP `register_block` stub
* Plugin: Implement block registering API
* Add .nvmrc
* Add babel-plugin-transform-runtime
Right now, this is to polyfill `Object.values` (used in `getBlocks`).
Node.js and older browsers lack support for this function.
* Add Mocha unit tests
  • Loading branch information
youknowriad authored Mar 22, 2017
1 parent d1feebe commit ffd1cef
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 25 deletions.
10 changes: 9 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,13 @@
"modules": false
}
} ]
]
],
"plugins": [
"transform-runtime"
],
"env": {
"test": {
"presets": [ "latest" ]
}
}
}
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
modules/*/build
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"extends": "wordpress",
"env": {
"browser": true,
"node": true
"node": true,
"mocha": true
},
"parserOptions": {
"sourceType": "module"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
build
*.log
yarn.lock
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v6.10.0
7 changes: 7 additions & 0 deletions bootstrap-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* External dependencies
*/
import chai from 'chai';
import dirtyChai from 'dirty-chai';

chai.use( dirtyChai );
12 changes: 6 additions & 6 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ function the_gutenberg_project() {
/**
* Registers a block.
*
* @param string $namespace Block grouping unique to package or plugin.
* @param string $block Block name.
* @param array $args Optional. Array of settings for the block. Default empty array.
* @return bool True on success, false on error.
* @param string $name Block name including namespace.
* @param array $args Optional. Array of settings for the block. Default
* empty array.
* @return bool True on success, false on error.
*/
function register_block( $namespace, $block, $args = array() ) {

function register_block( $name, $args = array() ) {
// Not implemented yet.
}
56 changes: 45 additions & 11 deletions modules/blocks/index.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,66 @@
export { default as Editable } from './components/editable';

/**
* Block settings keyed by block slug.
*
* @var {Object} blocks
*/
const blocks = {};

/**
* Registers a block.
*
* @param {string} namespace Block grouping unique to package or plugin
* @param {string} block Block name
* @param {Object} settings Block settings
* @param {string} slug Block slug
* @param {Object} settings Block settings
*/
export function registerBlock( namespace, block, settings ) {
export function registerBlock( slug, settings ) {
if ( typeof slug !== 'string' ) {
throw new Error(
'Block slugs must be strings.'
);
}
if ( ! /^[a-z0-9-]+\/[a-z0-9-]+$/.test( slug ) ) {
throw new Error(
'Block slugs must contain a namespace prefix. Example: my-plugin/my-custom-block'
);
}
if ( blocks[ slug ] ) {
throw new Error(
'Block "' + slug + '" is already registered.'
);
}
blocks[ slug ] = Object.assign( { slug }, settings );
}

/**
* Unregisters a block.
*
* @param {string} slug Block slug
*/
export function unregisterBlock( slug ) {
if ( ! blocks[ slug ] ) {
throw new Error(
'Block "' + slug + '" is not registered.'
);
}
delete blocks[ slug ];
}

/**
* Returns settings associated with a block.
*
* @param {string} namespace Block grouping unique to package or plugin
* @param {string} block Block name
* @return {?Object} Block settings
* @param {string} slug Block slug
* @return {?Object} Block settings
*/
export function getBlockSettings( namespace, block ) {

export function getBlockSettings( slug ) {
return blocks[ slug ];
}

/**
* Returns all registered blocks.
*
* @return {Object} Block settings keyed by block name
* @return {Array} Block settings
*/
export function getBlocks() {

return Object.values( blocks );
}
112 changes: 112 additions & 0 deletions modules/blocks/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* External dependencies
*/
import { expect } from 'chai';

/**
* Internal dependencies
*/
import * as blocks from '../';

describe( 'blocks API', () => {
// Reset block state before each test.
beforeEach( () => {
blocks.getBlocks().forEach( block => {
blocks.unregisterBlock( block.slug );
} );
} );

describe( 'registerBlock', () => {
it( 'should reject numbers', () => {
expect(
() => blocks.registerBlock( 999 )
).to.throw( 'Block slugs must be strings.' );
} );

it( 'should reject blocks without a namespace', () => {
expect(
() => blocks.registerBlock( 'doing-it-wrong' )
).to.throw( /^Block slugs must contain a namespace prefix/ );
} );

it( 'should reject blocks with invalid characters', () => {
expect(
() => blocks.registerBlock( 'still/_doing_it_wrong' )
).to.throw( /^Block slugs must contain a namespace prefix/ );
} );

it( 'should accept valid block names', () => {
expect(
() => blocks.registerBlock( 'my-plugin/fancy-block-4' )
).not.to.throw();
} );

it( 'should prohibit registering the same block twice', () => {
blocks.registerBlock( 'core/test-block' );
expect(
() => blocks.registerBlock( 'core/test-block' )
).to.throw( 'Block "core/test-block" is already registered.' );
} );

it( 'should store a copy of block settings', () => {
const blockSettings = { settingName: 'settingValue' };
blocks.registerBlock( 'core/test-block-with-settings', blockSettings );
blockSettings.mutated = true;
expect( blocks.getBlockSettings( 'core/test-block-with-settings' ) ).to.eql( {
slug: 'core/test-block-with-settings',
settingName: 'settingValue',
} );
} );
} );

describe( 'unregisterBlock', () => {
it( 'should fail if a block is not registered', () => {
expect(
() => blocks.unregisterBlock( 'core/test-block' )
).to.throw( 'Block "core/test-block" is not registered.' );
} );

it( 'should unregister existing blocks', () => {
blocks.registerBlock( 'core/test-block' );
expect( blocks.getBlocks() ).to.eql( [
{ slug: 'core/test-block' },
] );
blocks.unregisterBlock( 'core/test-block' );
expect( blocks.getBlocks() ).to.eql( [] );
} );
} );

describe( 'getBlockSettings', () => {
it( 'should return { slug } for blocks with no settings', () => {
blocks.registerBlock( 'core/test-block' );
expect( blocks.getBlockSettings( 'core/test-block' ) ).to.eql( {
slug: 'core/test-block',
} );
} );

it( 'should return all block settings', () => {
const blockSettings = { settingName: 'settingValue' };
blocks.registerBlock( 'core/test-block-with-settings', blockSettings );
expect( blocks.getBlockSettings( 'core/test-block-with-settings' ) ).to.eql( {
slug: 'core/test-block-with-settings',
settingName: 'settingValue',
} );
} );
} );

describe( 'getBlocks', () => {
it( 'should return an empty array at first', () => {
expect( blocks.getBlocks() ).to.eql( [] );
} );

it( 'should return all registered blocks', () => {
blocks.registerBlock( 'core/test-block' );
const blockSettings = { settingName: 'settingValue' };
blocks.registerBlock( 'core/test-block-with-settings', blockSettings );
expect( blocks.getBlocks() ).to.eql( [
{ slug: 'core/test-block' },
{ slug: 'core/test-block-with-settings', settingName: 'settingValue' },
] );
} );
} );
} );
2 changes: 1 addition & 1 deletion modules/editor/blocks/text-block/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
wp.blocks.registerBlock( 'wp', 'Text', {
wp.blocks.registerBlock( 'wp/text', {
edit( state, onChange ) {
return wp.element.createElement( wp.blocks.Editable, {
value: state.value,
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,25 @@
"editor"
],
"scripts": {
"test-unit": "cross-env NODE_ENV=test mocha modules/**/test/*.js --compilers js:babel-register --recursive --require ./bootstrap-test.js",
"build": "cross-env NODE_ENV=production webpack",
"lint": "eslint editor",
"test": "npm run lint"
"lint": "eslint modules",
"test": "npm run lint && npm run test-unit"
},
"devDependencies": {
"autoprefixer": "^6.7.7",
"babel-core": "^6.24.0",
"babel-loader": "^6.4.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-latest": "^6.24.0",
"babel-register": "^6.24.0",
"chai": "^3.5.0",
"cross-env": "^3.2.4",
"css-loader": "^0.27.3",
"dirty-chai": "^1.2.2",
"eslint": "^3.17.1",
"eslint-config-wordpress": "^1.1.0",
"mocha": "^3.2.0",
"node-sass": "^4.5.0",
"postcss-loader": "^1.3.3",
"sass-loader": "^6.0.3",
Expand Down
15 changes: 12 additions & 3 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const entry = fs.readdirSync( BASE_PATH ).reduce( ( memo, filename ) => {
return memo;
}, {} );

const config = module.exports = {
const config = {
entry: entry,
output: {
filename: '[name]/build/index.js',
Expand All @@ -39,15 +39,17 @@ const config = module.exports = {
},
resolve: {
modules: [
'editor',
'external',
...Object.keys( entry ).map( ( filename ) => {
return path.join( BASE_PATH, filename );
} ),
'node_modules'
]
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
Expand All @@ -62,6 +64,11 @@ const config = module.exports = {
]
},
plugins: [
new webpack.DefinePlugin( {
'process.env': {
NODE_ENV: JSON.stringify( process.env.NODE_ENV )
}
} ),
new webpack.LoaderOptionsPlugin( {
minimize: process.env.NODE_ENV === 'production',
debug: process.env.NODE_ENV !== 'production',
Expand All @@ -79,3 +86,5 @@ if ( 'production' === process.env.NODE_ENV ) {
} else {
config.devtool = 'source-map';
}

module.exports = config;

0 comments on commit ffd1cef

Please sign in to comment.