-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Editor: Deeply diff/merge edited property edits/updates
- Loading branch information
Showing
4 changed files
with
291 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { isPlainObject } from 'lodash'; | ||
|
||
/** | ||
* Returns an object against which it is safe to perform mutating operations, | ||
* given the original object and its current working copy. | ||
* | ||
* @param {Object} original Original object. | ||
* @param {Object} working Working object. | ||
* | ||
* @return {Object} Mutation-safe object. | ||
*/ | ||
export function getMutateSafeObject( original, working ) { | ||
if ( original === working ) { | ||
return { ...original }; | ||
} | ||
|
||
return working; | ||
} | ||
|
||
/** | ||
* Given two objects, returns the minimal object shape of the difference of | ||
* values contained in RHS and not LHS, recursively. Returns RHS reference if | ||
* result of difference would be the same as the RHS object. | ||
* | ||
* @param {Object} lhs Original object to compare against. | ||
* @param {Object} rhs New object from which to generate difference. | ||
* | ||
* @return {Object} Minimal difference. | ||
*/ | ||
export function diff( lhs, rhs ) { | ||
let diffed = rhs; | ||
for ( const key in rhs ) { | ||
let value = rhs[ key ]; | ||
if ( isPlainObject( value ) && isPlainObject( lhs[ key ] ) ) { | ||
// Recurse to generate diff of child values. | ||
value = diff( lhs[ key ], value ); | ||
|
||
// If a diff of the child value is non-empty, it's inferred to be | ||
// non-equal and should replace the value in the returned diff. | ||
// Otherwise, if equal, fall through to delete from diff. | ||
if ( Object.keys( value ).length ) { | ||
diffed = getMutateSafeObject( rhs, diffed ); | ||
diffed[ key ] = value; | ||
continue; | ||
} | ||
} else if ( value !== lhs[ key ] ) { | ||
// To preserve reference, invert the iteration to _keep_ values | ||
// which are different, and mutate only to delete equal values. | ||
continue; | ||
} | ||
|
||
diffed = getMutateSafeObject( rhs, diffed ); | ||
delete diffed[ key ]; | ||
} | ||
|
||
return diffed; | ||
} | ||
|
||
/** | ||
* Given objects, returns the combined object shape of the merging of their | ||
* values. Returns the reference of the first object if all objects are the | ||
* same. | ||
* | ||
* @param {...Object} objects Objects to merge. | ||
* | ||
* @return {Object} Merged object. | ||
*/ | ||
export function merge( ...objects ) { | ||
const firstObject = objects[ 0 ]; | ||
return objects.reduce( ( merged, object ) => { | ||
for ( const key in object ) { | ||
let value = object[ key ]; | ||
if ( isPlainObject( merged[ key ] ) && isPlainObject( value ) ) { | ||
value = merge( merged[ key ], value ); | ||
} | ||
|
||
if ( merged[ key ] !== value ) { | ||
merged = getMutateSafeObject( firstObject, merged ); | ||
merged[ key ] = value; | ||
} | ||
} | ||
|
||
return merged; | ||
}, firstObject ); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { getMutateSafeObject, diff, merge } from '../object'; | ||
|
||
describe( 'object', () => { | ||
describe( 'getMutateSafeObject', () => { | ||
let original; | ||
beforeEach( () => { | ||
// Verify cloned by mutating a frozen object. | ||
original = Object.freeze( { a: 1 } ); | ||
} ); | ||
|
||
it( 'creates a mutable clone if working with original', () => { | ||
let working = original; | ||
|
||
working = getMutateSafeObject( original, working ); | ||
working.b = 2; | ||
|
||
expect( working ).not.toBe( original ); | ||
expect( working ).toEqual( { a: 1, b: 2 } ); | ||
} ); | ||
|
||
it( 'creates returns identity if working with cloned', () => { | ||
let working = getMutateSafeObject( original, original ); | ||
|
||
for ( let i = 0; i < 2; i++ ) { | ||
const pendingWorking = getMutateSafeObject( original, working ); | ||
expect( pendingWorking ).toBe( working ); | ||
working = pendingWorking; | ||
} | ||
} ); | ||
} ); | ||
|
||
describe( 'diff', () => { | ||
it( 'should return RHS reference if result would be same as RHS', () => { | ||
const lhs = {}; | ||
const rhs = { a: { b: { c: 1 } } }; | ||
const result = diff( lhs, rhs ); | ||
|
||
expect( result ).toBe( rhs ); | ||
} ); | ||
|
||
it( 'should omit deeply equal property values', () => { | ||
const lhs = { a: { b: { c: 1 } } }; | ||
const rhs = { a: { b: { c: 1 } } }; | ||
const result = diff( lhs, rhs ); | ||
|
||
expect( result ).not.toBe( rhs ); | ||
expect( result ).toEqual( {} ); | ||
} ); | ||
|
||
it( 'should return minimal object difference', () => { | ||
const lhs = { a: { b: { c: 1 } } }; | ||
const rhs = { a: { b: { c: 1, d: 2 } } }; | ||
const result = diff( lhs, rhs ); | ||
|
||
expect( result ).not.toBe( rhs ); | ||
expect( result ).toEqual( { a: { b: { d: 2 } } } ); | ||
} ); | ||
} ); | ||
|
||
describe( 'merge', () => { | ||
it( 'should return equal reference if same, deeply', () => { | ||
const obj1 = { a: { b: { c: 1 } } }; | ||
const obj2 = { a: { b: { c: 1 } } }; | ||
const result = merge( obj1, obj2 ); | ||
|
||
expect( result ).toBe( obj1 ); | ||
} ); | ||
|
||
it( 'should deeply merged object', () => { | ||
const obj1 = { a: { b: { c: 1 } } }; | ||
const obj2 = { a: { b: { d: 2 } } }; | ||
const result = merge( obj1, obj2 ); | ||
|
||
expect( result ).not.toBe( obj1 ); | ||
expect( result ).toEqual( { a: { b: { c: 1, d: 2 } } } ); | ||
} ); | ||
} ); | ||
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters