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) {