diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index a6f3b8980cfd7a..36f3dc0ba73fa1 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -877,6 +877,10 @@ updateBlockBindings( { url: undefined } ); removeAllBlockBindings(); ``` +_Parameters_ + +- _clientId_ `?string`: Optional block client ID. If not set, it will use the current block client ID from the context. + _Returns_ - `?WPBlockBindingsUtils`: Object containing the block bindings utils. diff --git a/packages/block-editor/src/utils/block-bindings.js b/packages/block-editor/src/utils/block-bindings.js index 2deeb959371742..dcf80d985473b2 100644 --- a/packages/block-editor/src/utils/block-bindings.js +++ b/packages/block-editor/src/utils/block-bindings.js @@ -32,6 +32,8 @@ function isObjectEmpty( object ) { * * @since 6.7.0 Introduced in WordPress core. * + * @param {?string} clientId Optional block client ID. If not set, it will use the current block client ID from the context. + * * @return {?WPBlockBindingsUtils} Object containing the block bindings utils. * * @example @@ -62,8 +64,9 @@ function isObjectEmpty( object ) { * removeAllBlockBindings(); * ``` */ -export function useBlockBindingsUtils() { - const { clientId } = useBlockEditContext(); +export function useBlockBindingsUtils( clientId ) { + const { clientId: contextClientId } = useBlockEditContext(); + const blockClientId = clientId || contextClientId; const { updateBlockAttributes } = useDispatch( blockEditorStore ); const { getBlockAttributes } = useRegistry().select( blockEditorStore ); @@ -98,7 +101,7 @@ export function useBlockBindingsUtils() { */ const updateBlockBindings = ( bindings ) => { const { metadata: { bindings: currentBindings, ...metadata } = {} } = - getBlockAttributes( clientId ); + getBlockAttributes( blockClientId ); const newBindings = { ...currentBindings }; Object.entries( bindings ).forEach( ( [ attribute, binding ] ) => { @@ -118,7 +121,7 @@ export function useBlockBindingsUtils() { delete newMetadata.bindings; } - updateBlockAttributes( clientId, { + updateBlockAttributes( blockClientId, { metadata: isObjectEmpty( newMetadata ) ? undefined : newMetadata, } ); }; @@ -136,8 +139,8 @@ export function useBlockBindingsUtils() { */ const removeAllBlockBindings = () => { const { metadata: { bindings, ...metadata } = {} } = - getBlockAttributes( clientId ); - updateBlockAttributes( clientId, { + getBlockAttributes( blockClientId ); + updateBlockAttributes( blockClientId, { metadata: isObjectEmpty( metadata ) ? undefined : metadata, } ); }; diff --git a/packages/block-editor/src/utils/test/use-block-bindings-utils.js b/packages/block-editor/src/utils/test/use-block-bindings-utils.js new file mode 100644 index 00000000000000..e5e26bd24c20d1 --- /dev/null +++ b/packages/block-editor/src/utils/test/use-block-bindings-utils.js @@ -0,0 +1,174 @@ +/** + * External dependencies + */ +import { renderHook } from '@testing-library/react'; + +/** + * WordPress dependencies + */ +import { store as blockEditorStore } from '@wordpress/block-editor'; +import { dispatch, select } from '@wordpress/data'; +import { + createBlock, + getBlockTypes, + unregisterBlockType, +} from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; + +/** + * Internal dependencies + */ +import { useBlockBindingsUtils } from '../'; + +describe( 'useBlockBindingsUtils', () => { + beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); + } ); + + let clientId; + beforeEach( async () => { + const block = createBlock( 'core/paragraph', { + metadata: { + name: 'Block name', + bindings: { + prop1: { + source: 'core/post-meta', + args: { + key: 'initial_key', + }, + }, + prop2: { + source: 'core/post-meta', + args: { + key: 'initial_key', + }, + }, + }, + }, + } ); + await dispatch( blockEditorStore ).resetBlocks( [ block ] ); + clientId = block.clientId; + } ); + + afterAll( () => { + // Remove blocks after all tests. + dispatch( blockEditorStore ).resetBlocks( [] ); + + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); + } ); + + it( 'should be possible to update just one connection', async () => { + renderHook( () => { + const { updateBlockBindings } = useBlockBindingsUtils( clientId ); + updateBlockBindings( { + prop1: { + source: 'core/post-meta', + args: { + key: 'new_key', + }, + }, + } ); + } ); + const { metadata } = + await select( blockEditorStore ).getBlockAttributes( clientId ); + expect( metadata ).toMatchObject( { + // Other metadata properties shouldn't change. + name: 'Block name', + bindings: { + prop1: { + source: 'core/post-meta', + args: { + key: 'new_key', + }, + }, + prop2: { + source: 'core/post-meta', + args: { + key: 'initial_key', + }, + }, + }, + } ); + } ); + + it( 'should be possible to update multiple connections at once', async () => { + renderHook( () => { + const { updateBlockBindings } = useBlockBindingsUtils( clientId ); + updateBlockBindings( { + prop1: { + source: 'core/post-meta', + args: { + key: 'new_key', + }, + }, + prop2: { + source: 'core/post-meta', + args: { + key: 'new_key', + }, + }, + } ); + } ); + const { metadata } = + await select( blockEditorStore ).getBlockAttributes( clientId ); + expect( metadata ).toMatchObject( { + // Other metadata properties shouldn't change. + name: 'Block name', + bindings: { + prop1: { + source: 'core/post-meta', + args: { + key: 'new_key', + }, + }, + prop2: { + source: 'core/post-meta', + args: { + key: 'new_key', + }, + }, + }, + } ); + } ); + + it( 'should be possible to remove connections', async () => { + renderHook( () => { + const { updateBlockBindings } = useBlockBindingsUtils( clientId ); + updateBlockBindings( { + prop2: undefined, + } ); + } ); + const { metadata } = + await select( blockEditorStore ).getBlockAttributes( clientId ); + expect( metadata ).toMatchObject( { + // Other metadata properties shouldn't change. + name: 'Block name', + bindings: { + prop1: { + source: 'core/post-meta', + args: { + key: 'initial_key', + }, + }, + }, + } ); + } ); + + it( 'should be possible to remove all connections', async () => { + renderHook( () => { + const { removeAllBlockBindings } = + useBlockBindingsUtils( clientId ); + removeAllBlockBindings(); + } ); + const { metadata } = + await select( blockEditorStore ).getBlockAttributes( clientId ); + expect( metadata ).toMatchObject( { + // Other metadata properties shouldn't change. + name: 'Block name', + } ); + } ); +} );