From 823ba362fa3ca0016ef5f690e8f267420b994697 Mon Sep 17 00:00:00 2001 From: Enrique Piqueras Date: Tue, 30 Jul 2019 18:10:03 -0400 Subject: [PATCH] Core Data: Add util for minimally modifying items. (#16823) --- .../src/utils/conservative-map-item.js | 37 +++++++++++++++++++ packages/core-data/src/utils/index.js | 1 + .../src/utils/test/conservative-map-item.js | 33 +++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 packages/core-data/src/utils/conservative-map-item.js create mode 100644 packages/core-data/src/utils/test/conservative-map-item.js diff --git a/packages/core-data/src/utils/conservative-map-item.js b/packages/core-data/src/utils/conservative-map-item.js new file mode 100644 index 0000000000000..370c3cf0861fd --- /dev/null +++ b/packages/core-data/src/utils/conservative-map-item.js @@ -0,0 +1,37 @@ +/** + * External dependencies + */ +import { isEqual } from 'lodash'; + +/** + * Given the current and next item entity, returns the minimally "modified" + * result of the next item, preferring value references from the original item + * if equal. If all values match, the original item is returned. + * + * @param {Object} item Original item. + * @param {Object} nextItem Next item. + * + * @return {Object} Minimally modified merged item. + */ +export default function conservativeMapItem( item, nextItem ) { + // Return next item in its entirety if there is no original item. + if ( ! item ) { + return nextItem; + } + + let hasChanges = false; + const result = {}; + for ( const key in nextItem ) { + if ( isEqual( item[ key ], nextItem[ key ] ) ) { + result[ key ] = item[ key ]; + } else { + hasChanges = true; + result[ key ] = nextItem[ key ]; + } + } + + if ( ! hasChanges ) { + return item; + } + return result; +} diff --git a/packages/core-data/src/utils/index.js b/packages/core-data/src/utils/index.js index a52cb83430dcb..7adb57e48d5d7 100644 --- a/packages/core-data/src/utils/index.js +++ b/packages/core-data/src/utils/index.js @@ -1,3 +1,4 @@ +export { default as conservativeMapItem } from './conservative-map-item'; export { default as ifMatchingAction } from './if-matching-action'; export { default as onSubKey } from './on-sub-key'; export { default as replaceAction } from './replace-action'; diff --git a/packages/core-data/src/utils/test/conservative-map-item.js b/packages/core-data/src/utils/test/conservative-map-item.js new file mode 100644 index 0000000000000..5f42cc73fdde7 --- /dev/null +++ b/packages/core-data/src/utils/test/conservative-map-item.js @@ -0,0 +1,33 @@ +/** + * Internal dependencies + */ +import conservativeMapItem from '../conservative-map-item'; + +describe( 'conservativeMapItem', () => { + it( 'Returns the next item if there is no current item to compare with.', () => { + const item = undefined; + const nextItem = {}; + const result = conservativeMapItem( item, nextItem ); + + expect( result ).toBe( nextItem ); + } ); + + it( 'Returns the original item if all property values are the same, deeply.', () => { + const item = { a: [ {} ] }; + const nextItem = { a: [ {} ] }; + const result = conservativeMapItem( item, nextItem ); + + expect( result ).toBe( item ); + } ); + + it( 'Preserves original references of property values when unchanged, deeply.', () => { + const item = { a: [ {} ], b: [ 1 ] }; + const nextItem = { a: [ {} ], b: [ 2 ] }; + const result = conservativeMapItem( item, nextItem ); + + expect( result ).not.toBe( item ); + expect( result.a ).toBe( item.a ); + expect( result.b ).toBe( nextItem.b ); + expect( result ).toEqual( { a: [ {} ], b: [ 2 ] } ); + } ); +} );