@samitouri / QOS-React-2 / commits / 0cec4af8d7

Sync latest Immutable changes

Kunal Mehta committed Mar 18, 2014 at 14:52 UTC 0cec4af8d7bf1eb70f34f9f253a7897449bf1573
3 files changed +119 -160
src/utils/ImmutableObject.js
+82 -115
@@ -14,178 +14,145 @@
14 * limitations under the License.
15 *
16 * @providesModule ImmutableObject
17 - * @typechecks
17 */
18
19 "use strict";
20
21 +var Immutable = require('Immutable');
22 +
23 var invariant = require('invariant');
23 -var isNode = require('isNode');
24 -var merge = require('merge');
25 -var mergeInto = require('mergeInto');
24 +var keyOf = require('keyOf');
25 var mergeHelpers = require('mergeHelpers');
26
27 var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs;
28 var isTerminal = mergeHelpers.isTerminal;
29
30 +var SECRET_KEY = keyOf({_DONT_EVER_TYPE_THIS_SECRET_KEY: null});
31 +
32 /**
32 - * Wrapper around JavaScript objects that provide a guarantee of immutability at
33 - * developer time when strict mode is used. The extra computations required to
34 - * enforce immutability is stripped out in production for performance reasons.
33 + * Static methods creating and operating on instances of `Immutable`.
34 */
36 -var ImmutableObject;
37 -
38 -function assertImmutableObject(immutableObject) {
35 +function assertImmutable(immutable) {
36 invariant(
40 - immutableObject instanceof ImmutableObject,
37 + immutable instanceof Immutable,
38 'ImmutableObject: Attempted to set fields on an object that is not an ' +
42 - 'instance of ImmutableObject.'
39 + 'instance of Immutable.'
40 );
41 }
42
46 -if (__DEV__) {
47 - /**
48 - * Constructs an instance of `ImmutableObject`.
49 - *
50 - * @param {?object} initialProperties The initial set of properties.
51 - * @constructor
52 - */
53 - ImmutableObject = function ImmutableObject(initialProperties) {
54 - mergeInto(this, initialProperties);
55 - deepFreeze(this);
56 - };
57 -
43 +/**
44 + * Static methods for reasoning about instances of `ImmutableObject`. Execute
45 + * the freeze commands in `__DEV__` mode to alert the programmer that something
46 + * is attempting to mutate. Since freezing is very expensive, we avoid doing it
47 + * at all in production.
48 + */
49 +class ImmutableObject extends Immutable {
50 /**
59 - * Checks if an object should be deep frozen. Instances of `ImmutableObject`
60 - * are assumed to have already been deep frozen.
61 - *
62 - * @param {*} object The object to check.
63 - * @return {boolean} Whether or not deep freeze is needed.
51 + * @arguments {array<object>} The arguments is an array of objects that, when
52 + * merged together, will form the immutable objects.
53 */
65 - var shouldRecurseFreeze = function(object) {
66 - return (
67 - typeof object === 'object' &&
68 - !(object instanceof ImmutableObject) &&
69 - object !== null
70 - );
71 - };
54 + constructor() {
55 + super(Immutable[SECRET_KEY]);
56 + Immutable.mergeAllPropertiesInto(this, arguments);
57 + if (__DEV__) {
58 + Immutable.deepFreezeRootNode(this);
59 + }
60 + }
61
62 /**
74 - * Freezes the supplied object deeply.
63 + * DEPRECATED - prefer to instantiate with new ImmutableObject().
64 *
76 - * @param {*} object The object to freeze.
65 + * @arguments {array<object>} The arguments is an array of objects that, when
66 + * merged together, will form the immutable objects.
67 */
78 - var deepFreeze = function(object) {
79 - if (isNode(object)) {
80 - return; // Don't try to freeze DOM nodes.
81 - }
82 - Object.freeze(object); // First freeze the object.
83 - for (var prop in object) {
84 - var field = object[prop];
85 - if (object.hasOwnProperty(prop) && shouldRecurseFreeze(field)) {
86 - deepFreeze(field);
87 - }
88 - }
89 - };
68 + static create() {
69 + var obj = Object.create(ImmutableObject.prototype);
70 + ImmutableObject.apply(obj, arguments);
71 + return obj;
72 + }
73
74 /**
92 - * Returns a new ImmutableObject that is identical to the supplied object but
93 - * with the supplied changes, `put`.
75 + * Returns a new `Immutable` that is identical to the supplied `Immutable`
76 + * but with the specified changes, `put`. Any keys that are in the
77 + * intersection of `immutable` and `put` retain the ordering of `immutable.
78 + * New keys are placed after keys that exist in `immutable`.
79 *
95 - * @param {ImmutableObject} immutableObject Starting object.
80 + * @param {Immutable} immutable Starting object.
81 * @param {?object} put Fields to merge into the object.
97 - * @return {ImmutableObject} The result of merging in `put` fields.
82 + * @return {Immutable} The result of merging in `put` fields.
83 */
99 - ImmutableObject.set = function(immutableObject, put) {
100 - assertImmutableObject(immutableObject);
101 - var totalNewFields = merge(immutableObject, put);
102 - return new ImmutableObject(totalNewFields);
103 - };
84 + static set(immutable, put) {
85 + assertImmutable(immutable);
86 + invariant(
87 + typeof put === 'object' && put !== undefined && !Array.isArray(put),
88 + 'Invalid ImmutableMap.set argument `put`'
89 + );
90 + return new ImmutableObject(immutable, put);
91 + }
92
105 -} else {
93 /**
107 - * Constructs an instance of `ImmutableObject`.
94 + * Sugar for `ImmutableObject.set(ImmutableObject, {fieldName: putField})`.
95 + * Look out for key crushing: Use `keyOf()` to guard against it.
96 *
109 - * @param {?object} initialProperties The initial set of properties.
110 - * @constructor
97 + * @param {Immutable} immutable Object on which to set properties.
98 + * @param {string} fieldName Name of the field to set.
99 + * @param {*} putField Value of the field to set.
100 + * @return {Immutable} new Immutable as described in `set`.
101 */
112 - ImmutableObject = function ImmutableObject(initialProperties) {
113 - mergeInto(this, initialProperties);
114 - };
102 + static setProperty(immutableObject, fieldName, putField) {
103 + var put = {};
104 + put[fieldName] = putField;
105 + return ImmutableObject.set(immutableObject, put);
106 + }
107
108 /**
117 - * Returns a new ImmutableObject that is identical to the supplied object but
118 - * with the supplied changes, `put`.
109 + * Returns a new `Immutable` that is identical to the supplied object but
110 + * with the supplied changes recursively applied.
111 *
120 - * @param {ImmutableObject} immutableObject Starting object.
121 - * @param {?object} put Fields to merge into the object.
122 - * @return {ImmutableObject} The result of merging in `put` fields.
112 + * Experimental. Likely does not handle `Arrays` correctly.
113 + *
114 + * @param {Immutable} immutable Object on which to set fields.
115 + * @param {object} put Fields to merge into the object.
116 + * @return {Immutable} The result of merging in `put` fields.
117 */
124 - ImmutableObject.set = function(immutableObject, put) {
125 - assertImmutableObject(immutableObject);
126 - var newMap = new ImmutableObject(immutableObject);
127 - mergeInto(newMap, put);
128 - return newMap;
129 - };
118 + static setDeep(immutable, put) {
119 + assertImmutable(immutable);
120 + return _setDeep(immutable, put);
121 + }
122 }
123
132 -/**
133 - * Sugar for `ImmutableObject.set(ImmutableObject, {fieldName: putField})`.
134 - *
135 - * @param {ImmutableObject} immutableObject Object on which to set field.
136 - * @param {string} fieldName Name of the field to set.
137 - * @param {*} putField Value of the field to set.
138 - * @return {ImmutableObject} [description]
139 - */
140 -ImmutableObject.setField = function(immutableObject, fieldName, putField) {
141 - var put = {};
142 - put[fieldName] = putField;
143 - return ImmutableObject.set(immutableObject, put);
144 -};
145 -
146 -/**
147 - * Returns a new ImmutableObject that is identical to the supplied object but
148 - * with the supplied changes recursively applied.
149 - *
150 - * @param {ImmutableObject} immutableObject Object on which to set fields.
151 - * @param {object} put Fields to merge into the object.
152 - * @return {ImmutableObject} The result of merging in `put` fields.
153 - */
154 -ImmutableObject.setDeep = function(immutableObject, put) {
155 - assertImmutableObject(immutableObject);
156 - return _setDeep(immutableObject, put);
157 -};
158 -
159 -function _setDeep(object, put) {
160 - checkMergeObjectArgs(object, put);
124 +function _setDeep(obj, put) {
125 + checkMergeObjectArgs(obj, put);
126 var totalNewFields = {};
127
128 // To maintain the order of the keys, copy the base object's entries first.
164 - var keys = Object.keys(object);
129 + var keys = Object.keys(obj);
130 for (var ii = 0; ii < keys.length; ii++) {
131 var key = keys[ii];
132 if (!put.hasOwnProperty(key)) {
168 - totalNewFields[key] = object[key];
169 - } else if (isTerminal(object[key]) || isTerminal(put[key])) {
133 + totalNewFields[key] = obj[key];
134 + } else if (isTerminal(obj[key]) || isTerminal(put[key])) {
135 totalNewFields[key] = put[key];
136 } else {
172 - totalNewFields[key] = _setDeep(object[key], put[key]);
137 + totalNewFields[key] = _setDeep(obj[key], put[key]);
138 }
139 }
140
176 - // Apply any new keys that the base object didn't have.
141 + // Apply any new keys that the base obj didn't have.
142 var newKeys = Object.keys(put);
143 for (ii = 0; ii < newKeys.length; ii++) {
144 var newKey = newKeys[ii];
180 - if (object.hasOwnProperty(newKey)) {
145 + if (obj.hasOwnProperty(newKey)) {
146 continue;
147 }
148 totalNewFields[newKey] = put[newKey];
149 }
150
186 - return (object instanceof ImmutableObject || put instanceof ImmutableObject) ?
187 - new ImmutableObject(totalNewFields) :
188 - totalNewFields;
151 + return (
152 + obj instanceof Immutable ? new ImmutableObject(totalNewFields) :
153 + put instanceof Immutable ? new ImmutableObject(totalNewFields) :
154 + totalNewFields
155 + );
156 }
157
158 module.exports = ImmutableObject;
src/utils/__tests__/ImmutableObject-test.js
+35 -43
@@ -18,10 +18,8 @@
18
19 "use strict";
20
21 -require('mock-modules')
22 - .dontMock('ImmutableObject');
23 -
21 var ImmutableObject;
22 +var Immutable;
23
24 /**
25 * To perform performance testing of using `ImmutableObject` vs. not using
@@ -30,40 +28,27 @@ var ImmutableObject;
28 describe('ImmutableObject', function() {
29 var message;
30 beforeEach(function() {
33 - require('mock-modules').dumpCache();
31 ImmutableObject = require('ImmutableObject');
35 - this.addMatchers({
36 - /**
37 - * Equivalent with respect to serialization. Must stringify because
38 - * constructors are different and other comparison methods will not
39 - * consider them structurally equal. Probably not useful for use outside
40 - * of this test module.
41 - */
42 - toBeSeriallyEqualTo: function(expected) {
43 - var actual = this.actual;
44 - var notText = this.isNot ? " not" : "";
45 - this.message = function () {
46 - return "Expected " + JSON.stringify(actual) + notText +
47 - " to be serially equal to " + JSON.stringify(expected);
48 - };
49 -
50 - return JSON.stringify(actual) === JSON.stringify(expected);
51 - }
52 - });
32 + Immutable = require('Immutable');
33 });
34
55 - /**
56 - * We are in __DEV__ by default.
57 - */
35 var testDev = function(message, testFunc) {
59 - it(message, testFunc);
36 + it(message, function() {
37 + var old = window.__DEV__;
38 + window.__DEV__ = true;
39 + testFunc();
40 + window.__DEV__ = old;
41 + });
42 };
43
44 var testProd = function(message, testFunc) {
63 - // Temporarily enter production mode
64 - window.__DEV__ = false;
65 - it(message, testFunc);
66 - window.__DEV__ = true;
45 + it(message, function() {
46 + // Temporarily enter production mode
47 + var old = window.__DEV__;
48 + window.__DEV__ = false;
49 + testFunc();
50 + window.__DEV__ = old;
51 + });
52 };
53
54 var testDevAndProd = function(message, testFunc) {
@@ -89,6 +74,12 @@ describe('ImmutableObject', function() {
74 }).not.toThrow();
75 });
76
77 + testDevAndProd('should extend Immutable', function() {
78 + var object = new ImmutableObject({foo: 'bar'});
79 + expect (object instanceof Immutable).toBe(true);
80 + expect (object instanceof ImmutableObject).toBe(true);
81 + });
82 +
83 testDev('should not exceed maximum call stack size with nodes', function() {
84 var node = document.createElement('div');
85 var object = new ImmutableObject({node: node});
@@ -142,7 +133,7 @@ describe('ImmutableObject', function() {
133 var beforeIO =
134 new ImmutableObject({shallowField: {deepField: {oldField: null}}});
135 var afterIO = ImmutableObject.set(beforeIO, {});
145 - expect(afterIO).toBeSeriallyEqualTo(beforeIO);
136 + expect(afterIO).toSeriallyEqual(beforeIO);
137 expect(afterIO).not.toBe(beforeIO);
138 }
139 );
@@ -173,7 +164,7 @@ describe('ImmutableObject', function() {
164
165 var beforeIO = new ImmutableObject(beforeStructure);
166 var afterIO = ImmutableObject.set(beforeIO, delta);
176 - expect(afterIO).toBeSeriallyEqualTo(expectedAfterStructure);
167 + expect(afterIO).toSeriallyEqual(expectedAfterStructure);
168 expect(afterIO).not.toBe(beforeIO);
169 }
170 );
@@ -199,7 +190,7 @@ describe('ImmutableObject', function() {
190
191 var beforeIO = new ImmutableObject(beforeStructure);
192 var afterIO = ImmutableObject.set(beforeIO, delta);
202 - expect(afterIO).toBeSeriallyEqualTo(expectedAfterStructure);
193 + expect(afterIO).toSeriallyEqual(expectedAfterStructure);
194 expect(afterIO).not.toBe(beforeIO);
195 }
196 );
@@ -226,13 +217,13 @@ describe('ImmutableObject', function() {
217
218 var beforeIO = new ImmutableObject(beforeStructure);
219 var afterIO = ImmutableObject.set(beforeIO, delta);
229 - expect(afterIO).toBeSeriallyEqualTo(expectedAfterStructure);
220 + expect(afterIO).toSeriallyEqual(expectedAfterStructure);
221 expect(afterIO).not.toBe(beforeIO);
222 });
223
224 message =
225 'should tolerate arrays at deeper levels and prevent mutation on them';
235 - testDevAndProd(message, function() {
226 + testDev(message, function() {
227 if (window.callPhantom) {
228 // PhantomJS has a bug with Object.freeze and Arrays.
229 // https://github.com/ariya/phantomjs/issues/10817
@@ -251,12 +242,12 @@ describe('ImmutableObject', function() {
242 expect(io.shallowField[1]).toEqual('second field');
243 });
244
254 - message = 'should provide a setField interface as sugar for set()';
245 + message = 'should provide a setProperty interface as sugar for set()';
246 testDevAndProd(message, function() {
247 var beforeIO = new ImmutableObject({initialField: null});
248 var afterIO =
258 - ImmutableObject.setField(beforeIO, 'anotherField', 'anotherValue');
259 - expect(afterIO).toBeSeriallyEqualTo({
249 + ImmutableObject.setProperty(beforeIO, 'anotherField', 'anotherValue');
250 + expect(afterIO).toSeriallyEqual({
251 initialField: null,
252 anotherField: 'anotherValue'
253 });
@@ -271,7 +262,7 @@ describe('ImmutableObject', function() {
262 var afterIO = ImmutableObject.setDeep(beforeIO, {
263 a: {b: {}, c: 'C', e: {f: 'F', g: 'G'}, h: 'H'}
264 });
274 - expect(afterIO).toBeSeriallyEqualTo({
265 + expect(afterIO).toSeriallyEqual({
266 a: {b: {}, c: 'C', d: 'd', e: {f: 'F', g: 'G'}, h: 'H'}
267 });
268 expect(afterIO).not.toBe(beforeIO);
@@ -286,11 +277,12 @@ describe('ImmutableObject', function() {
277 var afterIO = ImmutableObject.setDeep(beforeIO, {
278 a: {b: {d: 'D'}, e: new ImmutableObject({g: 'G'})}
279 });
289 - expect(afterIO).toBeSeriallyEqualTo({
280 + expect(afterIO).toSeriallyEqual({
281 a: {b: {c: 'c', d: 'D'}, e: {f: 'f', g: 'G'}}
282 });
292 - expect(afterIO instanceof ImmutableObject).toBe(true);
293 - expect(afterIO.a.b instanceof ImmutableObject).toBe(true);
294 - expect(afterIO.a.e instanceof ImmutableObject).toBe(true);
283 + expect(afterIO instanceof Immutable).toBe(true);
284 + expect(afterIO.a.b instanceof Immutable).toBe(true);
285 + expect(afterIO.a.e instanceof Immutable).toBe(true);
286 });
287 });
288 +
src/utils/immutable/Immutable.js
+2 -2
@@ -1,5 +1,5 @@
1 /**
2 - * Copyright 2014 Facebook, Inc.
2 + * Copyright 2013-2014 Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
@@ -61,7 +61,7 @@ class Immutable {
61 /**
62 * Helper method for classes that make use of `Immutable`.
63 * @param {Immutable} immutable Object to merge properties into.
64 - * @param {Array<object>} propertyObjects List of objects to merge into
64 + * @param {array<object>} propertyObjects List of objects to merge into
65 * `destination`.
66 */
67 static mergeAllPropertiesInto(destination, propertyObjects) {