diff --git a/src/addons/__tests__/update-test.js b/src/addons/__tests__/update-test.js index 05a6578ee8976..40733d0423c91 100644 --- a/src/addons/__tests__/update-test.js +++ b/src/addons/__tests__/update-test.js @@ -115,6 +115,11 @@ describe('update', function() { update(obj, {$set: {c: 'd'}}); expect(obj).toEqual({a: 'b'}); }); + it('keeps reference equality when possible', function() { + var original = {a: 1}; + expect(update(original, {a: {$set: 1}})).toBe(original); + expect(update(original, {a: {$set: 2}})).toNotBe(original); + }); }); describe('$apply', function() { @@ -134,38 +139,82 @@ describe('update', function() { 'update(): expected spec of $apply to be a function; got 123.' ); }); + it('keeps reference equality when possible', function() { + var original = {a: {b: {}}}; + function identity(val) { + return val; + } + expect(update(original, {a: {$apply: identity}})).toBe(original); + expect(update(original, {a: {$apply: applier}})).toNotBe(original); + }); }); - it('should support deep updates', function() { - expect(update({ - a: 'b', - c: { - d: 'e', - f: [1], - g: [2], - h: [3], - i: {j: 'k'}, - l: 4, - }, - }, { - c: { - d: {$set: 'm'}, - f: {$push: [5]}, - g: {$unshift: [6]}, - h: {$splice: [[0, 1, 7]]}, - i: {$merge: {n: 'o'}}, - l: {$apply: (x) => x * 2}, - }, - })).toEqual({ - a: 'b', - c: { - d: 'm', - f: [1, 5], - g: [6, 2], - h: [7], - i: {j: 'k', n: 'o'}, - l: 8, - }, + describe('deep update', function() { + it('works', function() { + expect(update({ + a: 'b', + c: { + d: 'e', + f: [1], + g: [2], + h: [3], + i: {j: 'k'}, + l: 4, + }, + }, { + c: { + d: {$set: 'm'}, + f: {$push: [5]}, + g: {$unshift: [6]}, + h: {$splice: [[0, 1, 7]]}, + i: {$merge: {n: 'o'}}, + l: {$apply: (x) => x * 2}, + }, + })).toEqual({ + a: 'b', + c: { + d: 'm', + f: [1, 5], + g: [6, 2], + h: [7], + i: {j: 'k', n: 'o'}, + l: 8, + }, + }); + }); + it('keeps reference equality when possible', function() { + var original = {a: {b: 1}, c: {d: {e: 1}}}; + + expect(update(original, {a: {b: {$set: 1}}})).toBe(original); + expect(update(original, {a: {b: {$set: 1}}}).a).toBe(original.a); + + expect(update(original, {c: {d: {e: {$set: 1}}}})).toBe(original); + expect(update(original, {c: {d: {e: {$set: 1}}}}).c).toBe(original.c); + expect(update(original, {c: {d: {e: {$set: 1}}}}).c.d).toBe(original.c.d); + + expect(update(original, { + a: {b: {$set: 1}}, + c: {d: {e: {$set: 1}}}, + })).toBe(original); + expect(update(original, { + a: {b: {$set: 1}}, + c: {d: {e: {$set: 1}}}, + }).a).toBe(original.a); + expect(update(original, { + a: {b: {$set: 1}}, + c: {d: {e: {$set: 1}}}, + }).c).toBe(original.c); + expect(update(original, { + a: {b: {$set: 1}}, + c: {d: {e: {$set: 1}}}, + }).c.d).toBe(original.c.d); + + expect(update(original, {a: {b: {$set: 2}}})).toNotBe(original); + expect(update(original, {a: {b: {$set: 2}}}).a).toNotBe(original.a); + expect(update(original, {a: {b: {$set: 2}}}).a.b).toNotBe(original.a.b); + + expect(update(original, {a: {b: {$set: 2}}}).c).toBe(original.c); + expect(update(original, {a: {b: {$set: 2}}}).c.d).toBe(original.c.d); }); }); diff --git a/src/addons/update.js b/src/addons/update.js index 7c8ba4e3b1dbb..e300e94cbb779 100644 --- a/src/addons/update.js +++ b/src/addons/update.js @@ -82,13 +82,16 @@ function update(value, spec) { 'Cannot have more than one key in an object with %s', COMMAND_SET ); - return spec[COMMAND_SET]; } - var nextValue = shallowCopy(value); + // Make sure to shallowCopy() it before mutations + var nextValue = value; if (hasOwnProperty.call(spec, COMMAND_MERGE)) { + if (nextValue === value) { + nextValue = shallowCopy(value); + } var mergeObj = spec[COMMAND_MERGE]; invariant( mergeObj && typeof mergeObj === 'object', @@ -106,6 +109,9 @@ function update(value, spec) { } if (hasOwnProperty.call(spec, COMMAND_PUSH)) { + if (nextValue === value) { + nextValue = shallowCopy(value); + } invariantArrayCase(value, spec, COMMAND_PUSH); spec[COMMAND_PUSH].forEach(function(item) { nextValue.push(item); @@ -113,6 +119,9 @@ function update(value, spec) { } if (hasOwnProperty.call(spec, COMMAND_UNSHIFT)) { + if (nextValue === value) { + nextValue = shallowCopy(value); + } invariantArrayCase(value, spec, COMMAND_UNSHIFT); spec[COMMAND_UNSHIFT].forEach(function(item) { nextValue.unshift(item); @@ -120,6 +129,9 @@ function update(value, spec) { } if (hasOwnProperty.call(spec, COMMAND_SPLICE)) { + if (nextValue === value) { + nextValue = shallowCopy(value); + } invariant( Array.isArray(value), 'Expected %s target to be an array; got %s', @@ -157,7 +169,15 @@ function update(value, spec) { for (var k in spec) { if (!(ALL_COMMANDS_SET.hasOwnProperty(k) && ALL_COMMANDS_SET[k])) { - nextValue[k] = update(value[k], spec[k]); + var nextValueForKey = update(value[k], spec[k]); + if (nextValueForKey === value[k]) { + continue; + } + + if (nextValue === value) { + nextValue = shallowCopy(value); + } + nextValue[k] = nextValueForKey; } }