diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index d9d0ecbdd28b6..a2df297f3e136 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -54,6 +54,23 @@ function mapBlockOrder( blocks, rootClientId = '' ) { return result; } +/** + * Given an array of blocks, returns an object where each key contains + * the clientId of the block and the value is the parent of the block. + * + * @param {Array} blocks Blocks to map. + * @param {?string} rootClientId Assumed root client ID. + * + * @return {Object} Block order map object. + */ +function mapBlockParents( blocks, rootClientId = '' ) { + return blocks.reduce( ( result, block ) => Object.assign( + result, + { [ block.clientId ]: rootClientId }, + mapBlockParents( block.innerBlocks, block.clientId ) + ), {} ); +} + /** * Helper method to iterate through all blocks, recursing into inner blocks, * applying a transformation function to each one. @@ -264,16 +281,40 @@ function withIgnoredBlockChange( reducer ) { * @return {Function} Enhanced reducer function. */ const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => { - if ( state && action.type === 'REMOVE_BLOCKS' ) { - const clientIds = [ ...action.clientIds ]; + const getAllChildren = ( clientIds ) => { + let result = clientIds; + for ( let i = 0; i < result.length; i++ ) { + if ( ! state.order[ result[ i ] ] ) { + continue; + } - // For each removed client ID, include its inner blocks to remove, - // recursing into those so long as inner blocks exist. - for ( let i = 0; i < clientIds.length; i++ ) { - clientIds.push( ...state.order[ clientIds[ i ] ] ); + if ( result === clientIds ) { + result = [ ...result ]; + } + + result.push( ...state.order[ result[ i ] ] ); } - action = { ...action, clientIds }; + return result; + }; + + if ( state ) { + switch ( action.type ) { + case 'REMOVE_BLOCKS': + action = { + ...action, + type: 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN', + removedClientIds: getAllChildren( action.clientIds ), + }; + break; + case 'REPLACE_BLOCKS': + action = { + ...action, + type: 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN', + replacedClientIds: getAllChildren( action.clientIds ), + }; + break; + } } return reducer( state, action ); @@ -306,6 +347,10 @@ const withBlockReset = ( reducer ) => ( state, action ) => { ...omit( state.order, visibleClientIds ), ...mapBlockOrder( action.blocks ), }, + parents: { + ...omit( state.parents, visibleClientIds ), + ...mapBlockParents( action.blocks ), + }, }; } @@ -435,18 +480,18 @@ export const blocks = flow( ...getFlattenedBlocksWithoutAttributes( action.blocks ), }; - case 'REPLACE_BLOCKS': + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': if ( ! action.blocks ) { return state; } return { - ...omit( state, action.clientIds ), + ...omit( state, action.replacedClientIds ), ...getFlattenedBlocksWithoutAttributes( action.blocks ), }; - case 'REMOVE_BLOCKS': - return omit( state, action.clientIds ); + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': + return omit( state, action.removedClientIds ); } return state; @@ -511,18 +556,18 @@ export const blocks = flow( ...getFlattenedBlockAttributes( action.blocks ), }; - case 'REPLACE_BLOCKS': + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': if ( ! action.blocks ) { return state; } return { - ...omit( state, action.clientIds ), + ...omit( state, action.replacedClientIds ), ...getFlattenedBlockAttributes( action.blocks ), }; - case 'REMOVE_BLOCKS': - return omit( state, action.clientIds ); + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': + return omit( state, action.removedClientIds ); } return state; @@ -609,7 +654,7 @@ export const blocks = flow( }; } - case 'REPLACE_BLOCKS': { + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': { const { clientIds } = action; if ( ! action.blocks ) { return state; @@ -618,7 +663,7 @@ export const blocks = flow( const mappedBlocks = mapBlockOrder( action.blocks ); return flow( [ - ( nextState ) => omit( nextState, clientIds ), + ( nextState ) => omit( nextState, action.replacedClientIds ), ( nextState ) => ( { ...nextState, ...omit( mappedBlocks, '' ), @@ -642,20 +687,59 @@ export const blocks = flow( ] )( state ); } - case 'REMOVE_BLOCKS': + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': return flow( [ // Remove inner block ordering for removed blocks - ( nextState ) => omit( nextState, action.clientIds ), + ( nextState ) => omit( nextState, action.removedClientIds ), // Remove deleted blocks from other blocks' orderings ( nextState ) => mapValues( nextState, ( subState ) => ( - without( subState, ...action.clientIds ) + without( subState, ...action.removedClientIds ) ) ), ] )( state ); } return state; }, + + // While technically redundant data as the inverse of `order`, it serves as + // an optimization for the selectors which derive the ancestry of a block. + parents( state = {}, action ) { + switch ( action.type ) { + case 'RESET_BLOCKS': + return mapBlockParents( action.blocks ); + + case 'RECEIVE_BLOCKS': + return { + ...state, + ...mapBlockParents( action.blocks ), + }; + + case 'INSERT_BLOCKS': + return { + ...state, + ...mapBlockParents( action.blocks, action.rootClientId || '' ), + }; + + case 'MOVE_BLOCK_TO_POSITION': { + return { + ...state, + [ action.clientId ]: action.toRootClientId || '', + }; + } + + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': + return { + ...omit( state, action.replacedClientIds ), + ...mapBlockParents( action.blocks, state[ action.clientIds[ 0 ] ] ), + }; + + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': + return omit( state, action.removedClientIds ); + } + + return state; + }, } ); /** diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 6633611fbd9b1..21937e3bc3d8a 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -458,22 +458,11 @@ export function getSelectedBlock( state ) { * * @return {?string} Root client ID, if exists */ -export const getBlockRootClientId = createSelector( - ( state, clientId ) => { - const { order } = state.blocks; - - for ( const rootClientId in order ) { - if ( includes( order[ rootClientId ], clientId ) ) { - return rootClientId; - } - } - - return null; - }, - ( state ) => [ - state.blocks.order, - ] -); +export function getBlockRootClientId( state, clientId ) { + return state.blocks.parents[ clientId ] !== undefined ? + state.blocks.parents[ clientId ] : + null; +} /** * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. @@ -483,21 +472,15 @@ export const getBlockRootClientId = createSelector( * * @return {string} Root client ID */ -export const getBlockHierarchyRootClientId = createSelector( - ( state, clientId ) => { - let rootClientId = clientId; - let current = clientId; - while ( rootClientId ) { - current = rootClientId; - rootClientId = getBlockRootClientId( state, current ); - } - - return current; - }, - ( state ) => [ - state.blocks.order, - ] -); +export function getBlockHierarchyRootClientId( state, clientId ) { + let current = clientId; + let parent; + do { + parent = current; + current = state.blocks.parents[ current ]; + } while ( current ); + return parent; +} /** * Returns the client ID of the block adjacent one at the given reference diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 7543fdad57dd8..90116e7805254 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -193,27 +193,31 @@ describe( 'state', () => { it( 'can replace a child block', () => { const existingState = deepFreeze( { byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, }, - 'clicken-child': { + 'chicken-child': { clientId: 'chicken-child', name: 'core/test-child-block', isValid: true, }, }, attributes: { - clicken: {}, - 'clicken-child': { + chicken: {}, + 'chicken-child': { attr: true, }, }, order: { - '': [ 'clicken' ], - clicken: [ 'clicken-child' ], - 'clicken-child': [], + '': [ 'chicken' ], + chicken: [ 'chicken-child' ], + 'chicken-child': [], + }, + parents: { + chicken: '', + 'chicken-child': 'chicken', }, } ); @@ -226,7 +230,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_INNER_BLOCKS', - rootClientId: 'clicken', + rootClientId: 'chicken', blocks: [ newChildBlock ], }; @@ -236,7 +240,7 @@ describe( 'state', () => { isPersistentChange: true, isIgnoredChange: false, byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, @@ -248,35 +252,42 @@ describe( 'state', () => { }, }, attributes: { - clicken: {}, + chicken: {}, [ newChildBlockId ]: { attr: false, attr2: 'perfect', }, }, order: { - '': [ 'clicken' ], - clicken: [ newChildBlockId ], + '': [ 'chicken' ], + chicken: [ newChildBlockId ], [ newChildBlockId ]: [], }, + parents: { + [ newChildBlockId ]: 'chicken', + chicken: '', + }, } ); } ); it( 'can insert a child block', () => { const existingState = deepFreeze( { byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, }, }, attributes: { - clicken: {}, + chicken: {}, }, order: { - '': [ 'clicken' ], - clicken: [], + '': [ 'chicken' ], + chicken: [], + }, + parents: { + chicken: '', }, } ); @@ -289,7 +300,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_INNER_BLOCKS', - rootClientId: 'clicken', + rootClientId: 'chicken', blocks: [ newChildBlock ], }; @@ -299,7 +310,7 @@ describe( 'state', () => { isPersistentChange: true, isIgnoredChange: false, byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, @@ -311,53 +322,62 @@ describe( 'state', () => { }, }, attributes: { - clicken: {}, + chicken: {}, [ newChildBlockId ]: { attr: false, attr2: 'perfect', }, }, order: { - '': [ 'clicken' ], - clicken: [ newChildBlockId ], + '': [ 'chicken' ], + chicken: [ newChildBlockId ], [ newChildBlockId ]: [], }, + parents: { + [ newChildBlockId ]: 'chicken', + chicken: '', + }, } ); } ); it( 'can replace multiple child blocks', () => { const existingState = deepFreeze( { byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, }, - 'clicken-child': { + 'chicken-child': { clientId: 'chicken-child', name: 'core/test-child-block', isValid: true, }, - 'clicken-child-2': { + 'chicken-child-2': { clientId: 'chicken-child', name: 'core/test-child-block', isValid: true, }, }, attributes: { - clicken: {}, - 'clicken-child': { + chicken: {}, + 'chicken-child': { attr: true, }, - 'clicken-child-2': { + 'chicken-child-2': { attr2: 'ok', }, }, order: { - '': [ 'clicken' ], - clicken: [ 'clicken-child', 'clicken-child-2' ], - 'clicken-child': [], - 'clicken-child-2': [], + '': [ 'chicken' ], + chicken: [ 'chicken-child', 'chicken-child-2' ], + 'chicken-child': [], + 'chicken-child-2': [], + }, + parents: { + chicken: '', + 'chicken-child': 'chicken', + 'chicken-child-2': 'chicken', }, } ); @@ -381,7 +401,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_INNER_BLOCKS', - rootClientId: 'clicken', + rootClientId: 'chicken', blocks: [ newChildBlock1, newChildBlock2, newChildBlock3 ], }; @@ -391,7 +411,7 @@ describe( 'state', () => { isPersistentChange: true, isIgnoredChange: false, byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, @@ -413,7 +433,7 @@ describe( 'state', () => { }, }, attributes: { - clicken: {}, + chicken: {}, [ newChildBlockId1 ]: { attr: false, attr2: 'perfect', @@ -427,44 +447,55 @@ describe( 'state', () => { }, }, order: { - '': [ 'clicken' ], - clicken: [ newChildBlockId1, newChildBlockId2, newChildBlockId3 ], + '': [ 'chicken' ], + chicken: [ newChildBlockId1, newChildBlockId2, newChildBlockId3 ], [ newChildBlockId1 ]: [], [ newChildBlockId2 ]: [], [ newChildBlockId3 ]: [], }, + parents: { + chicken: '', + [ newChildBlockId1 ]: 'chicken', + [ newChildBlockId2 ]: 'chicken', + [ newChildBlockId3 ]: 'chicken', + }, } ); } ); it( 'can replace a child block that has other children', () => { const existingState = deepFreeze( { byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, }, - 'clicken-child': { + 'chicken-child': { clientId: 'chicken-child', name: 'core/test-child-block', isValid: true, }, - 'clicken-grand-child': { + 'chicken-grand-child': { clientId: 'chicken-child', name: 'core/test-block', isValid: true, }, }, attributes: { - clicken: {}, - 'clicken-child': {}, - 'clicken-grand-child': {}, + chicken: {}, + 'chicken-child': {}, + 'chicken-grand-child': {}, }, order: { - '': [ 'clicken' ], - clicken: [ 'clicken-child' ], - 'clicken-child': [ 'clicken-grand-child' ], - 'clicken-grand-child': [], + '': [ 'chicken' ], + chicken: [ 'chicken-child' ], + 'chicken-child': [ 'chicken-grand-child' ], + 'chicken-grand-child': [], + }, + parents: { + chicken: '', + 'chicken-child': 'chicken', + 'chicken-grand-child': 'chicken-child', }, } ); @@ -474,7 +505,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_INNER_BLOCKS', - rootClientId: 'clicken', + rootClientId: 'chicken', blocks: [ newChildBlock ], }; @@ -484,7 +515,7 @@ describe( 'state', () => { isPersistentChange: true, isIgnoredChange: false, byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, @@ -496,14 +527,18 @@ describe( 'state', () => { }, }, attributes: { - clicken: {}, + chicken: {}, [ newChildBlockId ]: {}, }, order: { - '': [ 'clicken' ], - clicken: [ newChildBlockId ], + '': [ 'chicken' ], + chicken: [ newChildBlockId ], [ newChildBlockId ]: [], }, + parents: { + chicken: '', + [ newChildBlockId ]: 'chicken', + }, } ); } ); } ); @@ -515,6 +550,7 @@ describe( 'state', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, isPersistentChange: true, isIgnoredChange: false, } ); @@ -612,6 +648,45 @@ describe( 'state', () => { '': [ 'wings' ], wings: [], } ); + expect( state.parents ).toEqual( { + wings: '', + } ); + } ); + it( 'should replace the block and remove references to its inner blocks', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [ + { + clientId: 'child', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], + } ], + } ); + const state = blocks( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks: [ { + clientId: 'wings', + name: 'core/freeform', + innerBlocks: [], + } ], + } ); + + expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); + expect( state.order ).toEqual( { + '': [ 'wings' ], + wings: [], + } ); + expect( state.parents ).toEqual( { + wings: '', + } ); } ); it( 'should replace the nested block', () => { @@ -634,6 +709,10 @@ describe( 'state', () => { [ wrapperBlock.clientId ]: [ replacementBlock.clientId ], [ replacementBlock.clientId ]: [], } ); + expect( state.parents ).toEqual( { + [ wrapperBlock.clientId ]: '', + [ replacementBlock.clientId ]: wrapperBlock.clientId, + } ); } ); it( 'should replace the block even if the new block clientId is the same', () => { @@ -1024,6 +1103,9 @@ describe( 'state', () => { expect( state.order[ '' ] ).toEqual( [ 'ribs' ] ); expect( state.order ).not.toHaveProperty( 'chicken' ); + expect( state.parents ).toEqual( { + ribs: '', + } ); expect( state.byClientId ).toEqual( { ribs: { clientId: 'ribs', @@ -1063,6 +1145,9 @@ describe( 'state', () => { expect( state.order[ '' ] ).toEqual( [ 'ribs' ] ); expect( state.order ).not.toHaveProperty( 'chicken' ); expect( state.order ).not.toHaveProperty( 'veggies' ); + expect( state.parents ).toEqual( { + ribs: '', + } ); expect( state.byClientId ).toEqual( { ribs: { clientId: 'ribs', @@ -1095,6 +1180,7 @@ describe( 'state', () => { expect( state.order ).toEqual( { '': [], } ); + expect( state.parents ).toEqual( {} ); } ); it( 'should insert at the specified index', () => { diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index c038a40621eff..bbb61fbfc04ec 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -156,6 +156,9 @@ describe( 'selectors', () => { '': rootOrder, 123: rootBlockOrder, }, + parents: { + 123: '', + }, }, }; @@ -171,6 +174,9 @@ describe( 'selectors', () => { '': rootOrder, 123: rootBlockOrder, }, + parents: { + 123: '', + }, }, }; @@ -192,6 +198,9 @@ describe( 'selectors', () => { '': rootOrder, 123: [], }, + parents: { + 123: '', + }, }, }; @@ -210,6 +219,10 @@ describe( 'selectors', () => { 123: [ 456 ], 456: [], }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -239,6 +252,10 @@ describe( 'selectors', () => { 123: rootBlockOrder, 456: childBlockOrder, }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -257,6 +274,10 @@ describe( 'selectors', () => { 123: rootBlockOrder, 456: childBlockOrder, }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -284,6 +305,10 @@ describe( 'selectors', () => { 123: rootBlockOrder, 456: childBlockOrder, }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -302,6 +327,10 @@ describe( 'selectors', () => { 123: rootBlockOrder, 456: childBlockOrder, }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -335,6 +364,11 @@ describe( 'selectors', () => { 456: childBlockOrder, 789: grandChildBlockOrder, }, + parents: { + 123: '', + 456: 123, + 789: 456, + }, }, }; @@ -356,6 +390,11 @@ describe( 'selectors', () => { 456: childBlockOrder, 789: grandChildBlockOrder, }, + parents: { + 123: '', + 456: 123, + 789: 456, + }, }, }; @@ -372,6 +411,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, }; @@ -396,6 +436,9 @@ describe( 'selectors', () => { '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], }, + parents: { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': '', + }, }, }; @@ -419,6 +462,9 @@ describe( 'selectors', () => { '': [ 123 ], 123: [], }, + parents: { + 123: '', + }, }, }; @@ -436,6 +482,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, }; @@ -458,6 +505,10 @@ describe( 'selectors', () => { 123: [ 456 ], 456: [], }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -507,6 +558,9 @@ describe( 'selectors', () => { '': [ 123 ], 123: [], }, + parents: { + 123: '', + }, }, }; @@ -538,6 +592,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 123: '', + 23: '', + }, }, }; @@ -603,6 +661,20 @@ describe( 'selectors', () => { 'uuid-26': [ ], 'uuid-28': [ 'uuid-30' ], }, + parents: { + 'uuid-6': '', + 'uuid-8': '', + 'uuid-10': '', + 'uuid-22': '', + 'uuid-12': 'uuid-10', + 'uuid-14': 'uuid-10', + 'uuid-16': 'uuid-12', + 'uuid-18': 'uuid-14', + 'uuid-24': 'uuid-18', + 'uuid-26': 'uuid-24', + 'uuid-28': 'uuid-24', + 'uuid-30': 'uuid-28', + }, }, }; expect( getClientIdsOfDescendants( state, [ 'uuid-10' ] ) ).toEqual( [ @@ -673,6 +745,20 @@ describe( 'selectors', () => { 'uuid-26': [ ], 'uuid-28': [ 'uuid-30' ], }, + parents: { + 'uuid-6': '', + 'uuid-8': '', + 'uuid-10': '', + 'uuid-22': '', + 'uuid-12': 'uuid-10', + 'uuid-14': 'uuid-10', + 'uuid-16': 'uuid-12', + 'uuid-18': 'uuid-14', + 'uuid-24': 'uuid-18', + 'uuid-26': 'uuid-24', + 'uuid-28': 'uuid-24', + 'uuid-30': 'uuid-28', + }, }, }; expect( getClientIdsWithDescendants( state ) ).toEqual( [ @@ -730,6 +816,11 @@ describe( 'selectors', () => { '': [ 123 ], 123: [ 456, 789 ], }, + parents: { + 123: '', + 456: 123, + 789: 123, + }, }, }; @@ -788,6 +879,10 @@ describe( 'selectors', () => { order: { '': [ 123, 456 ], }, + parents: { + 123: '', + 456: '', + }, }, }; @@ -805,6 +900,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, }; expect( getGlobalBlockCount( emptyState ) ).toBe( 0 ); @@ -862,6 +958,10 @@ describe( 'selectors', () => { 23: [], 123: [], }, + parents: { + 23: '', + 123: '', + }, }, blockSelection: { start: {}, end: {} }, }; @@ -885,6 +985,10 @@ describe( 'selectors', () => { 23: [], 123: [], }, + parents: { + 123: '', + 23: '', + }, }, blockSelection: { start: { clientId: 23 }, end: { clientId: 123 } }, }; @@ -908,6 +1012,10 @@ describe( 'selectors', () => { 23: [], 123: [], }, + parents: { + 123: '', + 23: '', + }, }, blockSelection: { start: { clientId: 23 }, end: { clientId: 23 } }, }; @@ -926,6 +1034,7 @@ describe( 'selectors', () => { const state = { blocks: { order: {}, + parents: {}, }, }; @@ -939,10 +1048,16 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 123: '', + 23: '', + 456: 123, + 56: 123, + }, }, }; - expect( getBlockRootClientId( state, 56 ) ).toBe( '123' ); + expect( getBlockRootClientId( state, 56 ) ).toBe( 123 ); } ); } ); @@ -951,37 +1066,51 @@ describe( 'selectors', () => { const state = { blocks: { order: {}, + parents: {}, }, }; - expect( getBlockHierarchyRootClientId( state, 56 ) ).toBe( 56 ); + expect( getBlockHierarchyRootClientId( state, '56' ) ).toBe( '56' ); } ); it( 'should return root ClientId relative the block ClientId', () => { const state = { blocks: { order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + }, + parents: { + a: '', + b: '', + c: 'a', + d: 'a', }, }, }; - expect( getBlockHierarchyRootClientId( state, 56 ) ).toBe( '123' ); + expect( getBlockHierarchyRootClientId( state, 'c' ) ).toBe( 'a' ); } ); it( 'should return the top level root ClientId relative the block ClientId', () => { const state = { blocks: { order: { - '': [ '123', '23' ], - 123: [ '456', '56' ], - 56: [ '12' ], + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + d: [ 'e' ], + }, + parents: { + a: '', + b: '', + c: 'a', + d: 'a', + e: 'd', }, }, }; - expect( getBlockHierarchyRootClientId( state, '12' ) ).toBe( '123' ); + expect( getBlockHierarchyRootClientId( state, 'e' ) ).toBe( 'a' ); } ); } ); @@ -992,6 +1121,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 123: '', + 23: '', + }, }, blockSelection: { start: {}, end: {} }, }; @@ -1005,6 +1138,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 2 } }, }; @@ -1018,6 +1158,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1032,6 +1179,17 @@ describe( 'selectors', () => { '': [ 5, 4, 3, 2, 1 ], 4: [ 9, 8, 7, 6 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + 6: 4, + 7: 4, + 8: 4, + 9: 4, + }, }, blockSelection: { start: { clientId: 7 }, end: { clientId: 9 } }, }; @@ -1047,6 +1205,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, blockSelection: { start: {}, end: {} }, }; @@ -1060,6 +1222,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1074,6 +1243,17 @@ describe( 'selectors', () => { '': [ 5, 4, 3, 2, 1 ], 4: [ 9, 8, 7, 6 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + 6: 4, + 7: 4, + 8: 4, + 9: 4, + }, }, blockSelection: { start: { clientId: 7 }, end: { clientId: 9 } }, }; @@ -1089,6 +1269,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, blockSelection: { start: {}, end: {} }, }; @@ -1142,6 +1323,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1155,6 +1340,11 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + }, }, }; @@ -1169,6 +1359,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1182,6 +1376,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 56: 123, + 456: 123, + }, }, }; @@ -1196,6 +1396,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1209,6 +1413,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + 56: 123, + }, }, }; @@ -1221,6 +1431,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1234,6 +1448,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + 56: 123, + }, }, }; @@ -1248,6 +1468,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1261,6 +1485,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + 56: 123, + }, }, }; @@ -1273,6 +1503,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1286,6 +1520,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + 56: 123, + }, }, }; @@ -1327,6 +1567,11 @@ describe( 'selectors', () => { order: { 4: [ 3, 2, 1 ], }, + parents: { + 1: 4, + 2: 4, + 3: 4, + }, }, }; @@ -1340,6 +1585,11 @@ describe( 'selectors', () => { order: { 4: [ 3, 2, 1 ], }, + parents: { + 1: 4, + 2: 4, + 3: 4, + }, }, }; @@ -1352,6 +1602,13 @@ describe( 'selectors', () => { order: { 6: [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: 6, + 2: 6, + 3: 6, + 4: 6, + 5: 6, + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1365,6 +1622,12 @@ describe( 'selectors', () => { 3: [ 2, 1 ], 6: [ 5, 4 ], }, + parents: { + 1: 3, + 2: 3, + 4: 6, + 5: 6, + }, }, blockSelection: { start: { clientId: 5 }, end: { clientId: 4 } }, }; @@ -1380,6 +1643,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, }; @@ -1393,6 +1663,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, }; @@ -1406,6 +1683,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, }; @@ -1419,6 +1703,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, }; @@ -1467,6 +1758,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1486,6 +1784,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1598,6 +1903,10 @@ describe( 'selectors', () => { clientId1: [ 'clientId2' ], clientId2: [], }, + parents: { + clientId1: '', + clientId2: 'clientId1', + }, }, insertionPoint: { rootClientId: undefined, @@ -1628,6 +1937,9 @@ describe( 'selectors', () => { '': [ 'clientId1' ], clientId1: [], }, + parents: { + clientId1: '', + }, }, insertionPoint: null, }; @@ -1658,6 +1970,10 @@ describe( 'selectors', () => { clientId1: [ 'clientId2' ], clientId2: [], }, + parents: { + clientId1: '', + clientId2: 'clientId1', + }, }, insertionPoint: null, }; @@ -1688,6 +2004,10 @@ describe( 'selectors', () => { clientId1: [], clientId2: [], }, + parents: { + clientId1: '', + clientId2: '', + }, }, insertionPoint: null, }; @@ -1718,6 +2038,10 @@ describe( 'selectors', () => { clientId1: [], clientId2: [], }, + parents: { + clientId1: '', + clientId2: '', + }, }, insertionPoint: null, }; @@ -1921,6 +2245,7 @@ describe( 'selectors', () => { block1: {}, }, order: {}, + parents: {}, }, settings: { __experimentalReusableBlocks: [ @@ -1991,6 +2316,9 @@ describe( 'selectors', () => { order: { '': [ 'block1ref' ], }, + parents: { + block1ref: '', + }, }, settings: { __experimentalReusableBlocks: [ @@ -2051,6 +2379,11 @@ describe( 'selectors', () => { referredBlock2: [ 'childReferredBlock2' ], childReferredBlock2: [ 'grandchildReferredBlock2' ], }, + parents: { + block2ref: '', + childReferredBlock2: 'referredBlock2', + grandchildReferredBlock2: 'childReferredBlock2', + }, }, settings: { @@ -2094,6 +2427,7 @@ describe( 'selectors', () => { block2: {}, }, order: {}, + parents: {}, }, settings: { __experimentalReusableBlocks: [ @@ -2137,6 +2471,10 @@ describe( 'selectors', () => { order: { '': [ 'block3', 'block4' ], }, + parents: { + block3: '', + block4: '', + }, }, settings: { __experimentalReusableBlocks: [ @@ -2214,6 +2552,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, preferences: { insertUsage: {}, @@ -2232,6 +2571,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, preferences: { insertUsage: { @@ -2259,6 +2599,9 @@ describe( 'selectors', () => { order: { '': [ 'block1' ], }, + parents: { + block1: '', + }, }, preferences: { insertUsage: {},