Skip to content

Commit

Permalink
Writing Flow: Ensure default block on removal / replacement (#9977)
Browse files Browse the repository at this point in the history
* Writing Flow: Ensure default block on removal / replacement

* Testing: Ensure focus preservation on ensure default block
  • Loading branch information
aduth committed Oct 5, 2018
1 parent 3770364 commit 6990448
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 28 deletions.
89 changes: 63 additions & 26 deletions packages/editor/src/store/effects.js
Expand Up @@ -26,11 +26,13 @@ import {
selectBlock,
resetBlocks,
setTemplateValidity,
insertDefaultBlock,
} from './actions';
import {
getBlock,
getBlockRootClientId,
getBlocks,
getBlockCount,
getPreviousBlockClientId,
getSelectedBlock,
getTemplate,
Expand Down Expand Up @@ -85,6 +87,60 @@ export function validateBlocksToTemplate( action, store ) {
}
}

/**
* Effect handler which will return a block select action to select the block
* occurring before the selected block in the previous state, unless it is the
* same block or the action includes a falsey `selectPrevious` option flag.
*
* @param {Object} action Action which had initiated the effect handler.
* @param {Object} store Store instance.
*
* @return {?Object} Block select action to select previous, if applicable.
*/
export function selectPreviousBlock( action, store ) {
// if the action says previous block should not be selected don't do anything.
if ( ! action.selectPrevious ) {
return;
}

const firstRemovedBlockClientId = action.clientIds[ 0 ];
const state = store.getState();
const currentSelectedBlock = getSelectedBlock( state );

// recreate the state before the block was removed.
const previousState = { ...state, editor: { present: last( state.editor.past ) } };

// rootClientId of the removed block.
const rootClientId = getBlockRootClientId( previousState, firstRemovedBlockClientId );

// Client ID of the block that was before the removed block or the
// rootClientId if the removed block was first amongst its siblings.
const blockClientIdToSelect = getPreviousBlockClientId( previousState, firstRemovedBlockClientId ) || rootClientId;

// Dispatch select block action if the currently selected block
// is not already the block we want to be selected.
if ( blockClientIdToSelect !== currentSelectedBlock ) {
return selectBlock( blockClientIdToSelect, -1 );
}
}

/**
* Effect handler which will return a default block insertion action if there
* are no other blocks at the root of the editor. This is expected to be used
* in actions which may result in no blocks remaining in the editor (removal,
* replacement, etc).
*
* @param {Object} action Action which had initiated the effect handler.
* @param {Object} store Store instance.
*
* @return {?Object} Default block insert action, if no other blocks exist.
*/
export function ensureDefaultBlock( action, store ) {
if ( ! getBlockCount( store.getState() ) ) {
return insertDefaultBlock();
}
}

export default {
REQUEST_POST_UPDATE: ( action, store ) => {
requestPostUpdate( action, store );
Expand Down Expand Up @@ -234,30 +290,11 @@ export default {
const message = spokenMessage || content;
speak( message, 'assertive' );
},
REMOVE_BLOCKS( action, { getState, dispatch } ) {
// if the action says previous block should not be selected don't do anything.
if ( ! action.selectPrevious ) {
return;
}

const firstRemovedBlockClientId = action.clientIds[ 0 ];
const state = getState();
const currentSelectedBlock = getSelectedBlock( state );

// recreate the state before the block was removed.
const previousState = { ...state, editor: { present: last( state.editor.past ) } };

// rootClientId of the removed block.
const rootClientId = getBlockRootClientId( previousState, firstRemovedBlockClientId );

// Client ID of the block that was before the removed block or the
// rootClientId if the removed block was first amongst its siblings.
const blockClientIdToSelect = getPreviousBlockClientId( previousState, firstRemovedBlockClientId ) || rootClientId;

// Dispatch select block action if the currently selected block
// is not already the block we want to be selected.
if ( blockClientIdToSelect !== currentSelectedBlock ) {
dispatch( selectBlock( blockClientIdToSelect, -1 ) );
}
},
REMOVE_BLOCKS: [
selectPreviousBlock,
ensureDefaultBlock,
],
REPLACE_BLOCKS: [
ensureDefaultBlock,
],
};
3 changes: 1 addition & 2 deletions test/e2e/specs/reusable-blocks.test.js
Expand Up @@ -194,8 +194,7 @@ describe( 'Reusable Blocks', () => {
await Promise.all( [ waitForAndAcceptDialog(), convertButton.click() ] );

// Check that we have an empty post again
const block = await page.$$( '.editor-block-list__block' );
expect( block ).toHaveLength( 0 );
expect( await getEditedPostContent() ).toBe( '' );

// Search for the block in the inserter
await searchForBlock( 'Surprised greeting block' );
Expand Down
30 changes: 30 additions & 0 deletions test/e2e/specs/splitting-merging.test.js
Expand Up @@ -181,4 +181,34 @@ describe( 'splitting and merging blocks', () => {

expect( await getEditedPostContent() ).toMatchSnapshot();
} );

it( 'should ensure always a default block', async () => {
// Feature: To avoid focus loss, removal of all blocks will result in a
// default block insertion at the root. Pressing backspace in a new
// paragraph should not effectively allow removal. This is counteracted
// with pre-save content processing to save post consisting of only the
// unmodified default block as an empty string.
//
// See: https://github.com/WordPress/gutenberg/issues/9626
await insertBlock( 'Paragraph' );
await page.keyboard.press( 'Backspace' );

// There is a default block:
expect( await page.$$( '.editor-block-list__block' ) ).toHaveLength( 1 );

// But the effective saved content is still empty:
expect( await getEditedPostContent() ).toBe( '' );

// And focus is retained:
const isInDefaultBlock = await page.evaluate( () => {
const activeBlockName = document.activeElement
.closest( '[data-type]' )
.getAttribute( 'data-type' );
const defaultBlockName = window.wp.blocks.getDefaultBlockName();

return activeBlockName === defaultBlockName;
} );

expect( isInDefaultBlock ).toBe( true );
} );
} );

0 comments on commit 6990448

Please sign in to comment.