Skip to content

Commit

Permalink
Writing Flow: Insert default block as provisional
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Mar 5, 2018
1 parent 79ae0f7 commit c4dc94d
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 21 deletions.
5 changes: 4 additions & 1 deletion editor/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,5 +539,8 @@ export function convertBlockToReusable( uid ) {
export function insertDefaultBlock( attributes, rootUID, index ) {
const block = createBlock( getDefaultBlockName(), attributes );

return insertBlock( block, index, rootUID );
return {
...insertBlock( block, index, rootUID ),
isProvisional: true,
};
}
6 changes: 4 additions & 2 deletions editor/store/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
getBlocks,
getReusableBlock,
getProvisionalBlockUID,
isBlockSelected,
POST_UPDATE_TRANSACTION_ID,
} from './selectors';

Expand All @@ -72,8 +73,9 @@ const REUSABLE_BLOCK_NOTICE_ID = 'REUSABLE_BLOCK_NOTICE_ID';
* @return {?Object} Remove action, if provisional block is set.
*/
export function removeProvisionalBlock( action, store ) {
const provisionalBlockUID = getProvisionalBlockUID( store.getState() );
if ( provisionalBlockUID ) {
const state = store.getState();
const provisionalBlockUID = getProvisionalBlockUID( state );
if ( provisionalBlockUID && ! isBlockSelected( state, provisionalBlockUID ) ) {
return removeBlock( provisionalBlockUID );
}
}
Expand Down
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 @@ -845,6 +888,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 @@ -1296,3 +1296,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 @@ -1417,6 +1418,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 @@ -73,6 +73,7 @@ const {
getInserterItems,
getRecentInserterItems,
getFrequentInserterItems,
getProvisionalBlockUID,
POST_UPDATE_TRANSACTION_ID,
} = selectors;

Expand Down Expand Up @@ -2779,4 +2780,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' );
} );
} );
} );
43 changes: 30 additions & 13 deletions test/e2e/integration/002-adding-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,52 @@ describe( 'Adding blocks', () => {
} );

it( 'Should insert content using the placeholder and the regular inserter', () => {
const lastBlockSelector = '.editor-block-list__block-edit:last [contenteditable="true"]:first';
const lastBlockSelector = '.editor-block-list__block-edit:last';

// Using the placeholder
cy.get( '.editor-default-block-appender' ).click();
cy.get( lastBlockSelector ).type( 'Paragraph block' );
cy.focused().type( 'Paragraph block' );

// Default block appender is provisional
cy.get( lastBlockSelector ).then( ( firstBlock ) => {
cy.get( '.editor-default-block-appender' ).click();
cy.get( '.edit-post-visual-editor' ).click();
cy.get( lastBlockSelector ).should( 'have.text', firstBlock.text() );
} );

// Using the slash command
// Test commented because Cypress is not update the selection object properly,
// so the slash inserter is not showing up.
/* cy.get( '.edit-post-header [aria-label="Add block"]' ).click();
cy.get( '[placeholder="Search for a block"]' ).type( 'Paragraph' );
cy.get( '.editor-inserter__block' ).contains( 'Paragraph' ).click();
cy.get( lastBlockSelector ).type( '/quote{enter}' );
cy.get( lastBlockSelector ).type( 'Quote block' ); */
// TODO: Test omitted because Cypress doesn't update the selection
// object properly, so the slash inserter is not showing up.

// Using the regular inserter
cy.get( '.edit-post-header [aria-label="Add block"]' ).click();
cy.get( '[placeholder="Search for a block"]' ).type( 'code' );
cy.get( '.editor-inserter__block' ).contains( 'Code' ).click();
cy.get( '[placeholder="Write code…"]' ).type( 'Code block' );

// Using the between inserter
cy.document().trigger( 'mousemove', { clientX: 200, clientY: 300 } );
cy.document().trigger( 'mousemove', { clientX: 250, clientY: 350 } );
cy.get( '[data-type="core/paragraph"] .editor-block-list__insertion-point-inserter' ).click();
cy.focused().type( 'Second paragraph' );

// Switch to Text Mode to check HTML Output
cy.get( '.edit-post-more-menu [aria-label="More"]' ).click();
cy.get( 'button' ).contains( 'Code Editor' ).click();

// Assertions
cy.get( '.edit-post-text-editor' )
.should( 'contain', 'Paragraph block' )
// .should( 'contain', 'Quote block' )
.should( 'contain', 'Code block' );
cy.get( '.editor-post-text-editor' ).should( 'have.value', [
'<!-- wp:paragraph -->',
'<p>Paragraph block</p>',
'<!-- /wp:paragraph -->',
'',
'<!-- wp:paragraph -->',
'<p>Second paragraph</p>',
'<!-- /wp:paragraph -->',
'',
'<!-- wp:code -->',
'<pre class="wp-block-code"><code>Code block</code></pre>',
'<!-- /wp:code -->',
].join( '\n' ) );
} );
} );
Loading

0 comments on commit c4dc94d

Please sign in to comment.