Skip to content

Commit ffd1cef

Browse files
authored
Plugin: Implement block registering API (#289)
* 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
1 parent d1feebe commit ffd1cef

File tree

12 files changed

+205
-25
lines changed

12 files changed

+205
-25
lines changed

.babelrc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,13 @@
55
"modules": false
66
}
77
} ]
8-
]
8+
],
9+
"plugins": [
10+
"transform-runtime"
11+
],
12+
"env": {
13+
"test": {
14+
"presets": [ "latest" ]
15+
}
16+
}
917
}

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
modules/*/build

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"extends": "wordpress",
44
"env": {
55
"browser": true,
6-
"node": true
6+
"node": true,
7+
"mocha": true
78
},
89
"parserOptions": {
910
"sourceType": "module"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
build
33
*.log
4+
yarn.lock

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v6.10.0

bootstrap-test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import chai from 'chai';
5+
import dirtyChai from 'dirty-chai';
6+
7+
chai.use( dirtyChai );

index.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ function the_gutenberg_project() {
7676
/**
7777
* Registers a block.
7878
*
79-
* @param string $namespace Block grouping unique to package or plugin.
80-
* @param string $block Block name.
81-
* @param array $args Optional. Array of settings for the block. Default empty array.
82-
* @return bool True on success, false on error.
79+
* @param string $name Block name including namespace.
80+
* @param array $args Optional. Array of settings for the block. Default
81+
* empty array.
82+
* @return bool True on success, false on error.
8383
*/
84-
function register_block( $namespace, $block, $args = array() ) {
85-
84+
function register_block( $name, $args = array() ) {
85+
// Not implemented yet.
8686
}

modules/blocks/index.js

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,66 @@
11
export { default as Editable } from './components/editable';
22

3+
/**
4+
* Block settings keyed by block slug.
5+
*
6+
* @var {Object} blocks
7+
*/
8+
const blocks = {};
9+
310
/**
411
* Registers a block.
512
*
6-
* @param {string} namespace Block grouping unique to package or plugin
7-
* @param {string} block Block name
8-
* @param {Object} settings Block settings
13+
* @param {string} slug Block slug
14+
* @param {Object} settings Block settings
915
*/
10-
export function registerBlock( namespace, block, settings ) {
16+
export function registerBlock( slug, settings ) {
17+
if ( typeof slug !== 'string' ) {
18+
throw new Error(
19+
'Block slugs must be strings.'
20+
);
21+
}
22+
if ( ! /^[a-z0-9-]+\/[a-z0-9-]+$/.test( slug ) ) {
23+
throw new Error(
24+
'Block slugs must contain a namespace prefix. Example: my-plugin/my-custom-block'
25+
);
26+
}
27+
if ( blocks[ slug ] ) {
28+
throw new Error(
29+
'Block "' + slug + '" is already registered.'
30+
);
31+
}
32+
blocks[ slug ] = Object.assign( { slug }, settings );
33+
}
1134

35+
/**
36+
* Unregisters a block.
37+
*
38+
* @param {string} slug Block slug
39+
*/
40+
export function unregisterBlock( slug ) {
41+
if ( ! blocks[ slug ] ) {
42+
throw new Error(
43+
'Block "' + slug + '" is not registered.'
44+
);
45+
}
46+
delete blocks[ slug ];
1247
}
1348

1449
/**
1550
* Returns settings associated with a block.
1651
*
17-
* @param {string} namespace Block grouping unique to package or plugin
18-
* @param {string} block Block name
19-
* @return {?Object} Block settings
52+
* @param {string} slug Block slug
53+
* @return {?Object} Block settings
2054
*/
21-
export function getBlockSettings( namespace, block ) {
22-
55+
export function getBlockSettings( slug ) {
56+
return blocks[ slug ];
2357
}
2458

2559
/**
2660
* Returns all registered blocks.
2761
*
28-
* @return {Object} Block settings keyed by block name
62+
* @return {Array} Block settings
2963
*/
3064
export function getBlocks() {
31-
65+
return Object.values( blocks );
3266
}

modules/blocks/test/index.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { expect } from 'chai';
5+
6+
/**
7+
* Internal dependencies
8+
*/
9+
import * as blocks from '../';
10+
11+
describe( 'blocks API', () => {
12+
// Reset block state before each test.
13+
beforeEach( () => {
14+
blocks.getBlocks().forEach( block => {
15+
blocks.unregisterBlock( block.slug );
16+
} );
17+
} );
18+
19+
describe( 'registerBlock', () => {
20+
it( 'should reject numbers', () => {
21+
expect(
22+
() => blocks.registerBlock( 999 )
23+
).to.throw( 'Block slugs must be strings.' );
24+
} );
25+
26+
it( 'should reject blocks without a namespace', () => {
27+
expect(
28+
() => blocks.registerBlock( 'doing-it-wrong' )
29+
).to.throw( /^Block slugs must contain a namespace prefix/ );
30+
} );
31+
32+
it( 'should reject blocks with invalid characters', () => {
33+
expect(
34+
() => blocks.registerBlock( 'still/_doing_it_wrong' )
35+
).to.throw( /^Block slugs must contain a namespace prefix/ );
36+
} );
37+
38+
it( 'should accept valid block names', () => {
39+
expect(
40+
() => blocks.registerBlock( 'my-plugin/fancy-block-4' )
41+
).not.to.throw();
42+
} );
43+
44+
it( 'should prohibit registering the same block twice', () => {
45+
blocks.registerBlock( 'core/test-block' );
46+
expect(
47+
() => blocks.registerBlock( 'core/test-block' )
48+
).to.throw( 'Block "core/test-block" is already registered.' );
49+
} );
50+
51+
it( 'should store a copy of block settings', () => {
52+
const blockSettings = { settingName: 'settingValue' };
53+
blocks.registerBlock( 'core/test-block-with-settings', blockSettings );
54+
blockSettings.mutated = true;
55+
expect( blocks.getBlockSettings( 'core/test-block-with-settings' ) ).to.eql( {
56+
slug: 'core/test-block-with-settings',
57+
settingName: 'settingValue',
58+
} );
59+
} );
60+
} );
61+
62+
describe( 'unregisterBlock', () => {
63+
it( 'should fail if a block is not registered', () => {
64+
expect(
65+
() => blocks.unregisterBlock( 'core/test-block' )
66+
).to.throw( 'Block "core/test-block" is not registered.' );
67+
} );
68+
69+
it( 'should unregister existing blocks', () => {
70+
blocks.registerBlock( 'core/test-block' );
71+
expect( blocks.getBlocks() ).to.eql( [
72+
{ slug: 'core/test-block' },
73+
] );
74+
blocks.unregisterBlock( 'core/test-block' );
75+
expect( blocks.getBlocks() ).to.eql( [] );
76+
} );
77+
} );
78+
79+
describe( 'getBlockSettings', () => {
80+
it( 'should return { slug } for blocks with no settings', () => {
81+
blocks.registerBlock( 'core/test-block' );
82+
expect( blocks.getBlockSettings( 'core/test-block' ) ).to.eql( {
83+
slug: 'core/test-block',
84+
} );
85+
} );
86+
87+
it( 'should return all block settings', () => {
88+
const blockSettings = { settingName: 'settingValue' };
89+
blocks.registerBlock( 'core/test-block-with-settings', blockSettings );
90+
expect( blocks.getBlockSettings( 'core/test-block-with-settings' ) ).to.eql( {
91+
slug: 'core/test-block-with-settings',
92+
settingName: 'settingValue',
93+
} );
94+
} );
95+
} );
96+
97+
describe( 'getBlocks', () => {
98+
it( 'should return an empty array at first', () => {
99+
expect( blocks.getBlocks() ).to.eql( [] );
100+
} );
101+
102+
it( 'should return all registered blocks', () => {
103+
blocks.registerBlock( 'core/test-block' );
104+
const blockSettings = { settingName: 'settingValue' };
105+
blocks.registerBlock( 'core/test-block-with-settings', blockSettings );
106+
expect( blocks.getBlocks() ).to.eql( [
107+
{ slug: 'core/test-block' },
108+
{ slug: 'core/test-block-with-settings', settingName: 'settingValue' },
109+
] );
110+
} );
111+
} );
112+
} );

modules/editor/blocks/text-block/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
wp.blocks.registerBlock( 'wp', 'Text', {
1+
wp.blocks.registerBlock( 'wp/text', {
22
edit( state, onChange ) {
33
return wp.element.createElement( wp.blocks.Editable, {
44
value: state.value,

0 commit comments

Comments
 (0)