diff --git a/packages/block-editor/src/components/inserter/block-list.js b/packages/block-editor/src/components/inserter/block-list.js
index f6bd172add888c..5477c4a42d8e8f 100644
--- a/packages/block-editor/src/components/inserter/block-list.js
+++ b/packages/block-editor/src/components/inserter/block-list.js
@@ -1,15 +1,7 @@
/**
* External dependencies
*/
-import {
- map,
- includes,
- findIndex,
- flow,
- sortBy,
- groupBy,
- isEmpty,
-} from 'lodash';
+import { map, findIndex, flow, sortBy, groupBy, isEmpty } from 'lodash';
/**
* WordPress dependencies
@@ -48,13 +40,14 @@ export function InserterBlockList( {
rootClientId,
onInsert
);
- const rootChildBlocks = useSelect(
+
+ const hasChildItems = useSelect(
( select ) => {
const { getBlockName } = select( 'core/block-editor' );
const { getChildBlockNames } = select( 'core/blocks' );
const rootBlockName = getBlockName( rootClientId );
- return getChildBlockNames( rootBlockName );
+ return !! getChildBlockNames( rootBlockName ).length;
},
[ rootClientId ]
);
@@ -63,12 +56,6 @@ export function InserterBlockList( {
return searchBlockItems( items, categories, collections, filterValue );
}, [ filterValue, items, categories, collections ] );
- const childItems = useMemo( () => {
- return filteredItems.filter( ( { name } ) =>
- includes( rootChildBlocks, name )
- );
- }, [ filteredItems, rootChildBlocks ] );
-
const suggestedItems = useMemo( () => {
return items.slice( 0, MAX_SUGGESTED_ITEMS );
}, [ items ] );
@@ -127,16 +114,21 @@ export function InserterBlockList( {
}, [ filterValue, debouncedSpeak ] );
const hasItems = ! isEmpty( filteredItems );
- const hasChildItems = childItems.length > 0;
return (
-
+ { hasChildItems && (
+
+
+
+ ) }
{ ! hasChildItems && !! suggestedItems.length && ! filterValue && (
diff --git a/packages/block-editor/src/components/inserter/child-blocks.js b/packages/block-editor/src/components/inserter/child-blocks.js
index 362a56b79035a9..36e61006d4c608 100644
--- a/packages/block-editor/src/components/inserter/child-blocks.js
+++ b/packages/block-editor/src/components/inserter/child-blocks.js
@@ -1,16 +1,25 @@
/**
* WordPress dependencies
*/
-import { withSelect } from '@wordpress/data';
-import { ifCondition, compose } from '@wordpress/compose';
+import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
-import BlockTypesList from '../block-types-list';
import BlockIcon from '../block-icon';
-function ChildBlocks( { rootBlockIcon, rootBlockTitle, items, ...props } ) {
+export default function ChildBlocks( { rootClientId, children } ) {
+ const { rootBlockTitle, rootBlockIcon } = useSelect( ( select ) => {
+ const { getBlockType } = select( 'core/blocks' );
+ const { getBlockName } = select( 'core/block-editor' );
+ const rootBlockName = getBlockName( rootClientId );
+ const rootBlockType = getBlockType( rootBlockName );
+ return {
+ rootBlockTitle: rootBlockType && rootBlockType.title,
+ rootBlockIcon: rootBlockType && rootBlockType.icon,
+ };
+ } );
+
return (
{ ( rootBlockIcon || rootBlockTitle ) && (
@@ -19,21 +28,7 @@ function ChildBlocks( { rootBlockIcon, rootBlockTitle, items, ...props } ) {
{ rootBlockTitle &&
{ rootBlockTitle }
}
) }
-
+ { children }
);
}
-
-export default compose(
- ifCondition( ( { items } ) => items && items.length > 0 ),
- withSelect( ( select, { rootClientId } ) => {
- const { getBlockType } = select( 'core/blocks' );
- const { getBlockName } = select( 'core/block-editor' );
- const rootBlockName = getBlockName( rootClientId );
- const rootBlockType = getBlockType( rootBlockName );
- return {
- rootBlockTitle: rootBlockType && rootBlockType.title,
- rootBlockIcon: rootBlockType && rootBlockType.icon,
- };
- } )
-)( ChildBlocks );
diff --git a/packages/block-editor/src/components/inserter/test/block-list.js b/packages/block-editor/src/components/inserter/test/block-list.js
index 2e3bf78b3ffb5f..2e69345d7b7f81 100644
--- a/packages/block-editor/src/components/inserter/test/block-list.js
+++ b/packages/block-editor/src/components/inserter/test/block-list.js
@@ -13,6 +13,13 @@ import { useSelect } from '@wordpress/data';
*/
import { InserterBlockList as BaseInserterBlockList } from '../block-list';
import items, { categories, collections } from './fixtures';
+import useBlockTypesState from '../hooks/use-block-types-state';
+
+jest.mock( '../hooks/use-block-types-state', () => {
+ // This allows us to tweak the returned value on each test
+ const mock = jest.fn();
+ return mock;
+} );
jest.mock( '@wordpress/data/src/components/use-select', () => {
// This allows us to tweak the returned value on each test
@@ -63,20 +70,22 @@ describe( 'InserterMenu', () => {
beforeEach( () => {
debouncedSpeak.mockClear();
- useSelect.mockImplementation( () => ( {
+ useBlockTypesState.mockImplementation( () => [
+ items,
categories,
collections,
- items,
- } ) );
+ ] );
+
+ useSelect.mockImplementation( () => false );
} );
it( 'should show nothing if there are no items', () => {
const noItems = [];
- useSelect.mockImplementation( () => ( {
+ useBlockTypesState.mockImplementation( () => [
+ noItems,
categories,
collections,
- items: noItems,
- } ) );
+ ] );
const { container } = render(
);
@@ -149,6 +158,20 @@ describe( 'InserterMenu', () => {
assertNoResultsMessageNotToBePresent( container );
} );
+ it( 'displays child blocks UI when root block has child blocks', () => {
+ useSelect.mockImplementation( () => true );
+
+ const { container } = render( );
+
+ const childBlocksContent = container.querySelector(
+ '.block-editor-inserter__child-blocks'
+ );
+
+ expect( childBlocksContent ).not.toBeNull();
+
+ assertNoResultsMessageNotToBePresent( container );
+ } );
+
it( 'should disable items with `isDisabled`', () => {
const { container } = initializeAllClosedMenuState();
const layoutTabContent = container.querySelectorAll(
diff --git a/packages/e2e-tests/plugins/child-blocks.php b/packages/e2e-tests/plugins/child-blocks.php
new file mode 100644
index 00000000000000..d13a2b8f10f611
--- /dev/null
+++ b/packages/e2e-tests/plugins/child-blocks.php
@@ -0,0 +1,28 @@
+ {
+ beforeAll( async () => {
+ await activatePlugin( 'gutenberg-test-child-blocks' );
+ } );
+
+ beforeEach( async () => {
+ await createNewPost();
+ } );
+
+ afterAll( async () => {
+ await deactivatePlugin( 'gutenberg-test-child-blocks' );
+ } );
+
+ it( 'are hidden from the global block inserter', async () => {
+ await openGlobalBlockInserter();
+ await expect( await getAllBlockInserterItemTitles() ).not.toContain(
+ 'Child Blocks Child'
+ );
+ } );
+
+ it( 'shows up in a parent block', async () => {
+ await insertBlock( 'Child Blocks Unrestricted Parent' );
+ await closeGlobalBlockInserter();
+ await page.waitForSelector(
+ '[data-type="test/child-blocks-unrestricted-parent"] .block-editor-default-block-appender'
+ );
+ await page.click(
+ '[data-type="test/child-blocks-unrestricted-parent"] .block-editor-default-block-appender'
+ );
+ await openGlobalBlockInserter();
+ const inserterItemTitles = await getAllBlockInserterItemTitles();
+ expect( inserterItemTitles ).toContain( 'Child Blocks Child' );
+ expect( inserterItemTitles.length ).toBeGreaterThan( 20 );
+ } );
+
+ it( 'display in a parent block with allowedItems', async () => {
+ await insertBlock( 'Child Blocks Restricted Parent' );
+ await closeGlobalBlockInserter();
+ await page.waitForSelector(
+ '[data-type="test/child-blocks-restricted-parent"] .block-editor-default-block-appender'
+ );
+ await page.click(
+ '[data-type="test/child-blocks-restricted-parent"] .block-editor-default-block-appender'
+ );
+ await openGlobalBlockInserter();
+ expect( await getAllBlockInserterItemTitles() ).toEqual( [
+ 'Child Blocks Child',
+ 'Image',
+ 'Paragraph',
+ ] );
+ } );
+} );