Skip to content

Commit

Permalink
Redo: add failing e2e tests (#17861)
Browse files Browse the repository at this point in the history
* Redo: add failing e2e tests

* Core Data: Don't lose flattened undos when undoing before timeout.
  • Loading branch information
ellatrix authored and gziolo committed Oct 15, 2019
1 parent 9b9bfc4 commit 4ada9fd
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 18 deletions.
47 changes: 35 additions & 12 deletions packages/core-data/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,17 +309,31 @@ export function undo( state = UNDO_INITIAL_STATE, action ) {
switch ( action.type ) {
case 'EDIT_ENTITY_RECORD':
case 'CREATE_UNDO_LEVEL':
const isCreateUndoLevel = action.type === 'CREATE_UNDO_LEVEL';
let isCreateUndoLevel = action.type === 'CREATE_UNDO_LEVEL';
const isUndoOrRedo =
! isCreateUndoLevel && ( action.meta.isUndo || action.meta.isRedo );
if ( isCreateUndoLevel ) {
action = lastEditAction;
} else {
} else if ( ! isUndoOrRedo ) {
lastEditAction = action;
}

if ( action.meta.isUndo || action.meta.isRedo ) {
const nextState = [ ...state ];
let nextState;
if ( isUndoOrRedo ) {
nextState = [ ...state ];
nextState.offset = state.offset + ( action.meta.isUndo ? -1 : 1 );
return nextState;

if ( state.flattenedUndo ) {
// The first undo in a sequence of undos might happen while we have
// flattened undos in state. If this is the case, we want execution
// to continue as if we were creating an explicit undo level. This
// will result in an extra undo level being appended with the flattened
// undo values.
isCreateUndoLevel = true;
action = lastEditAction;
} else {
return nextState;
}
}

if ( ! action.meta.undo ) {
Expand All @@ -329,16 +343,19 @@ export function undo( state = UNDO_INITIAL_STATE, action ) {
// Transient edits don't create an undo level, but are
// reachable in the next meaningful edit to which they
// are merged. They are defined in the entity's config.
if ( ! isCreateUndoLevel && ! Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] ) ) {
const nextState = [ ...state ];
if (
! isCreateUndoLevel &&
! Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] )
) {
nextState = [ ...state ];
nextState.flattenedUndo = { ...state.flattenedUndo, ...action.edits };
nextState.offset = state.offset;
return nextState;
}

// Clear potential redos, because this only supports linear history.
const nextState = state.slice( 0, state.offset || undefined );
nextState.offset = 0;
nextState = nextState || state.slice( 0, state.offset || undefined );
nextState.offset = nextState.offset || 0;
nextState.pop();
if ( ! isCreateUndoLevel ) {
nextState.push( {
Expand All @@ -350,14 +367,20 @@ export function undo( state = UNDO_INITIAL_STATE, action ) {
}
// When an edit is a function it's an optimization to avoid running some expensive operation.
// We can't rely on the function references being the same so we opt out of comparing them here.
const comparisonUndoEdits = Object.values( action.meta.undo.edits ).filter( ( edit ) => typeof edit !== 'function' );
const comparisonEdits = Object.values( action.edits ).filter( ( edit ) => typeof edit !== 'function' );
const comparisonUndoEdits = Object.values( action.meta.undo.edits ).filter(
( edit ) => typeof edit !== 'function'
);
const comparisonEdits = Object.values( action.edits ).filter(
( edit ) => typeof edit !== 'function'
);
if ( ! isShallowEqual( comparisonUndoEdits, comparisonEdits ) ) {
nextState.push( {
kind: action.kind,
name: action.name,
recordId: action.recordId,
edits: action.edits,
edits: isCreateUndoLevel ?
{ ...state.flattenedUndo, ...action.edits } :
action.edits,
} );
}
return nextState;
Expand Down
34 changes: 28 additions & 6 deletions packages/e2e-tests/specs/undo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,27 @@ describe( 'undo', () => {
await new Promise( ( resolve ) => setTimeout( resolve, 1000 ) );
await page.keyboard.type( ' after pause' );

expect( await getEditedPostContent() ).toMatchSnapshot();
const after = await getEditedPostContent();

expect( after ).toMatchSnapshot();

await pressKeyWithModifier( 'primary', 'z' );

expect( await getEditedPostContent() ).toMatchSnapshot();
const before = await getEditedPostContent();

expect( before ).toMatchSnapshot();

await pressKeyWithModifier( 'primary', 'z' );

expect( await getEditedPostContent() ).toBe( '' );

await pressKeyWithModifier( 'primaryShift', 'z' );

expect( await getEditedPostContent() ).toBe( before );

await pressKeyWithModifier( 'primaryShift', 'z' );

expect( await getEditedPostContent() ).toBe( after );
} );

it( 'should undo typing after non input change', async () => {
Expand All @@ -43,15 +55,27 @@ describe( 'undo', () => {
await pressKeyWithModifier( 'primary', 'b' );
await page.keyboard.type( 'after keyboard' );

expect( await getEditedPostContent() ).toMatchSnapshot();
const after = await getEditedPostContent();

expect( after ).toMatchSnapshot();

await pressKeyWithModifier( 'primary', 'z' );

expect( await getEditedPostContent() ).toMatchSnapshot();
const before = await getEditedPostContent();

expect( before ).toMatchSnapshot();

await pressKeyWithModifier( 'primary', 'z' );

expect( await getEditedPostContent() ).toBe( '' );

await pressKeyWithModifier( 'primaryShift', 'z' );

expect( await getEditedPostContent() ).toBe( before );

await pressKeyWithModifier( 'primaryShift', 'z' );

expect( await getEditedPostContent() ).toBe( after );
} );

it( 'Should undo/redo to expected level intervals', async () => {
Expand Down Expand Up @@ -79,8 +103,6 @@ describe( 'undo', () => {

const thirdText = await getEditedPostContent();

await new Promise( ( resolve ) => setTimeout( resolve, 1000 ) );

await pressKeyWithModifier( 'primary', 'z' ); // Undo 3rd paragraph text.

expect( await getEditedPostContent() ).toBe( thirdBlock );
Expand Down

0 comments on commit 4ada9fd

Please sign in to comment.