Files
react/src/utils/__tests__/ImmutableObject-test.js
T

291 lines
8.6 KiB
JavaScript

/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @emails react-core
*/
"use strict";
require('mock-modules')
.dontMock('ImmutableObject');
var ImmutableObject;
/**
* To perform performance testing of using `ImmutableObject` vs. not using
* `ImmutableObject`, such testing must be done with __DEV__ set to false.
*/
describe('ImmutableObject', function() {
var message;
beforeEach(function() {
require('mock-modules').dumpCache();
ImmutableObject = require('ImmutableObject');
this.addMatchers({
/**
* Equivalent with respect to serialization. Must stringify because
* constructors are different and other comparison methods will not
* consider them structurally equal. Probably not useful for use outside
* of this test module.
*/
toBeSeriallyEqualTo: function(expected) {
var actual = this.actual;
var notText = this.isNot ? " not" : "";
this.message = function () {
return "Expected " + JSON.stringify(actual) + notText +
" to be serially equal to " + JSON.stringify(expected);
};
return JSON.stringify(actual) === JSON.stringify(expected);
}
});
});
/**
* We are in __DEV__ by default.
*/
var testDev = function(message, testFunc) {
it(message, testFunc);
};
var testProd = function(message, testFunc) {
// Temporarily enter production mode
window.__DEV__ = false;
it(message, testFunc);
window.__DEV__ = true;
};
var testDevAndProd = function(message, testFunc) {
testDev(message + ':DEV', testFunc);
testProd(message + ':PROD', testFunc);
};
testDev('should be running in DEV', function() {
expect(window.__DEV__).toBe(true);
});
testDev('should require initial map to be an object', function() {
expect(function() {
new ImmutableObject([1,2,3]);
}).toThrow();
expect(function() {
new ImmutableObject('asdf');
}).toThrow();
expect(function() {
new ImmutableObject({oldField: 'asdf', fieldTwo: null});
}).not.toThrow();
});
testDevAndProd('should not throw when not mutating directly', function() {
var io = new ImmutableObject({oldField: 'asdf'});
expect(function() {
ImmutableObject.set(io, {newField: null}); // not a mutation!
}).not.toThrow();
});
testDev('should prevent shallow field addition when strict', function() {
if (window.callPhantom) return;
expect(function() {
var io = new ImmutableObject({oldField: 'asdf'});
io.newField = 'this will not work';
}).toThrow();
});
testDev('should prevent shallow field mutation when strict', function() {
if (window.callPhantom) return;
expect(function() {
var io = new ImmutableObject({oldField: 'asdf'});
io.oldField = 'this will not work!';
}).toThrow();
});
testDev('should prevent deep field addition when strict', function() {
if (window.callPhantom) return;
expect(function() {
var io =
new ImmutableObject({shallowField: {deepField: {oldField: null}}});
io.shallowField.deepField.oldField = 'this will not work!';
}).toThrow();
});
testDev('should prevent deep field mutation when strict', function() {
if (window.callPhantom) return;
expect(function() {
var io =
new ImmutableObject({shallowField: {deepField: {oldField: null}}});
io.shallowField.deepField.newField = 'this will not work!';
}).toThrow();
});
testDevAndProd(
'should create object with same structure when set with {}',
function() {
var beforeIO =
new ImmutableObject({shallowField: {deepField: {oldField: null}}});
var afterIO = ImmutableObject.set(beforeIO, {});
expect(afterIO).toBeSeriallyEqualTo(beforeIO);
expect(afterIO).not.toBe(beforeIO);
}
);
testDevAndProd(
'should create distinct object with shallow field insertion',
function() {
var beforeStructure = {
oldShallowField: {
deepField: {
oldField: null
}
}
};
var delta = {
newShallowField: 'newShallowFieldHere'
};
var expectedAfterStructure = {
oldShallowField: {
deepField: {
oldField: null
}
},
newShallowField: 'newShallowFieldHere'
};
var beforeIO = new ImmutableObject(beforeStructure);
var afterIO = ImmutableObject.set(beforeIO, delta);
expect(afterIO).toBeSeriallyEqualTo(expectedAfterStructure);
expect(afterIO).not.toBe(beforeIO);
}
);
testDevAndProd(
'should create distinct object with shallow field mutation',
function() {
var beforeStructure = {
oldShallowField: {
deepField: {
oldField: null
}
}
};
var delta = {
oldShallowField: 'this will clobber the old field'
};
var expectedAfterStructure = {
oldShallowField: 'this will clobber the old field'
};
var beforeIO = new ImmutableObject(beforeStructure);
var afterIO = ImmutableObject.set(beforeIO, delta);
expect(afterIO).toBeSeriallyEqualTo(expectedAfterStructure);
expect(afterIO).not.toBe(beforeIO);
}
);
message = 'should create distinct object with deep field insertion';
testDevAndProd(message, function() {
var beforeStructure = {
oldShallowField: {
deepField: {
oldField: null
}
}
};
var delta = {
oldShallowField: {newDeepField: 'hello'}
};
// ImmutableObject does not yet support deep merging with
// ImmutableObject.set().
var expectedAfterStructure = {
oldShallowField: {newDeepField: 'hello'}
};
var beforeIO = new ImmutableObject(beforeStructure);
var afterIO = ImmutableObject.set(beforeIO, delta);
expect(afterIO).toBeSeriallyEqualTo(expectedAfterStructure);
expect(afterIO).not.toBe(beforeIO);
});
message =
'should tolerate arrays at deeper levels and prevent mutation on them';
testDevAndProd(message, function() {
if (window.callPhantom) {
// PhantomJS has a bug with Object.freeze and Arrays.
// https://github.com/ariya/phantomjs/issues/10817
return;
}
var beforeStructure = {
shallowField: [1,'second field',3]
};
var io = new ImmutableObject(beforeStructure);
expect(function() {
io.newField = 'nope!';
}).toThrow();
expect(function() {
io.shallowField[0] = 'nope!';
}).toThrow();
expect(io.shallowField[1]).toEqual('second field');
});
message = 'should provide a setField interface as sugar for set()';
testDevAndProd(message, function() {
var beforeIO = new ImmutableObject({initialField: null});
var afterIO =
ImmutableObject.setField(beforeIO, 'anotherField', 'anotherValue');
expect(afterIO).toBeSeriallyEqualTo({
initialField: null,
anotherField: 'anotherValue'
});
expect(afterIO).not.toBe(beforeIO);
});
message = 'should recursively create distinct objects when deep copying';
testDevAndProd(message, function() {
var beforeIO = new ImmutableObject({
a: {b: 'b', c: {}, d: 'd', e: new ImmutableObject({f: 'f'}) }
});
var afterIO = ImmutableObject.setDeep(beforeIO, {
a: {b: {}, c: 'C', e: {f: 'F', g: 'G'}, h: 'H'}
});
expect(afterIO).toBeSeriallyEqualTo({
a: {b: {}, c: 'C', d: 'd', e: {f: 'F', g: 'G'}, h: 'H'}
});
expect(afterIO).not.toBe(beforeIO);
expect(afterIO.a).not.toBe(beforeIO.a);
expect(afterIO.a.e).not.toBe(beforeIO.a.e);
});
testDevAndProd('should deep copy member immutability', function() {
var beforeIO = new ImmutableObject({
a: {b: new ImmutableObject({c: 'c'}), e: {f: 'f'}}
});
var afterIO = ImmutableObject.setDeep(beforeIO, {
a: {b: {d: 'D'}, e: new ImmutableObject({g: 'G'})}
});
expect(afterIO).toBeSeriallyEqualTo({
a: {b: {c: 'c', d: 'D'}, e: {f: 'f', g: 'G'}}
});
expect(afterIO instanceof ImmutableObject).toBe(true);
expect(afterIO.a.b instanceof ImmutableObject).toBe(true);
expect(afterIO.a.e instanceof ImmutableObject).toBe(true);
});
});