Skip to content

Commit

Permalink
Writing Flow: Insert default block as provisional (#5417)
Browse files Browse the repository at this point in the history
* State: Refactor default block insertion into action creator

* Writing Flow: Insert default block as provisional
  • Loading branch information
aduth authored Mar 7, 2018
1 parent a468cf5 commit 3cb3d2e
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 29 deletions.
16 changes: 12 additions & 4 deletions editor/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
import uuid from 'uuid/v4';
import { partial, castArray } from 'lodash';

/**
* WordPress dependencies
*/
import {
getDefaultBlockName,
createBlock,
} from '@wordpress/blocks';

/**
* Returns an action object used in signalling that editor has initialized with
* the specified post object and editor settings.
Expand Down Expand Up @@ -529,10 +537,10 @@ export function convertBlockToReusable( uid ) {
* @return {Object} Action object
*/
export function insertDefaultBlock( attributes, rootUID, index ) {
const block = createBlock( getDefaultBlockName(), attributes );

return {
type: 'INSERT_DEFAULT_BLOCK',
attributes,
rootUID,
index,
...insertBlock( block, index, rootUID ),
isProvisional: true,
};
}
33 changes: 26 additions & 7 deletions editor/store/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
serialize,
createReusableBlock,
isReusableBlock,
getDefaultBlockName,
getDefaultBlockForPostFormat,
} from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
Expand All @@ -37,6 +36,7 @@ import {
saveReusableBlock,
insertBlock,
selectBlock,
removeBlock,
} from './actions';
import {
getCurrentPost,
Expand All @@ -51,6 +51,8 @@ import {
getBlockCount,
getBlocks,
getReusableBlock,
getProvisionalBlockUID,
isBlockSelected,
POST_UPDATE_TRANSACTION_ID,
} from './selectors';

Expand All @@ -61,6 +63,23 @@ const SAVE_POST_NOTICE_ID = 'SAVE_POST_NOTICE_ID';
const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID';
const REUSABLE_BLOCK_NOTICE_ID = 'REUSABLE_BLOCK_NOTICE_ID';

/**
* Effect handler returning an action to remove the provisional block, if one
* is set.
*
* @param {Object} action Action object.
* @param {Object} store Data store instance.
*
* @return {?Object} Remove action, if provisional block is set.
*/
export function removeProvisionalBlock( action, store ) {
const state = store.getState();
const provisionalBlockUID = getProvisionalBlockUID( state );
if ( provisionalBlockUID && ! isBlockSelected( state, provisionalBlockUID ) ) {
return removeBlock( provisionalBlockUID );
}
}

export default {
REQUEST_POST_UPDATE( action, store ) {
const { dispatch, getState } = store;
Expand Down Expand Up @@ -459,12 +478,6 @@ export default {
dispatch( saveReusableBlock( reusableBlock.id ) );
dispatch( replaceBlocks( [ oldBlock.uid ], [ newBlock ] ) );
},
INSERT_DEFAULT_BLOCK( action ) {
const { attributes, rootUID, index } = action;
const block = createBlock( getDefaultBlockName(), attributes );

return insertBlock( block, index, rootUID );
},
CREATE_NOTICE( { notice: { content, spokenMessage } } ) {
const message = spokenMessage || content;
speak( message, 'assertive' );
Expand All @@ -480,4 +493,10 @@ export default {
return insertBlock( createBlock( blockName ) );
}
},

CLEAR_SELECTED_BLOCK: removeProvisionalBlock,

SELECT_BLOCK: removeProvisionalBlock,

MULTI_SELECT: removeProvisionalBlock,
};
44 changes: 44 additions & 0 deletions editor/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
omitBy,
keys,
isEqual,
includes,
} from 'lodash';

/**
Expand Down Expand Up @@ -600,6 +601,48 @@ export function blockSelection( state = {
return state;
}

/**
* Reducer returning the UID of the provisional block. A provisional block is
* one which is to be removed if it does not receive updates in the time until
* the next selection or block reset.
*
* @param {string} state Current state.
* @param {Object} action Dispatched action.
*
* @return {string} Updated state.
*/
export function provisionalBlockUID( state = null, action ) {
switch ( action.type ) {
case 'INSERT_BLOCKS':
if ( action.isProvisional ) {
return first( action.blocks ).uid;
}
break;

case 'RESET_BLOCKS':
return null;

case 'UPDATE_BLOCK_ATTRIBUTES':
case 'UPDATE_BLOCK':
case 'CONVERT_BLOCK_TO_REUSABLE':
const { uid } = action;
if ( uid === state ) {
return null;
}
break;

case 'REPLACE_BLOCKS':
case 'REMOVE_BLOCKS':
const { uids } = action;
if ( includes( uids, state ) ) {
return null;
}
break;
}

return state;
}

export function blocksMode( state = {}, action ) {
if ( action.type === 'TOGGLE_BLOCK_MODE' ) {
const { uid } = action;
Expand Down Expand Up @@ -839,6 +882,7 @@ export default optimist( combineReducers( {
currentPost,
isTyping,
blockSelection,
provisionalBlockUID,
blocksMode,
isInsertionPointVisible,
preferences,
Expand Down
11 changes: 11 additions & 0 deletions editor/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1327,3 +1327,14 @@ export function isPublishingPost( state ) {
// considered published
return !! stateBeforeRequest && ! isCurrentPostPublished( stateBeforeRequest );
}

/**
* Returns the provisional block UID, or null if there is no provisional block.
*
* @param {Object} state Editor state.
*
* @return {?string} Provisional block UID, if set.
*/
export function getProvisionalBlockUID( state ) {
return state.provisionalBlockUID;
}
46 changes: 45 additions & 1 deletion editor/store/test/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ import {
convertBlockToStatic,
convertBlockToReusable,
selectBlock,
removeBlock,
} from '../../store/actions';
import reducer from '../reducer';
import effects from '../effects';
import effects, {
removeProvisionalBlock,
} from '../effects';
import * as selectors from '../../store/selectors';

// Make all generated UUIDs the same for testing
Expand All @@ -43,6 +46,47 @@ jest.mock( 'uuid/v4', () => {
describe( 'effects', () => {
const defaultBlockSettings = { save: () => 'Saved', category: 'common', title: 'block title' };

describe( 'removeProvisionalBlock()', () => {
const store = { getState: () => {} };

beforeAll( () => {
selectors.getProvisionalBlockUID = jest.spyOn( selectors, 'getProvisionalBlockUID' );
selectors.isBlockSelected = jest.spyOn( selectors, 'isBlockSelected' );
} );

beforeEach( () => {
selectors.getProvisionalBlockUID.mockReset();
selectors.isBlockSelected.mockReset();
} );

afterAll( () => {
selectors.getProvisionalBlockUID.mockRestore();
selectors.isBlockSelected.mockRestore();
} );

it( 'should return nothing if there is no provisional block', () => {
const action = removeProvisionalBlock( {}, store );

expect( action ).toBeUndefined();
} );

it( 'should return nothing if there is a provisional block and it is selected', () => {
selectors.getProvisionalBlockUID.mockReturnValue( 'chicken' );
selectors.isBlockSelected.mockImplementation( ( state, uid ) => uid === 'chicken' );
const action = removeProvisionalBlock( {}, store );

expect( action ).toBeUndefined();
} );

it( 'should return remove action for provisional block', () => {
selectors.getProvisionalBlockUID.mockReturnValue( 'chicken' );
selectors.isBlockSelected.mockImplementation( ( state, uid ) => uid === 'ribs' );
const action = removeProvisionalBlock( {}, store );

expect( action ).toEqual( removeBlock( 'chicken' ) );
} );
} );

describe( '.MERGE_BLOCKS', () => {
const handler = effects.MERGE_BLOCKS;
const defaultGetBlock = selectors.getBlock;
Expand Down
95 changes: 95 additions & 0 deletions editor/store/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
preferences,
saving,
notices,
provisionalBlockUID,
blocksMode,
isInsertionPointVisible,
reusableBlocks,
Expand Down Expand Up @@ -1404,6 +1405,100 @@ describe( 'state', () => {
} );
} );

describe( 'provisionalBlockUID()', () => {
const PROVISIONAL_UPDATE_ACTION_TYPES = [
'UPDATE_BLOCK_ATTRIBUTES',
'UPDATE_BLOCK',
'CONVERT_BLOCK_TO_REUSABLE',
];

const PROVISIONAL_REPLACE_ACTION_TYPES = [
'REPLACE_BLOCKS',
'REMOVE_BLOCKS',
];

it( 'returns null by default', () => {
const state = provisionalBlockUID( undefined, {} );

expect( state ).toBe( null );
} );

it( 'returns the uid of the first inserted provisional block', () => {
const state = provisionalBlockUID( null, {
type: 'INSERT_BLOCKS',
isProvisional: true,
blocks: [
{ uid: 'chicken' },
],
} );

expect( state ).toBe( 'chicken' );
} );

it( 'does not return uid of block if not provisional', () => {
const state = provisionalBlockUID( null, {
type: 'INSERT_BLOCKS',
blocks: [
{ uid: 'chicken' },
],
} );

expect( state ).toBe( null );
} );

it( 'returns null on block reset', () => {
const state = provisionalBlockUID( 'chicken', {
type: 'RESET_BLOCKS',
} );

expect( state ).toBe( null );
} );

it( 'returns null on block update', () => {
PROVISIONAL_UPDATE_ACTION_TYPES.forEach( ( type ) => {
const state = provisionalBlockUID( 'chicken', {
type,
uid: 'chicken',
} );

expect( state ).toBe( null );
} );
} );

it( 'does not return null if update occurs to non-provisional block', () => {
PROVISIONAL_UPDATE_ACTION_TYPES.forEach( ( type ) => {
const state = provisionalBlockUID( 'chicken', {
type,
uid: 'ribs',
} );

expect( state ).toBe( 'chicken' );
} );
} );

it( 'returns null if replacement of provisional block', () => {
PROVISIONAL_REPLACE_ACTION_TYPES.forEach( ( type ) => {
const state = provisionalBlockUID( 'chicken', {
type,
uids: [ 'chicken' ],
} );

expect( state ).toBe( null );
} );
} );

it( 'does not return null if replacement of non-provisional block', () => {
PROVISIONAL_REPLACE_ACTION_TYPES.forEach( ( type ) => {
const state = provisionalBlockUID( 'chicken', {
type,
uids: [ 'ribs' ],
} );

expect( state ).toBe( 'chicken' );
} );
} );
} );

describe( 'blocksMode', () => {
it( 'should set mode to html if not set', () => {
const action = {
Expand Down
19 changes: 19 additions & 0 deletions editor/store/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const {
isPublishingPost,
getInserterItems,
getFrecentInserterItems,
getProvisionalBlockUID,
POST_UPDATE_TRANSACTION_ID,
} = selectors;

Expand Down Expand Up @@ -2709,4 +2710,22 @@ describe( 'selectors', () => {
expect( isPublishing ).toBe( true );
} );
} );

describe( 'getProvisionalBlockUID()', () => {
it( 'should return null if not set', () => {
const provisionalBlockUID = getProvisionalBlockUID( {
provisionalBlockUID: null,
} );

expect( provisionalBlockUID ).toBe( null );
} );

it( 'should return UID of provisional block', () => {
const provisionalBlockUID = getProvisionalBlockUID( {
provisionalBlockUID: 'chicken',
} );

expect( provisionalBlockUID ).toBe( 'chicken' );
} );
} );
} );
Loading

0 comments on commit 3cb3d2e

Please sign in to comment.