diff --git a/editor/state.js b/editor/state.js index ae8b73df4f12ad..1ea18cd909a4fe 100644 --- a/editor/state.js +++ b/editor/state.js @@ -3,7 +3,7 @@ */ import { combineReducers, applyMiddleware, createStore } from 'redux'; import refx from 'refx'; -import { keyBy, first, last, omit, without, flowRight } from 'lodash'; +import { reduce, keyBy, first, last, omit, without, flowRight } from 'lodash'; /** * Internal dependencies @@ -30,11 +30,27 @@ export const editor = combineUndoableReducers( { switch ( action.type ) { case 'EDIT_POST': case 'SETUP_NEW_POST': - return { - ...state, - ...action.edits, - }; + return reduce( action.edits, ( result, value, key ) => { + // Only assign into result if not already same value + if ( value !== state[ key ] ) { + // Avoid mutating original state by creating shallow + // clone. Should only occur once per reduce. + if ( result === state ) { + result = { ...state }; + } + + result[ key ] = value; + } + + return result; + }, state ); + case 'CLEAR_POST_EDITS': + // Don't return a new object if there's not any edits + if ( ! Object.keys( state ).length ) { + return state; + } + return {}; } @@ -66,6 +82,32 @@ export const editor = combineUndoableReducers( { return keyBy( action.blocks, 'uid' ); case 'UPDATE_BLOCK': + // Ignore updates if block isn't known + if ( ! state[ action.uid ] ) { + return state; + } + + // Consider as updates only changed values + const nextBlock = reduce( action.updates, ( result, value, key ) => { + if ( value !== result[ key ] ) { + // Avoid mutating original block by creating shallow clone + if ( result === state[ action.uid ] ) { + result = { ...state[ action.uid ] }; + } + + result[ key ] = value; + } + + return result; + }, state[ action.uid ] ); + + // Skip update if nothing has been changed. The reference will + // match the original block if `reduce` had no changed values. + if ( nextBlock === state[ action.uid ] ) { + return state; + } + + // Otherwise merge updates into state return { ...state, [ action.uid ]: { diff --git a/editor/test/state.js b/editor/test/state.js index 569192ac30fcef..ee694159d55ac5 100644 --- a/editor/test/state.js +++ b/editor/test/state.js @@ -51,27 +51,6 @@ describe( 'state', () => { expect( state.blockOrder ).to.eql( [ 'bananas' ] ); } ); - it( 'should return with block updates', () => { - const original = editor( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - uid: 'kumquat', - attributes: {}, - } ], - } ); - const state = editor( original, { - type: 'UPDATE_BLOCK', - uid: 'kumquat', - updates: { - attributes: { - updated: true, - }, - }, - } ); - - expect( state.blocksByUid.kumquat.attributes.updated ).to.be.true(); - } ); - it( 'should insert block', () => { const original = editor( undefined, { type: 'RESET_BLOCKS', @@ -363,6 +342,25 @@ describe( 'state', () => { } ); } ); + it( 'should return same reference if no changed properties', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: { + status: 'draft', + title: 'post title', + }, + } ); + + const state = editor( original, { + type: 'EDIT_POST', + edits: { + status: 'draft', + }, + } ); + + expect( state.edits ).to.equal( original.edits ); + } ); + it( 'should save modified properties', () => { const original = editor( undefined, { type: 'EDIT_POST', @@ -405,6 +403,19 @@ describe( 'state', () => { expect( state.edits ).to.eql( {} ); } ); + it( 'should return same reference if clearing non-edited', () => { + const original = editor( undefined, { + type: 'EDIT_POST', + edits: {}, + } ); + + const state = editor( original, { + type: 'CLEAR_POST_EDITS', + } ); + + expect( state.edits ).to.equal( original.edits ); + } ); + it( 'should save initial post state', () => { const state = editor( undefined, { type: 'SETUP_NEW_POST', @@ -484,6 +495,70 @@ describe( 'state', () => { expect( state.dirty ).to.be.false(); } ); } ); + + describe( 'blocksByUid', () => { + it( 'should return with block updates', () => { + const original = editor( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + uid: 'kumquat', + attributes: {}, + } ], + } ); + const state = editor( original, { + type: 'UPDATE_BLOCK', + uid: 'kumquat', + updates: { + attributes: { + updated: true, + }, + }, + } ); + + expect( state.blocksByUid.kumquat.attributes.updated ).to.be.true(); + } ); + + it( 'should ignore updates to non-existant block', () => { + const original = editor( undefined, { + type: 'RESET_BLOCKS', + blocks: [], + } ); + const state = editor( original, { + type: 'UPDATE_BLOCK', + uid: 'kumquat', + updates: { + attributes: { + updated: true, + }, + }, + } ); + + expect( state.blocksByUid ).to.equal( original.blocksByUid ); + } ); + + it( 'should return with same reference if no changes in updates', () => { + const original = editor( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + uid: 'kumquat', + attributes: { + updated: true, + }, + } ], + } ); + const state = editor( original, { + type: 'UPDATE_BLOCK', + uid: 'kumquat', + updates: { + attributes: { + updated: true, + }, + }, + } ); + + expect( state.blocksByUid ).to.equal( state.blocksByUid ); + } ); + } ); } ); describe( 'currentPost()', () => {