From c14cbec090a856227be04d2ceae633a9b31a8bde Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 13 Feb 2019 16:07:15 -0500 Subject: [PATCH] Block Editor: Remove history handling as managed by Editor --- .../developers/data/data-core-block-editor.md | 25 - .../developers/data/data-core-editor.md | 39 + .../src/components/provider/index.js | 50 +- packages/block-editor/src/store/actions.js | 46 - packages/block-editor/src/store/reducer.js | 72 +- packages/block-editor/src/store/selectors.js | 58 +- .../block-editor/src/store/test/actions.js | 18 - .../block-editor/src/store/test/reducer.js | 350 +--- .../block-editor/src/store/test/selectors.js | 1664 ++++++++--------- packages/editor/src/store/actions.js | 32 +- packages/editor/src/store/reducer.js | 64 +- packages/editor/src/store/selectors.js | 49 +- packages/editor/src/store/test/actions.js | 18 + packages/editor/src/store/test/reducer.js | 158 +- packages/editor/src/store/test/selectors.js | 917 +++++---- .../src/utils/with-history/README.md | 0 .../src/utils/with-history/index.js | 0 .../src/utils/with-history/test/index.js | 0 18 files changed, 1768 insertions(+), 1792 deletions(-) rename packages/{block-editor => editor}/src/utils/with-history/README.md (100%) rename packages/{block-editor => editor}/src/utils/with-history/index.js (100%) rename packages/{block-editor => editor}/src/utils/with-history/test/index.js (100%) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 70fed0f2a87ee6..3e17a2bfa41b78 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -776,17 +776,6 @@ Whether redo history exists. ## Actions -### __unstableInitBlocks - -Returns an action object used in signalling that blocks state should be -intialized using a specified array of blocks, - -This action reset the undo/redo history - -*Parameters* - - * blocks: Array of blocks. - ### resetBlocks Returns an action object used in signalling that blocks state should be @@ -1059,20 +1048,6 @@ Returns an action object used in signalling that the editor settings have been u * settings: Updated settings -### redo - -Returns an action object used in signalling that undo history should -restore last popped state. - -### undo - -Returns an action object used in signalling that undo history should pop. - -### createUndoLevel - -Returns an action object used in signalling that undo history record should -be created. - ### __unstableSaveReusableBlock Returns an action object used in signalling that a temporary reusable blocks have been saved diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 5f8d0cc866dcf5..52153d8f281f48 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -2,6 +2,27 @@ ## Selectors +### hasEditorUndo + +Returns true if any past editor history snapshots exist, or false otherwise. + +*Parameters* + + * state: Global application state. + +### hasEditorRedo + +Returns true if any future editor history snapshots exist, or false +otherwise. + +*Parameters* + + * state: Global application state. + +*Returns* + +Whether redo history exists. + ### isEditedPostNew Returns true if the currently edited post is yet to be saved, or false if @@ -11,6 +32,10 @@ the post has been saved. * state: Global application state. +*Returns* + +Whether the post is new. + ### hasChangedContent Returns true if content includes unsaved changes, or false otherwise. @@ -767,6 +792,20 @@ Returns an action object used in signalling that the post should autosave. * options: Extra flags to identify the autosave. +### redo + +Returns an action object used in signalling that undo history should +restore last popped state. + +### undo + +Returns an action object used in signalling that undo history should pop. + +### createUndoLevel + +Returns an action object used in signalling that undo history record should +be created. + ### updatePostLock Returns an action object used to lock the editor. diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index 4570af6a9b8ca5..c941841d196198 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -7,31 +7,40 @@ import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; class BlockEditorProvider extends Component { - constructor( props ) { - super( ...arguments ); - props.updateEditorSettings( props.settings ); - props.initBlocks( props.value ); - this.persistedValue = props.select( 'core/block-editor' ).getBlocks(); + componentDidMount() { + this.props.updateEditorSettings( this.props.settings ); + this.props.resetBlocks( this.props.value ); + + this.isSyncingBlockValue = true; } componentDidUpdate( prevProps ) { - if ( this.props.settings !== prevProps.settings ) { - this.props.updateEditorSettings( this.props.settings ); + const { + settings, + updateEditorSettings, + value, + resetBlocks, + blocks, + onChange, + } = this.props; + + if ( settings !== prevProps.settings ) { + updateEditorSettings( settings ); } - if ( - this.props.blocks !== prevProps.blocks && - this.props.blocks !== this.persistedValue - ) { - this.persistedValue = this.props.blocks; - this.props.onChange( this.props.blocks ); + if ( this.isSyncingBlockValue ) { + this.isSyncingBlockValue = false; + } else if ( blocks !== prevProps.blocks ) { + onChange( blocks ); + this.isSyncingBlockValue = true; + } else if ( value !== prevProps.value ) { + resetBlocks( value ); + this.isSyncingBlockValue = true; } } render() { - const { - children, - } = this.props; + const { children } = this.props; return ( @@ -45,20 +54,21 @@ class BlockEditorProvider extends Component { export default compose( [ withSelect( ( select ) => { + const { getBlocks } = select( 'core/block-editor' ); + return { - blocks: select( 'core/block-editor' ).getBlocks(), - select, + blocks: getBlocks(), }; } ), withDispatch( ( dispatch ) => { const { updateEditorSettings, - __unstableInitBlocks: initBlocks, + resetBlocks, } = dispatch( 'core/block-editor' ); return { updateEditorSettings, - initBlocks, + resetBlocks, }; } ), ] )( BlockEditorProvider ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 989c62f9872988..620d7bc334d4f1 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -8,23 +8,6 @@ import { castArray } from 'lodash'; */ import { getDefaultBlockName, createBlock } from '@wordpress/blocks'; -/** - * Returns an action object used in signalling that blocks state should be - * intialized using a specified array of blocks, - * - * This action reset the undo/redo history - * - * @param {Array} blocks Array of blocks. - * - * @return {Object} Action object. - */ -export function __unstableInitBlocks( blocks ) { - return { - type: 'INIT_BLOCKS', - blocks, - }; -} - /** * Returns an action object used in signalling that blocks state should be * reset to the specified array of blocks, taking precedence over any other @@ -526,35 +509,6 @@ export function updateEditorSettings( settings ) { }; } -/** - * Returns an action object used in signalling that undo history should - * restore last popped state. - * - * @return {Object} Action object. - */ -export function redo() { - return { type: 'REDO' }; -} - -/** - * Returns an action object used in signalling that undo history should pop. - * - * @return {Object} Action object. - */ -export function undo() { - return { type: 'UNDO' }; -} - -/** - * Returns an action object used in signalling that undo history record should - * be created. - * - * @return {Object} Action object. - */ -export function createUndoLevel() { - return { type: 'CREATE_UNDO_LEVEL' }; -} - /** * Returns an action object used in signalling that a temporary reusable blocks have been saved * in order to switch its temporary id with the real id. diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 53e6bbe4261cc7..65b80603df0ef0 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -9,7 +9,6 @@ import { omit, without, mapValues, - keys, isEqual, isEmpty, } from 'lodash'; @@ -23,7 +22,6 @@ import { isReusableBlock } from '@wordpress/blocks'; /** * Internal dependencies */ -import withHistory from '../utils/with-history'; import { PREFERENCES_DEFAULTS, EDITOR_SETTINGS_DEFAULTS, @@ -142,55 +140,6 @@ function getMutateSafeObject( original, working ) { return working; } -/** - * Returns true if the two object arguments have the same keys, or false - * otherwise. - * - * @param {Object} a First object. - * @param {Object} b Second object. - * - * @return {boolean} Whether the two objects have the same keys. - */ -export function hasSameKeys( a, b ) { - return isEqual( keys( a ), keys( b ) ); -} - -/** - * Returns true if, given the currently dispatching action and the previously - * dispatched action, the two actions are updating the same block attribute, or - * false otherwise. - * - * @param {Object} action Currently dispatching action. - * @param {Object} previousAction Previously dispatched action. - * - * @return {boolean} Whether actions are updating the same block attribute. - */ -export function isUpdatingSameBlockAttribute( action, previousAction ) { - return ( - action.type === 'UPDATE_BLOCK_ATTRIBUTES' && - action.clientId === previousAction.clientId && - hasSameKeys( action.attributes, previousAction.attributes ) - ); -} - -/** - * Returns true if, given the currently dispatching action and the previously - * dispatched action, the two actions are modifying the same property such that - * undo history should be batched. - * - * @param {Object} action Currently dispatching action. - * @param {Object} previousAction Previously dispatched action. - * - * @return {boolean} Whether to overwrite present state. - */ -export function shouldOverwriteState( action, previousAction ) { - if ( ! previousAction || action.type !== previousAction.type ) { - return false; - } - - return isUpdatingSameBlockAttribute( action, previousAction ); -} - /** * Higher-order reducer targeting the combined editor reducer, augmenting * block client IDs in remove action to include cascade of inner blocks. @@ -226,10 +175,7 @@ const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => { * @return {Function} Enhanced reducer function. */ const withBlockReset = ( reducer ) => ( state, action ) => { - if ( - state && - ( action.type === 'RESET_BLOCKS' || action.type === 'INIT_BLOCKS' ) - ) { + if ( state && action.type === 'RESET_BLOCKS' ) { const visibleClientIds = getNestedBlockClientIds( state.order ); return { ...state, @@ -306,13 +252,6 @@ export const editor = flow( [ combineReducers, withInnerBlocksRemoveCascade, - - // Track undo history, starting at editor initialization. - withHistory( { - resetTypes: [ 'INIT_BLOCKS' ], - ignoreTypes: [ 'RECEIVE_BLOCKS' ], - shouldOverwriteState, - } ), ] )( { blocks: flow( combineReducers, @@ -321,6 +260,9 @@ export const editor = flow( [ )( { byClientId( state = {}, action ) { switch ( action.type ) { + case 'RESET_BLOCKS': + return getFlattenedBlocksWithoutAttributes( action.blocks ); + case 'RECEIVE_BLOCKS': return { ...state, @@ -372,6 +314,9 @@ export const editor = flow( [ attributes( state = {}, action ) { switch ( action.type ) { + case 'RESET_BLOCKS': + return getFlattenedBlockAttributes( action.blocks ); + case 'RECEIVE_BLOCKS': return { ...state, @@ -445,6 +390,9 @@ export const editor = flow( [ order( state = {}, action ) { switch ( action.type ) { + case 'RESET_BLOCKS': + return mapBlockOrder( action.blocks ); + case 'RECEIVE_BLOCKS': return { ...state, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 32599ebb237d73..c0c0cd545fd016 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -89,7 +89,7 @@ export const getBlockDependantsCacheBust = createSelector( * @return {string} Block name. */ export function getBlockName( state, clientId ) { - const block = state.editor.present.blocks.byClientId[ clientId ]; + const block = state.editor.blocks.byClientId[ clientId ]; return block ? block.name : null; } @@ -102,7 +102,7 @@ export function getBlockName( state, clientId ) { * @return {boolean} Is Valid. */ export function isBlockValid( state, clientId ) { - const block = state.editor.present.blocks.byClientId[ clientId ]; + const block = state.editor.blocks.byClientId[ clientId ]; return !! block && block.isValid; } @@ -117,12 +117,12 @@ export function isBlockValid( state, clientId ) { */ export const getBlockAttributes = createSelector( ( state, clientId ) => { - const block = state.editor.present.blocks.byClientId[ clientId ]; + const block = state.editor.blocks.byClientId[ clientId ]; if ( ! block ) { return null; } - let attributes = state.editor.present.blocks.attributes[ clientId ]; + let attributes = state.editor.blocks.attributes[ clientId ]; // Inject custom source attribute values. // @@ -146,8 +146,8 @@ export const getBlockAttributes = createSelector( return attributes; }, ( state, clientId ) => [ - state.editor.present.blocks.byClientId[ clientId ], - state.editor.present.blocks.attributes[ clientId ], + state.editor.blocks.byClientId[ clientId ], + state.editor.blocks.attributes[ clientId ], getPostMeta( state ), ] ); @@ -165,7 +165,7 @@ export const getBlockAttributes = createSelector( */ export const getBlock = createSelector( ( state, clientId ) => { - const block = state.editor.present.blocks.byClientId[ clientId ]; + const block = state.editor.blocks.byClientId[ clientId ]; if ( ! block ) { return null; } @@ -184,7 +184,7 @@ export const getBlock = createSelector( export const __unstableGetBlockWithoutInnerBlocks = createSelector( ( state, clientId ) => { - const block = state.editor.present.blocks.byClientId[ clientId ]; + const block = state.editor.blocks.byClientId[ clientId ]; if ( ! block ) { return null; } @@ -195,7 +195,7 @@ export const __unstableGetBlockWithoutInnerBlocks = createSelector( }; }, ( state, clientId ) => [ - state.editor.present.blocks.byClientId[ clientId ], + state.editor.blocks.byClientId[ clientId ], ...getBlockAttributes.getDependants( state, clientId ), ] ); @@ -219,7 +219,7 @@ export const getBlocks = createSelector( ( clientId ) => getBlock( state, clientId ) ); }, - ( state ) => [ state.editor.present.blocks ] + ( state ) => [ state.editor.blocks ] ); /** @@ -250,7 +250,7 @@ export const getClientIdsWithDescendants = createSelector( return [ ...topLevelIds, ...getClientIdsOfDescendants( state, topLevelIds ) ]; }, ( state ) => [ - state.editor.present.blocks.order, + state.editor.blocks.order, ] ); @@ -270,13 +270,13 @@ export const getGlobalBlockCount = createSelector( return clientIds.length; } return reduce( clientIds, ( count, clientId ) => { - const block = state.editor.present.blocks.byClientId[ clientId ]; + const block = state.editor.blocks.byClientId[ clientId ]; return block.name === blockName ? count + 1 : count; }, 0 ); }, ( state ) => [ - state.editor.present.blocks.order, - state.editor.present.blocks.byClientId, + state.editor.blocks.order, + state.editor.blocks.byClientId, ] ); @@ -296,7 +296,7 @@ export const getBlocksByClientId = createSelector( ), ( state ) => [ getPostMeta( state ), - state.editor.present.blocks, + state.editor.blocks, ] ); @@ -380,7 +380,7 @@ export function getSelectedBlockClientId( state ) { // We need to check the block exists because the current state.blockSelection reducer // doesn't take into account the UNDO / REDO actions to update selection. // To be removed when that's fixed. - return start && start === end && !! state.editor.present.blocks.byClientId[ start ] ? start : null; + return start && start === end && !! state.editor.blocks.byClientId[ start ] ? start : null; } /** @@ -407,7 +407,7 @@ export function getSelectedBlock( state ) { */ export const getBlockRootClientId = createSelector( ( state, clientId ) => { - const { order } = state.editor.present.blocks; + const { order } = state.editor.blocks; for ( const rootClientId in order ) { if ( includes( order[ rootClientId ], clientId ) ) { @@ -418,7 +418,7 @@ export const getBlockRootClientId = createSelector( return null; }, ( state ) => [ - state.editor.present.blocks.order, + state.editor.blocks.order, ] ); @@ -442,7 +442,7 @@ export const getBlockHierarchyRootClientId = createSelector( return current; }, ( state ) => [ - state.editor.present.blocks.order, + state.editor.blocks.order, ] ); @@ -487,7 +487,7 @@ export function getAdjacentBlockClientId( state, startClientId, modifier = 1 ) { return null; } - const { order } = state.editor.present.blocks; + const { order } = state.editor.blocks; const orderSet = order[ rootClientId ]; const index = orderSet.indexOf( startClientId ); const nextIndex = ( index + ( 1 * modifier ) ); @@ -587,7 +587,7 @@ export const getMultiSelectedBlockClientIds = createSelector( return blockOrder.slice( startIndex, endIndex + 1 ); }, ( state ) => [ - state.editor.present.blocks.order, + state.editor.blocks.order, state.blockSelection.start, state.blockSelection.end, ], @@ -612,7 +612,7 @@ export const getMultiSelectedBlocks = createSelector( }, ( state ) => [ ...getMultiSelectedBlockClientIds.getDependants( state ), - state.editor.present.blocks, + state.editor.blocks, getPostMeta( state ), ] ); @@ -660,7 +660,7 @@ const isAncestorOf = createSelector( return possibleAncestorId === idToCheck; }, ( state ) => [ - state.editor.present.blocks.order, + state.editor.blocks.order, ], ); @@ -712,7 +712,7 @@ export const isAncestorMultiSelected = createSelector( return isMultiSelected; }, ( state ) => [ - state.editor.present.blocks.order, + state.editor.blocks.order, state.blockSelection.start, state.blockSelection.end, ], @@ -768,7 +768,7 @@ export function getMultiSelectedBlocksEndClientId( state ) { * @return {Array} Ordered client IDs of editor blocks. */ export function getBlockOrder( state, rootClientId ) { - return state.editor.present.blocks.order[ rootClientId || '' ] || EMPTY_ARRAY; + return state.editor.blocks.order[ rootClientId || '' ] || EMPTY_ARRAY; } /** @@ -1070,7 +1070,7 @@ export const canInsertBlockType = createSelector( canInsertBlockTypeUnmemoized, ( state, blockName, rootClientId ) => [ state.blockListSettings[ rootClientId ], - state.editor.present.blocks.byClientId[ rootClientId ], + state.editor.blocks.byClientId[ rootClientId ], state.settings.allowedBlockTypes, state.settings.templateLock, ], @@ -1280,8 +1280,8 @@ export const getInserterItems = createSelector( }, ( state, rootClientId ) => [ state.blockListSettings[ rootClientId ], - state.editor.present.blocks.byClientId, - state.editor.present.blocks.order, + state.editor.blocks.byClientId, + state.editor.blocks.order, state.preferences.insertUsage, state.settings.allowedBlockTypes, state.settings.templateLock, @@ -1315,7 +1315,7 @@ export const hasInserterItems = createSelector( }, ( state, rootClientId ) => [ state.blockListSettings[ rootClientId ], - state.editor.present.blocks.byClientId, + state.editor.blocks.byClientId, state.settings.allowedBlockTypes, state.settings.templateLock, getReusableBlocks( state ), diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 19bf67c07c8f96..39d6315cb70c6c 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -24,8 +24,6 @@ import { showInsertionPoint, hideInsertionPoint, mergeBlocks, - redo, - undo, removeBlocks, removeBlock, toggleBlockMode, @@ -208,22 +206,6 @@ describe( 'actions', () => { } ); } ); - describe( 'redo', () => { - it( 'should return REDO action', () => { - expect( redo() ).toEqual( { - type: 'REDO', - } ); - } ); - } ); - - describe( 'undo', () => { - it( 'should return UNDO action', () => { - expect( undo() ).toEqual( { - type: 'UNDO', - } ); - } ); - } ); - describe( 'removeBlocks', () => { it( 'should return REMOVE_BLOCKS action', () => { const clientId = 'clientId'; diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index c95d478a0030ab..3da3e1abed864c 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -17,9 +17,6 @@ import { * Internal dependencies */ import { - hasSameKeys, - isUpdatingSameBlockAttribute, - shouldOverwriteState, editor, isTyping, isCaretWithinFormattedText, @@ -32,143 +29,6 @@ import { } from '../reducer'; describe( 'state', () => { - describe( 'hasSameKeys()', () => { - it( 'returns false if two objects do not have the same keys', () => { - const a = { foo: 10 }; - const b = { bar: 10 }; - - expect( hasSameKeys( a, b ) ).toBe( false ); - } ); - - it( 'returns false if two objects have the same keys', () => { - const a = { foo: 10 }; - const b = { foo: 20 }; - - expect( hasSameKeys( a, b ) ).toBe( true ); - } ); - } ); - - describe( 'isUpdatingSameBlockAttribute()', () => { - it( 'should return false if not updating block attributes', () => { - const action = { - type: 'EDIT_POST', - edits: {}, - }; - const previousAction = { - type: 'EDIT_POST', - edits: {}, - }; - - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); - } ); - - it( 'should return false if not updating the same block', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - attributes: { - foo: 20, - }, - }; - - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); - } ); - - it( 'should return false if not updating the same blockĀ attributes', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - bar: 20, - }, - }; - - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); - } ); - - it( 'should return true if updating the same blockĀ attributes', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 20, - }, - }; - - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( true ); - } ); - } ); - - describe( 'shouldOverwriteState()', () => { - it( 'should return false if no previous action', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = undefined; - - expect( shouldOverwriteState( action, previousAction ) ).toBe( false ); - } ); - - it( 'should return false if the action types are different', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = { - type: 'REPLACE_BLOCKS', - }; - - expect( shouldOverwriteState( action, previousAction ) ).toBe( false ); - } ); - - it( 'should return true if updating same block attribute', () => { - const action = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 10, - }, - }; - const previousAction = { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', - attributes: { - foo: 20, - }, - }; - - expect( shouldOverwriteState( action, previousAction ) ).toBe( true ); - } ); - } ); - describe( 'editor()', () => { beforeAll( () => { registerBlockType( 'core/test-block', { @@ -183,27 +43,29 @@ describe( 'state', () => { unregisterBlockType( 'core/test-block' ); } ); - it( 'should return history (empty edits, blocks) by default', () => { + it( 'should return empty edits, blocks by default', () => { const state = editor( undefined, {} ); - expect( state.past ).toEqual( [] ); - expect( state.future ).toEqual( [] ); - expect( state.present.blocks.byClientId ).toEqual( {} ); - expect( state.present.blocks.order ).toEqual( {} ); + expect( state.blocks.byClientId ).toEqual( {} ); + expect( state.blocks.order ).toEqual( {} ); } ); it( 'should key by reset blocks clientId', () => { - const original = editor( undefined, {} ); - const state = editor( original, { - type: 'RESET_BLOCKS', - blocks: [ { clientId: 'bananas', innerBlocks: [] } ], - } ); + [ + undefined, + editor( undefined, {} ), + ].forEach( ( original ) => { + const state = editor( original, { + type: 'RESET_BLOCKS', + blocks: [ { clientId: 'bananas', innerBlocks: [] } ], + } ); - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 1 ); - expect( values( state.present.blocks.byClientId )[ 0 ].clientId ).toBe( 'bananas' ); - expect( state.present.blocks.order ).toEqual( { - '': [ 'bananas' ], - bananas: [], + expect( Object.keys( state.blocks.byClientId ) ).toHaveLength( 1 ); + expect( values( state.blocks.byClientId )[ 0 ].clientId ).toBe( 'bananas' ); + expect( state.blocks.order ).toEqual( { + '': [ 'bananas' ], + bananas: [], + } ); } ); } ); @@ -217,8 +79,8 @@ describe( 'state', () => { } ], } ); - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 2 ); - expect( state.present.blocks.order ).toEqual( { + expect( Object.keys( state.blocks.byClientId ) ).toHaveLength( 2 ); + expect( state.blocks.order ).toEqual( { '': [ 'bananas' ], apples: [], bananas: [ 'apples' ], @@ -244,9 +106,9 @@ describe( 'state', () => { } ], } ); - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 2 ); - expect( values( state.present.blocks.byClientId )[ 1 ].clientId ).toBe( 'ribs' ); - expect( state.present.blocks.order ).toEqual( { + expect( Object.keys( state.blocks.byClientId ) ).toHaveLength( 2 ); + expect( values( state.blocks.byClientId )[ 1 ].clientId ).toBe( 'ribs' ); + expect( state.blocks.order ).toEqual( { '': [ 'chicken', 'ribs' ], chicken: [], ribs: [], @@ -273,10 +135,10 @@ describe( 'state', () => { } ], } ); - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 1 ); - expect( values( state.present.blocks.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); - expect( values( state.present.blocks.byClientId )[ 0 ].clientId ).toBe( 'wings' ); - expect( state.present.blocks.order ).toEqual( { + expect( Object.keys( state.blocks.byClientId ) ).toHaveLength( 1 ); + expect( values( state.blocks.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); + expect( values( state.blocks.byClientId )[ 0 ].clientId ).toBe( 'wings' ); + expect( state.blocks.order ).toEqual( { '': [ 'wings' ], wings: [], } ); @@ -297,7 +159,7 @@ describe( 'state', () => { blocks: [ replacementBlock ], } ); - expect( state.present.blocks.order ).toEqual( { + expect( state.blocks.order ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ replacementBlock.clientId ], [ replacementBlock.clientId ]: [], @@ -324,11 +186,11 @@ describe( 'state', () => { } ], } ); - expect( Object.keys( replacedState.present.blocks.byClientId ) ).toHaveLength( 1 ); - expect( values( originalState.present.blocks.byClientId )[ 0 ].name ).toBe( 'core/test-block' ); - expect( values( replacedState.present.blocks.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); - expect( values( replacedState.present.blocks.byClientId )[ 0 ].clientId ).toBe( 'chicken' ); - expect( replacedState.present.blocks.order ).toEqual( { + expect( Object.keys( replacedState.blocks.byClientId ) ).toHaveLength( 1 ); + expect( values( originalState.blocks.byClientId )[ 0 ].name ).toBe( 'core/test-block' ); + expect( values( replacedState.blocks.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); + expect( values( replacedState.blocks.byClientId )[ 0 ].clientId ).toBe( 'chicken' ); + expect( replacedState.blocks.order ).toEqual( { '': [ 'chicken' ], chicken: [], } ); @@ -358,14 +220,14 @@ describe( 'state', () => { blocks: [ replacementNestedBlock ], } ); - expect( replacedNestedState.present.blocks.order ).toEqual( { + expect( replacedNestedState.blocks.order ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ replacementNestedBlock.clientId ], [ replacementNestedBlock.clientId ]: [], } ); - expect( originalNestedState.present.blocks.byClientId.chicken.name ).toBe( 'core/test-block' ); - expect( replacedNestedState.present.blocks.byClientId.chicken.name ).toBe( 'core/freeform' ); + expect( originalNestedState.blocks.byClientId.chicken.name ).toBe( 'core/test-block' ); + expect( replacedNestedState.blocks.byClientId.chicken.name ).toBe( 'core/freeform' ); } ); it( 'should update the block', () => { @@ -388,13 +250,13 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.byClientId.chicken ).toEqual( { + expect( state.blocks.byClientId.chicken ).toEqual( { clientId: 'chicken', name: 'core/test-block', isValid: true, } ); - expect( state.present.blocks.attributes.chicken ).toEqual( { + expect( state.blocks.attributes.chicken ).toEqual( { content: 'ribs', } ); } ); @@ -419,13 +281,13 @@ describe( 'state', () => { updatedId: 3, } ); - expect( state.present.blocks.byClientId.chicken ).toEqual( { + expect( state.blocks.byClientId.chicken ).toEqual( { clientId: 'chicken', name: 'core/block', isValid: false, } ); - expect( state.present.blocks.attributes.chicken ).toEqual( { + expect( state.blocks.attributes.chicken ).toEqual( { ref: 3, } ); } ); @@ -450,7 +312,7 @@ describe( 'state', () => { clientIds: [ 'ribs' ], } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); + expect( state.blocks.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); } ); it( 'should move the nested block up', () => { @@ -467,7 +329,7 @@ describe( 'state', () => { rootClientId: wrapperBlock.clientId, } ); - expect( state.present.blocks.order ).toEqual( { + expect( state.blocks.order ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ movedBlock.clientId, siblingBlock.clientId ], [ movedBlock.clientId ]: [], @@ -500,7 +362,7 @@ describe( 'state', () => { clientIds: [ 'ribs', 'veggies' ], } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs', 'veggies', 'chicken' ] ); + expect( state.blocks.order[ '' ] ).toEqual( [ 'ribs', 'veggies', 'chicken' ] ); } ); it( 'should move multiple nested blocks up', () => { @@ -518,7 +380,7 @@ describe( 'state', () => { rootClientId: wrapperBlock.clientId, } ); - expect( state.present.blocks.order ).toEqual( { + expect( state.blocks.order ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ movedBlockA.clientId, movedBlockB.clientId, siblingBlock.clientId ], [ movedBlockA.clientId ]: [], @@ -547,7 +409,7 @@ describe( 'state', () => { clientIds: [ 'chicken' ], } ); - expect( state.present.blocks.order ).toBe( original.present.blocks.order ); + expect( state.blocks.order ).toBe( original.blocks.order ); } ); it( 'should move the block down', () => { @@ -570,7 +432,7 @@ describe( 'state', () => { clientIds: [ 'chicken' ], } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); + expect( state.blocks.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); } ); it( 'should move the nested block down', () => { @@ -587,7 +449,7 @@ describe( 'state', () => { rootClientId: wrapperBlock.clientId, } ); - expect( state.present.blocks.order ).toEqual( { + expect( state.blocks.order ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ siblingBlock.clientId, movedBlock.clientId ], [ movedBlock.clientId ]: [], @@ -620,7 +482,7 @@ describe( 'state', () => { clientIds: [ 'chicken', 'ribs' ], } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'veggies', 'chicken', 'ribs' ] ); + expect( state.blocks.order[ '' ] ).toEqual( [ 'veggies', 'chicken', 'ribs' ] ); } ); it( 'should move multiple nested blocks down', () => { @@ -638,7 +500,7 @@ describe( 'state', () => { rootClientId: wrapperBlock.clientId, } ); - expect( state.present.blocks.order ).toEqual( { + expect( state.blocks.order ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ siblingBlock.clientId, movedBlockA.clientId, movedBlockB.clientId ], [ movedBlockA.clientId ]: [], @@ -667,7 +529,7 @@ describe( 'state', () => { clientIds: [ 'ribs' ], } ); - expect( state.present.blocks.order ).toBe( original.present.blocks.order ); + expect( state.blocks.order ).toBe( original.blocks.order ); } ); it( 'should remove the block', () => { @@ -690,15 +552,15 @@ describe( 'state', () => { clientIds: [ 'chicken' ], } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs' ] ); - expect( state.present.blocks.order ).not.toHaveProperty( 'chicken' ); - expect( state.present.blocks.byClientId ).toEqual( { + expect( state.blocks.order[ '' ] ).toEqual( [ 'ribs' ] ); + expect( state.blocks.order ).not.toHaveProperty( 'chicken' ); + expect( state.blocks.byClientId ).toEqual( { ribs: { clientId: 'ribs', name: 'core/test-block', }, } ); - expect( state.present.blocks.attributes ).toEqual( { + expect( state.blocks.attributes ).toEqual( { ribs: {}, } ); } ); @@ -728,16 +590,16 @@ describe( 'state', () => { clientIds: [ 'chicken', 'veggies' ], } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs' ] ); - expect( state.present.blocks.order ).not.toHaveProperty( 'chicken' ); - expect( state.present.blocks.order ).not.toHaveProperty( 'veggies' ); - expect( state.present.blocks.byClientId ).toEqual( { + expect( state.blocks.order[ '' ] ).toEqual( [ 'ribs' ] ); + expect( state.blocks.order ).not.toHaveProperty( 'chicken' ); + expect( state.blocks.order ).not.toHaveProperty( 'veggies' ); + expect( state.blocks.byClientId ).toEqual( { ribs: { clientId: 'ribs', name: 'core/test-block', }, } ); - expect( state.present.blocks.attributes ).toEqual( { + expect( state.blocks.attributes ).toEqual( { ribs: {}, } ); } ); @@ -759,8 +621,8 @@ describe( 'state', () => { clientIds: [ block.clientId ], } ); - expect( state.present.blocks.byClientId ).toEqual( {} ); - expect( state.present.blocks.order ).toEqual( { + expect( state.blocks.byClientId ).toEqual( {} ); + expect( state.blocks.order ).toEqual( { '': [], } ); } ); @@ -791,8 +653,8 @@ describe( 'state', () => { } ], } ); - expect( Object.keys( state.present.blocks.byClientId ) ).toHaveLength( 3 ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'kumquat', 'persimmon', 'loquat' ] ); + expect( Object.keys( state.blocks.byClientId ) ).toHaveLength( 3 ); + expect( state.blocks.order[ '' ] ).toEqual( [ 'kumquat', 'persimmon', 'loquat' ] ); } ); it( 'should move block to lower index', () => { @@ -821,7 +683,7 @@ describe( 'state', () => { index: 0, } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'ribs', 'chicken', 'veggies' ] ); + expect( state.blocks.order[ '' ] ).toEqual( [ 'ribs', 'chicken', 'veggies' ] ); } ); it( 'should move block to higher index', () => { @@ -850,7 +712,7 @@ describe( 'state', () => { index: 2, } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'chicken', 'veggies', 'ribs' ] ); + expect( state.blocks.order[ '' ] ).toEqual( [ 'chicken', 'veggies', 'ribs' ] ); } ); it( 'should not move block if passed same index', () => { @@ -879,7 +741,7 @@ describe( 'state', () => { index: 1, } ); - expect( state.present.blocks.order[ '' ] ).toEqual( [ 'chicken', 'ribs', 'veggies' ] ); + expect( state.blocks.order[ '' ] ).toEqual( [ 'chicken', 'ribs', 'veggies' ] ); } ); describe( 'blocks', () => { @@ -925,7 +787,7 @@ describe( 'state', () => { ], } ); - expect( state.present.blocks.byClientId ).toEqual( { + expect( state.blocks.byClientId ).toEqual( { block2: { clientId: 'block2' }, block21: { clientId: 'block21' }, block22: { clientId: 'block22' }, @@ -949,7 +811,7 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.byClientId ).toBe( original.present.blocks.byClientId ); + expect( state.blocks.byClientId ).toBe( original.blocks.byClientId ); } ); it( 'should return with same reference if no changes in updates', () => { @@ -971,7 +833,7 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.byClientId ).toBe( state.present.blocks.byClientId ); + expect( state.blocks.byClientId ).toBe( state.blocks.byClientId ); } ); } ); @@ -993,7 +855,7 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.attributes.kumquat.updated ).toBe( true ); + expect( state.blocks.attributes.kumquat.updated ).toBe( true ); } ); it( 'should accumulate attribute block updates', () => { @@ -1015,7 +877,7 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.attributes.kumquat ).toEqual( { + expect( state.blocks.attributes.kumquat ).toEqual( { updated: true, moreUpdated: true, } ); @@ -1034,7 +896,7 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.attributes ).toBe( original.present.blocks.attributes ); + expect( state.blocks.attributes ).toBe( original.blocks.attributes ); } ); it( 'should return with same reference if no changes in updates', () => { @@ -1056,78 +918,10 @@ describe( 'state', () => { }, } ); - expect( state.present.blocks.attributes ).toBe( state.present.blocks.attributes ); + expect( state.blocks.attributes ).toBe( state.blocks.attributes ); } ); } ); } ); - - describe( 'withHistory', () => { - it( 'should overwrite present history if updating same attributes', () => { - let state; - - state = editor( state, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], - } ); - - expect( state.past ).toHaveLength( 1 ); - - state = editor( state, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - test: 1, - }, - } ); - - state = editor( state, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - test: 2, - }, - } ); - - expect( state.past ).toHaveLength( 2 ); - } ); - - it( 'should not overwrite present history if updating different attributes', () => { - let state; - - state = editor( state, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], - } ); - - expect( state.past ).toHaveLength( 1 ); - - state = editor( state, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - test: 1, - }, - } ); - - state = editor( state, { - type: 'UPDATE_BLOCK_ATTRIBUTES', - clientId: 'kumquat', - attributes: { - other: 1, - }, - } ); - - expect( state.past ).toHaveLength( 3 ); - } ); - } ); } ); describe( 'insertionPoint', () => { diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index b4baa2a69d4dd7..c43727c749eef7 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -19,8 +19,6 @@ import { RawHTML } from '@wordpress/element'; import * as selectors from '../selectors'; const { - hasEditorUndo, - hasEditorRedo, getBlockDependantsCacheBust, getBlockName, getBlock, @@ -137,54 +135,6 @@ describe( 'selectors', () => { setFreeformContentHandlerName( undefined ); } ); - describe( 'hasEditorUndo', () => { - it( 'should return true when the past history is not empty', () => { - const state = { - editor: { - past: [ - {}, - ], - }, - }; - - expect( hasEditorUndo( state ) ).toBe( true ); - } ); - - it( 'should return false when the past history is empty', () => { - const state = { - editor: { - past: [], - }, - }; - - expect( hasEditorUndo( state ) ).toBe( false ); - } ); - } ); - - describe( 'hasEditorRedo', () => { - it( 'should return true when the future history is not empty', () => { - const state = { - editor: { - future: [ - {}, - ], - }, - }; - - expect( hasEditorRedo( state ) ).toBe( true ); - } ); - - it( 'should return false when the future history is empty', () => { - const state = { - editor: { - future: [], - }, - }; - - expect( hasEditorRedo( state ) ).toBe( false ); - } ); - } ); - describe( 'getBlockDependantsCacheBust', () => { const rootBlock = { clientId: 123, name: 'core/paragraph' }; const rootBlockAttributes = {}; @@ -196,21 +146,19 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - }, - attributes: { - 123: rootBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - }, + blocks: { + byClientId: { + 123: rootBlock, + }, + attributes: { + 123: rootBlockAttributes, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -218,21 +166,19 @@ describe( 'selectors', () => { const nextState = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - }, - attributes: { - 123: rootBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - }, + blocks: { + byClientId: { + 123: rootBlock, + }, + attributes: { + 123: rootBlockAttributes, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -246,21 +192,19 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - }, - attributes: { - 123: rootBlockAttributes, - }, - order: { - '': rootOrder, - 123: [], - }, + blocks: { + byClientId: { + 123: rootBlock, + }, + attributes: { + 123: rootBlockAttributes, + }, + order: { + '': rootOrder, + 123: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -268,24 +212,22 @@ describe( 'selectors', () => { const nextState = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: {}, - }, - order: { - '': rootOrder, - 123: [ 456 ], - 456: [], - }, + blocks: { + byClientId: { + 123: rootBlock, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: {}, + }, + order: { + '': rootOrder, + 123: [ 456 ], + 456: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -304,24 +246,22 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, + blocks: { + byClientId: { + 123: rootBlock, + 456: childBlock, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -329,24 +269,22 @@ describe( 'selectors', () => { const nextState = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, + blocks: { + byClientId: { + 123: rootBlock, + 456: childBlock, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -363,24 +301,22 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: {}, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, + blocks: { + byClientId: { + 123: rootBlock, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: {}, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -388,24 +324,22 @@ describe( 'selectors', () => { const nextState = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: { content: [ 'foo' ] }, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, + blocks: { + byClientId: { + 123: rootBlock, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: { content: [ 'foo' ] }, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -425,27 +359,25 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - 789: {}, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - 789: grandChildBlockOrder, - }, + blocks: { + byClientId: { + 123: rootBlock, + 456: childBlock, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + 789: {}, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, + 789: grandChildBlockOrder, }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -453,27 +385,25 @@ describe( 'selectors', () => { const nextState = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - 789: { content: [ 'foo' ] }, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - 789: grandChildBlockOrder, - }, + blocks: { + byClientId: { + 123: rootBlock, + 456: childBlock, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: rootBlockAttributes, + 456: childBlockAttributes, + 789: { content: [ 'foo' ] }, + }, + order: { + '': rootOrder, + 123: rootBlockOrder, + 456: childBlockOrder, + 789: grandChildBlockOrder, }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -489,14 +419,12 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - edits: {}, + blocks: { + byClientId: {}, + attributes: {}, + order: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -510,24 +438,22 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { - clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', - name: 'core/paragraph', - }, - }, - attributes: { - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {}, - }, - order: { - '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], - 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], + blocks: { + byClientId: { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + name: 'core/paragraph', }, }, - edits: {}, + attributes: { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {}, + }, + order: { + '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], + }, }, + edits: {}, }, initialEdits: {}, }; @@ -543,21 +469,19 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - }, - order: { - '': [ 123 ], - 123: [], - }, + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + }, + order: { + '': [ 123 ], + 123: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -574,14 +498,12 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - edits: {}, + blocks: { + byClientId: {}, + attributes: {}, + order: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -593,24 +515,22 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/paragraph' }, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - }, - order: { - '': [ 123 ], - 123: [ 456 ], - 456: [], - }, + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/paragraph' }, + 456: { clientId: 456, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + 456: {}, + }, + order: { + '': [ 123 ], + 123: [ 456 ], + 456: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -651,21 +571,19 @@ describe( 'selectors', () => { }, }, editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/meta-block' }, - }, - attributes: { - 123: {}, - }, - order: { - '': [ 123 ], - 123: [], - }, + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/meta-block' }, + }, + attributes: { + 123: {}, + }, + order: { + '': [ 123 ], + 123: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -688,22 +606,20 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 123, 23 ], - }, + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 123, 23 ], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, }; @@ -720,62 +636,60 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, - 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, - 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, - 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, - 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, - 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, - 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, - 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, - 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, - 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, - 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, - 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, - 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, - 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, - 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, - }, - attributes: { - 'uuid-2': {}, - 'uuid-4': {}, - 'uuid-6': {}, - 'uuid-8': {}, - 'uuid-10': {}, - 'uuid-12': {}, - 'uuid-14': {}, - 'uuid-16': {}, - 'uuid-18': {}, - 'uuid-20': {}, - 'uuid-22': {}, - 'uuid-24': {}, - 'uuid-26': {}, - 'uuid-28': {}, - 'uuid-30': {}, - }, - order: { - '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], - 'uuid-2': [ ], - 'uuid-4': [ ], - 'uuid-6': [ ], - 'uuid-8': [ ], - 'uuid-10': [ 'uuid-12', 'uuid-14' ], - 'uuid-12': [ 'uuid-16' ], - 'uuid-14': [ 'uuid-18' ], - 'uuid-16': [ ], - 'uuid-18': [ 'uuid-24' ], - 'uuid-20': [ ], - 'uuid-22': [ ], - 'uuid-24': [ 'uuid-26', 'uuid-28' ], - 'uuid-26': [ ], - 'uuid-28': [ 'uuid-30' ], - }, + blocks: { + byClientId: { + 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, + 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, + 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, + 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, + 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, + 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, + 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, + 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, + 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, + 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, + 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, + 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, + 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, + 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, + 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, }, - edits: {}, - }, + attributes: { + 'uuid-2': {}, + 'uuid-4': {}, + 'uuid-6': {}, + 'uuid-8': {}, + 'uuid-10': {}, + 'uuid-12': {}, + 'uuid-14': {}, + 'uuid-16': {}, + 'uuid-18': {}, + 'uuid-20': {}, + 'uuid-22': {}, + 'uuid-24': {}, + 'uuid-26': {}, + 'uuid-28': {}, + 'uuid-30': {}, + }, + order: { + '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], + 'uuid-2': [ ], + 'uuid-4': [ ], + 'uuid-6': [ ], + 'uuid-8': [ ], + 'uuid-10': [ 'uuid-12', 'uuid-14' ], + 'uuid-12': [ 'uuid-16' ], + 'uuid-14': [ 'uuid-18' ], + 'uuid-16': [ ], + 'uuid-18': [ 'uuid-24' ], + 'uuid-20': [ ], + 'uuid-22': [ ], + 'uuid-24': [ 'uuid-26', 'uuid-28' ], + 'uuid-26': [ ], + 'uuid-28': [ 'uuid-30' ], + }, + }, + edits: {}, }, initialEdits: {}, }; @@ -797,62 +711,60 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, - 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, - 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, - 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, - 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, - 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, - 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, - 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, - 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, - 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, - 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, - 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, - 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, - 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, - 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, - }, - attributes: { - 'uuid-2': {}, - 'uuid-4': {}, - 'uuid-6': {}, - 'uuid-8': {}, - 'uuid-10': {}, - 'uuid-12': {}, - 'uuid-14': {}, - 'uuid-16': {}, - 'uuid-18': {}, - 'uuid-20': {}, - 'uuid-22': {}, - 'uuid-24': {}, - 'uuid-26': {}, - 'uuid-28': {}, - 'uuid-30': {}, - }, - order: { - '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], - 'uuid-2': [ ], - 'uuid-4': [ ], - 'uuid-6': [ ], - 'uuid-8': [ ], - 'uuid-10': [ 'uuid-12', 'uuid-14' ], - 'uuid-12': [ 'uuid-16' ], - 'uuid-14': [ 'uuid-18' ], - 'uuid-16': [ ], - 'uuid-18': [ 'uuid-24' ], - 'uuid-20': [ ], - 'uuid-22': [ ], - 'uuid-24': [ 'uuid-26', 'uuid-28' ], - 'uuid-26': [ ], - 'uuid-28': [ 'uuid-30' ], - }, + blocks: { + byClientId: { + 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, + 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, + 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, + 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, + 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, + 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, + 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, + 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, + 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, + 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, + 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, + 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, + 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, + 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, + 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, }, - edits: {}, - }, + attributes: { + 'uuid-2': {}, + 'uuid-4': {}, + 'uuid-6': {}, + 'uuid-8': {}, + 'uuid-10': {}, + 'uuid-12': {}, + 'uuid-14': {}, + 'uuid-16': {}, + 'uuid-18': {}, + 'uuid-20': {}, + 'uuid-22': {}, + 'uuid-24': {}, + 'uuid-26': {}, + 'uuid-28': {}, + 'uuid-30': {}, + }, + order: { + '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], + 'uuid-2': [ ], + 'uuid-4': [ ], + 'uuid-6': [ ], + 'uuid-8': [ ], + 'uuid-10': [ 'uuid-12', 'uuid-14' ], + 'uuid-12': [ 'uuid-16' ], + 'uuid-14': [ 'uuid-18' ], + 'uuid-16': [ ], + 'uuid-18': [ 'uuid-24' ], + 'uuid-20': [ ], + 'uuid-22': [ ], + 'uuid-24': [ 'uuid-26', 'uuid-28' ], + 'uuid-26': [ ], + 'uuid-28': [ 'uuid-30' ], + }, + }, + edits: {}, }, initialEdits: {}, }; @@ -877,19 +789,17 @@ describe( 'selectors', () => { it( 'should return the number of top-level blocks in the post', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 123, 23 ], - }, + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 123, 23 ], }, }, }, @@ -901,22 +811,20 @@ describe( 'selectors', () => { it( 'should return the number of blocks in a nested context', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/columns' }, - 456: { clientId: 456, name: 'core/paragraph' }, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - 789: {}, - }, - order: { - '': [ 123 ], - 123: [ 456, 789 ], - }, + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/columns' }, + 456: { clientId: 456, name: 'core/paragraph' }, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + 456: {}, + 789: {}, + }, + order: { + '': [ 123 ], + 123: [ 456, 789 ], }, }, }, @@ -964,21 +872,19 @@ describe( 'selectors', () => { describe( 'getGlobalBlockCount', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/heading' }, - 456: { clientId: 456, name: 'core/paragraph' }, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: {}, - 456: {}, - 789: {}, - }, - order: { - '': [ 123, 456 ], - }, + blocks: { + byClientId: { + 123: { clientId: 123, name: 'core/heading' }, + 456: { clientId: 456, name: 'core/paragraph' }, + 789: { clientId: 789, name: 'core/paragraph' }, + }, + attributes: { + 123: {}, + 456: {}, + 789: {}, + }, + order: { + '': [ 123, 456 ], }, }, }, @@ -995,12 +901,10 @@ describe( 'selectors', () => { it( 'should return 0 if no blocks exist', () => { const emptyState = { editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, + blocks: { + byClientId: {}, + attributes: {}, + order: {}, }, }, }; @@ -1028,7 +932,15 @@ describe( 'selectors', () => { it( 'should return the selected block ClientId', () => { const state = { - editor: { present: { blocks: { byClientId: { 23: { name: 'fake block' } } } } }, + editor: { + blocks: { + byClientId: { + 23: { + name: 'fake block', + }, + }, + }, + }, blockSelection: { start: 23, end: 23 }, }; @@ -1041,24 +953,22 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 23, 123 ], - 23: [], - 123: [], - }, + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 23, 123 ], + 23: [], + 123: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, blockSelection: { start: null, end: null }, @@ -1071,24 +981,22 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 23, 123 ], - 23: [], - 123: [], - }, + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 23, 123 ], + 23: [], + 123: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, blockSelection: { start: 23, end: 123 }, @@ -1101,24 +1009,22 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - present: { - blocks: { - byClientId: { - 23: { clientId: 23, name: 'core/heading' }, - 123: { clientId: 123, name: 'core/paragraph' }, - }, - attributes: { - 23: {}, - 123: {}, - }, - order: { - '': [ 23, 123 ], - 23: [], - 123: [], - }, + blocks: { + byClientId: { + 23: { clientId: 23, name: 'core/heading' }, + 123: { clientId: 123, name: 'core/paragraph' }, + }, + attributes: { + 23: {}, + 123: {}, + }, + order: { + '': [ 23, 123 ], + 23: [], + 123: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, blockSelection: { start: 23, end: 23 }, @@ -1137,10 +1043,8 @@ describe( 'selectors', () => { it( 'should return null if the block does not exist', () => { const state = { editor: { - present: { - blocks: { - order: {}, - }, + blocks: { + order: {}, }, }, }; @@ -1151,12 +1055,10 @@ describe( 'selectors', () => { it( 'should return root ClientId relative the block ClientId', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], }, }, }, @@ -1170,10 +1072,8 @@ describe( 'selectors', () => { it( 'should return the given block if the block has no parents', () => { const state = { editor: { - present: { - blocks: { - order: {}, - }, + blocks: { + order: {}, }, }, }; @@ -1184,12 +1084,10 @@ describe( 'selectors', () => { it( 'should return root ClientId relative the block ClientId', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], }, }, }, @@ -1201,13 +1099,11 @@ describe( 'selectors', () => { it( 'should return the top level root ClientId relative the block ClientId', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ '123', '23' ], - 123: [ '456', '56' ], - 56: [ '12' ], - }, + blocks: { + order: { + '': [ '123', '23' ], + 123: [ '456', '56' ], + 56: [ '12' ], }, }, }, @@ -1221,11 +1117,9 @@ describe( 'selectors', () => { it( 'should return empty if there is no multi selection', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, + blocks: { + order: { + '': [ 123, 23 ], }, }, }, @@ -1238,11 +1132,9 @@ describe( 'selectors', () => { it( 'should return selected block clientIds if there is multi selection', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], }, }, }, @@ -1255,12 +1147,10 @@ describe( 'selectors', () => { it( 'should return selected block clientIds if there is multi selection (nested context)', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - 4: [ 9, 8, 7, 6 ], - }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], + 4: [ 9, 8, 7, 6 ], }, }, }, @@ -1275,14 +1165,12 @@ describe( 'selectors', () => { it( 'should return the same reference on subsequent invocations of empty selection', () => { const state = { editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - edits: {}, + blocks: { + byClientId: {}, + attributes: {}, + order: {}, }, + edits: {}, }, initialEdits: {}, blockSelection: { start: null, end: null }, @@ -1335,11 +1223,9 @@ describe( 'selectors', () => { it( 'should return the ordered block ClientIds of top-level blocks by default', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, + blocks: { + order: { + '': [ 123, 23 ], }, }, }, @@ -1351,12 +1237,10 @@ describe( 'selectors', () => { it( 'should return the ordered block ClientIds at a specified rootClientId', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456 ], - }, + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456 ], }, }, }, @@ -1370,11 +1254,9 @@ describe( 'selectors', () => { it( 'should return the block order', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, + blocks: { + order: { + '': [ 123, 23 ], }, }, }, @@ -1386,12 +1268,10 @@ describe( 'selectors', () => { it( 'should return the block order (nested context)', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], }, }, }, @@ -1405,11 +1285,9 @@ describe( 'selectors', () => { it( 'should return the previous block', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, + blocks: { + order: { + '': [ 123, 23 ], }, }, }, @@ -1421,12 +1299,10 @@ describe( 'selectors', () => { it( 'should return the previous block (nested context)', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], }, }, }, @@ -1438,11 +1314,9 @@ describe( 'selectors', () => { it( 'should return null for the first block', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, + blocks: { + order: { + '': [ 123, 23 ], }, }, }, @@ -1454,12 +1328,10 @@ describe( 'selectors', () => { it( 'should return null for the first block (nested context)', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], }, }, }, @@ -1473,11 +1345,9 @@ describe( 'selectors', () => { it( 'should return the following block', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, + blocks: { + order: { + '': [ 123, 23 ], }, }, }, @@ -1489,12 +1359,10 @@ describe( 'selectors', () => { it( 'should return the following block (nested context)', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], }, }, }, @@ -1506,11 +1374,9 @@ describe( 'selectors', () => { it( 'should return null for the last block', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - }, + blocks: { + order: { + '': [ 123, 23 ], }, }, }, @@ -1522,12 +1388,10 @@ describe( 'selectors', () => { it( 'should return null for the last block (nested context)', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], - }, + blocks: { + order: { + '': [ 123, 23 ], + 123: [ 456, 56 ], }, }, }, @@ -1568,11 +1432,9 @@ describe( 'selectors', () => { const state = { blockSelection: { start: 5, end: 5 }, editor: { - present: { - blocks: { - order: { - 4: [ 3, 2, 1 ], - }, + blocks: { + order: { + 4: [ 3, 2, 1 ], }, }, }, @@ -1585,11 +1447,9 @@ describe( 'selectors', () => { const state = { blockSelection: { start: 3, end: 3 }, editor: { - present: { - blocks: { - order: { - 4: [ 3, 2, 1 ], - }, + blocks: { + order: { + 4: [ 3, 2, 1 ], }, }, }, @@ -1601,11 +1461,9 @@ describe( 'selectors', () => { it( 'should return true if a multi selection exists that contains children of the block with the given ClientId', () => { const state = { editor: { - present: { - blocks: { - order: { - 6: [ 5, 4, 3, 2, 1 ], - }, + blocks: { + order: { + 6: [ 5, 4, 3, 2, 1 ], }, }, }, @@ -1617,12 +1475,10 @@ describe( 'selectors', () => { it( 'should return false if a multi selection exists bot does not contains children of the block with the given ClientId', () => { const state = { editor: { - present: { - blocks: { - order: { - 3: [ 2, 1 ], - 6: [ 5, 4 ], - }, + blocks: { + order: { + 3: [ 2, 1 ], + 6: [ 5, 4 ], }, }, }, @@ -1637,11 +1493,9 @@ describe( 'selectors', () => { const state = { blockSelection: { start: 5, end: 3 }, editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], }, }, }, @@ -1654,11 +1508,9 @@ describe( 'selectors', () => { const state = { blockSelection: { start: 5, end: 3 }, editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], }, }, }, @@ -1671,11 +1523,9 @@ describe( 'selectors', () => { const state = { blockSelection: { start: 5, end: 3 }, editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], }, }, }, @@ -1688,11 +1538,9 @@ describe( 'selectors', () => { const state = { blockSelection: {}, editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], }, }, }, @@ -1740,11 +1588,9 @@ describe( 'selectors', () => { describe( 'isBlockMultiSelected', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], }, }, }, @@ -1763,11 +1609,9 @@ describe( 'selectors', () => { describe( 'isFirstMultiSelectedBlock', () => { const state = { editor: { - present: { - blocks: { - order: { - '': [ 5, 4, 3, 2, 1 ], - }, + blocks: { + order: { + '': [ 5, 4, 3, 2, 1 ], }, }, }, @@ -1871,24 +1715,22 @@ describe( 'selectors', () => { end: 'clientId2', }, editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1' ], - clientId1: [ 'clientId2' ], - clientId2: [], - }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + }, + attributes: { + clientId1: {}, + clientId2: {}, + }, + order: { + '': [ 'clientId1' ], + clientId1: [ 'clientId2' ], + clientId2: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, insertionPoint: { @@ -1912,21 +1754,19 @@ describe( 'selectors', () => { end: 'clientId1', }, editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - }, - attributes: { - clientId1: {}, - }, - order: { - '': [ 'clientId1' ], - clientId1: [], - }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + }, + attributes: { + clientId1: {}, + }, + order: { + '': [ 'clientId1' ], + clientId1: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, insertionPoint: null, @@ -1947,24 +1787,22 @@ describe( 'selectors', () => { end: 'clientId2', }, editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1' ], - clientId1: [ 'clientId2' ], - clientId2: [], - }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + }, + attributes: { + clientId1: {}, + clientId2: {}, + }, + order: { + '': [ 'clientId1' ], + clientId1: [ 'clientId2' ], + clientId2: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, insertionPoint: null, @@ -1985,24 +1823,22 @@ describe( 'selectors', () => { end: 'clientId2', }, editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1', 'clientId2' ], - clientId1: [], - clientId2: [], - }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + }, + attributes: { + clientId1: {}, + clientId2: {}, + }, + order: { + '': [ 'clientId1', 'clientId2' ], + clientId1: [], + clientId2: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, insertionPoint: null, @@ -2023,24 +1859,22 @@ describe( 'selectors', () => { end: null, }, editor: { - present: { - blocks: { - byClientId: { - clientId1: { clientId: 'clientId1' }, - clientId2: { clientId: 'clientId2' }, - }, - attributes: { - clientId1: {}, - clientId2: {}, - }, - order: { - '': [ 'clientId1', 'clientId2' ], - clientId1: [], - clientId2: [], - }, + blocks: { + byClientId: { + clientId1: { clientId: 'clientId1' }, + clientId2: { clientId: 'clientId2' }, + }, + attributes: { + clientId1: {}, + clientId2: {}, + }, + order: { + '': [ 'clientId1', 'clientId2' ], + clientId1: [], + clientId2: [], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, insertionPoint: null, @@ -2078,11 +1912,9 @@ describe( 'selectors', () => { it( 'should deny blocks that are not registered', () => { const state = { editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, + blocks: { + byClientId: {}, + attributes: {}, }, }, blockListSettings: {}, @@ -2094,11 +1926,9 @@ describe( 'selectors', () => { it( 'should deny blocks that are not allowed by the editor', () => { const state = { editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, + blocks: { + byClientId: {}, + attributes: {}, }, }, blockListSettings: {}, @@ -2112,11 +1942,9 @@ describe( 'selectors', () => { it( 'should allow blocks that are allowed by the editor', () => { const state = { editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, + blocks: { + byClientId: {}, + attributes: {}, }, }, blockListSettings: {}, @@ -2130,11 +1958,9 @@ describe( 'selectors', () => { it( 'should deny blocks when the editor has a template lock', () => { const state = { editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, + blocks: { + byClientId: {}, + attributes: {}, }, }, blockListSettings: {}, @@ -2148,11 +1974,9 @@ describe( 'selectors', () => { it( 'should deny blocks that restrict parent from being inserted into the root', () => { const state = { editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - }, + blocks: { + byClientId: {}, + attributes: {}, }, }, blockListSettings: {}, @@ -2164,14 +1988,12 @@ describe( 'selectors', () => { it( 'should deny blocks that restrict parent from being inserted into a restricted parent', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, }, }, }, @@ -2184,14 +2006,12 @@ describe( 'selectors', () => { it( 'should allow blocks that restrict parent to be inserted into an allowed parent', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-b' }, - }, - attributes: { - block1: {}, - }, + blocks: { + byClientId: { + block1: { name: 'core/test-block-b' }, + }, + attributes: { + block1: {}, }, }, }, @@ -2204,14 +2024,12 @@ describe( 'selectors', () => { it( 'should deny restricted blocks from being inserted into a block that restricts allowedBlocks', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, }, }, }, @@ -2228,14 +2046,12 @@ describe( 'selectors', () => { it( 'should allow allowed blocks to be inserted into a block that restricts allowedBlocks', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, }, }, }, @@ -2252,14 +2068,12 @@ describe( 'selectors', () => { it( 'should prioritise parent over allowedBlocks', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-b' }, - }, - attributes: { - block1: {}, - }, + blocks: { + byClientId: { + block1: { name: 'core/test-block-b' }, + }, + attributes: { + block1: {}, }, }, }, @@ -2278,18 +2092,16 @@ describe( 'selectors', () => { it( 'should properly list block type and reusable block items', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, - order: {}, + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, }, - edits: {}, + order: {}, }, + edits: {}, }, initialEdits: {}, settings: { @@ -2340,31 +2152,29 @@ describe( 'selectors', () => { it( 'should not list a reusable block item if it is being inserted inside it self', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1ref: { - name: 'core/block', - clientId: 'block1ref', - }, - itselfBlock1: { name: 'core/test-block-a' }, - itselfBlock2: { name: 'core/test-block-b' }, + blocks: { + byClientId: { + block1ref: { + name: 'core/block', + clientId: 'block1ref', }, - attributes: { - block1ref: { - attributes: { - ref: 1, - }, + itselfBlock1: { name: 'core/test-block-a' }, + itselfBlock2: { name: 'core/test-block-b' }, + }, + attributes: { + block1ref: { + attributes: { + ref: 1, }, - itselfBlock1: {}, - itselfBlock2: {}, - }, - order: { - '': [ 'block1ref' ], }, + itselfBlock1: {}, + itselfBlock2: {}, + }, + order: { + '': [ 'block1ref' ], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, settings: { @@ -2401,37 +2211,35 @@ describe( 'selectors', () => { it( 'should not list a reusable block item if it is being inserted inside a descendent', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block2ref: { - name: 'core/block', - clientId: 'block1ref', - }, - referredBlock1: { name: 'core/test-block-a' }, - referredBlock2: { name: 'core/test-block-b' }, - childReferredBlock2: { name: 'core/test-block-a' }, - grandchildReferredBlock2: { name: 'core/test-block-b' }, + blocks: { + byClientId: { + block2ref: { + name: 'core/block', + clientId: 'block1ref', }, - attributes: { - block2ref: { - attributes: { - ref: 2, - }, + referredBlock1: { name: 'core/test-block-a' }, + referredBlock2: { name: 'core/test-block-b' }, + childReferredBlock2: { name: 'core/test-block-a' }, + grandchildReferredBlock2: { name: 'core/test-block-b' }, + }, + attributes: { + block2ref: { + attributes: { + ref: 2, }, - referredBlock1: {}, - referredBlock2: {}, - childReferredBlock2: {}, - grandchildReferredBlock2: {}, - }, - order: { - '': [ 'block2ref' ], - referredBlock2: [ 'childReferredBlock2' ], - childReferredBlock2: [ 'grandchildReferredBlock2' ], }, + referredBlock1: {}, + referredBlock2: {}, + childReferredBlock2: {}, + grandchildReferredBlock2: {}, + }, + order: { + '': [ 'block2ref' ], + referredBlock2: [ 'childReferredBlock2' ], + childReferredBlock2: [ 'grandchildReferredBlock2' ], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, settings: { @@ -2467,20 +2275,18 @@ describe( 'selectors', () => { it( 'should order items by descending utility and frecency', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - block2: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - block2: {}, - }, - order: {}, + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + block2: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, + block2: {}, }, - edits: {}, + order: {}, }, + edits: {}, }, initialEdits: {}, settings: { @@ -2511,26 +2317,24 @@ describe( 'selectors', () => { it( 'should correctly cache the return values', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - block2: { name: 'core/test-block-a' }, - block3: { name: 'core/test-block-a' }, - block4: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - block2: {}, - block3: {}, - block4: {}, - }, - order: { - '': [ 'block3', 'block4' ], - }, + blocks: { + byClientId: { + block1: { name: 'core/test-block-a' }, + block2: { name: 'core/test-block-a' }, + block3: { name: 'core/test-block-a' }, + block4: { name: 'core/test-block-a' }, + }, + attributes: { + block1: {}, + block2: {}, + block3: {}, + block4: {}, + }, + order: { + '': [ 'block3', 'block4' ], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, settings: { @@ -2583,20 +2387,18 @@ describe( 'selectors', () => { it( 'should set isDisabled when a block with `multiple: false` has been used', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { clientId: 'block1', name: 'core/test-block-b' }, - }, - attributes: { - block1: { attribute: {} }, - }, - order: { - '': [ 'block1' ], - }, + blocks: { + byClientId: { + block1: { clientId: 'block1', name: 'core/test-block-b' }, + }, + attributes: { + block1: { attribute: {} }, + }, + order: { + '': [ 'block1' ], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, reusableBlocks: { @@ -2617,14 +2419,12 @@ describe( 'selectors', () => { it( 'should give common blocks a low utility', () => { const state = { editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - edits: {}, + blocks: { + byClientId: {}, + attributes: {}, + order: {}, }, + edits: {}, }, initialEdits: {}, reusableBlocks: { @@ -2645,14 +2445,12 @@ describe( 'selectors', () => { it( 'should give used blocks a medium utility and set a frecency', () => { const state = { editor: { - present: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - }, - edits: {}, + blocks: { + byClientId: {}, + attributes: {}, + order: {}, }, + edits: {}, }, initialEdits: {}, reusableBlocks: { @@ -2676,20 +2474,18 @@ describe( 'selectors', () => { it( 'should give contextual blocks a high utility', () => { const state = { editor: { - present: { - blocks: { - byClientId: { - block1: { name: 'core/test-block-b' }, - }, - attributes: { - block1: { attribute: {} }, - }, - order: { - '': [ 'block1' ], - }, + blocks: { + byClientId: { + block1: { name: 'core/test-block-b' }, + }, + attributes: { + block1: { attribute: {} }, + }, + order: { + '': [ 'block1' ], }, - edits: {}, }, + edits: {}, }, initialEdits: {}, reusableBlocks: { diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 0840ce7c816b1e..d3f9b06126da39 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -141,6 +141,35 @@ export function autosave( options ) { return savePost( { isAutosave: true, ...options } ); } +/** + * Returns an action object used in signalling that undo history should + * restore last popped state. + * + * @return {Object} Action object. + */ +export function redo() { + return { type: 'REDO' }; +} + +/** + * Returns an action object used in signalling that undo history should pop. + * + * @return {Object} Action object. + */ +export function undo() { + return { type: 'UNDO' }; +} + +/** + * Returns an action object used in signalling that undo history record should + * be created. + * + * @return {Object} Action object. + */ +export function createUndoLevel() { + return { type: 'CREATE_UNDO_LEVEL' }; +} + /** * Returns an action object used to lock the editor. * @@ -365,6 +394,3 @@ export const exitFormattedText = getBlockEditorAction( 'exitFormattedText' ); export const insertDefaultBlock = getBlockEditorAction( 'insertDefaultBlock' ); export const updateBlockListSettings = getBlockEditorAction( 'updateBlockListSettings' ); export const updateEditorSettings = getBlockEditorAction( 'updateEditorSettings' ); -export const undo = getBlockEditorAction( 'undo' ); -export const redo = getBlockEditorAction( 'redo' ); -export const createUndoLevel = getBlockEditorAction( 'createUndoLevel' ); diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index ae87be280c45d9..699b925311c6ba 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -7,6 +7,7 @@ import { reduce, omit, mapValues, + keys, isEqual, } from 'lodash'; @@ -25,6 +26,7 @@ import { } from './defaults'; import { EDIT_MERGE_PROPERTIES } from './constants'; import withChangeDetection from '../utils/with-change-detection'; +import withHistory from '../utils/with-history'; /** * Returns a post attribute value, flattening nested rendered content using its @@ -59,6 +61,54 @@ function getMutateSafeObject( original, working ) { return working; } +/** + * Returns true if the two object arguments have the same keys, or false + * otherwise. + * + * @param {Object} a First object. + * @param {Object} b Second object. + * + * @return {boolean} Whether the two objects have the same keys. + */ +export function hasSameKeys( a, b ) { + return isEqual( keys( a ), keys( b ) ); +} + +/** + * Returns true if, given the currently dispatching action and the previously + * dispatched action, the two actions are editing the same post property, or + * false otherwise. + * + * @param {Object} action Currently dispatching action. + * @param {Object} previousAction Previously dispatched action. + * + * @return {boolean} Whether actions are updating the same post property. + */ +export function isUpdatingSamePostProperty( action, previousAction ) { + return ( + action.type === 'EDIT_POST' && + hasSameKeys( action.edits, previousAction.edits ) + ); +} + +/** + * Returns true if, given the currently dispatching action and the previously + * dispatched action, the two actions are modifying the same property such that + * undo history should be batched. + * + * @param {Object} action Currently dispatching action. + * @param {Object} previousAction Previously dispatched action. + * + * @return {boolean} Whether to overwrite present state. + */ +export function shouldOverwriteState( action, previousAction ) { + if ( ! previousAction || action.type !== previousAction.type ) { + return false; + } + + return isUpdatingSamePostProperty( action, previousAction ); +} + /** * Undoable reducer returning the editor post state, including blocks parsed * from current HTML markup. @@ -75,12 +125,20 @@ function getMutateSafeObject( original, working ) { */ export const editor = flow( [ combineReducers, + + withHistory( { + resetTypes: [ 'SETUP_EDITOR_STATE' ], + ignoreTypes: [ 'RECEIVE_BLOCKS', 'RESET_POST', 'UPDATE_POST' ], + shouldOverwriteState, + } ), ] )( { // Track whether changes exist, resetting at each post save. Relies on // editor initialization firing post reset as an effect. - blocks: withChangeDetection( { - resetTypes: [ 'SETUP_EDITOR_STATE', 'REQUEST_POST_UPDATE_START' ], - } )( ( state = { value: [] }, action ) => { + blocks: flow( [ + withChangeDetection( { + resetTypes: [ 'SETUP_EDITOR_STATE', 'REQUEST_POST_UPDATE_START' ], + } ), + ] )( ( state = { value: [] }, action ) => { switch ( action.type ) { case 'RESET_BLOCKS': return { value: action.blocks }; diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index f4ac6bf9ca041f..5f0b683051f48c 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -41,6 +41,29 @@ export const INSERTER_UTILITY_NONE = 0; const ONE_MINUTE_IN_MS = 60 * 1000; const EMPTY_OBJECT = {}; +/** + * Returns true if any past editor history snapshots exist, or false otherwise. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether undo history exists. + */ +export function hasEditorUndo( state ) { + return state.editor.past.length > 0; +} + +/** + * Returns true if any future editor history snapshots exist, or false + * otherwise. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether redo history exists. + */ +export function hasEditorRedo( state ) { + return state.editor.future.length > 0; +} + /** * Returns true if the currently edited post is yet to be saved, or false if * the post has been saved. @@ -62,14 +85,14 @@ export function isEditedPostNew( state ) { */ export function hasChangedContent( state ) { return ( - state.editor.blocks.isDirty || + state.editor.present.blocks.isDirty || // `edits` is intended to contain only values which are different from // the saved post, so the mere presence of a property is an indicator // that the value is different than what is known to be saved. While // content in Visual mode is represented by the blocks state, in Text // mode it is tracked by `edits.content`. - 'content' in state.editor.edits + 'content' in state.editor.present.edits ); } @@ -89,7 +112,7 @@ export function isEditedPostDirty( state ) { // Edits should contain only fields which differ from the saved post (reset // at initial load and save complete). Thus, a non-empty edits state can be // inferred to contain unsaved values. - if ( Object.keys( state.editor.edits ).length > 0 ) { + if ( Object.keys( state.editor.present.edits ).length > 0 ) { return true; } @@ -184,11 +207,11 @@ export const getPostEdits = createSelector( ( state ) => { return { ...state.initialEdits, - ...state.editor.edits, + ...state.editor.present.edits, }; }, ( state ) => [ - state.editor.edits, + state.editor.present.edits, state.initialEdits, ] ); @@ -254,7 +277,7 @@ const getNestedEditedPostProperty = createSelector( }; }, ( state, attributeName ) => [ - get( state.editor.edits, [ attributeName ], EMPTY_OBJECT ), + get( state.editor.present.edits, [ attributeName ], EMPTY_OBJECT ), get( state.currentPost, [ attributeName ], EMPTY_OBJECT ), ] ); @@ -434,7 +457,7 @@ export function isEditedPostEmpty( state ) { // condition of the mere existence of blocks. Note that the value of edited // content takes precedent over block content, and must fall through to the // default logic. - const blocks = state.editor.blocks.value; + const blocks = state.editor.present.blocks.value; if ( blocks.length && ! ( 'content' in getPostEdits( state ) ) ) { // Pierce the abstraction of the serializer in knowing that blocks are @@ -647,7 +670,7 @@ export function getEditedPostPreviewLink( state ) { * @return {?string} Suggested post format. */ export function getSuggestedPostFormat( state ) { - const blocks = state.editor.blocks.value; + const blocks = state.editor.present.blocks.value; let name; // If there is only one block in the content of the post grab its name @@ -695,7 +718,7 @@ export function getSuggestedPostFormat( state ) { * @return {WPBlock[]} Filtered set of blocks for save. */ export function getBlocksForSerialization( state ) { - const blocks = state.editor.blocks.value; + const blocks = state.editor.present.blocks.value; // WARNING: Any changes to the logic of this function should be verified // against the implementation of isEditedPostEmpty, which bypasses this @@ -749,8 +772,8 @@ export const getEditedPostContent = createSelector( return content; }, ( state ) => [ - state.editor.blocks.value, - state.editor.edits.content, + state.editor.present.blocks.value, + state.editor.present.edits.content, state.initialEdits.content, ], ); @@ -1046,7 +1069,7 @@ export function isPublishSidebarEnabled( state ) { * @return {Array} Block list. */ export function getEditorBlocks( state ) { - return state.editor.blocks.value; + return state.editor.present.blocks.value; } /** @@ -1121,7 +1144,5 @@ export const getTemplateLock = getBlockEditorSelector( 'getTemplateLock' ); export const canInsertBlockType = getBlockEditorSelector( 'canInsertBlockType' ); export const getInserterItems = getBlockEditorSelector( 'getInserterItems' ); export const hasInserterItems = getBlockEditorSelector( 'hasInserterItems' ); -export const hasEditorUndo = getBlockEditorSelector( 'hasEditorUndo' ); -export const hasEditorRedo = getBlockEditorSelector( 'hasEditorRedo' ); export const getEditorSettings = getBlockEditorSelector( 'getEditorSettings' ); export const getBlockListSettings = getBlockEditorSelector( 'getBlockListSettings' ); diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index 4875576181b10b..ec46b287c394e9 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -12,6 +12,8 @@ import { editPost, savePost, trashPost, + redo, + undo, } from '../actions'; describe( 'actions', () => { @@ -75,6 +77,22 @@ describe( 'actions', () => { } ); } ); + describe( 'redo', () => { + it( 'should return REDO action', () => { + expect( redo() ).toEqual( { + type: 'REDO', + } ); + } ); + } ); + + describe( 'undo', () => { + it( 'should return UNDO action', () => { + expect( undo() ).toEqual( { + type: 'UNDO', + } ); + } ); + } ); + describe( 'fetchReusableBlocks', () => { it( 'should return the FETCH_REUSABLE_BLOCKS action', () => { expect( fetchReusableBlocks() ).toEqual( { diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index 5f84a7af974977..dda36ce4ac84ab 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -7,6 +7,9 @@ import deepFreeze from 'deep-freeze'; * Internal dependencies */ import { + hasSameKeys, + isUpdatingSamePostProperty, + shouldOverwriteState, getPostRawValue, initialEdits, editor, @@ -21,6 +24,125 @@ import { import { INITIAL_EDITS_DEFAULTS } from '../defaults'; describe( 'state', () => { + describe( 'hasSameKeys()', () => { + it( 'returns false if two objects do not have the same keys', () => { + const a = { foo: 10 }; + const b = { bar: 10 }; + + expect( hasSameKeys( a, b ) ).toBe( false ); + } ); + + it( 'returns false if two objects have the same keys', () => { + const a = { foo: 10 }; + const b = { foo: 20 }; + + expect( hasSameKeys( a, b ) ).toBe( true ); + } ); + } ); + + describe( 'isUpdatingSamePostProperty()', () => { + it( 'should return false if not editing post', () => { + const action = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + attributes: { + foo: 10, + }, + }; + const previousAction = { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + attributes: { + foo: 10, + }, + }; + + expect( isUpdatingSamePostProperty( action, previousAction ) ).toBe( false ); + } ); + + it( 'should return false if not editing the same post properties', () => { + const action = { + type: 'EDIT_POST', + edits: { + foo: 10, + }, + }; + const previousAction = { + type: 'EDIT_POST', + edits: { + bar: 20, + }, + }; + + expect( isUpdatingSamePostProperty( action, previousAction ) ).toBe( false ); + } ); + + it( 'should return true if updating the same post properties', () => { + const action = { + type: 'EDIT_POST', + edits: { + foo: 10, + }, + }; + const previousAction = { + type: 'EDIT_POST', + edits: { + foo: 20, + }, + }; + + expect( isUpdatingSamePostProperty( action, previousAction ) ).toBe( true ); + } ); + } ); + + describe( 'shouldOverwriteState()', () => { + it( 'should return false if no previous action', () => { + const action = { + type: 'EDIT_POST', + edits: { + foo: 10, + }, + }; + const previousAction = undefined; + + expect( shouldOverwriteState( action, previousAction ) ).toBe( false ); + } ); + + it( 'should return false if the action types are different', () => { + const action = { + type: 'EDIT_POST', + edits: { + foo: 10, + }, + }; + const previousAction = { + type: 'EDIT_DIFFERENT_POST', + edits: { + foo: 20, + }, + }; + + expect( shouldOverwriteState( action, previousAction ) ).toBe( false ); + } ); + + it( 'should return true if updating same post property', () => { + const action = { + type: 'EDIT_POST', + edits: { + foo: 10, + }, + }; + const previousAction = { + type: 'EDIT_POST', + edits: { + foo: 20, + }, + }; + + expect( shouldOverwriteState( action, previousAction ) ).toBe( true ); + } ); + } ); + describe( 'getPostRawValue', () => { it( 'returns original value for non-rendered content', () => { const value = getPostRawValue( '' ); @@ -36,6 +158,24 @@ describe( 'state', () => { } ); describe( 'editor()', () => { + describe( 'blocks()', () => { + it( 'should set its value by RESET_BLOCKS', () => { + const blocks = [ { + clientId: 'block3', + innerBlocks: [ + { clientId: 'block31', innerBlocks: [] }, + { clientId: 'block32', innerBlocks: [] }, + ], + } ]; + const state = editor( undefined, { + type: 'RESET_BLOCKS', + blocks, + } ); + + expect( state.present.blocks.value ).toBe( blocks ); + } ); + } ); + describe( 'edits()', () => { it( 'should save newly edited properties', () => { const original = editor( undefined, { @@ -53,7 +193,7 @@ describe( 'state', () => { }, } ); - expect( state.edits ).toEqual( { + expect( state.present.edits ).toEqual( { status: 'draft', title: 'post title', tags: [ 1 ], @@ -76,7 +216,7 @@ describe( 'state', () => { }, } ); - expect( state.edits ).toBe( original.edits ); + expect( state.present.edits ).toBe( original.present.edits ); } ); it( 'should save modified properties', () => { @@ -97,7 +237,7 @@ describe( 'state', () => { }, } ); - expect( state.edits ).toEqual( { + expect( state.present.edits ).toEqual( { status: 'draft', title: 'modified title', tags: [ 2 ], @@ -123,7 +263,7 @@ describe( 'state', () => { }, } ); - expect( state.edits ).toEqual( { + expect( state.present.edits ).toEqual( { meta: { a: 1, b: 2, @@ -139,7 +279,7 @@ describe( 'state', () => { edits: {}, } ); - expect( state.edits ).toBe( original.edits ); + expect( state.present.edits ).toBe( original.present.edits ); } ); it( 'unset reset post values which match by canonical value', () => { @@ -159,7 +299,7 @@ describe( 'state', () => { }, } ); - expect( state.edits ).toEqual( {} ); + expect( state.present.edits ).toEqual( {} ); } ); it( 'unset reset post values by deep match', () => { @@ -185,7 +325,7 @@ describe( 'state', () => { }, } ); - expect( state.edits ).toEqual( {} ); + expect( state.present.edits ).toEqual( {} ); } ); it( 'should omit content when resetting', () => { @@ -200,7 +340,7 @@ describe( 'state', () => { }, } ); - expect( state.edits ).toHaveProperty( 'content' ); + expect( state.present.edits ).toHaveProperty( 'content' ); state = editor( original, { type: 'RESET_BLOCKS', @@ -217,7 +357,7 @@ describe( 'state', () => { } ], } ); - expect( state.edits ).not.toHaveProperty( 'content' ); + expect( state.present.edits ).not.toHaveProperty( 'content' ); } ); } ); } ); diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index 29f74c701e9439..459161ee038014 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -24,6 +24,8 @@ import * as selectors from '../selectors'; import { PREFERENCES_DEFAULTS } from '../defaults'; const { + hasEditorUndo, + hasEditorRedo, isEditedPostNew, hasChangedContent, isEditedPostDirty, @@ -157,6 +159,54 @@ describe( 'selectors', () => { setDefaultBlockName( undefined ); } ); + describe( 'hasEditorUndo', () => { + it( 'should return true when the past history is not empty', () => { + const state = { + editor: { + past: [ + {}, + ], + }, + }; + + expect( hasEditorUndo( state ) ).toBe( true ); + } ); + + it( 'should return false when the past history is empty', () => { + const state = { + editor: { + past: [], + }, + }; + + expect( hasEditorUndo( state ) ).toBe( false ); + } ); + } ); + + describe( 'hasEditorRedo', () => { + it( 'should return true when the future history is not empty', () => { + const state = { + editor: { + future: [ + {}, + ], + }, + }; + + expect( hasEditorRedo( state ) ).toBe( true ); + } ); + + it( 'should return false when the future history is empty', () => { + const state = { + editor: { + future: [], + }, + }; + + expect( hasEditorRedo( state ) ).toBe( false ); + } ); + } ); + describe( 'isEditedPostNew', () => { it( 'should return true when the post is new', () => { const state = { @@ -199,11 +249,12 @@ describe( 'selectors', () => { it( 'should return false if no dirty blocks nor content property edit', () => { const state = { editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + }, + edits: {}, }, - edits: {}, }, }; @@ -213,11 +264,13 @@ describe( 'selectors', () => { it( 'should return true if dirty blocks', () => { const state = { editor: { - blocks: { - isDirty: true, - value: [], + present: { + blocks: { + isDirty: true, + value: [], + }, + edits: {}, }, - edits: {}, }, }; @@ -227,12 +280,14 @@ describe( 'selectors', () => { it( 'should return true if content property edit', () => { const state = { editor: { - blocks: { - isDirty: false, - value: [], - }, - edits: { - content: 'text mode edited', + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: { + content: 'text mode edited', + }, }, }, }; @@ -246,11 +301,13 @@ describe( 'selectors', () => { const state = { optimist: [], editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, }; @@ -261,11 +318,13 @@ describe( 'selectors', () => { const state = { optimist: [], editor: { - blocks: { - isDirty: true, - value: [], + present: { + blocks: { + isDirty: true, + value: [], + }, + edits: {}, }, - edits: {}, }, }; @@ -276,12 +335,14 @@ describe( 'selectors', () => { const state = { optimist: [], editor: { - blocks: { - isDirty: false, - value: [], - }, - edits: { - excerpt: 'hello world', + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: { + excerpt: 'hello world', + }, }, }, }; @@ -295,21 +356,25 @@ describe( 'selectors', () => { { beforeState: { editor: { - blocks: { - isDirty: true, - value: [], + present: { + blocks: { + isDirty: true, + value: [], + }, + edits: {}, }, - edits: {}, }, }, }, ], editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, }; @@ -321,11 +386,13 @@ describe( 'selectors', () => { it( 'should return true when the post is not dirty and has not been saved before', () => { const state = { editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, currentPost: { id: 1, @@ -342,11 +409,13 @@ describe( 'selectors', () => { it( 'should return false when the post is not dirty but the post has been saved', () => { const state = { editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, currentPost: { id: 1, @@ -363,11 +432,13 @@ describe( 'selectors', () => { it( 'should return false when the post is dirty but the post has not been saved', () => { const state = { editor: { - blocks: { - isDirty: true, - value: [], + present: { + blocks: { + isDirty: true, + value: [], + }, + edits: {}, }, - edits: {}, }, currentPost: { id: 1, @@ -443,7 +514,9 @@ describe( 'selectors', () => { const state = { currentPost: { slug: 'post slug' }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -455,8 +528,10 @@ describe( 'selectors', () => { const state = { currentPost: { slug: 'old slug' }, editor: { - edits: { - slug: 'new slug', + present: { + edits: { + slug: 'new slug', + }, }, }, initialEdits: {}, @@ -471,7 +546,9 @@ describe( 'selectors', () => { title: 'sassel', }, editor: { - edits: { status: 'private' }, + present: { + edits: { status: 'private' }, + }, }, initialEdits: {}, }; @@ -485,7 +562,9 @@ describe( 'selectors', () => { title: 'sassel', }, editor: { - edits: { title: 'youcha' }, + present: { + edits: { title: 'youcha' }, + }, }, initialEdits: {}, }; @@ -497,7 +576,9 @@ describe( 'selectors', () => { const state = { currentPost: {}, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -514,9 +595,11 @@ describe( 'selectors', () => { }, }, editor: { - edits: { - meta: { - b: 2, + present: { + edits: { + meta: { + b: 2, + }, }, }, }, @@ -634,7 +717,9 @@ describe( 'selectors', () => { it( 'should return the post edits', () => { const state = { editor: { - edits: { title: 'terga' }, + present: { + edits: { title: 'terga' }, + }, }, initialEdits: {}, }; @@ -645,7 +730,9 @@ describe( 'selectors', () => { it( 'should return value from initial edits', () => { const state = { editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: { title: 'terga' }, }; @@ -656,7 +743,9 @@ describe( 'selectors', () => { it( 'should prefer value from edits over initial edits', () => { const state = { editor: { - edits: { title: 'werga' }, + present: { + edits: { title: 'werga' }, + }, }, initialEdits: { title: 'terga' }, }; @@ -687,7 +776,9 @@ describe( 'selectors', () => { status: 'draft', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -701,7 +792,9 @@ describe( 'selectors', () => { status: 'private', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -716,7 +809,9 @@ describe( 'selectors', () => { password: 'chicken', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -731,9 +826,11 @@ describe( 'selectors', () => { password: 'chicken', }, editor: { - edits: { - status: 'private', - password: null, + present: { + edits: { + status: 'private', + password: null, + }, }, }, initialEdits: {}, @@ -862,11 +959,13 @@ describe( 'selectors', () => { it( 'should return true for pending posts', () => { const state = { editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, currentPost: { status: 'pending', @@ -882,11 +981,13 @@ describe( 'selectors', () => { it( 'should return true for draft posts', () => { const state = { editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, currentPost: { status: 'draft', @@ -902,11 +1003,13 @@ describe( 'selectors', () => { it( 'should return false for published posts', () => { const state = { editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, currentPost: { status: 'publish', @@ -922,11 +1025,13 @@ describe( 'selectors', () => { it( 'should return true for published, dirty posts', () => { const state = { editor: { - blocks: { - isDirty: true, - value: [], + present: { + blocks: { + isDirty: true, + value: [], + }, + edits: {}, }, - edits: {}, }, currentPost: { status: 'publish', @@ -942,11 +1047,13 @@ describe( 'selectors', () => { it( 'should return false for private posts', () => { const state = { editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, currentPost: { status: 'private', @@ -962,11 +1069,13 @@ describe( 'selectors', () => { it( 'should return false for scheduled posts', () => { const state = { editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, currentPost: { status: 'future', @@ -985,11 +1094,13 @@ describe( 'selectors', () => { status: 'private', }, editor: { - blocks: { - isDirty: true, - value: [], + present: { + blocks: { + isDirty: true, + value: [], + }, + edits: {}, }, - edits: {}, }, saving: { requesting: false, @@ -1026,10 +1137,12 @@ describe( 'selectors', () => { it( 'should return false if the post has no title, excerpt, content', () => { const state = { editor: { - blocks: { - value: [], + present: { + blocks: { + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1042,10 +1155,12 @@ describe( 'selectors', () => { it( 'should return false if the post has a title but save already in progress', () => { const state = { editor: { - blocks: { - value: [], + present: { + blocks: { + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1062,10 +1177,12 @@ describe( 'selectors', () => { it( 'should return true if the post has a title', () => { const state = { editor: { - blocks: { - value: [], + present: { + blocks: { + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1080,13 +1197,15 @@ describe( 'selectors', () => { it( 'should return true if the post has an excerpt', () => { const state = { editor: { - blocks: { - byClientId: {}, - attributes: {}, - order: {}, - value: [], + present: { + blocks: { + byClientId: {}, + attributes: {}, + order: {}, + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1101,19 +1220,21 @@ describe( 'selectors', () => { it( 'should return true if the post has content', () => { const state = { editor: { - blocks: { - value: [ - { - clientId: 123, - name: 'core/test-block-a', - isValid: true, - attributes: { - text: '', + present: { + blocks: { + value: [ + { + clientId: 123, + name: 'core/test-block-a', + isValid: true, + attributes: { + text: '', + }, }, - }, - ], + ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1126,19 +1247,21 @@ describe( 'selectors', () => { it( 'should return false if the post has no title, excerpt and empty classic block', () => { const state = { editor: { - blocks: { - value: [ - { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: '', + present: { + blocks: { + value: [ + { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: '', + }, }, - }, - ], + ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1151,19 +1274,21 @@ describe( 'selectors', () => { it( 'should return true if the post has a title and empty classic block', () => { const state = { editor: { - blocks: { - value: [ - { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: '', + present: { + blocks: { + value: [ + { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: '', + }, }, - }, - ], + ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1180,10 +1305,12 @@ describe( 'selectors', () => { it( 'should return false if the post is not saveable', () => { const state = { editor: { - blocks: { - value: [], + present: { + blocks: { + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1203,10 +1330,12 @@ describe( 'selectors', () => { it( 'should return true if there is not yet an autosave', () => { const state = { editor: { - blocks: { - value: [], + present: { + blocks: { + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1222,11 +1351,13 @@ describe( 'selectors', () => { it( 'should return false if none of title, excerpt, or content have changed', () => { const state = { editor: { - blocks: { - value: [], - isDirty: false, + present: { + blocks: { + value: [], + isDirty: false, + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1246,11 +1377,13 @@ describe( 'selectors', () => { it( 'should return true if content has changes', () => { const state = { editor: { - blocks: { - value: [], - isDirty: true, + present: { + blocks: { + value: [], + isDirty: true, + }, + edits: {}, }, - edits: {}, }, currentPost: { title: 'foo', @@ -1271,11 +1404,13 @@ describe( 'selectors', () => { for ( const constantField of without( [ 'title', 'excerpt' ], variantField ) ) { const state = { editor: { - blocks: { - isDirty: false, - value: [], + present: { + blocks: { + isDirty: false, + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1342,10 +1477,12 @@ describe( 'selectors', () => { it( 'should return true if no blocks and no content', () => { const state = { editor: { - blocks: { - value: [], + present: { + blocks: { + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1357,17 +1494,19 @@ describe( 'selectors', () => { it( 'should return false if blocks', () => { const state = { editor: { - blocks: { - value: [ { - clientId: 123, - name: 'core/test-block-a', - isValid: true, - attributes: { - text: '', - }, - } ], + present: { + blocks: { + value: [ { + clientId: 123, + name: 'core/test-block-a', + isValid: true, + attributes: { + text: '', + }, + } ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1384,16 +1523,18 @@ describe( 'selectors', () => { // See: https://github.com/WordPress/gutenberg/pull/13086 const state = { editor: { - blocks: { - value: [ { - clientId: 'block1', - name: 'core/test-default', - attributes: { - modified: false, - }, - } ], + present: { + blocks: { + value: [ { + clientId: 'block1', + name: 'core/test-default', + attributes: { + modified: false, + }, + } ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1405,18 +1546,20 @@ describe( 'selectors', () => { it( 'should return true if blocks, but empty content edit', () => { const state = { editor: { - blocks: { - value: [ { - clientId: 123, - name: 'core/test-block-a', - isValid: true, - attributes: { - text: '', - }, - } ], - }, - edits: { - content: '', + present: { + blocks: { + value: [ { + clientId: 123, + name: 'core/test-block-a', + isValid: true, + attributes: { + text: '', + }, + } ], + }, + edits: { + content: '', + }, }, }, initialEdits: {}, @@ -1431,10 +1574,12 @@ describe( 'selectors', () => { it( 'should return true if the post has an empty content property', () => { const state = { editor: { - blocks: { - value: [], + present: { + blocks: { + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1448,11 +1593,13 @@ describe( 'selectors', () => { it( 'should return false if edits include a non-empty content property', () => { const state = { editor: { - blocks: { - value: [], - }, - edits: { - content: 'sassel', + present: { + blocks: { + value: [], + }, + edits: { + content: 'sassel', + }, }, }, initialEdits: {}, @@ -1465,17 +1612,19 @@ describe( 'selectors', () => { it( 'should return true if empty classic block', () => { const state = { editor: { - blocks: { - value: [ { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: '', - }, - } ], + present: { + blocks: { + value: [ { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: '', + }, + } ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1487,17 +1636,19 @@ describe( 'selectors', () => { it( 'should return true if empty content freeform block', () => { const state = { editor: { - blocks: { - value: [ { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: '', - }, - } ], + present: { + blocks: { + value: [ { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: '', + }, + } ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1511,17 +1662,19 @@ describe( 'selectors', () => { it( 'should return false if non-empty content freeform block', () => { const state = { editor: { - blocks: { - value: [ { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: 'Test Data', - }, - } ], + present: { + blocks: { + value: [ { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: 'Test Data', + }, + } ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1535,27 +1688,29 @@ describe( 'selectors', () => { it( 'should return false for multiple empty freeform blocks', () => { const state = { editor: { - blocks: { - value: [ - { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: '', + present: { + blocks: { + value: [ + { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: '', + }, }, - }, - { - clientId: 456, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: '', + { + clientId: 456, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: '', + }, }, - }, - ], + ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: { @@ -1573,7 +1728,9 @@ describe( 'selectors', () => { const date = new Date( time ); const state = { editor: { - edits: { date: date.toUTCString() }, + present: { + edits: { date: date.toUTCString() }, + }, }, initialEdits: {}, }; @@ -1584,7 +1741,9 @@ describe( 'selectors', () => { it( 'should return false for posts with an old date', () => { const state = { editor: { - edits: { date: '2016-05-30T17:21:39' }, + present: { + edits: { date: '2016-05-30T17:21:39' }, + }, }, initialEdits: {}, }; @@ -1602,7 +1761,9 @@ describe( 'selectors', () => { status: 'draft', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -1618,7 +1779,9 @@ describe( 'selectors', () => { status: 'auto-draft', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -1634,7 +1797,9 @@ describe( 'selectors', () => { status: 'draft', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -1650,7 +1815,9 @@ describe( 'selectors', () => { status: 'auto-draft', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -1666,7 +1833,9 @@ describe( 'selectors', () => { status: 'publish', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -1763,10 +1932,12 @@ describe( 'selectors', () => { it( 'returns null if cannot be determined', () => { const state = { editor: { - blocks: { - value: [], + present: { + blocks: { + value: [], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1778,21 +1949,23 @@ describe( 'selectors', () => { it( 'returns null if there is more than one block in the post', () => { const state = { editor: { - blocks: { - value: [ - { - clientId: 123, - name: 'core/image', - attributes: {}, - }, - { - clientId: 456, - name: 'core/quote', - attributes: {}, - }, - ], + present: { + blocks: { + value: [ + { + clientId: 123, + name: 'core/image', + attributes: {}, + }, + { + clientId: 456, + name: 'core/quote', + attributes: {}, + }, + ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1804,16 +1977,18 @@ describe( 'selectors', () => { it( 'returns Image if the first block is of type `core/image`', () => { const state = { editor: { - blocks: { - value: [ - { - clientId: 123, - name: 'core/image', - attributes: {}, - }, - ], + present: { + blocks: { + value: [ + { + clientId: 123, + name: 'core/image', + attributes: {}, + }, + ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1825,16 +2000,18 @@ describe( 'selectors', () => { it( 'returns Quote if the first block is of type `core/quote`', () => { const state = { editor: { - blocks: { - value: [ - { - clientId: 456, - name: 'core/quote', - attributes: {}, - }, - ], + present: { + blocks: { + value: [ + { + clientId: 456, + name: 'core/quote', + attributes: {}, + }, + ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1846,16 +2023,18 @@ describe( 'selectors', () => { it( 'returns Video if the first block is of type `core-embed/youtube`', () => { const state = { editor: { - blocks: { - value: [ - { - clientId: 567, - name: 'core-embed/youtube', - attributes: {}, - }, - ], + present: { + blocks: { + value: [ + { + clientId: 567, + name: 'core-embed/youtube', + attributes: {}, + }, + ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1867,21 +2046,23 @@ describe( 'selectors', () => { it( 'returns Quote if the first block is of type `core/quote` and second is of type `core/paragraph`', () => { const state = { editor: { - blocks: { - value: [ - { - clientId: 456, - name: 'core/quote', - attributes: {}, - }, - { - clientId: 789, - name: 'core/paragraph', - attributes: {}, - }, - ], + present: { + blocks: { + value: [ + { + clientId: 456, + name: 'core/quote', + attributes: {}, + }, + { + clientId: 789, + name: 'core/paragraph', + attributes: {}, + }, + ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1923,11 +2104,13 @@ describe( 'selectors', () => { const state = { editor: { - blocks: { - value: [ block ], - }, - edits: { - content: 'custom edit', + present: { + blocks: { + value: [ block ], + }, + edits: { + content: 'custom edit', + }, }, }, initialEdits: {}, @@ -1944,10 +2127,12 @@ describe( 'selectors', () => { const state = { editor: { - blocks: { - value: [ block ], + present: { + blocks: { + value: [ block ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1964,10 +2149,12 @@ describe( 'selectors', () => { } ); const state = { editor: { - blocks: { - value: [ unknownBlock ], + present: { + blocks: { + value: [ unknownBlock ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -1987,10 +2174,12 @@ describe( 'selectors', () => { } ); const state = { editor: { - blocks: { - value: [ firstUnknown, secondUnknown ], + present: { + blocks: { + value: [ firstUnknown, secondUnknown ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -2005,10 +2194,12 @@ describe( 'selectors', () => { const defaultBlock = createBlock( getDefaultBlockName() ); const state = { editor: { - blocks: { - value: [ defaultBlock ], + present: { + blocks: { + value: [ defaultBlock ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -2023,16 +2214,18 @@ describe( 'selectors', () => { const defaultBlock = createBlock( getDefaultBlockName() ); const state = { editor: { - blocks: { - value: [ { - ...defaultBlock, - attributes: { - ...defaultBlock.attributes, - modified: true, - }, - } ], + present: { + blocks: { + value: [ { + ...defaultBlock, + attributes: { + ...defaultBlock.attributes, + modified: true, + }, + } ], + }, + edits: {}, }, - edits: {}, }, initialEdits: {}, currentPost: {}, @@ -2361,7 +2554,9 @@ describe( 'selectors', () => { const state = { currentPost: { permalink_template: '' }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -2373,7 +2568,9 @@ describe( 'selectors', () => { const state = { currentPost: { permalink_template: 'http://foo.test/bar/%baz%/' }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -2385,7 +2582,9 @@ describe( 'selectors', () => { const state = { currentPost: { permalink_template: 'http://foo.test/bar/%postname%/' }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -2397,7 +2596,9 @@ describe( 'selectors', () => { const state = { currentPost: { permalink_template: 'http://foo.test/bar/%pagename%/' }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -2412,7 +2613,9 @@ describe( 'selectors', () => { const state = { currentPost: { permalink_template: url }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -2427,7 +2630,9 @@ describe( 'selectors', () => { slug: 'baz', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -2438,7 +2643,9 @@ describe( 'selectors', () => { it( 'should return null if the post has no permalink template', () => { const state = { currentPost: {}, - editor: {}, + editor: { + present: {}, + }, }; expect( getPermalink( state ) ).toBeNull(); @@ -2458,7 +2665,9 @@ describe( 'selectors', () => { slug: 'baz', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -2477,7 +2686,9 @@ describe( 'selectors', () => { slug: 'baz', }, editor: { - edits: {}, + present: { + edits: {}, + }, }, initialEdits: {}, }; @@ -2488,7 +2699,11 @@ describe( 'selectors', () => { it( 'should return null if the post has no permalink template', () => { const state = { currentPost: {}, - editor: {}, + editor: { + present: { + edits: {}, + }, + }, }; expect( getPermalinkParts( state ) ).toBeNull(); diff --git a/packages/block-editor/src/utils/with-history/README.md b/packages/editor/src/utils/with-history/README.md similarity index 100% rename from packages/block-editor/src/utils/with-history/README.md rename to packages/editor/src/utils/with-history/README.md diff --git a/packages/block-editor/src/utils/with-history/index.js b/packages/editor/src/utils/with-history/index.js similarity index 100% rename from packages/block-editor/src/utils/with-history/index.js rename to packages/editor/src/utils/with-history/index.js diff --git a/packages/block-editor/src/utils/with-history/test/index.js b/packages/editor/src/utils/with-history/test/index.js similarity index 100% rename from packages/block-editor/src/utils/with-history/test/index.js rename to packages/editor/src/utils/with-history/test/index.js