diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index 4f014040d66cd..d0700bd8d05ab 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -8,7 +8,7 @@ import { __experimentalUseDropZone as useDropZone, } from '@wordpress/compose'; import { isRTL } from '@wordpress/i18n'; -import { isUnmodifiedDefaultBlock } from '@wordpress/blocks'; +import { isUnmodifiedDefaultBlock as getIsUnmodifiedDefaultBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -36,16 +36,25 @@ import { store as blockEditorStore } from '../../store'; */ /** - * Given a list of block DOM elements finds the index that a block should be dropped - * at. + * @typedef {Object} WPBlockData + * @property {boolean} isUnmodifiedDefaultBlock Is the block unmodified default block. + * @property {() => DOMRect} getBoundingClientRect Get the bounding client rect of the block. + * @property {number} blockIndex The index of the block. + */ + +/** + * Get the drop target position from a given drop point and the orientation. * - * @param {Element[]} elements Array of DOM elements that represent each block in a block list. + * @param {WPBlockData[]} blocksData The block data list. * @param {WPPoint} position The position of the item being dragged. - * @param {WPBlockListOrientation} orientation The orientation of a block list. - * - * @return {[number|undefined, WPInsertPosition]} The block index and the position that's closest to the drag position. + * @param {WPBlockListOrientation} orientation The orientation of the block list. + * @return {[number, WPDropOperation]} The drop target position. */ -export function getNearestBlockIndex( elements, position, orientation ) { +export function getDropTargetPosition( + blocksData, + position, + orientation = 'vertical' +) { const allowedEdges = orientation === 'horizontal' ? [ 'left', 'right' ] @@ -53,62 +62,49 @@ export function getNearestBlockIndex( elements, position, orientation ) { const isRightToLeft = isRTL(); - let candidateIndex; - let candidatePosition = 'after'; - let candidateDistance; - - elements.forEach( ( element, index ) => { - const rect = element.getBoundingClientRect(); - - let [ distance, edge ] = getDistanceToNearestEdge( - position, - rect, - allowedEdges - ); - // Prioritize the element if the point is inside of it. - if ( isPointContainedByRect( position, rect ) ) { - distance = 0; - } + let nearestIndex = 0; + let insertPosition = 'before'; + let minDistance = Infinity; - if ( candidateDistance === undefined || distance < candidateDistance ) { - // Where the dropped block will be inserted on the nearest block. - candidatePosition = - edge === 'bottom' || - ( ! isRightToLeft && edge === 'right' ) || - ( isRightToLeft && edge === 'left' ) - ? 'after' - : 'before'; - - // Update the currently known best candidate. - candidateDistance = distance; - candidateIndex = index; - } - } ); + blocksData.forEach( + ( { isUnmodifiedDefaultBlock, getBoundingClientRect, blockIndex } ) => { + const rect = getBoundingClientRect(); - return [ candidateIndex, candidatePosition ]; -} + let [ distance, edge ] = getDistanceToNearestEdge( + position, + rect, + allowedEdges + ); + // Prioritize the element if the point is inside of an unmodified default block. + if ( + isUnmodifiedDefaultBlock && + isPointContainedByRect( position, rect ) + ) { + distance = 0; + } + + if ( distance < minDistance ) { + // Where the dropped block will be inserted on the nearest block. + insertPosition = + edge === 'bottom' || + ( ! isRightToLeft && edge === 'right' ) || + ( isRightToLeft && edge === 'left' ) + ? 'after' + : 'before'; + + // Update the currently known best candidate. + minDistance = distance; + nearestIndex = blockIndex; + } + } + ); -/** - * Get the drop target index and operation based on the the blocks and the nearst block index. - * - * @param {number|undefined} nearestIndex The nearest block index calculated by getNearestBlockIndex. - * @param {WPInsertPosition} insertPosition Whether to insert before or after the nearestIndex. - * @param {WPBlock[]} blocks The blocks list. - * @return {[number, WPDropOperation]} The drop target. - */ -export function getDropTargetIndexAndOperation( - nearestIndex, - insertPosition, - blocks -) { const adjacentIndex = nearestIndex + ( insertPosition === 'after' ? 1 : -1 ); - const nearestBlock = blocks[ nearestIndex ]; - const adjacentBlock = blocks[ adjacentIndex ]; const isNearestBlockUnmodifiedDefaultBlock = - !! nearestBlock && isUnmodifiedDefaultBlock( nearestBlock ); + !! blocksData[ nearestIndex ]?.isUnmodifiedDefaultBlock; const isAdjacentBlockUnmodifiedDefaultBlock = - !! adjacentBlock && isUnmodifiedDefaultBlock( adjacentBlock ); + !! blocksData[ adjacentIndex ]?.isUnmodifiedDefaultBlock; // If both blocks are not unmodified default blocks then just insert between them. if ( @@ -170,7 +166,8 @@ export default function useBlockDropZone( { [ targetRootClientId ] ); - const { getBlockListSettings, getBlocks } = useSelect( blockEditorStore ); + const { getBlockListSettings, getBlocks, getBlockIndex } = + useSelect( blockEditorStore ); const { showInsertionPoint, hideInsertionPoint } = useDispatch( blockEditorStore ); @@ -179,16 +176,11 @@ export default function useBlockDropZone( { } ); const throttled = useThrottle( useCallback( - ( event, currentTarget ) => { - const blockElements = Array.from( - currentTarget.children - ).filter( - // Ensure the element is a block. It should have the `wp-block` class. - ( element ) => element.classList.contains( 'wp-block' ) - ); + ( event, ownerDocument ) => { + const blocks = getBlocks( targetRootClientId ); // The block list is empty, don't show the insertion point but still allow dropping. - if ( blockElements.length === 0 ) { + if ( blocks.length === 0 ) { setDropTarget( { index: 0, operation: 'insert', @@ -196,20 +188,25 @@ export default function useBlockDropZone( { return; } - const [ nearestBlockIndex, insertPosition ] = - getNearestBlockIndex( - blockElements, - { x: event.clientX, y: event.clientY }, - getBlockListSettings( targetRootClientId )?.orientation - ); + const blocksData = blocks.map( ( block ) => { + const clientId = block.clientId; + + return { + isUnmodifiedDefaultBlock: + getIsUnmodifiedDefaultBlock( block ), + getBoundingClientRect: () => + ownerDocument + .getElementById( `block-${ clientId }` ) + .getBoundingClientRect(), + blockIndex: getBlockIndex( clientId ), + }; + } ); - const blocks = getBlocks( targetRootClientId ); - const [ targetIndex, operation ] = - getDropTargetIndexAndOperation( - nearestBlockIndex, - insertPosition, - blocks - ); + const [ targetIndex, operation ] = getDropTargetPosition( + blocksData, + { x: event.clientX, y: event.clientY }, + getBlockListSettings( targetRootClientId )?.orientation + ); setDropTarget( { index: targetIndex, @@ -231,7 +228,7 @@ export default function useBlockDropZone( { // `currentTarget` is only available while the event is being // handled, so get it now and pass it to the thottled function. // https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget - throttled( event, event.currentTarget ); + throttled( event, event.currentTarget.ownerDocument ); }, onDragLeave() { throttled.cancel(); diff --git a/packages/block-editor/src/components/use-block-drop-zone/test/index.js b/packages/block-editor/src/components/use-block-drop-zone/test/index.js index 5cb1b17747acf..f5560c1cfdf13 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/test/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/test/index.js @@ -1,18 +1,7 @@ -/** - * WordPress dependencies - */ -import { - registerBlockType, - unregisterBlockType, - createBlock, - getDefaultBlockName, - setDefaultBlockName, -} from '@wordpress/blocks'; - /** * Internal dependencies */ -import { getNearestBlockIndex, getDropTargetIndexAndOperation } from '..'; +import { getDropTargetPosition } from '..'; const elementData = [ { @@ -42,19 +31,12 @@ const elementData = [ }, ]; -const createMockClassList = ( classes ) => { - return { - contains( textToMatch ) { - return classes.includes( textToMatch ); - }, - }; -}; - const mapElements = ( orientation ) => - ( { top, right, bottom, left }, index ) => { + ( { top, right, bottom, left, isUnmodifiedDefaultBlock }, index ) => { return { - dataset: { block: index + 1 }, + isUnmodifiedDefaultBlock: !! isUnmodifiedDefaultBlock, + blockIndex: index, getBoundingClientRect() { return orientation === 'vertical' ? { @@ -70,349 +52,509 @@ const mapElements = right: bottom, }; }, - classList: createMockClassList( 'wp-block' ), }; }; -const verticalElements = elementData.map( mapElements( 'vertical' ) ); +const verticalBlocksData = elementData.map( mapElements( 'vertical' ) ); // Flip the elementData to make a horizontal block list. -const horizontalElements = elementData.map( mapElements( 'horizontal' ) ); +const horizontalBlocksData = elementData.map( mapElements( 'horizontal' ) ); -describe( 'getNearestBlockIndex', () => { - it( 'returns `undefined` for an empty list of elements', () => { - const emptyElementList = []; +describe( 'getDropTargetPosition', () => { + it( 'returns `0` for an empty list of elements', () => { const position = { x: 0, y: 0 }; const orientation = 'horizontal'; - const result = getNearestBlockIndex( - emptyElementList, - position, - orientation - ); + const result = getDropTargetPosition( [], position, orientation ); - expect( result ).toEqual( [ undefined, 'after' ] ); + expect( result ).toEqual( [ 0, 'insert' ] ); } ); describe( 'Vertical block lists', () => { const orientation = 'vertical'; - it( "returns [0, 'before'] when the position is nearest to the start of the first block", () => { + it( 'returns `0` when the position is nearest to the start of the first block', () => { const position = { x: 0, y: 0 }; - const result = getNearestBlockIndex( - verticalElements, + const result = getDropTargetPosition( + verticalBlocksData, position, orientation ); - expect( result ).toEqual( [ 0, 'before' ] ); + expect( result ).toEqual( [ 0, 'insert' ] ); } ); - it( "returns [0, 'after'] when the position is nearest to the end of the first block", () => { + it( 'returns `1` when the position is nearest to the end of the first block', () => { const position = { x: 0, y: 190 }; - const result = getNearestBlockIndex( - verticalElements, + const result = getDropTargetPosition( + verticalBlocksData, position, orientation ); - expect( result ).toEqual( [ 0, 'after' ] ); + expect( result ).toEqual( [ 1, 'insert' ] ); } ); - it( "returns [1, 'before'] when the position is nearest to the start of the second block", () => { + it( 'returns `1` when the position is nearest to the start of the second block', () => { const position = { x: 0, y: 210 }; - const result = getNearestBlockIndex( - verticalElements, + const result = getDropTargetPosition( + verticalBlocksData, position, orientation ); - expect( result ).toEqual( [ 1, 'before' ] ); + expect( result ).toEqual( [ 1, 'insert' ] ); } ); - it( "returns [1, 'after'] when the position is nearest to the end of the second block", () => { + it( 'returns `2` when the position is nearest to the end of the second block', () => { const position = { x: 0, y: 450 }; - const result = getNearestBlockIndex( - verticalElements, + const result = getDropTargetPosition( + verticalBlocksData, position, orientation ); - expect( result ).toEqual( [ 1, 'after' ] ); + expect( result ).toEqual( [ 2, 'insert' ] ); } ); - it( "returns [2, 'before'] when the position is nearest to the start of the third block", () => { + it( 'returns `2` when the position is nearest to the start of the third block', () => { const position = { x: 0, y: 510 }; - const result = getNearestBlockIndex( - verticalElements, + const result = getDropTargetPosition( + verticalBlocksData, position, orientation ); - expect( result ).toEqual( [ 2, 'before' ] ); + expect( result ).toEqual( [ 2, 'insert' ] ); } ); - it( "returns [2, 'after'] when the position is nearest to the end of the third block", () => { + it( 'returns `3` when the position is nearest to the end of the third block', () => { const position = { x: 0, y: 880 }; - const result = getNearestBlockIndex( - verticalElements, + const result = getDropTargetPosition( + verticalBlocksData, position, orientation ); - expect( result ).toEqual( [ 2, 'after' ] ); + expect( result ).toEqual( [ 3, 'insert' ] ); } ); - it( "returns [2, 'after'] when the position is past the end of the third block", () => { + it( 'returns `3` when the position is past the end of the third block', () => { const position = { x: 0, y: 920 }; - const result = getNearestBlockIndex( - verticalElements, + const result = getDropTargetPosition( + verticalBlocksData, position, orientation ); - expect( result ).toEqual( [ 2, 'after' ] ); + expect( result ).toEqual( [ 3, 'insert' ] ); } ); - it( "returns [3, 'before'] when the position is nearest to the start of the last block", () => { + it( 'returns `4` when the position is nearest to the start of the fourth block', () => { const position = { x: 401, y: 0 }; - const result = getNearestBlockIndex( - verticalElements, + const result = getDropTargetPosition( + verticalBlocksData, position, orientation ); - expect( result ).toEqual( [ 3, 'before' ] ); + expect( result ).toEqual( [ 3, 'insert' ] ); } ); - it( "returns [3, 'after'] when the position is nearest to the end of the last block", () => { + it( 'returns `5` when the position is nearest to the end of the fourth block', () => { const position = { x: 401, y: 300 }; - const result = getNearestBlockIndex( - verticalElements, + const result = getDropTargetPosition( + verticalBlocksData, position, orientation ); - expect( result ).toEqual( [ 3, 'after' ] ); + expect( result ).toEqual( [ 4, 'insert' ] ); } ); } ); describe( 'Horizontal block lists', () => { const orientation = 'horizontal'; - it( "returns [0, 'before'] when the position is nearest to the start of the first block", () => { + it( 'returns `0` when the position is nearest to the start of the first block', () => { const position = { x: 0, y: 0 }; - const result = getNearestBlockIndex( - horizontalElements, + const result = getDropTargetPosition( + horizontalBlocksData, position, orientation ); - expect( result ).toEqual( [ 0, 'before' ] ); + expect( result ).toEqual( [ 0, 'insert' ] ); } ); - it( "returns [0, 'after'] when the position is nearest to the end of the first block", () => { + it( 'returns `1` when the position is nearest to the end of the first block', () => { const position = { x: 190, y: 0 }; - const result = getNearestBlockIndex( - horizontalElements, + const result = getDropTargetPosition( + horizontalBlocksData, position, orientation ); - expect( result ).toEqual( [ 0, 'after' ] ); + expect( result ).toEqual( [ 1, 'insert' ] ); } ); - it( "returns [1, 'before'] when the position is nearest to the start of the second block", () => { + it( 'returns `1` when the position is nearest to the start of the second block', () => { const position = { x: 210, y: 0 }; - const result = getNearestBlockIndex( - horizontalElements, + const result = getDropTargetPosition( + horizontalBlocksData, position, orientation ); - expect( result ).toEqual( [ 1, 'before' ] ); + expect( result ).toEqual( [ 1, 'insert' ] ); } ); - it( "returns [1, 'after'] when the position is nearest to the end of the second block", () => { + it( 'returns `2` when the position is nearest to the end of the second block', () => { const position = { x: 450, y: 0 }; - const result = getNearestBlockIndex( - horizontalElements, + const result = getDropTargetPosition( + horizontalBlocksData, position, orientation ); - expect( result ).toEqual( [ 1, 'after' ] ); + expect( result ).toEqual( [ 2, 'insert' ] ); } ); - it( "returns [2, 'before'] when the position is nearest to the start of the third block", () => { + it( 'returns `2` when the position is nearest to the start of the third block', () => { const position = { x: 510, y: 0 }; - const result = getNearestBlockIndex( - horizontalElements, + const result = getDropTargetPosition( + horizontalBlocksData, + position, + orientation + ); + + expect( result ).toEqual( [ 2, 'insert' ] ); + } ); + + it( 'returns `3` when the position is nearest to the end of the third block', () => { + const position = { x: 880, y: 0 }; + + const result = getDropTargetPosition( + horizontalBlocksData, position, orientation ); - expect( result ).toEqual( [ 2, 'before' ] ); + expect( result ).toEqual( [ 3, 'insert' ] ); } ); - it( "returns [2, 'after'] when the position is past the end of the third block", () => { + it( 'returns `3` when the position is past the end of the third block', () => { const position = { x: 920, y: 0 }; - const result = getNearestBlockIndex( - horizontalElements, + const result = getDropTargetPosition( + horizontalBlocksData, position, orientation ); - expect( result ).toEqual( [ 2, 'after' ] ); + expect( result ).toEqual( [ 3, 'insert' ] ); } ); - it( "returns [3, 'before'] when the position is nearest to the start of the last block", () => { + it( 'returns `3` when the position is nearest to the start of the last block', () => { const position = { x: 0, y: 401 }; - const result = getNearestBlockIndex( - horizontalElements, + const result = getDropTargetPosition( + horizontalBlocksData, position, orientation ); - expect( result ).toEqual( [ 3, 'before' ] ); + expect( result ).toEqual( [ 3, 'insert' ] ); } ); - it( "returns [3, 'after'] when the position is nearest to the end of the last block", () => { + it( 'returns `4` when the position is nearest to the end of the last block', () => { const position = { x: 300, y: 401 }; - const result = getNearestBlockIndex( - horizontalElements, + const result = getDropTargetPosition( + horizontalBlocksData, position, orientation ); - expect( result ).toEqual( [ 3, 'after' ] ); + expect( result ).toEqual( [ 4, 'insert' ] ); } ); } ); -} ); -describe( 'getDropTargetIndexAndOperation', () => { - let defaultBlockName; + describe( 'Unmodified default blocks', () => { + const orientation = 'vertical'; - beforeAll( () => { - defaultBlockName = getDefaultBlockName(); - registerBlockType( 'test/default-block', { title: 'default block' } ); - registerBlockType( 'test/not-default-block', { - title: 'not default block', + it( 'handles replacement index when only the first block is an unmodified default block', () => { + const blocksData = [ + { + left: 0, + top: 10, + right: 400, + bottom: 210, + isUnmodifiedDefaultBlock: true, + }, + { + left: 0, + top: 220, + right: 400, + bottom: 420, + isUnmodifiedDefaultBlock: false, + }, + ].map( mapElements( 'vertical' ) ); + + // Dropping above the first block. + expect( + getDropTargetPosition( blocksData, { x: 0, y: 0 }, orientation ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping on the top half of the first block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 20 }, + orientation + ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping on the bottom half of the first block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 200 }, + orientation + ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping slightly after the first block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 211 }, + orientation + ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping slightly above the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 219 }, + orientation + ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping on the top half of the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 230 }, + orientation + ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping on the bottom half of the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 410 }, + orientation + ) + ).toEqual( [ 2, 'insert' ] ); + + // Dropping below the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 421 }, + orientation + ) + ).toEqual( [ 2, 'insert' ] ); } ); - setDefaultBlockName( 'test/default-block' ); - } ); - - afterAll( () => { - setDefaultBlockName( defaultBlockName ); - unregisterBlockType( 'test/default-block' ); - unregisterBlockType( 'test/not-default-block' ); - } ); - - it( 'returns insertion index when there are no unmodified default blocks', () => { - const blocks = [ - createBlock( 'test/not-default-block' ), - createBlock( 'test/not-default-block' ), - ]; - - expect( getDropTargetIndexAndOperation( 0, 'before', blocks ) ).toEqual( - [ 0, 'insert' ] - ); - - expect( getDropTargetIndexAndOperation( 0, 'after', blocks ) ).toEqual( - [ 1, 'insert' ] - ); - expect( getDropTargetIndexAndOperation( 1, 'before', blocks ) ).toEqual( - [ 1, 'insert' ] - ); - - expect( getDropTargetIndexAndOperation( 1, 'after', blocks ) ).toEqual( - [ 2, 'insert' ] - ); - } ); - - it( 'handles replacement index when only the first block is an unmodified default block', () => { - const blocks = [ - createBlock( 'test/default-block' ), - createBlock( 'test/not-default-block' ), - ]; - - expect( getDropTargetIndexAndOperation( 0, 'before', blocks ) ).toEqual( - [ 0, 'replace' ] - ); - - expect( getDropTargetIndexAndOperation( 0, 'after', blocks ) ).toEqual( - [ 0, 'replace' ] - ); - - expect( getDropTargetIndexAndOperation( 1, 'before', blocks ) ).toEqual( - [ 0, 'replace' ] - ); - - expect( getDropTargetIndexAndOperation( 1, 'after', blocks ) ).toEqual( - [ 2, 'insert' ] - ); - } ); - - it( 'handles replacement index when only the second block is an unmodified default block', () => { - const blocks = [ - createBlock( 'test/not-default-block' ), - createBlock( 'test/default-block' ), - ]; - - expect( getDropTargetIndexAndOperation( 0, 'before', blocks ) ).toEqual( - [ 0, 'insert' ] - ); - - expect( getDropTargetIndexAndOperation( 0, 'after', blocks ) ).toEqual( - [ 1, 'replace' ] - ); - - expect( getDropTargetIndexAndOperation( 1, 'before', blocks ) ).toEqual( - [ 1, 'replace' ] - ); - - expect( getDropTargetIndexAndOperation( 1, 'after', blocks ) ).toEqual( - [ 1, 'replace' ] - ); - } ); - - it( 'returns replacement index when both blocks are unmodified default blocks', () => { - const blocks = [ - createBlock( 'test/default-block' ), - createBlock( 'test/default-block' ), - ]; - - expect( getDropTargetIndexAndOperation( 0, 'before', blocks ) ).toEqual( - [ 0, 'replace' ] - ); - - expect( getDropTargetIndexAndOperation( 0, 'after', blocks ) ).toEqual( - [ 0, 'replace' ] - ); - - expect( getDropTargetIndexAndOperation( 1, 'before', blocks ) ).toEqual( - [ 1, 'replace' ] - ); + it( 'handles replacement index when only the second block is an unmodified default block', () => { + const blocksData = [ + { + left: 0, + top: 10, + right: 400, + bottom: 210, + isUnmodifiedDefaultBlock: false, + }, + { + left: 0, + top: 220, + right: 400, + bottom: 420, + isUnmodifiedDefaultBlock: true, + }, + ].map( mapElements( 'vertical' ) ); + + // Dropping above the first block. + expect( + getDropTargetPosition( blocksData, { x: 0, y: 0 }, orientation ) + ).toEqual( [ 0, 'insert' ] ); + + // Dropping on the top half of the first block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 20 }, + orientation + ) + ).toEqual( [ 0, 'insert' ] ); + + // Dropping on the bottom half of the first block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 200 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + + // Dropping slightly after the first block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 211 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + + // Dropping slightly above the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 219 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + + // Dropping on the top half of the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 230 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + + // Dropping on the bottom half of the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 410 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + + // Dropping below the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 421 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + } ); - expect( getDropTargetIndexAndOperation( 1, 'after', blocks ) ).toEqual( - [ 1, 'replace' ] - ); + it( 'returns replacement index when both blocks are unmodified default blocks', () => { + const blocksData = [ + { + left: 0, + top: 10, + right: 400, + bottom: 210, + isUnmodifiedDefaultBlock: true, + }, + { + left: 0, + top: 220, + right: 400, + bottom: 420, + isUnmodifiedDefaultBlock: true, + }, + ].map( mapElements( 'vertical' ) ); + + // Dropping above the first block. + expect( + getDropTargetPosition( blocksData, { x: 0, y: 0 }, orientation ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping on the top half of the first block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 20 }, + orientation + ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping on the bottom half of the first block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 200 }, + orientation + ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping slightly after the first block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 211 }, + orientation + ) + ).toEqual( [ 0, 'replace' ] ); + + // Dropping slightly above the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 219 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + + // Dropping on the top half of the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 230 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + + // Dropping on the bottom half of the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 410 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + + // Dropping below the second block. + expect( + getDropTargetPosition( + blocksData, + { x: 0, y: 421 }, + orientation + ) + ).toEqual( [ 1, 'replace' ] ); + } ); } ); } );