From c28d2dbe2e5a01cdb30cfa884170c76e8b548fd6 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 17 Sep 2018 14:45:46 -0400 Subject: [PATCH] State: Set template validity on block reset (#9916) --- docs/reference/deprecated.md | 4 + packages/editor/CHANGELOG.md | 4 + .../src/components/post-text-editor/index.js | 3 +- packages/editor/src/store/actions.js | 9 ++ packages/editor/src/store/effects.js | 78 ++++++++++----- packages/editor/src/store/test/effects.js | 96 +++++++++++++++++-- 6 files changed, 161 insertions(+), 33 deletions(-) diff --git a/docs/reference/deprecated.md b/docs/reference/deprecated.md index c77e7496c3e63..41aa4db69b786 100644 --- a/docs/reference/deprecated.md +++ b/docs/reference/deprecated.md @@ -1,5 +1,9 @@ Gutenberg's deprecation policy is intended to support backwards-compatibility for two minor releases, when possible. The current deprecations are listed below and are grouped by _the version at which they will be removed completely_. If your plugin depends on these behaviors, you must update to the recommended alternative before the noted version. +## 4.1.0 + +- `wp.data.dispatch( 'core/editor' ).checkTemplateValidity` has been removed. Validity is verified automatically upon block reset. + ## 4.0.0 - `wp.components.RichTextProvider` has been removed. Please use `wp.data.select( 'core/editor' )` methods instead. diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index a9c728467e8c0..23252828f58c8 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -8,6 +8,10 @@ - `RichText` `getSettings` prop has been removed. The `unstableGetSettings` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice, even as part of a minor release. - `RichText` `onSetup` prop has been removed. The `unstableOnSetup` prop is available if continued use is required. Unstable APIs are strongly discouraged to be used, and are subject to removal without notice, even as part of a minor release. +### Deprecations + +- The `checkTemplateValidity` action has been deprecated. Validity is verified automatically upon block reset. + ## 3.0.0 (2018-09-05) ### New Features diff --git a/packages/editor/src/components/post-text-editor/index.js b/packages/editor/src/components/post-text-editor/index.js index 91037ab63a0bc..6ebe27cc88a1a 100644 --- a/packages/editor/src/components/post-text-editor/index.js +++ b/packages/editor/src/components/post-text-editor/index.js @@ -97,14 +97,13 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { editPost, resetBlocks, checkTemplateValidity } = dispatch( 'core/editor' ); + const { editPost, resetBlocks } = dispatch( 'core/editor' ); return { onChange( content ) { editPost( { content } ); }, onPersist( content ) { resetBlocks( parse( content ) ); - checkTemplateValidity(); }, }; } ), diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index d9d4e3186b73c..0fc1b6555bb18 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -11,6 +11,7 @@ import { getDefaultBlockName, createBlock, } from '@wordpress/blocks'; +import deprecated from '@wordpress/deprecated'; /** * Returns an action object used in signalling that editor has initialized with @@ -373,6 +374,14 @@ export function setTemplateValidity( isValid ) { * @return {Object} Action object. */ export function checkTemplateValidity() { + // TODO: Hello future deprecation remover. Please ensure also to remove all + // references to CHECK_TEMPLATE_VALIDITY, notably its effect handler. + deprecated( 'checkTemplateValidity action (`core/editor`)', { + version: '4.1', + plugin: 'Gutenberg', + hint: 'Validity is verified automatically upon block reset.', + } ); + return { type: 'CHECK_TEMPLATE_VALIDITY', }; diff --git a/packages/editor/src/store/effects.js b/packages/editor/src/store/effects.js index db0dad59063bd..d32753d41a5e7 100644 --- a/packages/editor/src/store/effects.js +++ b/packages/editor/src/store/effects.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { last } from 'lodash'; +import { compact, last } from 'lodash'; /** * WordPress dependencies @@ -35,6 +35,7 @@ import { getSelectedBlock, getTemplate, getTemplateLock, + isValidTemplate, } from './selectors'; import { fetchReusableBlocks, @@ -54,6 +55,36 @@ import { AUTOSAVE_POST_NOTICE_ID, } from './effects/posts'; +/** + * 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 { REQUEST_POST_UPDATE: ( action, store ) => { requestPostUpdate( action, store ); @@ -113,26 +144,18 @@ export default { ] ) ); }, - SETUP_EDITOR( action, { getState } ) { + SETUP_EDITOR( action, store ) { const { post, autosave } = action; - const state = getState(); - const template = getTemplate( state ); - const templateLock = getTemplateLock( state ); - const isNewPost = post.status === 'auto-draft'; + const state = store.getState(); // Parse content as blocks let blocks = parse( post.content.raw ); - let isValidTemplate = true; + + // Apply a template for new posts only, if exists. + const isNewPost = post.status === 'auto-draft'; + const template = getTemplate( state ); if ( isNewPost && template ) { - // Apply a template for new posts only, if exists. blocks = synchronizeBlocksWithTemplate( blocks, template ); - } else { - // Unlocked templates are considered always valid because they act as default values only. - isValidTemplate = ( - ! template || - templateLock !== 'all' || - doBlocksMatchTemplate( blocks, template ) - ); } // Include auto draft title in edits while not flagging post as dirty @@ -158,22 +181,29 @@ export default { ); } - return [ - setTemplateValidity( isValidTemplate ), - setupEditorState( post, blocks, edits ), - ...( autosaveAction ? [ autosaveAction ] : [] ), - ]; + const setupAction = setupEditorState( post, blocks, edits ); + + return compact( [ + setupAction, + autosaveAction, + + // TODO: This is temporary, necessary only so long as editor setup + // is a separate action from block resetting. + // + // See: https://github.com/WordPress/gutenberg/pull/9403 + validateBlocksToTemplate( setupAction, store ), + ] ); }, + RESET_BLOCKS: [ + validateBlocksToTemplate, + ], SYNCHRONIZE_TEMPLATE( action, { getState } ) { const state = getState(); const blocks = getBlocks( state ); const template = getTemplate( state ); const updatedBlockList = synchronizeBlocksWithTemplate( blocks, template ); - return [ - resetBlocks( updatedBlockList ), - setTemplateValidity( true ), - ]; + return resetBlocks( updatedBlockList ); }, CHECK_TEMPLATE_VALIDITY( action, { getState } ) { const state = getState(); diff --git a/packages/editor/src/store/test/effects.js b/packages/editor/src/store/test/effects.js index 11263cab61f3f..45a7121a95332 100644 --- a/packages/editor/src/store/test/effects.js +++ b/packages/editor/src/store/test/effects.js @@ -12,22 +12,26 @@ import { registerBlockType, createBlock, } from '@wordpress/blocks'; +import { createRegistry } from '@wordpress/data'; /** * Internal dependencies */ -import { +import actions, { + updateEditorSettings, setupEditorState, mergeBlocks, replaceBlocks, + resetBlocks, selectBlock, createErrorNotice, setTemplateValidity, editPost, } from '../actions'; -import effects from '../effects'; +import effects, { validateBlocksToTemplate } from '../effects'; import * as selectors from '../selectors'; import reducer from '../reducer'; +import applyMiddlewares from '../middlewares'; describe( 'effects', () => { const defaultBlockSettings = { save: () => 'Saved', category: 'common', title: 'block title' }; @@ -441,12 +445,14 @@ describe( 'effects', () => { template: null, templateLock: false, }, + template: { + isValid: true, + }, } ); const result = handler( { post, settings: {} }, { getState } ); expect( result ).toEqual( [ - setTemplateValidity( true ), setupEditorState( post, [], {} ), ] ); } ); @@ -468,14 +474,16 @@ describe( 'effects', () => { template: null, templateLock: false, }, + template: { + isValid: true, + }, } ); const result = handler( { post }, { getState } ); - expect( result[ 1 ].blocks ).toHaveLength( 1 ); + expect( result[ 0 ].blocks ).toHaveLength( 1 ); expect( result ).toEqual( [ - setTemplateValidity( true ), - setupEditorState( post, result[ 1 ].blocks, {} ), + setupEditorState( post, result[ 0 ].blocks, {} ), ] ); } ); @@ -495,14 +503,88 @@ describe( 'effects', () => { template: null, templateLock: false, }, + template: { + isValid: true, + }, } ); const result = handler( { post }, { getState } ); expect( result ).toEqual( [ - setTemplateValidity( true ), setupEditorState( post, [], { title: 'A History of Pork' } ), ] ); } ); } ); + + describe( 'validateBlocksToTemplate', () => { + let store; + beforeEach( () => { + store = createRegistry().registerStore( 'test', { + actions, + selectors, + reducer, + } ); + applyMiddlewares( store ); + + registerBlockType( 'core/test-block', defaultBlockSettings ); + } ); + + afterEach( () => { + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); + } ); + + it( 'should return undefined if no template assigned', () => { + const result = validateBlocksToTemplate( resetBlocks( [ + createBlock( 'core/test-block' ), + ] ), store ); + + expect( result ).toBe( undefined ); + } ); + + it( 'should return undefined if invalid but unlocked', () => { + store.dispatch( updateEditorSettings( { + template: [ + [ 'core/foo', {} ], + ], + } ) ); + + const result = validateBlocksToTemplate( resetBlocks( [ + createBlock( 'core/test-block' ), + ] ), store ); + + expect( result ).toBe( undefined ); + } ); + + it( 'should return undefined if locked and valid', () => { + store.dispatch( updateEditorSettings( { + template: [ + [ 'core/test-block' ], + ], + templateLock: 'all', + } ) ); + + const result = validateBlocksToTemplate( resetBlocks( [ + createBlock( 'core/test-block' ), + ] ), store ); + + expect( result ).toBe( undefined ); + } ); + + it( 'should return validity set action if invalid on default state', () => { + store.dispatch( updateEditorSettings( { + template: [ + [ 'core/foo' ], + ], + templateLock: 'all', + } ) ); + + const result = validateBlocksToTemplate( resetBlocks( [ + createBlock( 'core/test-block' ), + ] ), store ); + + expect( result ).toEqual( setTemplateValidity( false ) ); + } ); + } ); } );