Skip to content

Commit

Permalink
Blocks: move bootstrapped block types to Redux state (#53807)
Browse files Browse the repository at this point in the history
* Blocks: move bootstrapped block types to Redux state

* Fix the ADD_UNPROCESSED_BLOCK_TYPE reducer

* Unit test for reapplyBlockFilters, fixing block unregistration

* Fixup unprocessedBlockTypes reducer tests

* Remove the apiVersion and ancestor polyfills

* Improve action/selector documentation

* Add -Type suffix to block registration actions

* Stabilize reapplyBlockTypeFilters

* Revert back to boostrapping metadata only when provided
  • Loading branch information
jsnajdr committed Aug 30, 2023
1 parent 67dc1bc commit bd630b7
Show file tree
Hide file tree
Showing 15 changed files with 387 additions and 372 deletions.
6 changes: 5 additions & 1 deletion docs/reference-guides/data/data-core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,10 @@ The actions in this package shouldn't be used directly. Instead, use the functio

<!-- START TOKEN(Autogenerated actions|../../../packages/blocks/src/store/actions.js) -->

Nothing to document.
### reapplyBlockTypeFilters

Signals that all block types should be computed again. It uses stored unprocessed block types and all the most recent list of registered filters.

It addresses the issue where third party block filters get registered after third party blocks. A sample sequence: 1. Filter A. 2. Block B. 3. Block C. 4. Filter D. 5. Filter E. 6. Block F. 7. Filter G. In this scenario some filters would not get applied for all blocks because they are registered too late.

<!-- END TOKEN(Autogenerated actions|../../../packages/blocks/src/store/actions.js) -->
98 changes: 11 additions & 87 deletions packages/blocks/src/api/registration.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
/* eslint no-console: [ 'error', { allow: [ 'error', 'warn' ] } ] */

/**
* External dependencies
*/
import { camelCase } from 'change-case';

/**
* WordPress dependencies
*/
Expand All @@ -15,8 +10,8 @@ import { _x } from '@wordpress/i18n';
* Internal dependencies
*/
import i18nBlockSchema from './i18n-block.json';
import { BLOCK_ICON_DEFAULT } from './constants';
import { store as blocksStore } from '../store';
import { unlock } from '../lock-unlock';

/**
* An icon type definition. One of a Dashicon slug, an element,
Expand Down Expand Up @@ -129,8 +124,6 @@ import { store as blocksStore } from '../store';
* then no preview is shown.
*/

const serverSideBlockDefinitions = {};

function isObject( object ) {
return object !== null && typeof object === 'object';
}
Expand All @@ -142,65 +135,9 @@ function isObject( object ) {
*/
// eslint-disable-next-line camelcase
export function unstable__bootstrapServerSideBlockDefinitions( definitions ) {
for ( const blockName of Object.keys( definitions ) ) {
// Don't overwrite if already set. It covers the case when metadata
// was initialized from the server.
if ( serverSideBlockDefinitions[ blockName ] ) {
// We still need to polyfill `apiVersion` for WordPress version
// lower than 5.7. If it isn't present in the definition shared
// from the server, we try to fallback to the definition passed.
// @see https://github.com/WordPress/gutenberg/pull/29279
if (
serverSideBlockDefinitions[ blockName ].apiVersion ===
undefined &&
definitions[ blockName ].apiVersion
) {
serverSideBlockDefinitions[ blockName ].apiVersion =
definitions[ blockName ].apiVersion;
}
// The `ancestor` prop is not included in the definitions shared
// from the server yet, so it needs to be polyfilled as well.
// @see https://github.com/WordPress/gutenberg/pull/39894
if (
serverSideBlockDefinitions[ blockName ].ancestor ===
undefined &&
definitions[ blockName ].ancestor
) {
serverSideBlockDefinitions[ blockName ].ancestor =
definitions[ blockName ].ancestor;
}
// The `selectors` prop is not yet included in the server provided
// definitions. Polyfill it as well. This can be removed when the
// minimum supported WordPress is >= 6.3.
if (
serverSideBlockDefinitions[ blockName ].selectors ===
undefined &&
definitions[ blockName ].selectors
) {
serverSideBlockDefinitions[ blockName ].selectors =
definitions[ blockName ].selectors;
}

if (
serverSideBlockDefinitions[ blockName ]
.__experimentalAutoInsert === undefined &&
definitions[ blockName ].__experimentalAutoInsert
) {
serverSideBlockDefinitions[
blockName
].__experimentalAutoInsert =
definitions[ blockName ].__experimentalAutoInsert;
}
continue;
}

serverSideBlockDefinitions[ blockName ] = Object.fromEntries(
Object.entries( definitions[ blockName ] )
.filter(
( [ , value ] ) => value !== null && value !== undefined
)
.map( ( [ key, value ] ) => [ camelCase( key ), value ] )
);
const { addBootstrappedBlockType } = unlock( dispatch( blocksStore ) );
for ( const [ name, blockType ] of Object.entries( definitions ) ) {
addBootstrappedBlockType( name, blockType );
}
}

Expand Down Expand Up @@ -302,29 +239,16 @@ export function registerBlockType( blockNameOrMetadata, settings ) {
return;
}

const { addBootstrappedBlockType, addUnprocessedBlockType } = unlock(
dispatch( blocksStore )
);

if ( isObject( blockNameOrMetadata ) ) {
unstable__bootstrapServerSideBlockDefinitions( {
[ name ]: getBlockSettingsFromMetadata( blockNameOrMetadata ),
} );
const metadata = getBlockSettingsFromMetadata( blockNameOrMetadata );
addBootstrappedBlockType( name, metadata );
}

const blockType = {
name,
icon: BLOCK_ICON_DEFAULT,
keywords: [],
attributes: {},
providesContext: {},
usesContext: [],
selectors: {},
supports: {},
styles: [],
variations: [],
save: () => null,
...serverSideBlockDefinitions?.[ name ],
...settings,
};

dispatch( blocksStore ).__experimentalRegisterBlockType( blockType );
addUnprocessedBlockType( name, settings );

return select( blocksStore ).getBlockType( name );
}
Expand Down
118 changes: 34 additions & 84 deletions packages/blocks/src/api/test/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import { addFilter, removeAllFilters, removeFilter } from '@wordpress/hooks';
import { logged } from '@wordpress/deprecated';
import { select } from '@wordpress/data';
import { select, dispatch } from '@wordpress/data';

/**
* Internal dependencies
Expand Down Expand Up @@ -33,6 +33,7 @@ import {
import { BLOCK_ICON_DEFAULT, DEPRECATED_ENTRY_KEYS } from '../constants';
import { omit } from '../utils';
import { store as blocksStore } from '../../store';
import { unlock } from '../../lock-unlock';

const noop = () => {};

Expand All @@ -48,19 +49,14 @@ describe( 'blocks', () => {
title: 'block title',
};

beforeAll( () => {
// Initialize the block store.
require( '../../store' );
} );

afterEach( () => {
getBlockTypes().forEach( ( block ) => {
unregisterBlockType( block.name );
} );
const registeredNames = Object.keys(
unlock( select( blocksStore ) ).getUnprocessedBlockTypes()
);
dispatch( blocksStore ).removeBlockTypes( registeredNames );
setFreeformContentHandlerName( undefined );
setUnregisteredTypeHandlerName( undefined );
setDefaultBlockName( undefined );
unstable__bootstrapServerSideBlockDefinitions( {} );

// Reset deprecation logging to ensure we properly track warnings.
for ( const key in logged ) {
Expand Down Expand Up @@ -392,80 +388,6 @@ describe( 'blocks', () => {
} );
} );

// This test can be removed once the polyfill for apiVersion gets removed.
it( 'should apply apiVersion on the client when not set on the server', () => {
const blockName = 'core/test-block-back-compat';
unstable__bootstrapServerSideBlockDefinitions( {
[ blockName ]: {
category: 'widgets',
},
} );
unstable__bootstrapServerSideBlockDefinitions( {
[ blockName ]: {
apiVersion: 3,
category: 'ignored',
},
} );

const blockType = {
title: 'block title',
};
registerBlockType( blockName, blockType );
expect( getBlockType( blockName ) ).toEqual( {
apiVersion: 3,
name: blockName,
save: expect.any( Function ),
title: 'block title',
category: 'widgets',
icon: { src: BLOCK_ICON_DEFAULT },
attributes: {},
providesContext: {},
usesContext: [],
keywords: [],
selectors: {},
supports: {},
styles: [],
variations: [],
} );
} );

// This test can be removed once the polyfill for ancestor gets removed.
it( 'should apply ancestor on the client when not set on the server', () => {
const blockName = 'core/test-block-with-ancestor';
unstable__bootstrapServerSideBlockDefinitions( {
[ blockName ]: {
category: 'widgets',
},
} );
unstable__bootstrapServerSideBlockDefinitions( {
[ blockName ]: {
ancestor: 'core/test-block-ancestor',
category: 'ignored',
},
} );

const blockType = {
title: 'block title',
};
registerBlockType( blockName, blockType );
expect( getBlockType( blockName ) ).toEqual( {
ancestor: 'core/test-block-ancestor',
name: blockName,
save: expect.any( Function ),
title: 'block title',
category: 'widgets',
icon: { src: BLOCK_ICON_DEFAULT },
attributes: {},
providesContext: {},
usesContext: [],
keywords: [],
selectors: {},
supports: {},
styles: [],
variations: [],
} );
} );

// This can be removed once polyfill adding selectors has been removed.
it( 'should apply selectors on the client when not set on the server', () => {
const blockName = 'core/test-block-with-selectors';
Expand Down Expand Up @@ -920,6 +842,34 @@ describe( 'blocks', () => {
'Declaring non-string block descriptions is deprecated since version 6.2.'
);
} );

it( 're-applies block filters', () => {
// register block
registerBlockType( 'test/block', defaultBlockSettings );

// register a filter after registering a block
addFilter(
'blocks.registerBlockType',
'core/blocks/reapply',
( settings ) => ( {
...settings,
title: settings.title + ' filtered',
} )
);

// check that block type has unfiltered values
expect( getBlockType( 'test/block' ).title ).toBe(
'block title'
);

// reapply the block filters
dispatch( blocksStore ).reapplyBlockTypeFilters();

// check that block type has filtered values
expect( getBlockType( 'test/block' ).title ).toBe(
'block title filtered'
);
} );
} );

test( 'registers block from metadata', () => {
Expand Down
Loading

1 comment on commit bd630b7

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in bd630b7.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6025363265
📝 Reported issues:

Please sign in to comment.