Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Writing Flow: Insert default block as provisional #5417

Merged
merged 2 commits into from
Mar 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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