diff --git a/blocks/api/factory.js b/blocks/api/factory.js
index 310fcae6c0708..eb72d1c040399 100644
--- a/blocks/api/factory.js
+++ b/blocks/api/factory.js
@@ -142,7 +142,8 @@ export function switchToBlockType( blocks, name ) {
*/
export function createReusableBlock( type, attributes ) {
return {
- id: uuid(),
+ id: uuid(), // Temorary id replaced when the block is saved server side
+ isTemporary: true,
name: __( 'Untitled block' ),
type,
attributes,
diff --git a/blocks/api/index.js b/blocks/api/index.js
index 86bbf662d9c23..0cf3c8c1c4108 100644
--- a/blocks/api/index.js
+++ b/blocks/api/index.js
@@ -14,5 +14,6 @@ export {
getBlockType,
getBlockTypes,
hasBlockSupport,
+ isReusableBlock,
} from './registration';
diff --git a/blocks/api/registration.js b/blocks/api/registration.js
index c123a7ea4de1d..6d44f13c83dd8 100644
--- a/blocks/api/registration.js
+++ b/blocks/api/registration.js
@@ -214,3 +214,15 @@ export function hasBlockSupport( nameOrType, feature, defaultSupports ) {
feature,
], defaultSupports );
}
+
+/**
+ * Determines whether or not the given block is a reusable block. This is a
+ * special block type that is used to point to a global block stored via the
+ * API.
+ *
+ * @param {Object} blockOrType Block or Block Type to test
+ * @return {Boolean} Whether the given block is a reusable block
+ */
+export function isReusableBlock( blockOrType ) {
+ return blockOrType.name === 'core/block';
+}
diff --git a/blocks/api/test/registration.js b/blocks/api/test/registration.js
index da09563a400f4..af724294a06e2 100644
--- a/blocks/api/test/registration.js
+++ b/blocks/api/test/registration.js
@@ -18,6 +18,7 @@ import {
getBlockType,
getBlockTypes,
hasBlockSupport,
+ isReusableBlock,
} from '../registration';
describe( 'blocks', () => {
@@ -412,4 +413,16 @@ describe( 'blocks', () => {
expect( hasBlockSupport( settings, 'foo' ) ).toBe( true );
} );
} );
+
+ describe( 'isReusableBlock', () => {
+ it( 'should return true for a reusable block', () => {
+ const block = { name: 'core/block' };
+ expect( isReusableBlock( block ) ).toBe( true );
+ } );
+
+ it( 'should return false for other blocks', () => {
+ const block = { name: 'core/paragraph' };
+ expect( isReusableBlock( block ) ).toBe( false );
+ } );
+ } );
} );
diff --git a/blocks/library/block/edit-panel/index.js b/blocks/library/block/edit-panel/index.js
new file mode 100644
index 0000000000000..793ef1f932ca0
--- /dev/null
+++ b/blocks/library/block/edit-panel/index.js
@@ -0,0 +1,68 @@
+/**
+ * WordPress dependencies
+ */
+import { Button } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import './style.scss';
+
+function ReusableBlockEditPanel( props ) {
+ const { isEditing, name, isSaving, onEdit, onDetach, onChangeName, onSave, onCancel } = props;
+
+ return (
+
+ { ! isEditing && ! isSaving && [
+
+ { name }
+ ,
+ ,
+ ,
+ ] }
+ { ( isEditing || isSaving ) && [
+ onChangeName( event.target.value ) } />,
+ ,
+ ,
+ ] }
+
+ );
+}
+
+export default ReusableBlockEditPanel;
+
diff --git a/blocks/library/block/edit-panel/style.scss b/blocks/library/block/edit-panel/style.scss
new file mode 100644
index 0000000000000..4baef7e219d37
--- /dev/null
+++ b/blocks/library/block/edit-panel/style.scss
@@ -0,0 +1,32 @@
+.reusable-block-edit-panel {
+ align-items: center;
+ background: $light-gray-100;
+ color: $dark-gray-500;
+ display: flex;
+ font-family: $default-font;
+ font-size: $default-font-size;
+ justify-content: flex-end;
+ margin: $block-padding (-$block-padding) (-$block-padding);
+ padding: 10px $block-padding;
+
+ .reusable-block-edit-panel__spinner {
+ margin: 0 5px;
+ }
+
+ .reusable-block-edit-panel__info {
+ margin-right: auto;
+ }
+
+ .reusable-block-edit-panel__name {
+ flex-grow: 1;
+ font-size: 14px;
+ height: 30px;
+ margin: 0 auto 0 0;
+ max-width: 230px;
+ }
+
+ // Needs specificity to override the margin-bottom set by .button
+ .wp-core-ui & .reusable-block-edit-panel__button {
+ margin: 0 0 0 5px;
+ }
+}
diff --git a/blocks/library/block/index.js b/blocks/library/block/index.js
new file mode 100644
index 0000000000000..2a6bb8dce4667
--- /dev/null
+++ b/blocks/library/block/index.js
@@ -0,0 +1,172 @@
+/**
+ * External dependencies
+ */
+import { pickBy, noop } from 'lodash';
+import { connect } from 'react-redux';
+import classnames from 'classnames';
+
+/**
+ * WordPress dependencies
+ */
+import { Component } from '@wordpress/element';
+import { Placeholder, Spinner } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { getBlockType, registerBlockType, hasBlockSupport, getBlockDefaultClassname } from '../../api';
+import ReusableBlockEditPanel from './edit-panel';
+
+class ReusableBlockEdit extends Component {
+ constructor() {
+ super( ...arguments );
+
+ this.startEditing = this.startEditing.bind( this );
+ this.stopEditing = this.stopEditing.bind( this );
+ this.setAttributes = this.setAttributes.bind( this );
+ this.setName = this.setName.bind( this );
+ this.updateReusableBlock = this.updateReusableBlock.bind( this );
+
+ this.state = {
+ isEditing: false,
+ name: null,
+ attributes: null,
+ };
+ }
+
+ componentDidMount() {
+ if ( ! this.props.reusableBlock ) {
+ this.props.fetchReusableBlock();
+ }
+ }
+
+ startEditing() {
+ this.setState( { isEditing: true } );
+ }
+
+ stopEditing() {
+ this.setState( {
+ isEditing: false,
+ name: null,
+ attributes: null,
+ } );
+ }
+
+ setAttributes( attributes ) {
+ this.setState( ( prevState ) => ( {
+ attributes: { ...prevState.attributes, ...attributes },
+ } ) );
+ }
+
+ setName( name ) {
+ this.setState( { name } );
+ }
+
+ updateReusableBlock() {
+ const { name, attributes } = this.state;
+
+ // Use pickBy to include only changed (assigned) values in payload
+ const payload = pickBy( {
+ name,
+ attributes,
+ } );
+
+ this.props.updateReusableBlock( payload );
+ this.props.saveReusableBlock();
+ this.stopEditing();
+ }
+
+ render() {
+ const { focus, reusableBlock, isSaving, convertBlockToStatic } = this.props;
+ const { isEditing, name, attributes } = this.state;
+
+ if ( ! reusableBlock ) {
+ return ;
+ }
+
+ const reusableBlockAttributes = { ...reusableBlock.attributes, ...attributes };
+ const blockType = getBlockType( reusableBlock.type );
+ const BlockEdit = blockType.edit || blockType.save;
+
+ // Generate a class name for the block's editable form
+ const generatedClassName = hasBlockSupport( blockType, 'className', true ) ?
+ getBlockDefaultClassname( reusableBlock.type ) :
+ null;
+ const className = classnames( generatedClassName, reusableBlockAttributes.className );
+ return [
+ // We fake the block being read-only by wrapping it with an element that has pointer-events: none
+
+
+
,
+ focus && (
+
+ ),
+ ];
+ }
+}
+
+const ConnectedReusableBlockEdit = connect(
+ ( state, ownProps ) => ( {
+ reusableBlock: state.reusableBlocks.data[ ownProps.attributes.ref ],
+ isSaving: state.reusableBlocks.isSaving[ ownProps.attributes.ref ],
+ } ),
+ ( dispatch, ownProps ) => ( {
+ fetchReusableBlock() {
+ dispatch( {
+ type: 'FETCH_REUSABLE_BLOCKS',
+ id: ownProps.attributes.ref,
+ } );
+ },
+ updateReusableBlock( reusableBlock ) {
+ dispatch( {
+ type: 'UPDATE_REUSABLE_BLOCK',
+ id: ownProps.attributes.ref,
+ reusableBlock,
+ } );
+ },
+ saveReusableBlock() {
+ dispatch( {
+ type: 'SAVE_REUSABLE_BLOCK',
+ id: ownProps.attributes.ref,
+ } );
+ },
+ convertBlockToStatic() {
+ dispatch( {
+ type: 'CONVERT_BLOCK_TO_STATIC',
+ uid: ownProps.id,
+ } );
+ },
+ } )
+)( ReusableBlockEdit );
+
+registerBlockType( 'core/block', {
+ title: __( 'Reusable Block' ),
+ category: 'reusable-blocks',
+ isPrivate: true,
+
+ attributes: {
+ ref: {
+ type: 'string',
+ },
+ },
+
+ edit: ConnectedReusableBlockEdit,
+ save: () => null,
+} );
diff --git a/blocks/library/block/index.php b/blocks/library/block/index.php
new file mode 100644
index 0000000000000..ecafb9d2d2146
--- /dev/null
+++ b/blocks/library/block/index.php
@@ -0,0 +1,39 @@
+post_content );
+
+ $block = array_shift( $blocks );
+ if ( ! $block ) {
+ return '';
+ }
+
+ return gutenberg_render_block( $block );
+}
+
+register_block_type( 'core/block', array(
+ 'attributes' => array(
+ 'ref' => array(
+ 'type' => 'string',
+ ),
+ ),
+
+ 'render_callback' => 'gutenberg_render_block_core_reusable_block',
+) );
diff --git a/blocks/library/index.js b/blocks/library/index.js
index d12af4f1efafe..2076db1f9dc5a 100644
--- a/blocks/library/index.js
+++ b/blocks/library/index.js
@@ -21,5 +21,5 @@ import './text-columns';
import './verse';
import './video';
import './audio';
-import './reusable-block';
+import './block';
import './paragraph';
diff --git a/blocks/library/reusable-block/index.js b/blocks/library/reusable-block/index.js
deleted file mode 100644
index 800a5cc4cd3b2..0000000000000
--- a/blocks/library/reusable-block/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { __ } from '@wordpress/i18n';
-
-/**
- * Internal dependencies
- */
-import { registerBlockType } from '../../api';
-
-registerBlockType( 'core/reusable-block', {
- title: __( 'Reusable Block' ),
- category: 'reusable-blocks',
- isPrivate: true,
-
- attributes: {
- ref: {
- type: 'string',
- },
- },
-
- edit: () => { __( 'Reusable Blocks are coming soon!' ) }
,
- save: () => null,
-} );
diff --git a/blocks/test/fixtures/core__block.html b/blocks/test/fixtures/core__block.html
new file mode 100644
index 0000000000000..a0693a60e7f95
--- /dev/null
+++ b/blocks/test/fixtures/core__block.html
@@ -0,0 +1 @@
+
diff --git a/blocks/test/fixtures/core__reusable-block.json b/blocks/test/fixtures/core__block.json
similarity index 82%
rename from blocks/test/fixtures/core__reusable-block.json
rename to blocks/test/fixtures/core__block.json
index 366a117a9e5f1..e27c8fe772b1b 100644
--- a/blocks/test/fixtures/core__reusable-block.json
+++ b/blocks/test/fixtures/core__block.json
@@ -1,7 +1,7 @@
[
{
"uid": "_uid_0",
- "name": "core/reusable-block",
+ "name": "core/block",
"isValid": true,
"attributes": {
"ref": "358b59ee-bab3-4d6f-8445-e8c6971a5605"
diff --git a/blocks/test/fixtures/core__reusable-block.parsed.json b/blocks/test/fixtures/core__block.parsed.json
similarity index 83%
rename from blocks/test/fixtures/core__reusable-block.parsed.json
rename to blocks/test/fixtures/core__block.parsed.json
index aaaac11724462..4644ce602201b 100644
--- a/blocks/test/fixtures/core__reusable-block.parsed.json
+++ b/blocks/test/fixtures/core__block.parsed.json
@@ -1,6 +1,6 @@
[
{
- "blockName": "core/reusable-block",
+ "blockName": "core/block",
"attrs": {
"ref": "358b59ee-bab3-4d6f-8445-e8c6971a5605"
},
diff --git a/blocks/test/fixtures/core__block.serialized.html b/blocks/test/fixtures/core__block.serialized.html
new file mode 100644
index 0000000000000..db861d912a356
--- /dev/null
+++ b/blocks/test/fixtures/core__block.serialized.html
@@ -0,0 +1 @@
+
diff --git a/blocks/test/fixtures/core__reusable-block.html b/blocks/test/fixtures/core__reusable-block.html
deleted file mode 100644
index 1d4d5f0c09b92..0000000000000
--- a/blocks/test/fixtures/core__reusable-block.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/blocks/test/fixtures/core__reusable-block.serialized.html b/blocks/test/fixtures/core__reusable-block.serialized.html
deleted file mode 100644
index 2b3a42824ef3e..0000000000000
--- a/blocks/test/fixtures/core__reusable-block.serialized.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/editor/components/block-list/block.js b/editor/components/block-list/block.js
index 429a79440c563..4d1e4b550dbc0 100644
--- a/editor/components/block-list/block.js
+++ b/editor/components/block-list/block.js
@@ -10,7 +10,14 @@ import { get, partial, reduce, size } from 'lodash';
*/
import { Component, compose, createElement } from '@wordpress/element';
import { keycodes } from '@wordpress/utils';
-import { getBlockType, BlockEdit, getBlockDefaultClassname, createBlock, hasBlockSupport } from '@wordpress/blocks';
+import {
+ getBlockType,
+ BlockEdit,
+ getBlockDefaultClassname,
+ createBlock,
+ hasBlockSupport,
+ isReusableBlock,
+} from '@wordpress/blocks';
import { withFilters, withContext } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
@@ -359,6 +366,7 @@ class BlockListBlock extends Component {
'is-selected': showUI,
'is-multi-selected': isMultiSelected,
'is-hovered': isHovered,
+ 'is-reusable': isReusableBlock( blockType ),
} );
const { onMouseLeave, onFocus, onReplace } = this.props;
diff --git a/editor/components/block-list/style.scss b/editor/components/block-list/style.scss
index e93e79581a534..d73257f2240ee 100644
--- a/editor/components/block-list/style.scss
+++ b/editor/components/block-list/style.scss
@@ -72,6 +72,11 @@
outline: 1px solid $light-gray-500;
}
+ // give reusable blocks a dashed outline
+ &.is-reusable.is-selected:before {
+ outline: 1px dashed $light-gray-500;
+ }
+
// selection style for textarea
::-moz-selection {
background: $blue-medium-highlight;
diff --git a/editor/components/block-settings-menu/index.js b/editor/components/block-settings-menu/index.js
index c8d3b5a72c337..cdeff64d345b7 100644
--- a/editor/components/block-settings-menu/index.js
+++ b/editor/components/block-settings-menu/index.js
@@ -17,6 +17,7 @@ import './style.scss';
import BlockInspectorButton from './block-inspector-button';
import BlockModeToggle from './block-mode-toggle';
import BlockDeleteButton from './block-delete-button';
+import ReusableBlockToggle from './reusable-block-toggle';
import UnknownConverter from './unknown-converter';
import { selectBlock } from '../../actions';
@@ -56,6 +57,7 @@ function BlockSettingsMenu( { uids, onSelect, focus } ) {
{ count === 1 && }
{ count === 1 && }
+ { count === 1 && }
) }
/>
diff --git a/editor/components/block-settings-menu/reusable-block-toggle.js b/editor/components/block-settings-menu/reusable-block-toggle.js
new file mode 100644
index 0000000000000..aa007031eca06
--- /dev/null
+++ b/editor/components/block-settings-menu/reusable-block-toggle.js
@@ -0,0 +1,49 @@
+/**
+ * External dependencies
+ */
+import { connect } from 'react-redux';
+import { noop } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { IconButton } from '@wordpress/components';
+import { isReusableBlock } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import { getBlock } from '../../selectors';
+import { convertBlockToStatic, convertBlockToReusable } from '../../actions';
+
+export function ReusableBlockToggle( { isReusable, convertToStatic, convertToReusable } ) {
+ return (
+
+ { isReusable ? __( 'Detach from Reusable Block' ) : __( 'Convert to Reusable Block' ) }
+
+ );
+}
+
+export default connect(
+ ( state, { uid } ) => {
+ const block = getBlock( state, uid );
+ return {
+ isReusable: isReusableBlock( block ),
+ };
+ },
+ ( dispatch, { uid, onToggle = noop } ) => ( {
+ convertToStatic() {
+ dispatch( convertBlockToStatic( uid ) );
+ onToggle();
+ },
+ convertToReusable() {
+ dispatch( convertBlockToReusable( uid ) );
+ onToggle();
+ },
+ } )
+)( ReusableBlockToggle );
diff --git a/editor/components/block-settings-menu/test/reusable-block-toggle.js b/editor/components/block-settings-menu/test/reusable-block-toggle.js
new file mode 100644
index 0000000000000..4c6258d49f194
--- /dev/null
+++ b/editor/components/block-settings-menu/test/reusable-block-toggle.js
@@ -0,0 +1,29 @@
+/**
+ * External dependencies
+ */
+import { shallow } from 'enzyme';
+
+/**
+ * Internal dependencies
+ */
+import { ReusableBlockToggle } from '../reusable-block-toggle';
+
+describe( 'ReusableBlockToggle', () => {
+ it( 'should allow converting a reusable block to static', () => {
+ const wrapper = shallow(
+
+ );
+ const text = wrapper.find( 'IconButton' ).first().prop( 'children' );
+
+ expect( text ).toEqual( 'Detach from Reusable Block' );
+ } );
+
+ it( 'should allow converting a static block to reusable', () => {
+ const wrapper = shallow(
+
+ );
+ const text = wrapper.find( 'IconButton' ).first().prop( 'children' );
+
+ expect( text ).toEqual( 'Convert to Reusable Block' );
+ } );
+} );
diff --git a/editor/components/inserter/group.js b/editor/components/inserter/group.js
index 50939aa1c61ed..c32f801338f1b 100644
--- a/editor/components/inserter/group.js
+++ b/editor/components/inserter/group.js
@@ -48,7 +48,7 @@ export default class InserterGroup extends Component {
role="menuitem"
key={ block.name }
className="editor-inserter__block"
- onClick={ selectBlock( block.name ) }
+ onClick={ selectBlock( block ) }
ref={ bindReferenceNode( block.name ) }
tabIndex={ current === block.name || disabled ? null : '-1' }
onMouseEnter={ ! disabled ? this.props.showInsertionPoint : null }
diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js
index 4976b4ed03ad0..c00d83388c0c8 100644
--- a/editor/components/inserter/index.js
+++ b/editor/components/inserter/index.js
@@ -89,9 +89,10 @@ class Inserter extends Component {
) }
renderContent={ ( { onClose } ) => {
- const onInsert = ( name ) => {
+ const onInsert = ( name, initialAttributes ) => {
onInsertBlock(
name,
+ initialAttributes,
insertionPoint
);
@@ -114,10 +115,10 @@ export default flowRight( [
};
},
( dispatch ) => ( {
- onInsertBlock( name, position ) {
+ onInsertBlock( name, initialAttributes, position ) {
dispatch( hideInsertionPoint() );
dispatch( insertBlock(
- createBlock( name ),
+ createBlock( name, initialAttributes ),
position
) );
},
diff --git a/editor/components/inserter/menu.js b/editor/components/inserter/menu.js
index 4c2f49f3c4cef..30f55b047cbdf 100644
--- a/editor/components/inserter/menu.js
+++ b/editor/components/inserter/menu.js
@@ -34,8 +34,8 @@ import { keycodes } from '@wordpress/utils';
*/
import './style.scss';
-import { getBlocks, getRecentlyUsedBlocks } from '../../selectors';
-import { showInsertionPoint, hideInsertionPoint } from '../../actions';
+import { getBlocks, getRecentlyUsedBlocks, getReusableBlocks } from '../../selectors';
+import { showInsertionPoint, hideInsertionPoint, fetchReusableBlocks } from '../../actions';
import { default as InserterGroup } from './group';
export const searchBlocks = ( blocks, searchTerm ) => {
@@ -71,6 +71,10 @@ export class InserterMenu extends Component {
this.switchTab = this.switchTab.bind( this );
}
+ componentDidMount() {
+ this.props.fetchReusableBlocks();
+ }
+
componentDidUpdate( prevProps, prevState ) {
const searchResults = this.searchBlocks( this.getBlockTypes() );
// Announce the blocks search results to screen readers.
@@ -103,16 +107,16 @@ export class InserterMenu extends Component {
} );
}
- selectBlock( name ) {
+ selectBlock( block ) {
return () => {
- this.props.onSelect( name );
+ this.props.onSelect( block.name, block.initialAttributes );
this.setState( {
filterValue: '',
} );
};
}
- getBlockTypes() {
+ getStaticBlockTypes() {
const { blockTypes } = this.props;
// If all block types disabled, return empty set
@@ -136,6 +140,28 @@ export class InserterMenu extends Component {
} );
}
+ getReusableBlockTypes() {
+ const { reusableBlocks } = this.props;
+
+ // Display reusable blocks that we've fetched in the inserter
+ return reusableBlocks.map( ( reusableBlock ) => ( {
+ name: 'core/block',
+ initialAttributes: {
+ ref: reusableBlock.id,
+ },
+ title: reusableBlock.name,
+ icon: 'layout',
+ category: 'reusable-blocks',
+ } ) );
+ }
+
+ getBlockTypes() {
+ return [
+ ...this.getStaticBlockTypes(),
+ ...this.getReusableBlockTypes(),
+ ];
+ }
+
searchBlocks( blockTypes ) {
return searchBlocks( blockTypes, this.state.filterValue );
}
@@ -154,12 +180,16 @@ export class InserterMenu extends Component {
( { name } ) => find( blockTypes, { name } ) );
case 'blocks':
- predicate = ( block ) => block.category !== 'embed';
+ predicate = ( block ) => block.category !== 'embed' && block.category !== 'reusable-blocks';
break;
case 'embeds':
predicate = ( block ) => block.category === 'embed';
break;
+
+ case 'saved':
+ predicate = ( block ) => block.category === 'reusable-blocks';
+ break;
}
return filter( blockTypes, predicate );
@@ -300,6 +330,11 @@ export class InserterMenu extends Component {
title: __( 'Embeds' ),
className: 'editor-inserter__tab',
},
+ {
+ name: 'saved',
+ title: __( 'Saved' ),
+ className: 'editor-inserter__tab',
+ },
] }
>
{ ( tabKey ) => (
@@ -311,7 +346,7 @@ export class InserterMenu extends Component {
}
{ isSearching &&
- { this.renderCategories( this.getVisibleBlocksByCategory( getBlockTypes() ) ) }
+ { this.renderCategories( this.getVisibleBlocksByCategory( this.getBlockTypes() ) ) }
}
@@ -324,9 +359,10 @@ const connectComponent = connect(
return {
recentlyUsedBlocks: getRecentlyUsedBlocks( state ),
blocks: getBlocks( state ),
+ reusableBlocks: getReusableBlocks( state ),
};
},
- { showInsertionPoint, hideInsertionPoint }
+ { showInsertionPoint, hideInsertionPoint, fetchReusableBlocks }
);
export default flow(
diff --git a/editor/components/inserter/test/menu.js b/editor/components/inserter/test/menu.js
index 2e2132f8f1ed1..77c47aa21a1c9 100644
--- a/editor/components/inserter/test/menu.js
+++ b/editor/components/inserter/test/menu.js
@@ -95,8 +95,10 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
blocks={ [] }
+ reusableBlocks={ [] }
recentlyUsedBlocks={ [] }
debouncedSpeak={ noop }
+ fetchReusableBlocks={ noop }
blockTypes
/>
);
@@ -114,8 +116,10 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
blocks={ [] }
+ reusableBlocks={ [] }
recentlyUsedBlocks={ [ advancedTextBlock ] }
debouncedSpeak={ noop }
+ fetchReusableBlocks={ noop }
blockTypes={ false }
/>
);
@@ -130,8 +134,10 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
blocks={ [] }
+ reusableBlocks={ [] }
recentlyUsedBlocks={ [ textBlock, advancedTextBlock ] }
debouncedSpeak={ noop }
+ fetchReusableBlocks={ noop }
blockTypes={ [ textBlock.name ] }
/>
);
@@ -147,6 +153,7 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
blocks={ [] }
+ reusableBlocks={ [] }
recentlyUsedBlocks={ [
// Actually recently used by user, thus present at the top.
advancedTextBlock,
@@ -157,6 +164,7 @@ describe( 'InserterMenu', () => {
someOtherBlock,
] }
debouncedSpeak={ noop }
+ fetchReusableBlocks={ noop }
blockTypes
/>
);
@@ -173,8 +181,10 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
blocks={ [] }
+ reusableBlocks={ [] }
recentlyUsedBlocks={ [] }
debouncedSpeak={ noop }
+ fetchReusableBlocks={ noop }
blockTypes
/>
);
@@ -197,8 +207,10 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
blocks={ [] }
+ reusableBlocks={ [] }
recentlyUsedBlocks={ [] }
debouncedSpeak={ noop }
+ fetchReusableBlocks={ noop }
blockTypes
/>
);
@@ -223,8 +235,10 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
blocks={ [ { name: moreBlock.name } ] }
+ reusableBlocks={ [] }
recentlyUsedBlocks={ [] }
debouncedSpeak={ noop }
+ fetchReusableBlocks={ noop }
blockTypes
/>
);
@@ -244,8 +258,10 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
blocks={ [] }
+ reusableBlocks={ [] }
recentlyUsedBlocks={ [] }
debouncedSpeak={ noop }
+ fetchReusableBlocks={ noop }
blockTypes
/>
);
@@ -267,8 +283,10 @@ describe( 'InserterMenu', () => {
position={ 'top center' }
instanceId={ 1 }
blocks={ [] }
+ reusableBlocks={ [] }
recentlyUsedBlocks={ [] }
debouncedSpeak={ noop }
+ fetchReusableBlocks={ noop }
blockTypes
/>
);
diff --git a/editor/effects.js b/editor/effects.js
index 8dd5000ba7746..9cc3def391708 100644
--- a/editor/effects.js
+++ b/editor/effects.js
@@ -346,12 +346,16 @@ export default {
const { id } = action;
const { getState, dispatch } = store;
- const { name, type, attributes } = getReusableBlock( getState(), id );
+ const { name, type, attributes, isTemporary } = getReusableBlock( getState(), id );
const content = serialize( createBlock( type, attributes ) );
-
- new wp.api.models.ReusableBlocks( { id, name, content } ).save().then(
- () => {
- dispatch( { type: 'SAVE_REUSABLE_BLOCK_SUCCESS', id } );
+ const requestData = isTemporary ? { name, content } : { id, name, content };
+ new wp.api.models.ReusableBlocks( requestData ).save().then(
+ ( updatedReusableBlock ) => {
+ dispatch( {
+ type: 'SAVE_REUSABLE_BLOCK_SUCCESS',
+ updatedId: updatedReusableBlock.id,
+ id,
+ } );
dispatch( createSuccessNotice(
__( 'Reusable block updated' ),
{ id: SAVE_REUSABLE_BLOCK_NOTICE_ID }
@@ -379,7 +383,7 @@ export default {
const oldBlock = getBlock( getState(), action.uid );
const reusableBlock = createReusableBlock( oldBlock.name, oldBlock.attributes );
- const newBlock = createBlock( 'core/reusable-block', { ref: reusableBlock.id } );
+ const newBlock = createBlock( 'core/block', { ref: reusableBlock.id } );
dispatch( updateReusableBlock( reusableBlock.id, reusableBlock ) );
dispatch( saveReusableBlock( reusableBlock.id ) );
dispatch( replaceBlocks( [ oldBlock.uid ], [ newBlock ] ) );
diff --git a/editor/reducer.js b/editor/reducer.js
index c7597db556b34..8f62d685fe5b7 100644
--- a/editor/reducer.js
+++ b/editor/reducer.js
@@ -203,6 +203,29 @@ export const editor = flow( [
case 'REMOVE_BLOCKS':
return omit( state, action.uids );
+
+ case 'SAVE_REUSABLE_BLOCK_SUCCESS': {
+ const { id, updatedId } = action;
+
+ // If a temporary reusable block is saved, we swap the temporary id with the final one
+ if ( id === updatedId ) {
+ return state;
+ }
+
+ return mapValues( state, ( block ) => {
+ if ( block.name === 'core/block' && block.attributes.ref === id ) {
+ return {
+ ...block,
+ attributes: {
+ ...block.attributes,
+ ref: updatedId,
+ },
+ };
+ }
+
+ return block;
+ } );
+ }
}
return state;
@@ -731,6 +754,22 @@ export const reusableBlocks = combineReducers( {
},
};
}
+
+ case 'SAVE_REUSABLE_BLOCK_SUCCESS': {
+ const { id, updatedId } = action;
+
+ // If a temporary reusable block is saved, we swap the temporary id with the final one
+ if ( id === updatedId ) {
+ return state;
+ }
+ return {
+ ...omit( state, id ),
+ [ updatedId ]: {
+ ...omit( state[ id ], [ 'id', 'isTemporary' ] ),
+ id: updatedId,
+ },
+ };
+ }
}
return state;
diff --git a/editor/test/effects.js b/editor/test/effects.js
index bf2ab9ce4fb5e..b72cdced692b5 100644
--- a/editor/test/effects.js
+++ b/editor/test/effects.js
@@ -402,7 +402,7 @@ describe( 'effects', () => {
name: { type: 'string' },
},
} );
- registerBlockType( 'core/reusable-block', {
+ registerBlockType( 'core/block', {
title: 'Reusable Block',
category: 'common',
save: () => null,
@@ -414,7 +414,7 @@ describe( 'effects', () => {
afterAll( () => {
unregisterBlockType( 'core/test-block' );
- unregisterBlockType( 'core/reusable-block' );
+ unregisterBlockType( 'core/block' );
} );
describe( '.FETCH_REUSABLE_BLOCKS', () => {
@@ -529,9 +529,9 @@ describe( 'effects', () => {
describe( '.SAVE_REUSABLE_BLOCK', () => {
const handler = effects.SAVE_REUSABLE_BLOCK;
- it( 'should save a reusable block', () => {
+ it( 'should save a reusable block and swaps its id', () => {
let modelAttributes;
- const promise = Promise.resolve();
+ const promise = Promise.resolve( { id: 3 } );
set( global, 'wp.api.models.ReusableBlocks', class {
constructor( attributes ) {
@@ -570,6 +570,7 @@ describe( 'effects', () => {
expect( dispatch ).toHaveBeenCalledWith( {
type: 'SAVE_REUSABLE_BLOCK_SUCCESS',
id: reusableBlock.id,
+ updatedId: 3,
} );
} );
} );
@@ -624,7 +625,7 @@ describe( 'effects', () => {
};
const staticBlock = {
uid: 'd6b55aa9-16b5-4123-9675-749d75a7f14d',
- name: 'core/reusable-block',
+ name: 'core/block',
attributes: {
ref: reusableBlock.id,
},
@@ -674,6 +675,7 @@ describe( 'effects', () => {
expect( dispatch ).toHaveBeenCalledWith(
updateReusableBlock( 'this-is-a-mock-uuid', {
id: 'this-is-a-mock-uuid',
+ isTemporary: true,
name: 'Untitled block',
type: staticBlock.name,
attributes: staticBlock.attributes,
@@ -685,7 +687,7 @@ describe( 'effects', () => {
expect( dispatch ).toHaveBeenCalledWith(
replaceBlocks(
[ staticBlock.uid ],
- [ createBlock( 'core/reusable-block', { ref: 'this-is-a-mock-uuid' } ) ]
+ [ createBlock( 'core/block', { ref: 'this-is-a-mock-uuid' } ) ]
)
);
} );
diff --git a/editor/test/reducer.js b/editor/test/reducer.js
index fdf0875f53d09..08426a96d4d4f 100644
--- a/editor/test/reducer.js
+++ b/editor/test/reducer.js
@@ -153,6 +153,35 @@ describe( 'state', () => {
} );
} );
+ it( 'should update the reusable block reference if the temporary id is swapped', () => {
+ const original = editor( undefined, {
+ type: 'RESET_BLOCKS',
+ blocks: [ {
+ uid: 'chicken',
+ name: 'core/block',
+ attributes: {
+ ref: 'random-uid',
+ },
+ isValid: false,
+ } ],
+ } );
+
+ const state = editor( deepFreeze( original ), {
+ type: 'SAVE_REUSABLE_BLOCK_SUCCESS',
+ id: 'random-uid',
+ updatedId: 3,
+ } );
+
+ expect( state.present.blocksByUid.chicken ).toEqual( {
+ uid: 'chicken',
+ name: 'core/block',
+ attributes: {
+ ref: 3,
+ },
+ isValid: false,
+ } );
+ } );
+
it( 'should move the block up', () => {
const original = editor( undefined, {
type: 'RESET_BLOCKS',
@@ -1384,6 +1413,46 @@ describe( 'state', () => {
} );
} );
+ it( 'should update the reusable block\'s id if it was temporary', () => {
+ const id = '358b59ee-bab3-4d6f-8445-e8c6971a5605';
+ const initialState = {
+ data: {
+ [ id ]: {
+ id,
+ isTemporary: true,
+ name: 'My cool block',
+ type: 'core/paragraph',
+ attributes: {
+ content: 'Hello!',
+ dropCap: true,
+ },
+ },
+ },
+ isSaving: {},
+ };
+
+ const state = reusableBlocks( initialState, {
+ type: 'SAVE_REUSABLE_BLOCK_SUCCESS',
+ id,
+ updatedId: 3,
+ } );
+
+ expect( state ).toEqual( {
+ data: {
+ 3: {
+ id: 3,
+ name: 'My cool block',
+ type: 'core/paragraph',
+ attributes: {
+ content: 'Hello!',
+ dropCap: true,
+ },
+ },
+ },
+ isSaving: {},
+ } );
+ } );
+
it( 'should indicate that a reusable block is saving', () => {
const id = '358b59ee-bab3-4d6f-8445-e8c6971a5605';
const initialState = {
@@ -1407,7 +1476,9 @@ describe( 'state', () => {
it( 'should stop indicating that a reusable block is saving when the save succeeded', () => {
const id = '358b59ee-bab3-4d6f-8445-e8c6971a5605';
const initialState = {
- data: {},
+ data: {
+ [ id ]: { id },
+ },
isSaving: {
[ id ]: true,
},
@@ -1416,10 +1487,13 @@ describe( 'state', () => {
const state = reusableBlocks( initialState, {
type: 'SAVE_REUSABLE_BLOCK_SUCCESS',
id,
+ updatedId: id,
} );
expect( state ).toEqual( {
- data: {},
+ data: {
+ [ id ]: { id },
+ },
isSaving: {},
} );
} );
diff --git a/lib/blocks.php b/lib/blocks.php
index 801bbf326ee4b..d5e4091b09cd0 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -131,6 +131,33 @@ function gutenberg_serialize_block( $block ) {
return $content;
}
+/**
+ * Renders a single block into a HTML string.
+ *
+ * @since 1.9.0
+ *
+ * @param array $block A single parsed block object.
+ * @return string String of rendered HTML.
+ */
+function gutenberg_render_block( $block ) {
+ $block_name = isset( $block['blockName'] ) ? $block['blockName'] : null;
+ $attributes = is_array( $block['attrs'] ) ? $block['attrs'] : array();
+ $raw_content = isset( $block['innerHTML'] ) ? $block['innerHTML'] : null;
+
+ if ( $block_name ) {
+ $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
+ if ( null !== $block_type ) {
+ return $block_type->render( $attributes, $raw_content );
+ }
+ }
+
+ if ( $raw_content ) {
+ return $raw_content;
+ }
+
+ return '';
+}
+
/**
* Parses dynamic blocks out of `post_content` and re-renders them.
*
@@ -140,30 +167,12 @@ function gutenberg_serialize_block( $block ) {
* @return string Updated post content.
*/
function do_blocks( $content ) {
- $registry = WP_Block_Type_Registry::get_instance();
-
$blocks = gutenberg_parse_blocks( $content );
$content_after_blocks = '';
-
foreach ( $blocks as $block ) {
- $block_name = isset( $block['blockName'] ) ? $block['blockName'] : null;
- $attributes = is_array( $block['attrs'] ) ? $block['attrs'] : array();
- $raw_content = isset( $block['innerHTML'] ) ? $block['innerHTML'] : null;
-
- if ( $block_name ) {
- $block_type = $registry->get_registered( $block_name );
- if ( null !== $block_type ) {
- $content_after_blocks .= $block_type->render( $attributes, $raw_content );
- continue;
- }
- }
-
- if ( $raw_content ) {
- $content_after_blocks .= $raw_content;
- }
+ $content_after_blocks .= gutenberg_render_block( $block );
}
-
return $content_after_blocks;
}
add_filter( 'the_content', 'do_blocks', 9 ); // BEFORE do_shortcode().
diff --git a/lib/class-wp-rest-reusable-blocks-controller.php b/lib/class-wp-rest-reusable-blocks-controller.php
index 6d8f00a13d359..17b6d80054d73 100644
--- a/lib/class-wp-rest-reusable-blocks-controller.php
+++ b/lib/class-wp-rest-reusable-blocks-controller.php
@@ -43,6 +43,11 @@ public function register_routes() {
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
+ array(
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => array( $this, 'save_item' ),
+ 'permission_callback' => array( $this, 'save_item_permissions_check' ),
+ ),
'schema' => array( $this, 'get_public_item_schema' ),
) );
@@ -54,8 +59,8 @@ public function register_routes() {
),
array(
'methods' => WP_REST_Server::EDITABLE,
- 'callback' => array( $this, 'update_item' ),
- 'permission_callback' => array( $this, 'update_item_permissions_check' ),
+ 'callback' => array( $this, 'save_item' ),
+ 'permission_callback' => array( $this, 'save_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
@@ -133,14 +138,8 @@ public function get_item_permissions_check( $request ) {
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
- $uuid = $request['id'];
- if ( ! $this->is_valid_uuid4( $uuid ) ) {
- return new WP_Error( 'gutenberg_reusable_block_invalid_id', __( 'ID is not a valid UUID v4.', 'gutenberg' ), array(
- 'status' => 404,
- ) );
- }
-
- $reusable_block = $this->get_reusable_block( $uuid );
+ $id = $request['id'];
+ $reusable_block = get_post( $id );
if ( ! $reusable_block ) {
return new WP_Error( 'gutenberg_reusable_block_not_found', __( 'No reusable block with that ID found.', 'gutenberg' ), array(
'status' => 404,
@@ -151,7 +150,7 @@ public function get_item( $request ) {
}
/**
- * Checks if a given request has access to update a reusable block.
+ * Checks if a given request has access to update/create a reusable block.
*
* @since 0.10.0
* @access public
@@ -159,7 +158,7 @@ public function get_item( $request ) {
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
*/
- public function update_item_permissions_check( $request ) {
+ public function save_item_permissions_check( $request ) {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error( 'gutenberg_reusable_block_cannot_edit', __( 'Sorry, you are not allowed to edit reusable blocks as this user.', 'gutenberg' ), array(
'status' => rest_authorization_required_code(),
@@ -170,7 +169,7 @@ public function update_item_permissions_check( $request ) {
}
/**
- * Updates a single reusable block.
+ * Updates a single reusable block or creates a new one if no id provided.
*
* @since 0.10.0
* @access public
@@ -178,14 +177,7 @@ public function update_item_permissions_check( $request ) {
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
- public function update_item( $request ) {
- $uuid = $request['id'];
- if ( ! $this->is_valid_uuid4( $uuid ) ) {
- return new WP_Error( 'gutenberg_reusable_block_invalid_id', __( 'ID is not a valid UUID v4.', 'gutenberg' ), array(
- 'status' => 404,
- ) );
- }
-
+ public function save_item( $request ) {
$reusable_block = $this->prepare_item_for_database( $request );
if ( is_wp_error( $reusable_block ) ) {
return $reusable_block;
@@ -214,7 +206,7 @@ public function update_item( $request ) {
protected function prepare_item_for_database( $request ) {
$prepared_reusable_block = new stdClass();
- $existing_reusable_block = $this->get_reusable_block( $request['id'] );
+ $existing_reusable_block = get_post( $request['id'] );
if ( $existing_reusable_block ) {
$prepared_reusable_block->ID = $existing_reusable_block->ID;
}
@@ -222,9 +214,6 @@ protected function prepare_item_for_database( $request ) {
$prepared_reusable_block->post_type = 'wp_block';
$prepared_reusable_block->post_status = 'publish';
- // ID. We already validated this in self::update_item().
- $prepared_reusable_block->post_name = $request['id'];
-
// Name.
if ( isset( $request['name'] ) && is_string( $request['name'] ) ) {
$prepared_reusable_block->post_title = $request['name'];
@@ -258,7 +247,7 @@ protected function prepare_item_for_database( $request ) {
*/
public function prepare_item_for_response( $reusable_block, $request ) {
$data = array(
- 'id' => $reusable_block->post_name,
+ 'id' => $reusable_block->ID,
'name' => $reusable_block->post_title,
'content' => $reusable_block->post_content,
);
@@ -294,7 +283,7 @@ public function get_item_schema() {
'type' => 'object',
'properties' => array(
'id' => array(
- 'description' => __( 'UUID that identifies this reusable block.', 'gutenberg' ),
+ 'description' => __( 'ID that identifies this reusable block.', 'gutenberg' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
@@ -314,40 +303,4 @@ public function get_item_schema() {
),
);
}
-
- /**
- * Fetches a reusable block by its UUID ID. Reusable blocks are stored as posts with a custom post type.
- *
- * @since 0.10.0
- * @access private
- *
- * @param string $uuid A UUID string that uniquely identifies the reusable block.
- *
- * @return WP_Post|null The block (a WP_Post), or null if none was found.
- */
- private function get_reusable_block( $uuid ) {
- $reusable_blocks = get_posts( array(
- 'post_type' => 'wp_block',
- 'name' => $uuid,
- ) );
-
- return array_shift( $reusable_blocks );
- }
-
- /**
- * Checks if the given value is a valid UUID v4 string.
- *
- * @since 0.10.0
- * @access private
- *
- * @param mixed $uuid The value to validate.
- * @return bool Whether or not the string is a valid UUID v4 string.
- */
- private function is_valid_uuid4( $uuid ) {
- if ( ! is_string( $uuid ) ) {
- return false;
- }
-
- return (bool) preg_match( '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $uuid );
- }
}
diff --git a/lib/client-assets.php b/lib/client-assets.php
index a38f7c6045d39..de8b32efbde63 100644
--- a/lib/client-assets.php
+++ b/lib/client-assets.php
@@ -802,7 +802,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
$script = '( function() {';
$script .= sprintf( 'var editorSettings = %s;', wp_json_encode( $editor_settings ) );
$script .= << 'wp_block',
'post_status' => 'publish',
- 'post_name' => '2d66a5c5-776c-43b1-98c7-49521cef8ea6',
'post_title' => 'My cool block',
'post_content' => 'Hello!
',
) );
@@ -70,7 +69,7 @@ public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/gutenberg/v1/reusable-blocks', $routes );
- $this->assertCount( 1, $routes['/gutenberg/v1/reusable-blocks'] );
+ $this->assertCount( 2, $routes['/gutenberg/v1/reusable-blocks'] );
$this->assertArrayHasKey( '/gutenberg/v1/reusable-blocks/(?P[\w-]+)', $routes );
$this->assertCount( 2, $routes['/gutenberg/v1/reusable-blocks/(?P[\w-]+)'] );
}
@@ -87,7 +86,7 @@ public function test_get_items() {
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( array(
array(
- 'id' => '2d66a5c5-776c-43b1-98c7-49521cef8ea6',
+ 'id' => self::$reusable_block_post_id,
'name' => 'My cool block',
'content' => 'Hello!
',
),
@@ -114,12 +113,12 @@ public function test_get_items_when_not_allowed() {
public function test_get_item() {
wp_set_current_user( self::$editor_id );
- $request = new WP_REST_Request( 'GET', '/gutenberg/v1/reusable-blocks/2d66a5c5-776c-43b1-98c7-49521cef8ea6' );
+ $request = new WP_REST_Request( 'GET', '/gutenberg/v1/reusable-blocks/' . self::$reusable_block_post_id );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( array(
- 'id' => '2d66a5c5-776c-43b1-98c7-49521cef8ea6',
+ 'id' => self::$reusable_block_post_id,
'name' => 'My cool block',
'content' => 'Hello!
',
), $response->get_data() );
@@ -131,7 +130,7 @@ public function test_get_item() {
public function test_get_item_when_not_allowed() {
wp_set_current_user( self::$subscriber_id );
- $request = new WP_REST_Request( 'GET', '/gutenberg/v1/reusable-blocks/2d66a5c5-776c-43b1-98c7-49521cef8ea6' );
+ $request = new WP_REST_Request( 'GET', '/gutenberg/v1/reusable-blocks/' . self::$reusable_block_post_id );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
@@ -139,27 +138,13 @@ public function test_get_item_when_not_allowed() {
$this->assertEquals( 'gutenberg_reusable_block_cannot_read', $data['code'] );
}
- /**
- * Check that invalid UUIDs 404.
- */
- public function test_get_item_invalid_id() {
- wp_set_current_user( self::$editor_id );
-
- $request = new WP_REST_Request( 'GET', '/gutenberg/v1/reusable-blocks/invalid-uuid' );
- $response = $this->server->dispatch( $request );
- $data = $response->get_data();
-
- $this->assertEquals( 404, $response->get_status() );
- $this->assertEquals( 'gutenberg_reusable_block_invalid_id', $data['code'] );
- }
-
/**
* Check that we get a 404 when we GET a non-existent reusable block.
*/
public function test_get_item_not_found() {
wp_set_current_user( self::$editor_id );
- $request = new WP_REST_Request( 'GET', '/gutenberg/v1/reusable-blocks/6e614ced-e80d-4e10-bd04-1e890b5f7f83' );
+ $request = new WP_REST_Request( 'GET', '/gutenberg/v1/reusable-blocks/unknownid' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
@@ -173,7 +158,7 @@ public function test_get_item_not_found() {
public function test_update_item() {
wp_set_current_user( self::$editor_id );
- $request = new WP_REST_Request( 'PUT', '/gutenberg/v1/reusable-blocks/75236553-f4ba-4f12-aa25-4ba402044bd5' );
+ $request = new WP_REST_Request( 'PUT', '/gutenberg/v1/reusable-blocks/' . self::$reusable_block_post_id );
$request->set_body_params( array(
'name' => 'Another cool block',
'content' => '
',
@@ -183,7 +168,7 @@ public function test_update_item() {
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( array(
- 'id' => '75236553-f4ba-4f12-aa25-4ba402044bd5',
+ 'id' => self::$reusable_block_post_id,
'name' => 'Another cool block',
'content' => '
',
), $response->get_data() );
@@ -192,10 +177,10 @@ public function test_update_item() {
/**
* Check that users without permission can't PUT a single reusable block.
*/
- public function test_update_item_when_not_allowed() {
+ public function test_save_item_when_not_allowed() {
wp_set_current_user( self::$subscriber_id );
- $request = new WP_REST_Request( 'PUT', '/gutenberg/v1/reusable-blocks/2d66a5c5-776c-43b1-98c7-49521cef8ea6' );
+ $request = new WP_REST_Request( 'PUT', '/gutenberg/v1/reusable-blocks/' . self::$reusable_block_post_id );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
@@ -204,11 +189,11 @@ public function test_update_item_when_not_allowed() {
}
/**
- * Test cases for test_update_item_with_invalid_fields().
+ * Test cases for test_save_item_with_invalid_fields().
*
* @return array
*/
- public function data_update_item_with_invalid_fields() {
+ public function data_save_item_with_invalid_fields() {
return array(
array(
array(),
@@ -239,12 +224,12 @@ public function data_update_item_with_invalid_fields() {
/**
* Check that attributes are validated correctly when we PUT a single reusable block.
*
- * @dataProvider data_update_item_with_invalid_fields
+ * @dataProvider data_save_item_with_invalid_fields
*/
- public function test_update_item_with_invalid_fields( $body_params, $expected_message ) {
+ public function test_save_item_with_invalid_fields( $body_params, $expected_message ) {
wp_set_current_user( self::$editor_id );
- $request = new WP_REST_Request( 'PUT', '/gutenberg/v1/reusable-blocks/75236553-f4ba-4f12-aa25-4ba402044bd5' );
+ $request = new WP_REST_Request( 'PUT', '/gutenberg/v1/reusable-blocks/' . self::$reusable_block_post_id );
$request->set_body_params( $body_params );
$response = $this->server->dispatch( $request );