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 b6da0e47ef9b3..3ab9d0bd1cd3e 100644
--- a/docs/designers-developers/developers/data/data-core-block-editor.md
+++ b/docs/designers-developers/developers/data/data-core-block-editor.md
@@ -1134,10 +1134,6 @@ _Parameters_
- _firstBlockClientId_ `string`: Client ID of the first block to merge.
- _secondBlockClientId_ `string`: Client ID of the second block to merge.
-_Returns_
-
-- `Object`: Action object.
-
# **moveBlocksDown**
Undocumented declaration.
@@ -1179,10 +1175,6 @@ _Parameters_
- _start_ `string`: First block of the multi selection.
- _end_ `string`: Last block of the multiselection.
-_Returns_
-
-- `Object`: Action object.
-
# **receiveBlocks**
Returns an action object used in signalling that blocks have been received.
@@ -1273,10 +1265,6 @@ _Parameters_
- _blocks_ `Array`: Array of blocks.
-_Returns_
-
-- `Object`: Action object.
-
# **resetSelection**
Returns an action object used in signalling that selection state should be
@@ -1540,5 +1528,16 @@ _Returns_
- `Object`: Action object
+# **validateBlocksToTemplate**
+
+Block validity is a function of blocks state (at the point of a
+reset) and the template setting. As a compromise to its placement
+across distinct parts of state, it is implemented here as a side-
+effect of the block reset action.
+
+_Parameters_
+
+- _blocks_ `Array`: Array of blocks.
+
diff --git a/package-lock.json b/package-lock.json
index 8bc191f5b665d..f5fb7ffd02d3c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17315,6 +17315,7 @@
"@wordpress/components": "file:packages/components",
"@wordpress/compose": "file:packages/compose",
"@wordpress/data": "file:packages/data",
+ "@wordpress/data-controls": "file:packages/data-controls",
"@wordpress/deprecated": "file:packages/deprecated",
"@wordpress/dom": "file:packages/dom",
"@wordpress/element": "file:packages/element",
@@ -17342,7 +17343,6 @@
"react-spring": "^8.0.19",
"reakit": "1.1.0",
"redux-multi": "^0.1.12",
- "refx": "^3.0.0",
"rememo": "^3.0.0",
"tinycolor2": "^1.4.1",
"traverse": "^0.6.6"
diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json
index 398e79d7286df..7716626c224a2 100644
--- a/packages/block-editor/package.json
+++ b/packages/block-editor/package.json
@@ -35,6 +35,7 @@
"@wordpress/components": "file:../components",
"@wordpress/compose": "file:../compose",
"@wordpress/data": "file:../data",
+ "@wordpress/data-controls": "file:../data-controls",
"@wordpress/deprecated": "file:../deprecated",
"@wordpress/dom": "file:../dom",
"@wordpress/element": "file:../element",
@@ -62,7 +63,6 @@
"react-spring": "^8.0.19",
"reakit": "1.1.0",
"redux-multi": "^0.1.12",
- "refx": "^3.0.0",
"rememo": "^3.0.0",
"tinycolor2": "^1.4.1",
"traverse": "^0.6.6"
diff --git a/packages/block-editor/src/components/provider/with-registry-provider.js b/packages/block-editor/src/components/provider/with-registry-provider.js
index 60109fea59f3c..3b9f7b24c651e 100644
--- a/packages/block-editor/src/components/provider/with-registry-provider.js
+++ b/packages/block-editor/src/components/provider/with-registry-provider.js
@@ -13,7 +13,6 @@ import { createHigherOrderComponent } from '@wordpress/compose';
* Internal dependencies
*/
import { storeConfig } from '../../store';
-import applyMiddlewares from '../../store/middlewares';
const withRegistryProvider = createHigherOrderComponent(
( WrappedComponent ) => {
@@ -28,12 +27,10 @@ const withRegistryProvider = createHigherOrderComponent(
const [ subRegistry, setSubRegistry ] = useState( null );
useEffect( () => {
const newRegistry = createRegistry( {}, registry );
- const store = newRegistry.registerStore(
+ newRegistry.registerStore(
'core/block-editor',
storeConfig
);
- // This should be removed after the refactoring of the effects to controls.
- applyMiddlewares( store );
setSubRegistry( newRegistry );
}, [ registry ] );
diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js
index 48eef28b2f25d..cf69468a598a2 100644
--- a/packages/block-editor/src/store/actions.js
+++ b/packages/block-editor/src/store/actions.js
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { castArray, first, last, some } from 'lodash';
+import { castArray, findKey, first, last, some } from 'lodash';
/**
* WordPress dependencies
@@ -9,12 +9,22 @@ import { castArray, first, last, some } from 'lodash';
import {
cloneBlock,
createBlock,
+ doBlocksMatchTemplate,
+ getBlockType,
getDefaultBlockName,
hasBlockSupport,
+ switchToBlockType,
+ synchronizeBlocksWithTemplate,
} from '@wordpress/blocks';
import { speak } from '@wordpress/a11y';
-import { __ } from '@wordpress/i18n';
+import { __, _n, sprintf } from '@wordpress/i18n';
import { controls } from '@wordpress/data';
+import { create, insert, remove, toHTMLString } from '@wordpress/rich-text';
+
+/**
+ * Internal dependencies
+ */
+import { __unstableMarkAutomaticChangeFinalControl } from '../store/controls';
/**
* Generator which will yield a default block insert action if there
@@ -38,14 +48,50 @@ function* ensureDefaultBlock() {
* content reflected as an edit in state.
*
* @param {Array} blocks Array of blocks.
- *
- * @return {Object} Action object.
*/
-export function resetBlocks( blocks ) {
- return {
+export function* resetBlocks( blocks ) {
+ yield {
type: 'RESET_BLOCKS',
blocks,
};
+ return yield* validateBlocksToTemplate( blocks );
+}
+
+/**
+ * Block validity is a function of blocks state (at the point of a
+ * reset) and the template setting. As a compromise to its placement
+ * across distinct parts of state, it is implemented here as a side-
+ * effect of the block reset action.
+ *
+ * @param {Array} blocks Array of blocks.
+ */
+export function* validateBlocksToTemplate( blocks ) {
+ const template = yield controls.select(
+ 'core/block-editor',
+ 'getTemplate'
+ );
+ const templateLock = yield controls.select(
+ 'core/block-editor',
+ 'getTemplateLock'
+ );
+
+ // Unlocked templates are considered always valid because they act
+ // as default values only.
+ const isBlocksValidToTemplate =
+ ! template ||
+ templateLock !== 'all' ||
+ doBlocksMatchTemplate( blocks, template );
+
+ // Update if validity has changed.
+ const isValidTemplate = yield controls.select(
+ 'core/block-editor',
+ 'isValidTemplate'
+ );
+
+ if ( isBlocksValidToTemplate !== isValidTemplate ) {
+ yield setTemplateValidity( isBlocksValidToTemplate );
+ return isBlocksValidToTemplate;
+ }
}
/**
@@ -211,15 +257,27 @@ export function stopMultiSelect() {
*
* @param {string} start First block of the multi selection.
* @param {string} end Last block of the multiselection.
- *
- * @return {Object} Action object.
*/
-export function multiSelect( start, end ) {
- return {
+export function* multiSelect( start, end ) {
+ yield {
type: 'MULTI_SELECT',
start,
end,
};
+
+ const blockCount = yield controls.select(
+ 'core/block-editor',
+ 'getSelectedBlockCount'
+ );
+
+ speak(
+ sprintf(
+ /* translators: %s: number of selected blocks */
+ _n( '%s block selected.', '%s blocks selected.', blockCount ),
+ blockCount
+ ),
+ 'assertive'
+ );
}
/**
@@ -592,10 +650,18 @@ export function setTemplateValidity( isValid ) {
*
* @return {Object} Action object.
*/
-export function synchronizeTemplate() {
- return {
+export function* synchronizeTemplate() {
+ yield {
type: 'SYNCHRONIZE_TEMPLATE',
};
+ const blocks = yield controls.select( 'core/block-editor', 'getBlocks' );
+ const template = yield controls.select(
+ 'core/block-editor',
+ 'getTemplate'
+ );
+ const updatedBlockList = synchronizeBlocksWithTemplate( blocks, template );
+
+ return yield resetBlocks( updatedBlockList );
}
/**
@@ -603,14 +669,165 @@ export function synchronizeTemplate() {
*
* @param {string} firstBlockClientId Client ID of the first block to merge.
* @param {string} secondBlockClientId Client ID of the second block to merge.
- *
- * @return {Object} Action object.
*/
-export function mergeBlocks( firstBlockClientId, secondBlockClientId ) {
- return {
+export function* mergeBlocks( firstBlockClientId, secondBlockClientId ) {
+ const blocks = [ firstBlockClientId, secondBlockClientId ];
+ yield {
type: 'MERGE_BLOCKS',
- blocks: [ firstBlockClientId, secondBlockClientId ],
+ blocks,
};
+
+ const [ clientIdA, clientIdB ] = blocks;
+ const blockA = yield controls.select(
+ 'core/block-editor',
+ 'getBlock',
+ clientIdA
+ );
+ const blockAType = getBlockType( blockA.name );
+
+ // Only focus the previous block if it's not mergeable
+ if ( ! blockAType.merge ) {
+ yield selectBlock( blockA.clientId );
+ return;
+ }
+
+ const blockB = yield controls.select(
+ 'core/block-editor',
+ 'getBlock',
+ clientIdB
+ );
+ const blockBType = getBlockType( blockB.name );
+ const { clientId, attributeKey, offset } = yield controls.select(
+ 'core/block-editor',
+ 'getSelectionStart'
+ );
+ const selectedBlockType = clientId === clientIdA ? blockAType : blockBType;
+ const attributeDefinition = selectedBlockType.attributes[ attributeKey ];
+ const canRestoreTextSelection =
+ ( clientId === clientIdA || clientId === clientIdB ) &&
+ attributeKey !== undefined &&
+ offset !== undefined &&
+ // We cannot restore text selection if the RichText identifier
+ // is not a defined block attribute key. This can be the case if the
+ // fallback intance ID is used to store selection (and no RichText
+ // identifier is set), or when the identifier is wrong.
+ !! attributeDefinition;
+
+ if ( ! attributeDefinition ) {
+ if ( typeof attributeKey === 'number' ) {
+ window.console.error(
+ `RichText needs an identifier prop that is the block attribute key of the attribute it controls. Its type is expected to be a string, but was ${ typeof attributeKey }`
+ );
+ } else {
+ window.console.error(
+ 'The RichText identifier prop does not match any attributes defined by the block.'
+ );
+ }
+ }
+
+ // A robust way to retain selection position through various transforms
+ // is to insert a special character at the position and then recover it.
+ const START_OF_SELECTED_AREA = '\u0086';
+
+ // Clone the blocks so we don't insert the character in a "live" block.
+ const cloneA = cloneBlock( blockA );
+ const cloneB = cloneBlock( blockB );
+
+ if ( canRestoreTextSelection ) {
+ const selectedBlock = clientId === clientIdA ? cloneA : cloneB;
+ const html = selectedBlock.attributes[ attributeKey ];
+ const {
+ multiline: multilineTag,
+ __unstableMultilineWrapperTags: multilineWrapperTags,
+ __unstablePreserveWhiteSpace: preserveWhiteSpace,
+ } = attributeDefinition;
+ const value = insert(
+ create( {
+ html,
+ multilineTag,
+ multilineWrapperTags,
+ preserveWhiteSpace,
+ } ),
+ START_OF_SELECTED_AREA,
+ offset,
+ offset
+ );
+
+ selectedBlock.attributes[ attributeKey ] = toHTMLString( {
+ value,
+ multilineTag,
+ preserveWhiteSpace,
+ } );
+ }
+
+ // We can only merge blocks with similar types
+ // thus, we transform the block to merge first
+ const blocksWithTheSameType =
+ blockA.name === blockB.name
+ ? [ cloneB ]
+ : switchToBlockType( cloneB, blockA.name );
+
+ // If the block types can not match, do nothing
+ if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) {
+ return;
+ }
+
+ // Calling the merge to update the attributes and remove the block to be merged
+ const updatedAttributes = blockAType.merge(
+ cloneA.attributes,
+ blocksWithTheSameType[ 0 ].attributes
+ );
+
+ if ( canRestoreTextSelection ) {
+ const newAttributeKey = findKey(
+ updatedAttributes,
+ ( v ) =>
+ typeof v === 'string' &&
+ v.indexOf( START_OF_SELECTED_AREA ) !== -1
+ );
+ const convertedHtml = updatedAttributes[ newAttributeKey ];
+ const {
+ multiline: multilineTag,
+ __unstableMultilineWrapperTags: multilineWrapperTags,
+ __unstablePreserveWhiteSpace: preserveWhiteSpace,
+ } = blockAType.attributes[ newAttributeKey ];
+ const convertedValue = create( {
+ html: convertedHtml,
+ multilineTag,
+ multilineWrapperTags,
+ preserveWhiteSpace,
+ } );
+ const newOffset = convertedValue.text.indexOf( START_OF_SELECTED_AREA );
+ const newValue = remove( convertedValue, newOffset, newOffset + 1 );
+ const newHtml = toHTMLString( {
+ value: newValue,
+ multilineTag,
+ preserveWhiteSpace,
+ } );
+
+ updatedAttributes[ newAttributeKey ] = newHtml;
+
+ yield selectionChange(
+ blockA.clientId,
+ newAttributeKey,
+ newOffset,
+ newOffset
+ );
+ }
+
+ yield* replaceBlocks(
+ [ blockA.clientId, blockB.clientId ],
+ [
+ {
+ ...blockA,
+ attributes: {
+ ...blockA.attributes,
+ ...updatedAttributes,
+ },
+ },
+ ...blocksWithTheSameType.slice( 1 ),
+ ]
+ );
}
/**
@@ -907,11 +1124,16 @@ export function __unstableMarkNextChangeAsNotPersistent() {
* after the change was made, and any actions that are a consequence of it, so
* it is recommended to be called at the next idle period to ensure all
* selection changes have been recorded.
- *
- * @return {Object} Action object.
*/
-export function __unstableMarkAutomaticChange() {
- return { type: 'MARK_AUTOMATIC_CHANGE' };
+export function* __unstableMarkAutomaticChange() {
+ yield { type: 'MARK_AUTOMATIC_CHANGE' };
+ yield __unstableMarkAutomaticChangeFinalControl();
+}
+
+export function __unstableMarkAutomaticChangeFinal() {
+ return {
+ type: 'MARK_AUTOMATIC_CHANGE_FINAL',
+ };
}
/**
diff --git a/packages/block-editor/src/store/controls.js b/packages/block-editor/src/store/controls.js
index 83b4f45342546..84d97f38654bb 100644
--- a/packages/block-editor/src/store/controls.js
+++ b/packages/block-editor/src/store/controls.js
@@ -1,9 +1,34 @@
+/**
+ * WordPress dependencies
+ */
+import { createRegistryControl } from '@wordpress/data';
+
+export const __unstableMarkAutomaticChangeFinalControl = function () {
+ return {
+ type: 'MARK_AUTOMATIC_CHANGE_FINAL_CONTROL',
+ };
+};
+
const controls = {
SLEEP( { duration } ) {
return new Promise( ( resolve ) => {
setTimeout( resolve, duration );
} );
},
+
+ MARK_AUTOMATIC_CHANGE_FINAL_CONTROL: createRegistryControl(
+ ( registry ) => () => {
+ const {
+ requestIdleCallback = ( callback ) =>
+ setTimeout( callback, 100 ),
+ } = window;
+ requestIdleCallback( () =>
+ registry
+ .dispatch( 'core/block-editor' )
+ .__unstableMarkAutomaticChangeFinal()
+ );
+ }
+ ),
};
export default controls;
diff --git a/packages/block-editor/src/store/effects.js b/packages/block-editor/src/store/effects.js
deleted file mode 100644
index 5142020ffffa1..0000000000000
--- a/packages/block-editor/src/store/effects.js
+++ /dev/null
@@ -1,256 +0,0 @@
-/**
- * External dependencies
- */
-import { findKey } from 'lodash';
-
-/**
- * WordPress dependencies
- */
-import { speak } from '@wordpress/a11y';
-import {
- getBlockType,
- doBlocksMatchTemplate,
- switchToBlockType,
- synchronizeBlocksWithTemplate,
- cloneBlock,
-} from '@wordpress/blocks';
-import { _n, sprintf } from '@wordpress/i18n';
-import { create, toHTMLString, insert, remove } from '@wordpress/rich-text';
-
-/**
- * Internal dependencies
- */
-import {
- replaceBlocks,
- selectBlock,
- setTemplateValidity,
- resetBlocks,
- selectionChange,
-} from './actions';
-import {
- getBlock,
- getBlocks,
- getSelectedBlockCount,
- getTemplateLock,
- getTemplate,
- isValidTemplate,
- getSelectionStart,
-} from './selectors';
-
-/**
- * Block validity is a function of blocks state (at the point of a
- * reset) and the template setting. As a compromise to its placement
- * across distinct parts of state, it is implemented here as a side-
- * effect of the block reset action.
- *
- * @param {Object} action RESET_BLOCKS action.
- * @param {Object} store Store instance.
- *
- * @return {?Object} New validity set action if validity has changed.
- */
-export function validateBlocksToTemplate( action, store ) {
- const state = store.getState();
- const template = getTemplate( state );
- const templateLock = getTemplateLock( state );
-
- // Unlocked templates are considered always valid because they act
- // as default values only.
- const isBlocksValidToTemplate =
- ! template ||
- templateLock !== 'all' ||
- doBlocksMatchTemplate( action.blocks, template );
-
- // Update if validity has changed.
- if ( isBlocksValidToTemplate !== isValidTemplate( state ) ) {
- return setTemplateValidity( isBlocksValidToTemplate );
- }
-}
-
-export default {
- MERGE_BLOCKS( action, store ) {
- const { dispatch } = store;
- const state = store.getState();
- const [ clientIdA, clientIdB ] = action.blocks;
- const blockA = getBlock( state, clientIdA );
- const blockAType = getBlockType( blockA.name );
-
- // Only focus the previous block if it's not mergeable
- if ( ! blockAType.merge ) {
- dispatch( selectBlock( blockA.clientId ) );
- return;
- }
-
- const blockB = getBlock( state, clientIdB );
- const blockBType = getBlockType( blockB.name );
- const { clientId, attributeKey, offset } = getSelectionStart( state );
- const selectedBlockType =
- clientId === clientIdA ? blockAType : blockBType;
- const attributeDefinition =
- selectedBlockType.attributes[ attributeKey ];
- const canRestoreTextSelection =
- ( clientId === clientIdA || clientId === clientIdB ) &&
- attributeKey !== undefined &&
- offset !== undefined &&
- // We cannot restore text selection if the RichText identifier
- // is not a defined block attribute key. This can be the case if the
- // fallback intance ID is used to store selection (and no RichText
- // identifier is set), or when the identifier is wrong.
- !! attributeDefinition;
-
- if ( ! attributeDefinition ) {
- if ( typeof attributeKey === 'number' ) {
- window.console.error(
- `RichText needs an identifier prop that is the block attribute key of the attribute it controls. Its type is expected to be a string, but was ${ typeof attributeKey }`
- );
- } else {
- window.console.error(
- 'The RichText identifier prop does not match any attributes defined by the block.'
- );
- }
- }
-
- // A robust way to retain selection position through various transforms
- // is to insert a special character at the position and then recover it.
- const START_OF_SELECTED_AREA = '\u0086';
-
- // Clone the blocks so we don't insert the character in a "live" block.
- const cloneA = cloneBlock( blockA );
- const cloneB = cloneBlock( blockB );
-
- if ( canRestoreTextSelection ) {
- const selectedBlock = clientId === clientIdA ? cloneA : cloneB;
- const html = selectedBlock.attributes[ attributeKey ];
- const {
- multiline: multilineTag,
- __unstableMultilineWrapperTags: multilineWrapperTags,
- __unstablePreserveWhiteSpace: preserveWhiteSpace,
- } = attributeDefinition;
- const value = insert(
- create( {
- html,
- multilineTag,
- multilineWrapperTags,
- preserveWhiteSpace,
- } ),
- START_OF_SELECTED_AREA,
- offset,
- offset
- );
-
- selectedBlock.attributes[ attributeKey ] = toHTMLString( {
- value,
- multilineTag,
- preserveWhiteSpace,
- } );
- }
-
- // We can only merge blocks with similar types
- // thus, we transform the block to merge first
- const blocksWithTheSameType =
- blockA.name === blockB.name
- ? [ cloneB ]
- : switchToBlockType( cloneB, blockA.name );
-
- // If the block types can not match, do nothing
- if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) {
- return;
- }
-
- // Calling the merge to update the attributes and remove the block to be merged
- const updatedAttributes = blockAType.merge(
- cloneA.attributes,
- blocksWithTheSameType[ 0 ].attributes
- );
-
- if ( canRestoreTextSelection ) {
- const newAttributeKey = findKey(
- updatedAttributes,
- ( v ) =>
- typeof v === 'string' &&
- v.indexOf( START_OF_SELECTED_AREA ) !== -1
- );
- const convertedHtml = updatedAttributes[ newAttributeKey ];
- const {
- multiline: multilineTag,
- __unstableMultilineWrapperTags: multilineWrapperTags,
- __unstablePreserveWhiteSpace: preserveWhiteSpace,
- } = blockAType.attributes[ newAttributeKey ];
- const convertedValue = create( {
- html: convertedHtml,
- multilineTag,
- multilineWrapperTags,
- preserveWhiteSpace,
- } );
- const newOffset = convertedValue.text.indexOf(
- START_OF_SELECTED_AREA
- );
- const newValue = remove( convertedValue, newOffset, newOffset + 1 );
- const newHtml = toHTMLString( {
- value: newValue,
- multilineTag,
- preserveWhiteSpace,
- } );
-
- updatedAttributes[ newAttributeKey ] = newHtml;
-
- dispatch(
- selectionChange(
- blockA.clientId,
- newAttributeKey,
- newOffset,
- newOffset
- )
- );
- }
-
- dispatch(
- replaceBlocks(
- [ blockA.clientId, blockB.clientId ],
- [
- {
- ...blockA,
- attributes: {
- ...blockA.attributes,
- ...updatedAttributes,
- },
- },
- ...blocksWithTheSameType.slice( 1 ),
- ]
- )
- );
- },
- RESET_BLOCKS: [ validateBlocksToTemplate ],
- MULTI_SELECT: ( action, { getState } ) => {
- const blockCount = getSelectedBlockCount( getState() );
-
- speak(
- sprintf(
- /* translators: %s: number of selected blocks */
- _n( '%s block selected.', '%s blocks selected.', blockCount ),
- blockCount
- ),
- 'assertive'
- );
- },
- SYNCHRONIZE_TEMPLATE( action, { getState } ) {
- const state = getState();
- const blocks = getBlocks( state );
- const template = getTemplate( state );
- const updatedBlockList = synchronizeBlocksWithTemplate(
- blocks,
- template
- );
-
- return resetBlocks( updatedBlockList );
- },
- MARK_AUTOMATIC_CHANGE( action, store ) {
- const {
- setTimeout,
- requestIdleCallback = ( callback ) => setTimeout( callback, 100 ),
- } = window;
-
- requestIdleCallback( () => {
- store.dispatch( { type: 'MARK_AUTOMATIC_CHANGE_FINAL' } );
- } );
- },
-};
diff --git a/packages/block-editor/src/store/index.js b/packages/block-editor/src/store/index.js
index 8fd1f3532c632..4ba6668916677 100644
--- a/packages/block-editor/src/store/index.js
+++ b/packages/block-editor/src/store/index.js
@@ -7,7 +7,6 @@ import { createReduxStore, registerStore } from '@wordpress/data';
* Internal dependencies
*/
import reducer from './reducer';
-import applyMiddlewares from './middlewares';
import * as selectors from './selectors';
import * as actions from './actions';
import controls from './controls';
@@ -44,10 +43,7 @@ export const store = createReduxStore( STORE_NAME, {
} );
// Ideally we'd use register instead of register stores.
-// We should be able to make the switch once we remove the "effects" middleware.
-// We also need a more generic way of defining persistence and not rely on a plugin.
-const instantiatedStore = registerStore( STORE_NAME, {
+registerStore( STORE_NAME, {
...storeConfig,
persist: [ 'preferences' ],
} );
-applyMiddlewares( instantiatedStore );
diff --git a/packages/block-editor/src/store/middlewares.js b/packages/block-editor/src/store/middlewares.js
deleted file mode 100644
index 0f4c5aef8df70..0000000000000
--- a/packages/block-editor/src/store/middlewares.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * External dependencies
- */
-import refx from 'refx';
-import multi from 'redux-multi';
-import { flowRight } from 'lodash';
-
-/**
- * Internal dependencies
- */
-import effects from './effects';
-
-/**
- * Applies the custom middlewares used specifically in the editor module.
- *
- * @param {Object} store Store Object.
- *
- * @return {Object} Update Store Object.
- */
-function applyMiddlewares( store ) {
- const middlewares = [ refx( effects ), multi ];
-
- let enhancedDispatch = () => {
- throw new Error(
- 'Dispatching while constructing your middleware is not allowed. ' +
- 'Other middleware would not be applied to this dispatch.'
- );
- };
- let chain = [];
-
- const middlewareAPI = {
- getState: store.getState,
- dispatch: ( ...args ) => enhancedDispatch( ...args ),
- };
- chain = middlewares.map( ( middleware ) => middleware( middlewareAPI ) );
- enhancedDispatch = flowRight( ...chain )( store.dispatch );
-
- store.dispatch = enhancedDispatch;
- return store;
-}
-
-export default applyMiddlewares;
diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js
index aa5f622598700..d9598b9a7e6b8 100644
--- a/packages/block-editor/src/store/test/actions.js
+++ b/packages/block-editor/src/store/test/actions.js
@@ -41,10 +41,10 @@ import {
describe( 'actions', () => {
describe( 'resetBlocks', () => {
- it( 'should return the RESET_BLOCKS actions', () => {
+ it( 'should yield the RESET_BLOCKS actions', () => {
const blocks = [];
- const result = resetBlocks( blocks );
- expect( result ).toEqual( {
+ const fulfillment = resetBlocks( blocks );
+ expect( fulfillment.next().value ).toEqual( {
type: 'RESET_BLOCKS',
blocks,
} );
@@ -119,7 +119,8 @@ describe( 'actions', () => {
it( 'should return MULTI_SELECT action', () => {
const start = 'start';
const end = 'end';
- expect( multiSelect( start, end ) ).toEqual( {
+ const fulfillment = multiSelect( start, end );
+ expect( fulfillment.next().value ).toEqual( {
type: 'MULTI_SELECT',
start,
end,
@@ -740,9 +741,11 @@ describe( 'actions', () => {
it( 'should return MERGE_BLOCKS action', () => {
const firstBlockClientId = 'blockA';
const secondBlockClientId = 'blockB';
- expect(
- mergeBlocks( firstBlockClientId, secondBlockClientId )
- ).toEqual( {
+ const fulfillment = mergeBlocks(
+ firstBlockClientId,
+ secondBlockClientId
+ );
+ expect( fulfillment.next().value ).toEqual( {
type: 'MERGE_BLOCKS',
blocks: [ firstBlockClientId, secondBlockClientId ],
} );
diff --git a/packages/block-editor/src/store/test/effects.js b/packages/block-editor/src/store/test/effects.js
index 1832e7fe2a829..9a8ecc4980187 100644
--- a/packages/block-editor/src/store/test/effects.js
+++ b/packages/block-editor/src/store/test/effects.js
@@ -21,16 +21,13 @@ import { createRegistry } from '@wordpress/data';
import actions, {
updateSettings,
mergeBlocks,
- replaceBlocks,
resetBlocks,
selectBlock,
selectionChange,
- setTemplateValidity,
+ validateBlocksToTemplate,
} from '../actions';
-import effects, { validateBlocksToTemplate } from '../effects';
import * as selectors from '../selectors';
import reducer from '../reducer';
-import applyMiddlewares from '../middlewares';
import '../../';
describe( 'effects', () => {
@@ -44,14 +41,10 @@ describe( 'effects', () => {
};
describe( '.MERGE_BLOCKS', () => {
- const handler = effects.MERGE_BLOCKS;
- const defaultGetBlock = selectors.getBlock;
-
afterEach( () => {
getBlockTypes().forEach( ( block ) => {
unregisterBlockType( block.name );
} );
- selectors.getBlock = defaultGetBlock;
} );
it( 'should only focus the blockA if the blockA has no merge function', () => {
@@ -64,19 +57,21 @@ describe( 'effects', () => {
clientId: 'ribs',
name: 'core/test-block',
} );
- selectors.getBlock = ( state, clientId ) => {
- return blockA.clientId === clientId ? blockA : blockB;
- };
- const dispatch = jest.fn();
- const getState = () => ( {} );
- handler( mergeBlocks( blockA.clientId, blockB.clientId ), {
- dispatch,
- getState,
+ const fulfillment = mergeBlocks( blockA.clientId, blockB.clientId );
+ expect( fulfillment.next() ).toEqual( {
+ done: false,
+ value: {
+ type: 'MERGE_BLOCKS',
+ blocks: [ blockA.clientId, blockB.clientId ],
+ },
} );
-
- expect( dispatch ).toHaveBeenCalledTimes( 1 );
- expect( dispatch ).toHaveBeenCalledWith( selectBlock( 'chicken' ) );
+ fulfillment.next();
+ expect( fulfillment.next( blockA ) ).toEqual( {
+ done: false,
+ value: selectBlock( 'chicken' ),
+ } );
+ expect( fulfillment.next( blockA ).done ).toEqual( true );
} );
it( 'should merge the blocks if blocks of the same type', () => {
@@ -108,24 +103,23 @@ describe( 'effects', () => {
attributes: { content: 'ribs' },
innerBlocks: [],
} );
- selectors.getBlock = ( state, clientId ) => {
- return blockA.clientId === clientId ? blockA : blockB;
- };
- const dispatch = jest.fn();
- const getState = () => ( {
- selectionStart: {
- clientId: blockB.clientId,
- attributeKey: 'content',
- offset: 0,
- },
- } );
- handler( mergeBlocks( blockA.clientId, blockB.clientId ), {
- dispatch,
- getState,
- } );
- expect( dispatch ).toHaveBeenCalledTimes( 2 );
- expect( dispatch ).toHaveBeenCalledWith(
+ const fulfillment = mergeBlocks( blockA.clientId, blockB.clientId );
+ // MERGE_BLOCKS
+ fulfillment.next();
+ // getBlock A
+ fulfillment.next();
+ fulfillment.next( blockA );
+ // getBlock B
+ fulfillment.next( blockB );
+ // getSelectionStart
+ fulfillment.next( {
+ clientId: blockB.clientId,
+ attributeKey: 'content',
+ offset: 0,
+ } );
+ // selectionChange
+ fulfillment.next(
selectionChange(
blockA.clientId,
'content',
@@ -133,22 +127,19 @@ describe( 'effects', () => {
'chicken'.length + 1
)
);
- const lastCall = dispatch.mock.calls[ 1 ];
- expect( lastCall ).toHaveLength( 1 );
- const [ lastCallArgument ] = lastCall;
- const expectedGenerator = replaceBlocks(
- [ 'chicken', 'ribs' ],
- [
+ fulfillment.next();
+ fulfillment.next();
+ expect( fulfillment.next( blockA ).value ).toMatchObject( {
+ type: 'REPLACE_BLOCKS',
+ clientIds: [ 'chicken', 'ribs' ],
+ blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: { content: 'chicken ribs' },
},
- ]
- );
- expect( Array.from( lastCallArgument ) ).toEqual(
- Array.from( expectedGenerator )
- );
+ ],
+ } );
} );
it( 'should not merge the blocks have different types without transformation', () => {
@@ -181,23 +172,28 @@ describe( 'effects', () => {
attributes: { content: 'ribs' },
innerBlocks: [],
} );
- selectors.getBlock = ( state, clientId ) => {
- return blockA.clientId === clientId ? blockA : blockB;
- };
- const dispatch = jest.fn();
- const getState = () => ( {
- selectionStart: {
- clientId: blockB.clientId,
- attributeKey: 'content',
- offset: 0,
- },
+
+ const fulfillment = mergeBlocks( blockA.clientId, blockB.clientId );
+ // MERGE_BLOCKS
+ fulfillment.next();
+ // getBlock A
+ fulfillment.next();
+ fulfillment.next( blockA );
+ // getBlock B
+ expect( fulfillment.next( blockB ).value ).toEqual( {
+ args: [],
+ selectorName: 'getSelectionStart',
+ storeKey: 'core/block-editor',
+ type: '@@data/SELECT',
} );
- handler( mergeBlocks( blockA.clientId, blockB.clientId ), {
- dispatch,
- getState,
+ // getSelectionStart
+ const next = fulfillment.next( {
+ clientId: blockB.clientId,
+ attributeKey: 'content',
+ offset: 0,
} );
-
- expect( dispatch ).not.toHaveBeenCalled();
+ expect( next.value ).toEqual( undefined );
+ expect( next.done ).toBe( true );
} );
it( 'should transform and merge the blocks', () => {
@@ -254,24 +250,27 @@ describe( 'effects', () => {
attributes: { content2: 'ribs' },
innerBlocks: [],
} );
- selectors.getBlock = ( state, clientId ) => {
- return blockA.clientId === clientId ? blockA : blockB;
- };
- const dispatch = jest.fn();
- const getState = () => ( {
- selectionStart: {
+
+ const fulfillment = mergeBlocks( blockA.clientId, blockB.clientId );
+ // MERGE_BLOCKS
+ fulfillment.next();
+ // getBlock A
+ fulfillment.next();
+ fulfillment.next( blockA );
+ // getBlock B
+ expect( fulfillment.next( blockB ).value ).toEqual( {
+ args: [],
+ selectorName: 'getSelectionStart',
+ storeKey: 'core/block-editor',
+ type: '@@data/SELECT',
+ } );
+ expect(
+ fulfillment.next( {
clientId: blockB.clientId,
attributeKey: 'content2',
offset: 0,
- },
- } );
- handler( mergeBlocks( blockA.clientId, blockB.clientId ), {
- dispatch,
- getState,
- } );
-
- expect( dispatch ).toHaveBeenCalledTimes( 2 );
- expect( dispatch ).toHaveBeenCalledWith(
+ } ).value
+ ).toEqual(
selectionChange(
blockA.clientId,
'content',
@@ -279,34 +278,32 @@ describe( 'effects', () => {
'chicken'.length + 1
)
);
- const expectedGenerator = replaceBlocks(
- [ 'chicken', 'ribs' ],
- [
+
+ fulfillment.next();
+ fulfillment.next();
+ fulfillment.next();
+ expect( fulfillment.next( blockA ).value ).toMatchObject( {
+ type: 'REPLACE_BLOCKS',
+ clientIds: [ 'chicken', 'ribs' ],
+ blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: { content: 'chicken ribs' },
},
- ]
- );
- const lastCall = dispatch.mock.calls[ 1 ];
- expect( lastCall ).toHaveLength( 1 );
- const [ lastCallArgument ] = lastCall;
- expect( Array.from( lastCallArgument ) ).toEqual(
- Array.from( expectedGenerator )
- );
+ ],
+ } );
} );
} );
describe( 'validateBlocksToTemplate', () => {
let store;
beforeEach( () => {
- store = createRegistry().registerStore( 'test', {
+ store = createRegistry().registerStore( 'core/block-editor', {
actions,
selectors,
reducer,
} );
- applyMiddlewares( store );
registerBlockType( 'core/test-block', defaultBlockSettings );
} );
@@ -317,31 +314,32 @@ describe( 'effects', () => {
} );
} );
- it( 'should return undefined if no template assigned', () => {
- const result = validateBlocksToTemplate(
- resetBlocks( [ createBlock( 'core/test-block' ) ] ),
- store
+ it( 'should return undefined if no template assigned', async () => {
+ const result = await store.dispatch(
+ validateBlocksToTemplate(
+ resetBlocks( [ createBlock( 'core/test-block' ) ] ),
+ store
+ )
);
- expect( result ).toBe( undefined );
+ expect( result ).toEqual( undefined );
} );
- it( 'should return undefined if invalid but unlocked', () => {
+ it( 'should return undefined if invalid but unlocked', async () => {
store.dispatch(
updateSettings( {
template: [ [ 'core/foo', {} ] ],
} )
);
- const result = validateBlocksToTemplate(
- resetBlocks( [ createBlock( 'core/test-block' ) ] ),
- store
+ const result = await store.dispatch(
+ validateBlocksToTemplate( [ createBlock( 'core/test-block' ) ] )
);
- expect( result ).toBe( undefined );
+ expect( result ).toEqual( undefined );
} );
- it( 'should return undefined if locked and valid', () => {
+ it( 'should return undefined if locked and valid', async () => {
store.dispatch(
updateSettings( {
template: [ [ 'core/test-block' ] ],
@@ -349,15 +347,14 @@ describe( 'effects', () => {
} )
);
- const result = validateBlocksToTemplate(
- resetBlocks( [ createBlock( 'core/test-block' ) ] ),
- store
+ const result = await store.dispatch(
+ validateBlocksToTemplate( [ createBlock( 'core/test-block' ) ] )
);
- expect( result ).toBe( undefined );
+ expect( result ).toEqual( undefined );
} );
- it( 'should return validity set action if invalid on default state', () => {
+ it( 'should return validity set action if invalid on default state', async () => {
store.dispatch(
updateSettings( {
template: [ [ 'core/foo' ] ],
@@ -365,12 +362,11 @@ describe( 'effects', () => {
} )
);
- const result = validateBlocksToTemplate(
- resetBlocks( [ createBlock( 'core/test-block' ) ] ),
- store
+ const result = await store.dispatch(
+ validateBlocksToTemplate( [ createBlock( 'core/test-block' ) ] )
);
- expect( result ).toEqual( setTemplateValidity( false ) );
+ expect( result ).toEqual( false );
} );
} );
} );