From 092fe89b3f88b3862d03f2db4d9e06c50cdaad33 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 11 May 2017 11:50:27 +0100 Subject: [PATCH] Test: Move the merge block handler to the actions file and unit test it --- editor/actions.js | 55 +++++++++ editor/block-switcher/index.js | 10 +- editor/modes/visual-editor/block.js | 56 ++-------- editor/test/actions.js | 168 ++++++++++++++++++++++++++++ 4 files changed, 235 insertions(+), 54 deletions(-) create mode 100644 editor/test/actions.js diff --git a/editor/actions.js b/editor/actions.js index 978780b9f7c7f4..85024762b01387 100644 --- a/editor/actions.js +++ b/editor/actions.js @@ -4,6 +4,22 @@ import { get } from 'lodash'; import { parse, stringify } from 'querystring'; +export function focusBlock( uid, config ) { + return { + type: 'UPDATE_FOCUS', + uid, + config, + }; +} + +export function replaceBlocks( uids, blocks ) { + return { + type: 'REPLACE_BLOCKS', + uids, + blocks, + }; +} + export function savePost( dispatch, postId, edits ) { const toSend = postId ? { id: postId, ...edits } : edits; const isNew = ! postId; @@ -41,3 +57,42 @@ export function savePost( dispatch, postId, edits ) { } ); } ); } + +export function mergeBlocks( dispatch, blockA, blockB ) { + const blockASettings = wp.blocks.getBlockSettings( blockA.blockType ); + + // Only focus the previous block if it's not mergeable + if ( ! blockASettings.merge ) { + dispatch( focusBlock( blockA.uid ) ); + return; + } + + // We can only merge blocks with similar types + // thus, we transform the block to merge first + const blocksWithTheSameType = blockA.blockType === blockB.blockType + ? [ blockB ] + : wp.blocks.switchToBlockType( blockB, blockA.blockType ); + + // If the block types can not match, do nothing + if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { + return; + } + + // Calling the merge to update the attributes and remove the block to be merged + const updatedAttributes = blockASettings.merge( blockA.attributes, blocksWithTheSameType[ 0 ].attributes ); + + dispatch( focusBlock( blockA.uid, { offset: -1 } ) ); + dispatch( replaceBlocks( + [ blockA.uid, blockB.uid ], + [ + { + ...blockA, + attributes: { + ...blockA.attributes, + ...updatedAttributes, + }, + }, + ...blocksWithTheSameType.slice( 1 ), + ] + ) ); +} diff --git a/editor/block-switcher/index.js b/editor/block-switcher/index.js index 49d6683656b261..052b9dd4257b63 100644 --- a/editor/block-switcher/index.js +++ b/editor/block-switcher/index.js @@ -15,6 +15,7 @@ import Dashicon from 'components/dashicon'; * Internal dependencies */ import './style.scss'; +import { replaceBlocks } from '../actions'; import { getBlock } from '../selectors'; class BlockSwitcher extends wp.element.Component { @@ -111,11 +112,10 @@ export default connect( } ), ( dispatch, ownProps ) => ( { onTransform( block, blockType ) { - dispatch( { - type: 'REPLACE_BLOCKS', - uids: [ ownProps.uid ], - blocks: wp.blocks.switchToBlockType( block, blockType ), - } ); + dispatch( replaceBlocks( + [ ownProps.uid ], + wp.blocks.switchToBlockType( block, blockType ) + ) ); }, } ) )( clickOutside( BlockSwitcher ) ); diff --git a/editor/modes/visual-editor/block.js b/editor/modes/visual-editor/block.js index 788f98189ccfac..ac0131ac6ba888 100644 --- a/editor/modes/visual-editor/block.js +++ b/editor/modes/visual-editor/block.js @@ -16,6 +16,7 @@ import Toolbar from 'components/toolbar'; */ import BlockMover from '../../block-mover'; import BlockSwitcher from '../../block-switcher'; +import { focusBlock, mergeBlocks } from '../../actions'; import { getPreviousBlock, getBlock, @@ -101,49 +102,14 @@ class VisualEditorBlock extends wp.element.Component { } mergeWithPrevious() { - const { block, previousBlock, onFocus, replaceBlocks } = this.props; + const { block, previousBlock, onMerge } = this.props; // Do nothing when it's the first block if ( ! previousBlock ) { return; } - const previousBlockSettings = wp.blocks.getBlockSettings( previousBlock.blockType ); - - // Do nothing if the previous block is not mergeable - if ( ! previousBlockSettings.merge ) { - onFocus( previousBlock.uid ); - return; - } - - // We can only merge blocks with similar types - // thus, we transform the block to merge first - const blocksWithTheSameType = previousBlock.blockType === block.blockType - ? [ block ] - : wp.blocks.switchToBlockType( block, previousBlock.blockType ); - - // If the block types can not match, do nothing - if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { - return; - } - - // Calling the merge to update the attributes and remove the block to be merged - const updatedAttributes = previousBlockSettings.merge( previousBlock.attributes, blocksWithTheSameType[ 0 ].attributes ); - - onFocus( previousBlock.uid, { offset: -1 } ); - replaceBlocks( - [ previousBlock.uid, block.uid ], - [ - { - ...previousBlock, - attributes: { - ...previousBlock.attributes, - ...updatedAttributes, - }, - }, - ...blocksWithTheSameType.slice( 1 ), - ] - ); + onMerge( previousBlock, block ); } componentDidUpdate( prevProps ) { @@ -316,12 +282,8 @@ export default connect( } ); }, - onFocus( uid, config ) { - dispatch( { - type: 'UPDATE_FOCUS', - uid, - config, - } ); + onFocus( ...args ) { + dispatch( focusBlock( ...args ) ); }, onRemove( uid ) { @@ -331,12 +293,8 @@ export default connect( } ); }, - replaceBlocks( uids, blocks ) { - dispatch( { - type: 'REPLACE_BLOCKS', - uids, - blocks, - } ); + onMerge( ...args ) { + mergeBlocks( dispatch, ...args ); }, } ) )( VisualEditorBlock ); diff --git a/editor/test/actions.js b/editor/test/actions.js new file mode 100644 index 00000000000000..5d12bf6cdf8c97 --- /dev/null +++ b/editor/test/actions.js @@ -0,0 +1,168 @@ +/** + * External dependencies + */ +import { expect } from 'chai'; +import sinon from 'sinon'; + +/** + * WordPress dependencies + */ +import { getBlocks, unregisterBlock, registerBlock, createBlock } from 'blocks'; + +/** + * Internal dependencies + */ +import { focusBlock, replaceBlocks, mergeBlocks } from '../actions'; + +describe( 'actions', () => { + describe( 'focusBlock', () => { + it( 'should return the UPDATE_FOCUS action', () => { + const focusConfig = { + editable: 'cite', + }; + + expect( focusBlock( 'chicken', focusConfig ) ).to.eql( { + type: 'UPDATE_FOCUS', + uid: 'chicken', + config: focusConfig, + } ); + } ); + } ); + + describe( 'replaceBlocks', () => { + it( 'should return the REPLACE_BLOCKS action', () => { + const blocks = [ { + uid: 'ribs', + } ]; + + expect( replaceBlocks( [ 'chicken' ], blocks ) ).to.eql( { + type: 'REPLACE_BLOCKS', + uids: [ 'chicken' ], + blocks, + } ); + } ); + } ); + + describe( 'mergeBlocks', () => { + afterEach( () => { + getBlocks().forEach( ( block ) => { + unregisterBlock( block.slug ); + } ); + } ); + + it( 'should only focus the blockA if the blockA has no merge function', () => { + registerBlock( 'core/test-block', {} ); + const blockA = { + uid: 'chicken', + blockType: 'core/test-block', + }; + const blockB = { + uid: 'ribs', + blockType: 'core/test-block', + }; + const dispatch = sinon.spy(); + mergeBlocks( dispatch, blockA, blockB ); + + expect( dispatch ).to.have.been.calledOnce(); + expect( dispatch ).to.have.been.calledWith( focusBlock( 'chicken' ) ); + } ); + + it( 'should merge the blocks if blocks of the same type', () => { + registerBlock( 'core/test-block', { + merge( attributes, attributesToMerge ) { + return { + content: attributes.content + ' ' + attributesToMerge.content, + }; + }, + } ); + const blockA = { + uid: 'chicken', + blockType: 'core/test-block', + attributes: { content: 'chicken' }, + }; + const blockB = { + uid: 'ribs', + blockType: 'core/test-block', + attributes: { content: 'ribs' }, + }; + const dispatch = sinon.spy(); + mergeBlocks( dispatch, blockA, blockB ); + + expect( dispatch ).to.have.been.calledTwice(); + expect( dispatch ).to.have.been.calledWith( focusBlock( 'chicken', { offset: -1 } ) ); + expect( dispatch ).to.have.been.calledWith( replaceBlocks( [ 'chicken', 'ribs' ], [ { + uid: 'chicken', + blockType: 'core/test-block', + attributes: { content: 'chicken ribs' }, + } ] ) ); + } ); + + it( 'should not merge the blocks have different types without transformation', () => { + registerBlock( 'core/test-block', { + merge( attributes, attributesToMerge ) { + return { + content: attributes.content + ' ' + attributesToMerge.content, + }; + }, + } ); + registerBlock( 'core/test-block-2', {} ); + const blockA = { + uid: 'chicken', + blockType: 'core/test-block', + attributes: { content: 'chicken' }, + }; + const blockB = { + uid: 'ribs', + blockType: 'core/test-block2', + attributes: { content: 'ribs' }, + }; + const dispatch = sinon.spy(); + mergeBlocks( dispatch, blockA, blockB ); + + expect( dispatch ).to.have.not.been.called(); + } ); + + it( 'should transform and merge the blocks', () => { + registerBlock( 'core/test-block', { + merge( attributes, attributesToMerge ) { + return { + content: attributes.content + ' ' + attributesToMerge.content, + }; + }, + } ); + registerBlock( 'core/test-block-2', { + transforms: { + to: [ { + type: 'blocks', + blocks: [ 'core/test-block' ], + transform: ( { content2 } ) => { + return createBlock( 'core/test-block', { + content: content2, + } ); + }, + } ], + }, + } ); + const blockA = { + uid: 'chicken', + blockType: 'core/test-block', + attributes: { content: 'chicken' }, + }; + const blockB = { + uid: 'ribs', + blockType: 'core/test-block-2', + attributes: { content2: 'ribs' }, + }; + const dispatch = sinon.spy(); + mergeBlocks( dispatch, blockA, blockB ); + + expect( dispatch ).to.have.been.calledTwice(); + expect( dispatch ).to.have.been.calledWith( focusBlock( 'chicken', { offset: -1 } ) ); + expect( dispatch ).to.have.been.calledWith( replaceBlocks( [ 'chicken', 'ribs' ], [ { + uid: 'chicken', + blockType: 'core/test-block', + attributes: { content: 'chicken ribs' }, + } ] ) ); + } ); + } ); +} );