diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index f4df432976bc12..cc339e59d27053 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -1703,6 +1703,43 @@ describe( 'blocks', () => { 'Block bindings source "core/test-source" is already registered.' ); } ); + + it( 'should correctly merge properties when bootstrap happens after registration', () => { + // Register source in the client. + const clientOnlyProperties = { + getValues: () => 'values', + setValues: () => 'new values', + getPlaceholder: () => 'placeholder', + canUserEditValue: () => true, + }; + registerBlockBindingsSource( { + name: 'core/custom-source', + label: 'Client Label', + usesContext: [ 'postId', 'postType' ], + ...clientOnlyProperties, + } ); + + // Bootstrap source from the server. + unlock( + dispatch( blocksStore ) + ).addBootstrappedBlockBindingsSource( { + name: 'core/custom-source', + label: 'Server Label', + usesContext: [ 'postId', 'serverContext' ], + } ); + + // Check that the bootstrap values prevail and the client properties are still there. + expect( getBlockBindingsSource( 'core/custom-source' ) ).toEqual( { + // Should use the server label. + label: 'Server Label', + // Should merge usesContext from server and client. + usesContext: [ 'postId', 'postType', 'serverContext' ], + // Should keep client properties. + ...clientOnlyProperties, + } ); + + unregisterBlockBindingsSource( 'core/custom-source' ); + } ); } ); describe( 'unregisterBlockBindingsSource', () => { diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 5e0714b6064fde..2f141fb0cf9927 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -372,19 +372,17 @@ export function collections( state = {}, action ) { } export function blockBindingsSources( state = {}, action ) { + // Merge usesContext with existing values, potentially defined in the server registration. + const existingUsesContext = state[ action.name ]?.usesContext || []; + const newUsesContext = action.usesContext || []; + const mergedArrays = Array.from( + new Set( existingUsesContext.concat( newUsesContext ) ) + ); + const mergedUsesContext = + mergedArrays.length > 0 ? mergedArrays : undefined; + switch ( action.type ) { case 'ADD_BLOCK_BINDINGS_SOURCE': - // Merge usesContext with existing values, potentially defined in the server registration. - let mergedUsesContext = [ - ...( state[ action.name ]?.usesContext || [] ), - ...( action.usesContext || [] ), - ]; - // Remove duplicates. - mergedUsesContext = - mergedUsesContext.length > 0 - ? [ ...new Set( mergedUsesContext ) ] - : undefined; - return { ...state, [ action.name ]: { @@ -401,8 +399,13 @@ export function blockBindingsSources( state = {}, action ) { return { ...state, [ action.name ]: { + /* + * Keep the exisitng properties in case the source has been registered + * in the client before bootstrapping. + */ + ...state[ action.name ], label: action.label, - usesContext: action.usesContext, + usesContext: mergedUsesContext, }, }; case 'REMOVE_BLOCK_BINDINGS_SOURCE':