Skip to content

Commit

Permalink
Strapping down of Extend-routines in util.js (almende#3442)
Browse files Browse the repository at this point in the history
* The next fix on Travis unit test failure

This is the next escalation on the war against the Travis unit tests failing (which came into being by yours truly).
By accident, I could recreate the unit test failure on my development machine. This led to a more directed effort to
squash the bug.

The insight here is that test `(window === undefined)` fails, but `(typeof window === 'undefined`)` succeeds. This undoubtedly has to do with the special status `window` has as a global object.

Changes:

- Added check on presence of `window` in `Canvas._requestNextFrame()`, fixed local source errors.
- Added catch clause in `CanvasRendered._determinePixelRatio()`
- small fix: raised timeout for the network `worldCup2014` unit test

* Preliminary refactoring in utils.js

* Added unit tests for extend routines, commenting and small fixes

* More unit tests for extend routines

* - Completed unit tests for extend routines in
- Small fixes and cleanup in `util.js`
- Removed `util.protoExtend()`, not used anywhere

* Added unit tests for known font options
  • Loading branch information
wimrijnders authored and Primoz Susa committed Jan 3, 2019
1 parent bf43dc8 commit 398718e
Show file tree
Hide file tree
Showing 3 changed files with 509 additions and 121 deletions.
230 changes: 111 additions & 119 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,24 +107,49 @@ exports.assignAllKeys = function (obj, value) {


/**
* Fill an object with a possibly partially defined other object. Only copies values if the a object has an object requiring values.
* Copy property from b to a if property present in a.
* If property in b explicitly set to null, delete it if `allowDeletion` set.
*
* Internal helper routine, should not be exported. Not added to `exports` for that reason.
*
* @param {object} a target object
* @param {object} b source object
* @param {string} prop name of property to copy to a
* @param {boolean} allowDeletion if true, delete property in a if explicitly set to null in b
* @private
*/
function copyOrDelete(a, b, prop, allowDeletion) {
var doDeletion = false;
if (allowDeletion === true) {
doDeletion = (b[prop] === null && a[prop] !== undefined);
}

if (doDeletion) {
delete a[prop];
} else {
a[prop] = b[prop]; // Remember, this is a reference copy!
}
}


/**
* Fill an object with a possibly partially defined other object.
*
* Only copies values for the properties already present in a.
* That means an object is not created on a property if only the b object has it.
*
* @param {object} a
* @param {object} b
* @param {boolean} [allowDeletion=false]
* @param {boolean} [allowDeletion=false] if true, delete properties in a that are explicitly set to null in b
*/
exports.fillIfDefined = function (a, b, allowDeletion = false) {
// NOTE: iteration of properties of a
// NOTE: prototype properties iterated over as well
for (var prop in a) {
if (b[prop] !== undefined) {
if (typeof b[prop] !== 'object') {
if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
delete a[prop];
}
else {
a[prop] = b[prop];
}
}
else {
if (b[prop] === null || typeof b[prop] !== 'object') { // Note: typeof null === 'object'
copyOrDelete(a, b, prop, allowDeletion);
} else {
if (typeof a[prop] === 'object') {
exports.fillIfDefined(a[prop], b[prop], allowDeletion);
}
Expand All @@ -134,24 +159,6 @@ exports.fillIfDefined = function (a, b, allowDeletion = false) {
};



/**
* Extend object a with the properties of object b or a series of objects
* Only properties with defined values are copied
* @param {Object} a
* @param {...Object} b
* @return {Object} a
*/
exports.protoExtend = function (a, b) { // eslint-disable-line no-unused-vars
for (var i = 1; i < arguments.length; i++) {
var other = arguments[i];
for (var prop in other) {
a[prop] = other[prop];
}
}
return a;
};

/**
* Extend object a with the properties of object b or a series of objects
* Only properties with defined values are copied
Expand Down Expand Up @@ -197,153 +204,138 @@ exports.selectiveExtend = function (props, a, b) { // eslint-disable-line no-un
return a;
};


/**
* Extend object a with selected properties of object b or a series of objects
* Only properties with defined values are copied
* @param {Array.<string>} props
* @param {Object} a
* @param {Object} b
* @param {boolean} [allowDeletion=false]
* @return {Object} a
* Extend object a with selected properties of object b.
* Only properties with defined values are copied.
*
* **Note:** Previous version of this routine implied that multiple source objects
* could be used; however, the implementation was **wrong**.
* Since multiple (>1) sources weren't used anywhere in the `vis.js` code,
* this has been removed
*
* @param {Array.<string>} props names of first-level properties to copy over
* @param {object} a target object
* @param {object} b source object
* @param {boolean} [allowDeletion=false] if true, delete property in a if explicitly set to null in b
* @returns {Object} a
*/
exports.selectiveDeepExtend = function (props, a, b, allowDeletion = false) {
// TODO: add support for Arrays to deepExtend
if (Array.isArray(b)) {
throw new TypeError('Arrays are not supported by deepExtend');
}
for (var i = 2; i < arguments.length; i++) {
var other = arguments[i];
for (var p = 0; p < props.length; p++) {
var prop = props[p];
if (other.hasOwnProperty(prop)) {
if (b[prop] && b[prop].constructor === Object) {
if (a[prop] === undefined) {
a[prop] = {};
}
if (a[prop].constructor === Object) {
exports.deepExtend(a[prop], b[prop], false, allowDeletion);
}
else {
if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
delete a[prop];
}
else {
a[prop] = b[prop];
}
}
} else if (Array.isArray(b[prop])) {
throw new TypeError('Arrays are not supported by deepExtend');
} else {
if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
delete a[prop];
}
else {
a[prop] = b[prop];
}
}

for (var p = 0; p < props.length; p++) {
var prop = props[p];
if (b.hasOwnProperty(prop)) {
if (b[prop] && b[prop].constructor === Object) {
if (a[prop] === undefined) {
a[prop] = {};
}
if (a[prop].constructor === Object) {
exports.deepExtend(a[prop], b[prop], false, allowDeletion);
}
else {
copyOrDelete(a, b, prop, allowDeletion);
}
} else if (Array.isArray(b[prop])) {
throw new TypeError('Arrays are not supported by deepExtend');
} else {
copyOrDelete(a, b, prop, allowDeletion);
}
}
}
return a;
};


/**
* Extend object a with selected properties of object b or a series of objects
* Extend object `a` with properties of object `b`, ignoring properties which are explicitly specified
* to be excluded.
*
* The properties of `b` are considered for copying.
* Properties which are themselves objects are are also extended.
* Only properties with defined values are copied
* @param {Array.<string>} props
* @param {Object} a
* @param {Object} b
* @param {boolean} [allowDeletion=false]
*
* @param {Array.<string>} propsToExclude names of properties which should *not* be copied
* @param {Object} a object to extend
* @param {Object} b object to take properties from for extension
* @param {boolean} [allowDeletion=false] if true, delete properties in a that are explicitly set to null in b
* @return {Object} a
*/
exports.selectiveNotDeepExtend = function (props, a, b, allowDeletion = false) {
exports.selectiveNotDeepExtend = function (propsToExclude, a, b, allowDeletion = false) {
// TODO: add support for Arrays to deepExtend
// NOTE: array properties have an else-below; apparently, there is a problem here.
if (Array.isArray(b)) {
throw new TypeError('Arrays are not supported by deepExtend');
}

for (var prop in b) {
if (b.hasOwnProperty(prop)) {
if (props.indexOf(prop) == -1) {
if (b[prop] && b[prop].constructor === Object) {
if (a[prop] === undefined) {
a[prop] = {};
}
if (a[prop].constructor === Object) {
exports.deepExtend(a[prop], b[prop]);
}
else {
if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
delete a[prop];
}
else {
a[prop] = b[prop];
}
}
} else if (Array.isArray(b[prop])) {
a[prop] = [];
for (let i = 0; i < b[prop].length; i++) {
a[prop].push(b[prop][i]);
}
} else {
if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
delete a[prop];
}
else {
a[prop] = b[prop];
}
}
if (!b.hasOwnProperty(prop)) continue; // Handle local properties only
if (propsToExclude.indexOf(prop) !== -1) continue; // In exclusion list, skip

if (b[prop] && b[prop].constructor === Object) {
if (a[prop] === undefined) {
a[prop] = {};
}
if (a[prop].constructor === Object) {
exports.deepExtend(a[prop], b[prop]); // NOTE: allowDeletion not propagated!
}
else {
copyOrDelete(a, b, prop, allowDeletion);
}
} else if (Array.isArray(b[prop])) {
a[prop] = [];
for (let i = 0; i < b[prop].length; i++) {
a[prop].push(b[prop][i]);
}
} else {
copyOrDelete(a, b, prop, allowDeletion);
}
}

return a;
};


/**
* Deep extend an object a with the properties of object b
*
* @param {Object} a
* @param {Object} b
* @param {boolean} [protoExtend] --> optional parameter. If true, the prototype values will also be extended.
* (ie. the options objects that inherit from others will also get the inherited options)
* @param {boolean} [allowDeletion] --> optional parameter. If true, the values of fields that are null will be deleted
* @param {boolean} [protoExtend=false] If true, the prototype values will also be extended.
* (ie. the options objects that inherit from others will also get the inherited options)
* @param {boolean} [allowDeletion=false] If true, the values of fields that are null will be deleted
* @returns {Object}
*/
exports.deepExtend = function (a, b, protoExtend, allowDeletion) {
exports.deepExtend = function (a, b, protoExtend=false, allowDeletion=false) {
for (var prop in b) {
if (b.hasOwnProperty(prop) || protoExtend === true) {
if (b[prop] && b[prop].constructor === Object) {
if (a[prop] === undefined) {
a[prop] = {};
}
if (a[prop].constructor === Object) {
exports.deepExtend(a[prop], b[prop], protoExtend);
exports.deepExtend(a[prop], b[prop], protoExtend); // NOTE: allowDeletion not propagated!
}
else {
if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
delete a[prop];
}
else {
a[prop] = b[prop];
}
copyOrDelete(a, b, prop, allowDeletion);
}
} else if (Array.isArray(b[prop])) {
a[prop] = [];
for (let i = 0; i < b[prop].length; i++) {
a[prop].push(b[prop][i]);
}
} else {
if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
delete a[prop];
}
else {
a[prop] = b[prop];
}
copyOrDelete(a, b, prop, allowDeletion);
}
}
}
return a;
};


/**
* Test whether all elements in two arrays are equal.
* @param {Array} a
Expand Down
Loading

0 comments on commit 398718e

Please sign in to comment.