diff --git a/editor/components/autocompleters/block.js b/editor/components/autocompleters/block.js
index 65dc6b6ccf7c22..e87d217f34fd5a 100644
--- a/editor/components/autocompleters/block.js
+++ b/editor/components/autocompleters/block.js
@@ -1,12 +1,8 @@
-/**
- * External dependencies
- */
-import { filter, sortBy, once, flow } from 'lodash';
-
/**
* WordPress dependencies
*/
-import { createBlock, getBlockTypes, hasBlockSupport } from '@wordpress/blocks';
+import { select } from '@wordpress/data';
+import { createBlock } from '@wordpress/blocks';
/**
* Internal dependencies
@@ -14,46 +10,65 @@ import { createBlock, getBlockTypes, hasBlockSupport } from '@wordpress/blocks';
import './style.scss';
import BlockIcon from '../block-icon';
-function filterBlockTypes( blockTypes ) {
- // Exclude blocks that don't support being shown in the inserter
- return filter( blockTypes, ( blockType ) => hasBlockSupport( blockType, 'inserter', true ) );
+function defaultGetBlockInsertionPoint() {
+ return select( 'core/editor' ).getBlockInsertionPoint();
+}
+
+function defaultGetInserterItems( parentUID ) {
+ // TODO: Update call to getInserterItems when the child block support PR is merged and that function simplified.
+ const {
+ getEditorSettings,
+ getSupportedBlocks,
+ getInserterItems,
+ } = select( 'core/editor' );
+ const supportedBlocks = getSupportedBlocks( parentUID, getEditorSettings().allowedBlockTypes );
+ return getInserterItems( supportedBlocks );
}
-function sortBlockTypes( blockTypes ) {
- // Prioritize blocks in the common common category
- return sortBy( blockTypes, ( { category } ) => 'common' !== category );
+/**
+ * Creates a blocks repeater for replacing the current block with a selected block type.
+ *
+ * @return {Completer} A blocks completer.
+ */
+export function createBlockCompleter( {
+ // Allow store-based selectors to be overridden for unit test.
+ getBlockInsertionPoint = defaultGetBlockInsertionPoint,
+ getInserterItems = defaultGetInserterItems,
+} = {} ) {
+ return {
+ name: 'blocks',
+ className: 'editor-autocompleters__block',
+ triggerPrefix: '/',
+ options() {
+ return getInserterItems( getBlockInsertionPoint() );
+ },
+ getOptionKeywords( inserterItem ) {
+ const { title, keywords = [] } = inserterItem;
+ return [ ...keywords, title ];
+ },
+ getOptionLabel( inserterItem ) {
+ const { icon, title } = inserterItem;
+ return [
+ ,
+ title,
+ ];
+ },
+ allowContext( before, after ) {
+ return ! ( /\S/.test( before.toString() ) || /\S/.test( after.toString() ) );
+ },
+ getOptionCompletion( inserterItem ) {
+ const { name, initialAttributes } = inserterItem;
+ return {
+ action: 'replace',
+ value: createBlock( name, initialAttributes ),
+ };
+ },
+ };
}
/**
- * A blocks repeater for replacing the current block with a selected block type.
+ * Creates a blocks repeater for replacing the current block with a selected block type.
*
- * @type {Completer}
+ * @return {Completer} A blocks completer.
*/
-export default {
- name: 'blocks',
- className: 'editor-autocompleters__block',
- triggerPrefix: '/',
- options: once( function options() {
- return Promise.resolve( flow( filterBlockTypes, sortBlockTypes )( getBlockTypes() ) );
- } ),
- getOptionKeywords( blockSettings ) {
- const { title, keywords = [] } = blockSettings;
- return [ ...keywords, title ];
- },
- getOptionLabel( blockSettings ) {
- const { icon, title } = blockSettings;
- return [
- ,
- title,
- ];
- },
- allowContext( before, after ) {
- return ! ( /\S/.test( before.toString() ) || /\S/.test( after.toString() ) );
- },
- getOptionCompletion( blockData ) {
- return {
- action: 'replace',
- value: createBlock( blockData.name ),
- };
- },
-};
+export default createBlockCompleter();
diff --git a/editor/components/autocompleters/test/block.js b/editor/components/autocompleters/test/block.js
index 9453c066edfad0..2a39110101d3e7 100644
--- a/editor/components/autocompleters/test/block.js
+++ b/editor/components/autocompleters/test/block.js
@@ -1,95 +1,67 @@
/**
* External dependencies
*/
-import { noop } from 'lodash';
-
-/**
- * WordPress dependencies
- */
-import { registerBlockType, unregisterBlockType, getBlockTypes } from '@wordpress/blocks';
+import { shallow } from 'enzyme';
/**
* Internal dependencies
*/
-import { blockAutocompleter } from '../';
+import blockCompleter, { createBlockCompleter } from '../block';
describe( 'block', () => {
- const blockTypes = {
- 'core/foo': {
- save: noop,
- category: 'common',
+ it( 'should retrieve block options for current insertion point', () => {
+ const expectedOptions = [ {}, {}, {} ];
+ const mockGetBlockInsertionPoint = jest.fn( () => 'expected-insertion-point' );
+ const mockGetInserterItems = jest.fn( () => expectedOptions );
+
+ const completer = createBlockCompleter( {
+ getBlockInsertionPoint: mockGetBlockInsertionPoint,
+ getInserterItems: mockGetInserterItems,
+ } );
+
+ const actualOptions = completer.options();
+ expect( mockGetBlockInsertionPoint ).toHaveBeenCalled();
+ expect( mockGetInserterItems ).toHaveBeenCalledWith( 'expected-insertion-point' );
+ expect( actualOptions ).toBe( expectedOptions );
+ } );
+
+ it( 'should derive option keywords from block keywords and block title', () => {
+ const inserterItemWithTitleAndKeywords = {
+ name: 'core/foo',
title: 'foo',
keywords: [ 'foo-keyword-1', 'foo-keyword-2' ],
- },
- 'core/bar': {
- save: noop,
- category: 'layout',
+ };
+ const inserterItemWithTitleAndEmptyKeywords = {
+ name: 'core/bar',
title: 'bar',
// Intentionally empty keyword list
keywords: [],
- },
- 'core/baz': {
- save: noop,
- category: 'common',
+ };
+ const inserterItemWithTitleAndUndefinedKeywords = {
+ name: 'core/baz',
title: 'baz',
// Intentionally omitted keyword list
- },
- };
+ };
- beforeEach( () => {
- Object.entries( blockTypes ).forEach(
- ( [ name, settings ] ) => registerBlockType( name, settings )
- );
+ expect( blockCompleter.getOptionKeywords( inserterItemWithTitleAndKeywords ) )
+ .toEqual( [ 'foo-keyword-1', 'foo-keyword-2', 'foo' ] );
+ expect( blockCompleter.getOptionKeywords( inserterItemWithTitleAndEmptyKeywords ) )
+ .toEqual( [ 'bar' ] );
+ expect( blockCompleter.getOptionKeywords( inserterItemWithTitleAndUndefinedKeywords ) )
+ .toEqual( [ 'baz' ] );
} );
- afterEach( () => {
- getBlockTypes().forEach( ( block ) => {
- unregisterBlockType( block.name );
- } );
- } );
-
- it( 'should prioritize common blocks in options', () => {
- return blockAutocompleter.options().then( ( options ) => {
- expect( options ).toMatchObject( [
- blockTypes[ 'core/foo' ],
- blockTypes[ 'core/baz' ],
- blockTypes[ 'core/bar' ],
- ] );
- } );
- } );
-
- it( 'should render a block option label composed of @wordpress/element Elements and/or strings', () => {
- expect.hasAssertions();
-
- // Only verify that a populated label is returned.
- // It is likely to be fragile to assert that the contents are renderable by @wordpress/element.
- const isAllowedLabelType = ( label ) => Array.isArray( label ) || ( typeof label === 'string' );
-
- getBlockTypes().forEach( ( blockType ) => {
- const label = blockAutocompleter.getOptionLabel( blockType );
- expect( isAllowedLabelType( label ) ).toBeTruthy();
- } );
- } );
-
- it( 'should derive option keywords from block keywords and block title', () => {
- const optionKeywords = getBlockTypes().reduce(
- ( map, blockType ) => map.set(
- blockType.name,
- blockAutocompleter.getOptionKeywords( blockType )
- ),
- new Map()
- );
+ it( 'should render a block option label', () => {
+ const labelComponents = shallow(
+ { blockCompleter.getOptionLabel( {
+ icon: 'expected-icon',
+ title: 'expected-text',
+ } ) }
+
).children();
- expect( optionKeywords.get( 'core/foo' ) ).toEqual( [
- 'foo-keyword-1',
- 'foo-keyword-2',
- blockTypes[ 'core/foo' ].title,
- ] );
- expect( optionKeywords.get( 'core/bar' ) ).toEqual( [
- blockTypes[ 'core/bar' ].title,
- ] );
- expect( optionKeywords.get( 'core/baz' ) ).toEqual( [
- blockTypes[ 'core/baz' ].title,
- ] );
+ expect( labelComponents ).toHaveLength( 2 );
+ expect( labelComponents.at( 0 ).name() ).toBe( 'BlockIcon' );
+ expect( labelComponents.at( 0 ).prop( 'icon' ) ).toBe( 'expected-icon' );
+ expect( labelComponents.at( 1 ).text() ).toBe( 'expected-text' );
} );
} );
diff --git a/editor/hooks/default-autocompleters.js b/editor/hooks/default-autocompleters.js
index 8e132e417bfa80..21c30ab7efa2f6 100644
--- a/editor/hooks/default-autocompleters.js
+++ b/editor/hooks/default-autocompleters.js
@@ -8,6 +8,7 @@ import { clone } from 'lodash';
*/
import { addFilter } from '@wordpress/hooks';
import { getDefaultBlockName } from '@wordpress/blocks';
+import { dispatch } from '@wordpress/data';
/**
* Internal dependencies
@@ -23,6 +24,14 @@ function setDefaultCompleters( completers, blockName ) {
// Add blocks autocompleter for Paragraph block
if ( blockName === getDefaultBlockName() ) {
completers.push( clone( blockAutocompleter ) );
+
+ /*
+ * NOTE: This is a hack to help ensure shared blocks are loaded
+ * so they may be included in the block completer. It can be removed
+ * once we have a way for completers to Promise options while
+ * store-based data dependencies are being resolved.
+ */
+ dispatch( 'core/editor' ).fetchSharedBlocks();
}
}
return completers;