vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

471 lines
15 KiB

var assert = require('assert');
var util = require('../lib/util');
describe('util', function () {
/**
* Tests for copy and extend methods.
*
* Goal: to cover all possible paths within the tested method(s)
*
*
* **NOTES**
*
* - All these methods have the inherent flaw that it's possible to define properties
* on an object with value 'undefined'. e.g. in `node`:
*
* > a = { b:undefined }
* > a.hasOwnProperty('b')
* true
*
* The logic for handling this in the code is minimal and accidental. For the time being,
* this flaw is ignored.
*/
describe('extend routines', function () {
/**
* Check if values have been copied over from b to a as intended
*/
function checkExtended(a, b, checkCopyTarget = false) {
var result = {
color: 'green',
sub: {
enabled: false,
sub2: {
font: 'awesome'
}
}
};
assert(a.color !== undefined && a.color === result.color);
assert(a.notInSource === true);
if (checkCopyTarget) {
assert(a.notInTarget === true);
} else {
assert(a.notInTarget === undefined);
}
var sub = a.sub;
assert(sub !== undefined);
assert(sub.enabled !== undefined && sub.enabled === result.sub.enabled);
assert(sub.notInSource === true);
if (checkCopyTarget) {
assert(sub.notInTarget === true);
} else {
assert(sub.notInTarget === undefined);
}
sub = a.sub.sub2;
assert(sub !== undefined);
assert(sub !== undefined && sub.font !== undefined && sub.font === result.sub.sub2.font);
assert(sub.notInSource === true);
assert(a.subNotInSource !== undefined);
if (checkCopyTarget) {
assert(a.subNotInTarget.enabled === true);
assert(sub.notInTarget === true);
} else {
assert(a.subNotInTarget === undefined);
assert(sub.notInTarget === undefined);
}
}
/**
* Spot check on values of a unchanged as intended
*/
function testAUnchanged(a) {
var sub = a.sub;
assert(sub !== undefined);
assert(sub.enabled !== undefined && sub.enabled === true);
assert(sub.notInSource === true);
assert(sub.notInTarget === undefined);
assert(sub.deleteThis === true);
sub = a.sub.sub2;
assert(sub !== undefined);
assert(sub !== undefined && sub.font !== undefined && sub.font === 'arial');
assert(sub.notInSource === true);
assert(sub.notInTarget === undefined);
assert(a.subNotInSource !== undefined);
assert(a.subNotInTarget === undefined);
}
function initA() {
return {
color: 'red',
notInSource: true,
sub: {
enabled: true,
notInSource: true,
sub2: {
font: 'arial',
notInSource: true,
},
deleteThis: true,
},
subNotInSource: {
enabled: true,
},
deleteThis: true,
subDeleteThis: {
enabled: true,
},
};
}
beforeEach(function() {
this.a = initA();
this.b = {
color: 'green',
notInTarget: true,
sub: {
enabled: false,
notInTarget: true,
sub2: {
font: 'awesome',
notInTarget: true,
},
deleteThis: null,
},
subNotInTarget: {
enabled: true,
},
deleteThis: null,
subDeleteThis: null
};
});
it('performs fillIfDefined() as advertized', function () {
var a = this.a;
var b = this.b;
util.fillIfDefined(a, b);
checkExtended(a, b);
// NOTE: if allowDeletion === false, null values are copied over!
// This is due to existing logic; it might not be the intention and hence a bug
assert(a.sub.deleteThis === null);
assert(a.deleteThis === null);
assert(a.subDeleteThis === null);
});
it('performs fillIfDefined() as advertized with deletion', function () {
var a = this.a;
var b = this.b;
util.fillIfDefined(a, b, true); // thrid param: allowDeletion
checkExtended(a, b);
// Following should be removed now
assert(a.sub.deleteThis === undefined);
assert(a.deleteThis === undefined);
assert(a.subDeleteThis === undefined);
});
it('performs selectiveDeepExtend() as advertized', function () {
var a = this.a;
var b = this.b;
// pedantic: copy nothing
util.selectiveDeepExtend([], a, b);
assert(a.color !== undefined && a.color === 'red');
assert(a.notInSource === true);
assert(a.notInTarget === undefined);
// pedantic: copy nonexistent property (nothing happens)
assert(b.iDontExist === undefined);
util.selectiveDeepExtend(['iDontExist'], a, b, true);
assert(a.iDontExist === undefined);
// At this point nothing should have changed yet.
testAUnchanged(a);
// Copy one property
util.selectiveDeepExtend(['color'], a, b);
assert(a.color !== undefined && a.color === 'green');
// Copy property Object
var sub = a.sub;
assert(sub.deleteThis === true); // pre
util.selectiveDeepExtend(['sub'], a, b);
assert(sub !== undefined);
assert(sub.enabled !== undefined && sub.enabled === false);
assert(sub.notInSource === true);
assert(sub.notInTarget === true);
assert(sub.deleteThis === null);
// Copy new Objects
assert(a.notInTarget === undefined); // pre
assert(a.subNotInTarget === undefined); // pre
util.selectiveDeepExtend(['notInTarget', 'subNotInTarget'], a, b);
assert(a.notInTarget === true);
assert(a.subNotInTarget.enabled === true);
// Copy null objects
assert(a.deleteThis !== null); // pre
assert(a.subDeleteThis !== null); // pre
util.selectiveDeepExtend(['deleteThis', 'subDeleteThis'], a, b);
// NOTE: if allowDeletion === false, null values are copied over!
// This is due to existing logic; it might not be the intention and hence a bug
assert(a.deleteThis === null);
assert(a.subDeleteThis === null);
});
it('performs selectiveDeepExtend() as advertized with deletion', function () {
var a = this.a;
var b = this.b;
// Only test expected differences here with test allowDeletion === false
// Copy object property with properties to be deleted
var sub = a.sub;
assert(sub.deleteThis === true); // pre
util.selectiveDeepExtend(['sub'], a, b, true);
assert(sub.deleteThis === undefined); // should be deleted
// Spot check on rest of properties in `a.sub` - there should have been copied
sub = a.sub;
assert(sub !== undefined);
assert(sub.enabled !== undefined && sub.enabled === false);
assert(sub.notInSource === true);
assert(sub.notInTarget === true);
// Copy null objects
assert(a.deleteThis === true); // pre
assert(a.subDeleteThis !== undefined); // pre
assert(a.subDeleteThis.enabled === true); // pre
util.selectiveDeepExtend(['deleteThis', 'subDeleteThis'], a, b, true);
assert(a.deleteThis === undefined); // should be deleted
assert(a.subDeleteThis === undefined); // should be deleted
});
it('performs selectiveNotDeepExtend() as advertized', function () {
var a = this.a;
var b = this.b;
// Exclude all properties, nothing copied
util.selectiveNotDeepExtend(Object.keys(b), a, b);
testAUnchanged(a);
// Exclude nothing, everything copied
util.selectiveNotDeepExtend([], a, b);
checkExtended(a, b, true);
// Exclude some
a = initA();
assert(a.notInTarget === undefined); // pre
assert(a.subNotInTarget === undefined); // pre
util.selectiveNotDeepExtend(['notInTarget', 'subNotInTarget'], a, b);
assert(a.notInTarget === undefined); // not copied
assert(a.subNotInTarget === undefined); // not copied
assert(a.sub.notInTarget === true); // copied!
});
it('performs selectiveNotDeepExtend() as advertized with deletion', function () {
var a = this.a;
var b = this.b;
// Exclude all properties, nothing copied
util.selectiveNotDeepExtend(Object.keys(b), a, b, true);
testAUnchanged(a);
// Exclude nothing, everything copied and some deleted
util.selectiveNotDeepExtend([], a, b, true);
checkExtended(a, b, true);
// Exclude some
a = initA();
assert(a.notInTarget === undefined); // pre
assert(a.subNotInTarget === undefined); // pre
assert(a.deleteThis === true); // pre
assert(a.subDeleteThis !== undefined); // pre
assert(a.sub.deleteThis === true); // pre
assert(a.subDeleteThis.enabled === true); // pre
util.selectiveNotDeepExtend(['notInTarget', 'subNotInTarget'], a, b, true);
assert(a.deleteThis === undefined); // should be deleted
assert(a.sub.deleteThis !== undefined); // not deleted! Original logic, could be a bug
assert(a.subDeleteThis === undefined); // should be deleted
// Spot check: following should be same as allowDeletion === false
assert(a.notInTarget === undefined); // not copied
assert(a.subNotInTarget === undefined); // not copied
assert(a.sub.notInTarget === true); // copied!
});
/**
* NOTE: parameter `protoExtend` not tested here!
*/
it('performs deepExtend() as advertized', function () {
var a = this.a;
var b = this.b;
util.deepExtend(a, b);
checkExtended(a, b, true);
});
/**
* NOTE: parameter `protoExtend` not tested here!
*/
it('performs deepExtend() as advertized with delete', function () {
var a = this.a;
var b = this.b;
// Copy null objects
assert(a.deleteThis === true); // pre
assert(a.subDeleteThis !== undefined); // pre
assert(a.subDeleteThis.enabled === true); // pre
util.deepExtend(a, b, false, true);
checkExtended(a, b, true); // Normal copy should be good
assert(a.deleteThis === undefined); // should be deleted
assert(a.subDeleteThis === undefined); // should be deleted
assert(a.sub.deleteThis !== undefined); // not deleted!!! Original logic, could be a bug
});
}); // extend routines
//
// The important thing with mergeOptions() is that 'enabled' is always set in target option.
//
describe('mergeOptions', function () {
it('handles good input without global options', function () {
var options = {
someValue: "silly value",
aBoolOption: false,
anObject: {
answer:42
},
anotherObject: {
enabled: false,
},
merge: null
};
// Case with empty target
var mergeTarget = {};
util.mergeOptions(mergeTarget, options, 'someValue');
assert(mergeTarget.someValue === undefined, 'Non-object option should not be copied');
assert(mergeTarget.anObject === undefined);
util.mergeOptions(mergeTarget, options, 'aBoolOption');
assert(mergeTarget.aBoolOption !== undefined, 'option aBoolOption should now be an object');
assert(mergeTarget.aBoolOption.enabled === false, 'enabled value option aBoolOption should have been copied into object');
util.mergeOptions(mergeTarget, options, 'anObject');
assert(mergeTarget.anObject !== undefined, 'Option object is not copied');
assert(mergeTarget.anObject.answer === 42);
assert(mergeTarget.anObject.enabled === true);
util.mergeOptions(mergeTarget, options, 'anotherObject');
assert(mergeTarget.anotherObject.enabled === false, 'enabled value from options must have priority');
util.mergeOptions(mergeTarget, options, 'merge');
assert(mergeTarget.merge === undefined, 'Explicit null option should not be copied, there is no global option for it');
// Case with non-empty target
mergeTarget = {
someValue: false,
aBoolOption: true,
anObject: {
answer: 49
},
anotherObject: {
enabled: true,
},
merge: 'hello'
};
util.mergeOptions(mergeTarget, options, 'someValue');
assert(mergeTarget.someValue === false, 'Non-object option should not be copied');
assert(mergeTarget.anObject.answer === 49, 'Sibling option should not be changed');
util.mergeOptions(mergeTarget, options, 'aBoolOption');
assert(mergeTarget.aBoolOption !== true, 'option enabled should have been overwritten');
assert(mergeTarget.aBoolOption.enabled === false, 'enabled value option aBoolOption should have been copied into object');
util.mergeOptions(mergeTarget, options, 'anObject');
assert(mergeTarget.anObject.answer === 42);
assert(mergeTarget.anObject.enabled === true);
util.mergeOptions(mergeTarget, options, 'anotherObject');
assert(mergeTarget.anotherObject !== undefined, 'Option object is not copied');
assert(mergeTarget.anotherObject.enabled === false, 'enabled value from options must have priority');
util.mergeOptions(mergeTarget, options, 'merge');
assert(mergeTarget.merge === 'hello', 'Explicit null-option should not be copied, already present in target');
});
it('gracefully handles bad input', function () {
var mergeTarget = {};
var options = {
merge: null
};
var errMsg = 'Non-object parameters should not be accepted';
assert.throws(() => util.mergeOptions(null, options, 'anything'), Error, errMsg);
assert.throws(() => util.mergeOptions(undefined, options, 'anything'), Error, errMsg);
assert.throws(() => util.mergeOptions(42, options, 'anything'), Error, errMsg);
assert.throws(() => util.mergeOptions(mergeTarget, null, 'anything'), Error, errMsg);
assert.throws(() => util.mergeOptions(mergeTarget, undefined, 'anything'), Error, errMsg);
assert.throws(() => util.mergeOptions(mergeTarget, 42, 'anything'), Error, errMsg);
assert.throws(() => util.mergeOptions(mergeTarget, options, null), Error, errMsg);
assert.throws(() => util.mergeOptions(mergeTarget, options, undefined), Error, errMsg);
assert.throws(() => util.mergeOptions(mergeTarget, options, 'anything', null), Error, errMsg);
assert.throws(() => util.mergeOptions(mergeTarget, options, 'anything', 'not an object'), Error, errMsg);
util.mergeOptions(mergeTarget, options, 'iDontExist');
assert(mergeTarget.iDontExist === undefined);
});
it('handles good input with global options', function () {
var mergeTarget = {
};
var options = {
merge: null,
missingEnabled: {
answer: 42
},
alsoMissingEnabled: { // has no enabled in globals
answer: 42
}
};
var globalOptions = {
merge: {
enabled: false
},
missingEnabled: {
enabled: false
}
};
util.mergeOptions(mergeTarget, options, 'merge', globalOptions);
assert(mergeTarget.merge.enabled === false, "null-option should create an empty target object");
util.mergeOptions(mergeTarget, options, 'missingEnabled', globalOptions);
assert(mergeTarget.missingEnabled.enabled === false);
util.mergeOptions(mergeTarget, options, 'alsoMissingEnabled', globalOptions);
assert(mergeTarget.alsoMissingEnabled.enabled === true);
});
}); // mergeOptions
}); // util