var jsdom_global = require('jsdom-global'); var assert = require('assert'); var util = require('../lib/util'); var moment = require('../lib//module/moment'); var ASPDateRegex = /^\/?Date\((\-?\d+)/i; 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 describe('recursiveDOMDelete', function () { beforeEach(function() { this.jsdom_global = jsdom_global(); }); afterEach(function() { this.jsdom_global(); }); it('removes children', function () { var root = document.createElement("div"); // Create children for root var parent = document.createElement("div"); var parentSibiling = document.createElement("div"); // Attach parents to root root.appendChild(parent); root.appendChild(parentSibiling); // Create children for the respective parents var child = document.createElement("div"); var childSibling = document.createElement("div"); // Attach children to parents parent.appendChild(child); parentSibiling.appendChild(childSibling); util.recursiveDOMDelete(root); assert.equal(root.children.length, 0); assert.equal(parent.children.length, 0); assert.equal(parentSibiling.children.length, 0); assert.equal(child.children.length, 0); assert.equal(childSibling.children.length, 0); }); }); describe('isDate', function () { it('identifies a Date', function () { assert(util.isDate(new Date())); }); it('identifies an ASPDate as String', function () { assert(util.isDate('Date(1198908717056)')); }); it('identifies a date string', function () { assert(util.isDate('1995-01-01')); }); it('identifies a date string', function () { assert.equal(util.isDate(''), false); }); it('identifies non-dates', function () { assert.equal(util.isDate(null), false); assert.equal(util.isDate(undefined), false); assert.equal(util.isDate([1, 2, 3]), false); assert.equal(util.isDate({a: 42}), false); assert.equal(util.isDate(42), false); assert.equal(util.isDate('meow'), false); }); }); describe('convert', function () { it('handles null', function () { assert.equal(util.convert(null), null); }); it('handles undefined', function () { assert.equal(util.convert(undefined), undefined); }); it('undefined type returns original object', function () { assert.deepEqual(util.convert({}), {}); }); it('non-string type throws', function () { assert.throws(function () {util.convert({}, {});}, Error, null); }); it('converts to boolean', function () { assert(util.convert({}, 'boolean')); }); it('converts to number', function () { assert.equal(typeof util.convert('1198908717056', 'number'), "number"); }); it('converts to String', function () { assert.equal(typeof util.convert({}, 'string'), "string"); }); it('converts to Date from Number', function () { assert(util.convert(1198908717056, 'Date') instanceof Date); }); it('converts to Date from String', function () { assert(util.convert('1198908717056', 'Date') instanceof Date); }); it('converts to Date from Moment', function () { assert(util.convert(new moment(), 'Date') instanceof Date); }); it('throws when converting unknown object to Date', function () { assert.throws(function () {util.convert({}, 'Date');}, Error, null); }); xit('converts to Moment from Numbern - Throws a deprecation warning', function () { assert(util.convert(1198908717056, 'Moment') instanceof moment); }); it('converts to Moment from String', function () { assert(util.convert('1198908717056', 'Moment') instanceof moment); }); it('converts to Moment from Date', function () { assert(util.convert(new Date(), 'Moment') instanceof moment); }); it('converts to Moment from Moment', function () { assert(util.convert(new moment(), 'Moment') instanceof moment); }); it('throws when converting unknown object to Moment', function () { assert.throws(function () {util.convert({}, 'Moment');}, Error, null); }); it('converts to ISODate from Number', function () { assert(util.convert(1198908717056, 'ISODate') instanceof Date); }); it('converts to ISODate from String', function () { assert.equal(typeof util.convert('1995-01-01', 'ISODate'), 'string'); }); it('converts to ISODate from Date - Throws a deprecation warning', function () { assert.equal(typeof util.convert(new Date(), 'ISODate'), 'string'); }); it('converts to ISODate from Moment', function () { assert.equal(typeof util.convert(new moment(), 'ISODate'), 'string'); }); it('throws when converting unknown object to ISODate', function () { assert.throws(function () {util.convert({}, 'ISODate');}, Error, null); }); it('converts to ASPDate from Number', function () { assert(ASPDateRegex.test(util.convert(1198908717056, 'ASPDate'))); }); it('converts to ASPDate from String', function () { assert(ASPDateRegex.test(util.convert('1995-01-01', 'ASPDate'))); }); it('converts to ASPDate from Date', function () { assert(ASPDateRegex.test(util.convert(new Date(), 'ASPDate'))); }); it('converts to ASPDate from ASPDate', function () { assert(ASPDateRegex.test(util.convert('/Date(12344444)/', 'ASPDate'))); }); xit('converts to ASPDate from Moment - skipped, because it fails', function () { assert(ASPDateRegex.test(util.convert(new moment(), 'ASPDate'))); }); it('throws when converting unknown object to ASPDate', function () { assert.throws(function () {util.convert({}, 'ASPDate');}, Error, null); }); it('throws when converting unknown type', function () { assert.throws(function () {util.convert({}, 'UnknownType');}, Error, null); }); }); describe('getType', function () { it('of object null is null', function () { assert.equal(util.getType(null), 'null'); }); it('of object Boolean is Boolean', function () { function Tester () {} Tester.prototype = Object.create(Boolean.prototype); assert.equal(util.getType(new Tester('true')), 'Boolean'); }); it('of object Number is Number', function () { function Tester () {} Tester.prototype = Object.create(Number.prototype); assert.equal(util.getType(new Tester(1)), 'Number'); }); it('of object String is String', function () { function Tester () {} Tester.prototype = Object.create(String.prototype); assert.equal(util.getType(new Tester('stringy!')), 'String'); }); it('of object Array is Array', function () { assert.equal(util.getType(new Array([])), 'Array'); }); it('of object Date is Date', function () { assert.equal(util.getType(new Date()), 'Date'); }); it('of object any other type is Object', function () { assert.equal(util.getType({}), 'Object'); }); it('of number is Number', function () { assert.equal(util.getType(1), 'Number'); }); it('of boolean is Boolean', function () { assert.equal(util.getType(true), 'Boolean'); }); it('of string is String', function () { assert.equal(util.getType('string'), 'String'); }); it('of undefined is undefined', function () { assert.equal(util.getType(), 'undefined'); }); }); describe('easingFunctions', function () { it('take a number and output a number', function () { for (var key in util.easingFunctions) { if (util.easingFunctions.hasOwnProperty(key)) { assert.equal(typeof util.easingFunctions[key](1), 'number'); assert.equal(typeof util.easingFunctions[key](0.2), 'number'); } } }); }); describe('getScrollBarWidth', function () { beforeEach(function() { this.jsdom_global = jsdom_global(); }); afterEach(function() { this.jsdom_global(); }); it('returns 0 when there is no content', function () { assert.equal(util.getScrollBarWidth(), 0); }); }); describe('equalArray', function () { it('arrays of different lengths are not equal', function () { assert.equal(util.equalArray([1, 2, 3], [1, 2]), false) }); it('arrays with different content are not equal', function () { assert.equal(util.equalArray([1, 2, 3], [3, 2, 1]), false) }); it('same content arrays are equal', function () { assert(util.equalArray([1, 2, 3], [1, 2, 3])) }); it('empty arrays are equal', function () { assert(util.equalArray([], [])) }); it('the same array is equal', function () { var arr = [1, 2, 3]; assert(util.equalArray(arr, arr)) }); }); describe('asBoolean', function () { it('resolves value from a function', function () { assert(util.option.asBoolean(function () {return true}, false)); }); it('returns default value for null', function () { assert(util.option.asBoolean(null, true)); }); it('returns true for other types', function () { assert(util.option.asBoolean('should be true', false)); }); it('returns null for undefined', function () { assert.equal(util.option.asBoolean(), null); }); }); describe('asNumber', function () { it('resolves value from a function', function () { assert.equal(util.option.asNumber(function () {return 777}, 13), 777); }); it('returns default value for null', function () { assert.equal(util.option.asNumber(null, 13), 13); }); it('returns number for other types', function () { assert.equal(util.option.asNumber('777', 13), 777); }); it('returns default for NaN', function () { assert.equal(util.option.asNumber(NaN, 13), 13); }); it('returns null for undefined', function () { assert.equal(util.option.asNumber(), null); }); }); describe('asString', function () { it('resolves value from a function', function () { assert.equal(util.option.asString(function () {return 'entered'}, 'default'), 'entered'); }); it('returns default value for null', function () { assert.equal(util.option.asString(null, 'default'), 'default'); }); it('returns string for other types', function () { assert.equal(util.option.asString(777, 'default'), '777'); }); it('returns default for undefined', function () { assert.equal(util.option.asString(undefined, 'default'), 'default'); }); it('returns null for undefined', function () { assert.equal(util.option.asString(), null); }); }); describe('asSize', function () { it('resolves value from a function', function () { assert.equal(util.option.asSize(function () {return '100px'}, '50px'), '100px'); }); it('returns default value for null', function () { assert.equal(util.option.asSize(null, '50px'), '50px'); }); it('returns string with px for other number', function () { assert.equal(util.option.asSize(100, '50px'), '100px'); }); it('returns default for undefined', function () { assert.equal(util.option.asSize(undefined, '50px'), '50px'); }); it('returns null for undefined', function () { assert.equal(util.option.asSize(), null); }); }); describe('asElement', function () { before(function() { this.jsdom_global = jsdom_global(); this.value = document.createElement("div"); this.defaultValue = document.createElement("div"); }); it('resolves value from a function', function () { var me = this; assert.equal(util.option.asElement(function () {return me.value}, this.defaultValue), this.value); }); it('returns Element', function () { assert.equal(util.option.asElement(this.value, this.defaultValue), this.value); }); it('returns default value for null', function () { assert.equal(util.option.asElement(null, this.defaultValue), this.defaultValue); }); it('returns null for undefined', function () { assert.equal(util.option.asElement(), null); }); }); describe('binarySearchValue', function () { it('Finds center target on odd sized array', function () { assert.equal( util.binarySearchValue( [{id: 'a', val: 0}, {id: 'b', val: 1}, {id: 'c', val: 2}], 1, 'val' ), 1 ); }); it('Finds target on odd sized array', function () { assert.equal( util.binarySearchValue( [{id: 'a', val: 0}, {id: 'b', val: 1}, {id: 'c', val: 2}], 2, 'val' ), 2 ); }); it('Cannot find target', function () { assert.equal( util.binarySearchValue( [{id: 'a', val: 0}, {id: 'b', val: 1}, {id: 'c', val: 2}], 7, 'val' ), -1 ); }); }); });