diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index f7c54a73da54b3..9a8eacad09aa5c 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -309,7 +309,8 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { switch ( action.type ) { case 'EDIT_ENTITY_RECORD': case 'CREATE_UNDO_LEVEL': - if ( action.type === 'CREATE_UNDO_LEVEL' ) { + const isCreateUndoLevel = action.type === 'CREATE_UNDO_LEVEL'; + if ( isCreateUndoLevel ) { action = lastEditAction; } else { lastEditAction = action; @@ -328,7 +329,7 @@ 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 ( ! Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] ) ) { + if ( ! isCreateUndoLevel && ! Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] ) ) { const nextState = [ ...state ]; nextState.flattenedUndo = { ...state.flattenedUndo, ...action.edits }; nextState.offset = state.offset; @@ -339,12 +340,14 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { const nextState = state.slice( 0, state.offset || undefined ); nextState.offset = 0; nextState.pop(); - nextState.push( { - kind: action.meta.undo.kind, - name: action.meta.undo.name, - recordId: action.meta.undo.recordId, - edits: { ...state.flattenedUndo, ...action.meta.undo.edits }, - } ); + if ( ! isCreateUndoLevel ) { + nextState.push( { + kind: action.meta.undo.kind, + name: action.meta.undo.name, + recordId: action.meta.undo.recordId, + edits: { ...state.flattenedUndo, ...action.meta.undo.edits }, + } ); + } // 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' ); diff --git a/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap b/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap index 6c0308f402a826..5d1601b9f0d9a0 100644 --- a/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap @@ -1,19 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`undo Should undo to expected level intervals 1`] = ` -" -
This
- - - -is
- - - -test
-" -`; - exports[`undo should immediately create an undo level on typing 1`] = ` "1
diff --git a/packages/e2e-tests/specs/undo.test.js b/packages/e2e-tests/specs/undo.test.js index 6d108c7cfdb466..b5da166dfaedda 100644 --- a/packages/e2e-tests/specs/undo.test.js +++ b/packages/e2e-tests/specs/undo.test.js @@ -54,27 +54,84 @@ describe( 'undo', () => { expect( await getEditedPostContent() ).toBe( '' ); } ); - it( 'Should undo to expected level intervals', async () => { + it( 'Should undo/redo to expected level intervals', async () => { await clickBlockAppender(); + const firstBlock = await getEditedPostContent(); + await page.keyboard.type( 'This' ); + + const firstText = await getEditedPostContent(); + await page.keyboard.press( 'Enter' ); + + const secondBlock = await getEditedPostContent(); + await page.keyboard.type( 'is' ); + + const secondText = await getEditedPostContent(); + await page.keyboard.press( 'Enter' ); + + const thirdBlock = await getEditedPostContent(); + await page.keyboard.type( 'test' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); + const thirdText = await getEditedPostContent(); + + await new Promise( ( resolve ) => setTimeout( resolve, 1000 ) ); await pressKeyWithModifier( 'primary', 'z' ); // Undo 3rd paragraph text. + + expect( await getEditedPostContent() ).toBe( thirdBlock ); + await pressKeyWithModifier( 'primary', 'z' ); // Undo 3rd block. + + expect( await getEditedPostContent() ).toBe( secondText ); + await pressKeyWithModifier( 'primary', 'z' ); // Undo 2nd paragraph text. + + expect( await getEditedPostContent() ).toBe( secondBlock ); + await pressKeyWithModifier( 'primary', 'z' ); // Undo 2nd block. + + expect( await getEditedPostContent() ).toBe( firstText ); + await pressKeyWithModifier( 'primary', 'z' ); // Undo 1st paragraph text. + + expect( await getEditedPostContent() ).toBe( firstBlock ); + await pressKeyWithModifier( 'primary', 'z' ); // Undo 1st block. expect( await getEditedPostContent() ).toBe( '' ); // After undoing every action, there should be no more undo history. expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).not.toBeNull(); + + await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 1st block. + + expect( await getEditedPostContent() ).toBe( firstBlock ); + // After redoing one change, the undo button should be enabled again. + expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).toBeNull(); + + await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 1st paragraph text. + + expect( await getEditedPostContent() ).toBe( firstText ); + + await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 2nd block. + + expect( await getEditedPostContent() ).toBe( secondBlock ); + + await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 2nd paragraph text. + + expect( await getEditedPostContent() ).toBe( secondText ); + + await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 3rd block. + + expect( await getEditedPostContent() ).toBe( thirdBlock ); + + await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 3rd paragraph text. + + expect( await getEditedPostContent() ).toBe( thirdText ); } ); it( 'should undo for explicit persistence editing post', async () => {