diff --git a/package.json b/package.json index 5d44721baa..1bd810b272 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "devDependencies": { "closure-util": "0.19.0", + "expect.js": "~0.3.1", "fs-extra": "~0.8.1", "graceful-fs": "~3.0.2", "jquery": "~2.1.1", diff --git a/test/README.md b/test/README.md index d41a58914e..4852c189bc 100644 --- a/test/README.md +++ b/test/README.md @@ -4,9 +4,6 @@ - spec - includes the OpenLayers test/spec files. -- expect-0.2.0 - Minimalistic BDD-style assertion framework. - https://github.com/LearnBoost/expect.js/ - - test-extensions.js - includes OpenLayers-specific extensions to the testing frameworks. diff --git a/test/expect-0.2.0-ol3/expect.js b/test/expect-0.2.0-ol3/expect.js deleted file mode 100644 index d8be00a0f1..0000000000 --- a/test/expect-0.2.0-ol3/expect.js +++ /dev/null @@ -1,1472 +0,0 @@ - -(function (global, module) { - - if ('undefined' == typeof module) { - var module = { exports: {} } - , exports = module.exports - } - - /** - * Exports. - */ - - module.exports = expect; - expect.Assertion = Assertion; - - /** - * Exports version. - */ - - expect.version = '0.1.2'; - - /** - * Possible assertion flags. - */ - - var flags = { - not: ['to', 'be', 'have', 'include', 'only'] - , to: ['be', 'have', 'include', 'only', 'not'] - , only: ['have'] - , have: ['own'] - , be: ['an'] - }; - - function expect (obj) { - return new Assertion(obj); - } - - /** - * Constructor - * - * @api private - */ - - function Assertion (obj, flag, parent) { - this.obj = obj; - this.flags = {}; - - if (undefined != parent) { - this.flags[flag] = true; - - for (var i in parent.flags) { - if (parent.flags.hasOwnProperty(i)) { - this.flags[i] = true; - } - } - } - - var $flags = flag ? flags[flag] : keys(flags) - , self = this - - if ($flags) { - for (var i = 0, l = $flags.length; i < l; i++) { - // avoid recursion - if (this.flags[$flags[i]]) continue; - - var name = $flags[i] - , assertion = new Assertion(this.obj, name, this) - - if ('function' == typeof Assertion.prototype[name]) { - // clone the function, make sure we dont touch the prot reference - var old = this[name]; - this[name] = function () { - return old.apply(self, arguments); - } - - for (var fn in Assertion.prototype) { - if (Assertion.prototype.hasOwnProperty(fn) && fn != name) { - this[name][fn] = bind(assertion[fn], assertion); - } - } - } else { - this[name] = assertion; - } - } - } - }; - - /** - * Performs an assertion - * - * @api private - */ - - Assertion.prototype.assert = function (truth, msg, error) { - var msg = this.flags.not ? error : msg - , ok = this.flags.not ? !truth : truth; - - if (!ok) { - throw new Error(msg.call(this)); - } - - this.and = new Assertion(this.obj); - }; - - /** - * Check if the value is truthy - * - * @api public - */ - - Assertion.prototype.ok = function () { - this.assert( - !!this.obj - , function(){ return 'expected ' + i(this.obj) + ' to be truthy' } - , function(){ return 'expected ' + i(this.obj) + ' to be falsy' }); - }; - - /** - * Assert that the function throws. - * - * @param {Function|RegExp} callback, or regexp to match error string against - * @api public - */ - - Assertion.prototype.throwError = - Assertion.prototype.throwException = function (fn) { - expect(this.obj).to.be.a('function'); - - var thrown = false - , not = this.flags.not - - try { - this.obj(); - } catch (e) { - if ('function' == typeof fn) { - fn(e); - } else if ('object' == typeof fn) { - var subject = 'string' == typeof e ? e : e.message; - if (not) { - expect(subject).to.not.match(fn); - } else { - expect(subject).to.match(fn); - } - } - thrown = true; - } - - if ('object' == typeof fn && not) { - // in the presence of a matcher, ensure the `not` only applies to - // the matching. - this.flags.not = false; - } - - var name = this.obj.name || 'fn'; - this.assert( - thrown - , function(){ return 'expected ' + name + ' to throw an exception' } - , function(){ return 'expected ' + name + ' not to throw an exception' }); - }; - - /** - * Checks if the array is empty. - * - * @api public - */ - - Assertion.prototype.empty = function () { - var expectation; - - if ('object' == typeof this.obj && null !== this.obj && !isArray(this.obj)) { - if ('number' == typeof this.obj.length) { - expectation = !this.obj.length; - } else { - expectation = !keys(this.obj).length; - } - } else { - if ('string' != typeof this.obj) { - expect(this.obj).to.be.an('object'); - } - - expect(this.obj).to.have.property('length'); - expectation = !this.obj.length; - } - - this.assert( - expectation - , function(){ return 'expected ' + i(this.obj) + ' to be empty' } - , function(){ return 'expected ' + i(this.obj) + ' to not be empty' }); - return this; - }; - - /** - * Checks if the obj exactly equals another. - * - * @api public - */ - - Assertion.prototype.be = - Assertion.prototype.equal = function (obj) { - this.assert( - obj === this.obj - , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) } - , function(){ return 'expected ' + i(this.obj) + ' to not equal ' + i(obj) }); - return this; - }; - - function getChildNodes(node, options) { - // check whitespace - if (options && options.includeWhiteSpace) { - return node.childNodes; - } else { - var nodes = []; - for (var i = 0, ii=node.childNodes.length; i < ii; i++ ) { - var child = node.childNodes[i]; - if (child.nodeType == 1) { - // element node, add it - nodes.push(child); - } else if (child.nodeType == 3) { - // text node, add if non empty - if (child.nodeValue && - child.nodeValue.replace(/^\s*(.*?)\s*$/, '$1') != '') { - nodes.push(child); - } - } - } - return nodes; - } - }; - - function assertElementNodesEqual(node1, node2, options, errors) { - var testPrefix = (options && options.prefix === true); - try { - expect(node1.nodeType).to.equal(node2.nodeType); - } catch(e) { - errors.push('nodeType test failed for: ' + node1.nodeName + ' | ' + node2.nodeName + ' | ' + e.message); - } - if (testPrefix) { - try { - expect(node1.nodeName).to.equal(node2.nodeName); - } catch(e) { - errors.push('nodeName test failed for: ' + node1.nodeName + ' | ' + node2.nodeName + ' | ' + e.message); - } - } else { - try { - expect(node1.nodeName.split(':').pop()).to.equal(node2.nodeName.split(':').pop()); - } catch(e) { - errors.push('nodeName test failed for: ' + node1.nodeName + ' | ' + node2.nodeName + ' | ' + e.message); - } - } - // for text nodes compare value - if (node1.nodeType === 3) { - try { - // TODO should we make this optional? - expect(node1.nodeValue.replace(/\s/g, '')).to.equal(node2.nodeValue.replace(/\s/g, '')); - } catch(e) { - errors.push('nodeValue test failed | ' + e.message); - } - } - // for element type nodes compare namespace, attributes, and children - else if (node1.nodeType === 1) { - // test namespace alias and uri - if (node1.prefix || node2.prefix) { - if (testPrefix) { - try { - expect(node1.prefix).to.equal(node2.prefix); - } catch(e) { - errors.push('Prefix test failed for: ' + node1.nodeName + ' | ' + e.message); - } - } - } - if (node1.namespaceURI || node2.namespaceURI) { - try { - expect(node1.namespaceURI).to.equal(node2.namespaceURI); - } catch(e) { - errors.push('namespaceURI test failed for: ' + node1.nodeName + ' | ' + e.message); - } - } - // compare attributes - disregard xmlns given namespace handling above - var node1AttrLen = 0; - var node1Attr = {}; - var node2AttrLen = 0; - var node2Attr = {}; - var ga, ea, gn, en; - var i, ii; - if (node1.attributes) { - for (i=0, ii=node1.attributes.length; i= start && this.obj <= finish - , function(){ return 'expected ' + i(this.obj) + ' to be within ' + range } - , function(){ return 'expected ' + i(this.obj) + ' to not be within ' + range }); - return this; - }; - - /** - * Assert typeof / instance of - * - * @api public - */ - - Assertion.prototype.a = - Assertion.prototype.an = function (type) { - if ('string' == typeof type) { - // proper english in error msg - var n = /^[aeiou]/.test(type) ? 'n' : ''; - - // typeof with support for 'array' - this.assert( - 'array' == type ? isArray(this.obj) : - 'object' == type - ? 'object' == typeof this.obj && null !== this.obj - : type == typeof this.obj - , function(){ return 'expected ' + i(this.obj) + ' to be a' + n + ' ' + type } - , function(){ return 'expected ' + i(this.obj) + ' not to be a' + n + ' ' + type }); - } else { - // instanceof - var name = type.name || 'supplied constructor'; - this.assert( - this.obj instanceof type - , function(){ return 'expected ' + i(this.obj) + ' to be an instance of ' + name } - , function(){ return 'expected ' + i(this.obj) + ' not to be an instance of ' + name }); - } - - return this; - }; - - /** - * Assert numeric value above _n_. - * - * @param {Number} n - * @api public - */ - - Assertion.prototype.greaterThan = - Assertion.prototype.above = function (n) { - this.assert( - this.obj > n - , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n } - , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n }); - return this; - }; - - /** - * Assert numeric value below _n_. - * - * @param {Number} n - * @api public - */ - - Assertion.prototype.lessThan = - Assertion.prototype.below = function (n) { - this.assert( - this.obj < n - , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n } - , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }); - return this; - }; - - /** - * Assert value is within _tol_ of _n_. - * - * @param {Number} n - * @param {Number} tol - * - * @api public - */ - - Assertion.prototype.roughlyEqual = - Assertion.prototype.kindaEqual = function(n, tol) { - this.assert( - Math.abs(this.obj - n) <= tol - , function(){ return 'expected ' + i(this.obj) + ' to be within ' + tol + ' of ' + n } - , function(){ return 'expected ' + i(this.obj) + ' not to be within ' + tol + ' of ' + n }); - return this; - }; - - /** - * Assert string value matches _regexp_. - * - * @param {RegExp} regexp - * @api public - */ - - Assertion.prototype.match = function (regexp) { - this.assert( - regexp.exec(this.obj) - , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp } - , function(){ return 'expected ' + i(this.obj) + ' not to match ' + regexp }); - return this; - }; - - /** - * Assert property "length" exists and has value of _n_. - * - * @param {Number} n - * @api public - */ - - Assertion.prototype.length = function (n) { - expect(this.obj).to.have.property('length'); - var len = this.obj.length; - this.assert( - n == len - , function(){ return 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len } - , function(){ return 'expected ' + i(this.obj) + ' to not have a length of ' + len }); - return this; - }; - - /** - * Assert property _name_ exists, with optional _val_. - * - * @param {String} name - * @param {Mixed} val - * @api public - */ - - Assertion.prototype.property = function (name, val) { - if (this.flags.own) { - this.assert( - Object.prototype.hasOwnProperty.call(this.obj, name) - , function(){ return 'expected ' + i(this.obj) + ' to have own property ' + i(name) } - , function(){ return 'expected ' + i(this.obj) + ' to not have own property ' + i(name) }); - return this; - } - - if (this.flags.not && undefined !== val) { - if (undefined === this.obj[name]) { - throw new Error(i(this.obj) + ' has no property ' + i(name)); - } - } else { - var hasProp; - try { - hasProp = name in this.obj - } catch (e) { - hasProp = undefined !== this.obj[name] - } - - this.assert( - hasProp - , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) } - , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) }); - } - - if (undefined !== val) { - this.assert( - val === this.obj[name] - , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) - + ' of ' + i(val) + ', but got ' + i(this.obj[name]) } - , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) - + ' of ' + i(val) }); - } - - this.obj = this.obj[name]; - return this; - }; - - /** - * Assert that the array contains _obj_ or string contains _obj_. - * - * @param {Mixed} obj|string - * @api public - */ - - Assertion.prototype.string = - Assertion.prototype.contain = function (obj) { - if ('string' == typeof this.obj) { - this.assert( - ~this.obj.indexOf(obj) - , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } - , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); - } else { - this.assert( - ~indexOf(this.obj, obj) - , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) } - , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) }); - } - return this; - }; - - /** - * Assert exact keys or inclusion of keys by using - * the `.own` modifier. - * - * @param {Array|String ...} keys - * @api public - */ - - Assertion.prototype.key = - Assertion.prototype.keys = function ($keys) { - var str - , ok = true; - - $keys = isArray($keys) - ? $keys - : Array.prototype.slice.call(arguments); - - if (!$keys.length) throw new Error('keys required'); - - var actual = keys(this.obj) - , len = $keys.length; - - // Inclusion - ok = every($keys, function (key) { - return ~indexOf(actual, key); - }); - - // Strict - if (!this.flags.not && this.flags.only) { - ok = ok && $keys.length == actual.length; - } - - // Key string - if (len > 1) { - $keys = map($keys, function (key) { - return i(key); - }); - var last = $keys.pop(); - str = $keys.join(', ') + ', and ' + last; - } else { - str = i($keys[0]); - } - - // Form - str = (len > 1 ? 'keys ' : 'key ') + str; - - // Have / include - str = (!this.flags.only ? 'include ' : 'only have ') + str; - - // Assertion - this.assert( - ok - , function(){ return 'expected ' + i(this.obj) + ' to ' + str } - , function(){ return 'expected ' + i(this.obj) + ' to not ' + str }); - - return this; - }; - - /** - * Assert that a sinon spy was called. - * - * @api public - */ - Assertion.prototype.called = - Assertion.prototype.totallyWantsToSpeakToYou = function() { - this.assert( - this.obj.called - , function(){ return 'expected ' + i(this.obj) + ' to be called' } - , function(){ return 'expected ' + i(this.obj) + ' not to be called' }); - return this; - }; - - /** - * Assert a failure. - * - * @param {String ...} custom message - * @api public - */ - Assertion.prototype.fail = function (msg) { - msg = msg || "explicit failure"; - this.assert(false, msg, msg); - return this; - }; - - /** - * Function bind implementation. - */ - - function bind (fn, scope) { - return function () { - return fn.apply(scope, arguments); - } - } - - /** - * Array every compatibility - * - * @see bit.ly/5Fq1N2 - * @api public - */ - - function every (arr, fn, thisObj) { - var scope = thisObj || global; - for (var i = 0, j = arr.length; i < j; ++i) { - if (!fn.call(scope, arr[i], i, arr)) { - return false; - } - } - return true; - }; - - /** - * Array indexOf compatibility. - * - * @see bit.ly/a5Dxa2 - * @api public - */ - - function indexOf (arr, o, i) { - if (Array.prototype.indexOf) { - return Array.prototype.indexOf.call(arr, o, i); - } - - if (arr.length === undefined) { - return -1; - } - - for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0 - ; i < j && arr[i] !== o; i++); - - return j <= i ? -1 : i; - }; - - // https://gist.github.com/1044128/ - var getOuterHTML = function(element) { - if ('outerHTML' in element) return element.outerHTML; - var ns = "http://www.w3.org/1999/xhtml"; - var container = document.createElementNS(ns, '_'); - var elemProto = (window.HTMLElement || window.Element).prototype; - var html; - if (typeof XMLSerializer != 'undefined') { - var xmlSerializer = new XMLSerializer(); - return xmlSerializer.serializeToString(element); - } else { - container.appendChild(element.cloneNode(false)); - html = container.innerHTML.replace('><', '>' + element.innerHTML + '<'); - container.innerHTML = ''; - return html; - } - }; - - // Returns true if object is a DOM element. - var isDOMElement = function (object) { - if (typeof HTMLElement === 'object') { - return object instanceof HTMLElement; - } else { - return object && - typeof object === 'object' && - object.nodeType === 1 && - typeof object.nodeName === 'string'; - } - }; - - /** - * Inspects an object. - * - * @see taken from node.js `util` module (copyright Joyent, MIT license) - * @api private - */ - - function i (obj, showHidden, depth) { - var seen = []; - - function stylize (str) { - return str; - }; - - function format (value, recurseTimes) { - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it - if (value && typeof value.inspect === 'function' && - // Filter out the util module, it's inspect function is special - value !== exports && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - return value.inspect(recurseTimes); - } - - // Primitive types cannot have properties - switch (typeof value) { - case 'undefined': - return stylize('undefined', 'undefined'); - - case 'string': - var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return stylize(simple, 'string'); - - case 'number': - return stylize('' + value, 'number'); - - case 'boolean': - return stylize('' + value, 'boolean'); - } - // For some reason typeof null is "object", so special case here. - if (value === null) { - return stylize('null', 'null'); - } - - if (isDOMElement(value)) { - return getOuterHTML(value); - } - - // Look up the keys of the object. - var visible_keys = keys(value); - var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys; - - // Functions without properties can be shortcutted. - if (typeof value === 'function' && $keys.length === 0) { - if (isRegExp(value)) { - return stylize('' + value, 'regexp'); - } else { - var name = value.name ? ': ' + value.name : ''; - return stylize('[Function' + name + ']', 'special'); - } - } - - // Dates without properties can be shortcutted - if (isDate(value) && $keys.length === 0) { - return stylize(value.toUTCString(), 'date'); - } - - var base, type, braces; - // Determine the object type - if (isArray(value)) { - type = 'Array'; - braces = ['[', ']']; - } else { - type = 'Object'; - braces = ['{', '}']; - } - - // Make functions say that they are functions - if (typeof value === 'function') { - var n = value.name ? ': ' + value.name : ''; - base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; - } else { - base = ''; - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + value.toUTCString(); - } - - if ($keys.length === 0) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return stylize('' + value, 'regexp'); - } else { - return stylize('[Object]', 'special'); - } - } - - seen.push(value); - - var output = map($keys, function (key) { - var name, str; - if (value.__lookupGetter__) { - if (value.__lookupGetter__(key)) { - if (value.__lookupSetter__(key)) { - str = stylize('[Getter/Setter]', 'special'); - } else { - str = stylize('[Getter]', 'special'); - } - } else { - if (value.__lookupSetter__(key)) { - str = stylize('[Setter]', 'special'); - } - } - } - if (indexOf(visible_keys, key) < 0) { - name = '[' + key + ']'; - } - if (!str) { - if (indexOf(seen, value[key]) < 0) { - if (recurseTimes === null) { - str = format(value[key]); - } else { - str = format(value[key], recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (isArray(value)) { - str = map(str.split('\n'), function (line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + map(str.split('\n'), function (line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = stylize('[Circular]', 'special'); - } - } - if (typeof name === 'undefined') { - if (type === 'Array' && key.match(/^\d+$/)) { - return str; - } - name = json.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = stylize(name, 'string'); - } - } - - return name + ': ' + str; - }); - - seen.pop(); - - var numLinesEst = 0; - var length = reduce(output, function (prev, cur) { - numLinesEst++; - if (indexOf(cur, '\n') >= 0) numLinesEst++; - return prev + cur.length + 1; - }, 0); - - if (length > 50) { - output = braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; - - } else { - output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; - } - - return output; - } - return format(obj, (typeof depth === 'undefined' ? 2 : depth)); - }; - - function isArray (ar) { - return Object.prototype.toString.call(ar) == '[object Array]'; - }; - - function isRegExp(re) { - var s; - try { - s = '' + re; - } catch (e) { - return false; - } - - return re instanceof RegExp || // easy case - // duck-type for context-switching evalcx case - typeof(re) === 'function' && - re.constructor.name === 'RegExp' && - re.compile && - re.test && - re.exec && - s.match(/^\/.*\/[gim]{0,3}$/); - }; - - function isDate(d) { - if (d instanceof Date) return true; - return false; - }; - - function keys (obj) { - if (Object.keys) { - return Object.keys(obj); - } - - var keys = []; - - for (var i in obj) { - if (Object.prototype.hasOwnProperty.call(obj, i)) { - keys.push(i); - } - } - - return keys; - } - - function map (arr, mapper, that) { - if (Array.prototype.map) { - return Array.prototype.map.call(arr, mapper, that); - } - - var other= new Array(arr.length); - - for (var i= 0, n = arr.length; i= 2) { - var rv = arguments[1]; - } else { - do { - if (i in this) { - rv = this[i++]; - break; - } - - // if array contains no values, no initial value to return - if (++i >= len) - throw new TypeError(); - } while (true); - } - - for (; i < len; i++) { - if (i in this) - rv = fun.call(null, rv, this[i], i, this); - } - - return rv; - }; - - /** - * Asserts deep equality - * - * @see taken from node.js `assert` module (copyright Joyent, MIT license) - * @api private - */ - - expect.eql = function eql (actual, expected) { - // 7.1. All identical values are equivalent, as determined by ===. - if (actual === expected) { - return true; - } else if ('undefined' != typeof Buffer - && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { - if (actual.length != expected.length) return false; - - for (var i = 0; i < actual.length; i++) { - if (actual[i] !== expected[i]) return false; - } - - return true; - - // 7.2. If the expected value is a Date object, the actual value is - // equivalent if it is also a Date object that refers to the same time. - } else if (actual instanceof Date && expected instanceof Date) { - return actual.getTime() === expected.getTime(); - - // 7.3. Other pairs that do not both pass typeof value == "object", - // equivalence is determined by ==. - } else if (typeof actual != 'object' && typeof expected != 'object') { - if (typeof actual == 'number' && isNaN(actual) && - typeof expected == 'number' && isNaN(expected)) { - return true; - } else { - return actual == expected; - } - - // 7.4. For all other Object pairs, including Array objects, equivalence is - // determined by having the same number of owned properties (as verified - // with Object.prototype.hasOwnProperty.call), the same set of keys - // (although not necessarily the same order), equivalent values for every - // corresponding key, and an identical "prototype" property. Note: this - // accounts for both named and indexed properties on Arrays. - } else { - return objEquiv(actual, expected); - } - } - - function isUndefinedOrNull (value) { - return value === null || value === undefined; - } - - function isArguments (object) { - return Object.prototype.toString.call(object) == '[object Arguments]'; - } - - function objEquiv (a, b) { - if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) - return false; - // an identical "prototype" property. - if (a.prototype !== b.prototype) return false; - //~~~I've managed to break Object.keys through screwy arguments passing. - // Converting to array solves the problem. - if (isArguments(a)) { - if (!isArguments(b)) { - return false; - } - a = pSlice.call(a); - b = pSlice.call(b); - return expect.eql(a, b); - } - try{ - var ka = keys(a), - kb = keys(b), - key, i; - } catch (e) {//happens when one is a string literal and the other isn't - return false; - } - // having the same number of owned properties (keys incorporates hasOwnProperty) - if (ka.length != kb.length) - return false; - //the same set of keys (although not necessarily the same order), - ka.sort(); - kb.sort(); - //~~~cheap key test - for (i = ka.length - 1; i >= 0; i--) { - if (ka[i] != kb[i]) - return false; - } - //equivalent values for every corresponding key, and - //~~~possibly expensive deep test - for (i = ka.length - 1; i >= 0; i--) { - key = ka[i]; - if (!expect.eql(a[key], b[key])) - return false; - } - return true; - } - - var json = (function () { - "use strict"; - - if ('object' == typeof JSON && JSON.parse && JSON.stringify) { - return { - parse: nativeJSON.parse - , stringify: nativeJSON.stringify - } - } - - var JSON = {}; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - function date(d, key) { - return isFinite(d.valueOf()) ? - d.getUTCFullYear() + '-' + - f(d.getUTCMonth() + 1) + '-' + - f(d.getUTCDate()) + 'T' + - f(d.getUTCHours()) + ':' + - f(d.getUTCMinutes()) + ':' + - f(d.getUTCSeconds()) + 'Z' : null; - }; - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - - // If the string contains no control characters, no quote characters, and no - // backslash characters, then we can safely slap some quotes around it. - // Otherwise we must also replace the offending characters with safe escape - // sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - - // Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - - // If the value has a toJSON method, call it to obtain a replacement value. - - if (value instanceof Date) { - value = date(key); - } - - // If we were called with a replacer function, then call the replacer to - // obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - - // What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - - // JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - - // If the value is a boolean or null, convert it to a string. Note: - // typeof null does not produce 'null'. The case is included here in - // the remote chance that this gets fixed someday. - - return String(value); - - // If the type is 'object', we might be dealing with an object or an array or - // null. - - case 'object': - - // Due to a specification blunder in ECMAScript, typeof null is 'object', - // so watch out for that case. - - if (!value) { - return 'null'; - } - - // Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - - // Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - - // The value is an array. Stringify every element. Use null as a placeholder - // for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - - // Join all of the elements together, separated with commas, and wrap them in - // brackets. - - v = partial.length === 0 ? '[]' : gap ? - '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - - // If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - - // Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - - // Join all of the member texts together, separated with commas, - // and wrap them in braces. - - v = partial.length === 0 ? '{}' : gap ? - '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - - // If the JSON object does not yet have a stringify method, give it one. - - JSON.stringify = function (value, replacer, space) { - - // The stringify method takes a value and an optional replacer, and an optional - // space parameter, and returns a JSON text. The replacer can be a function - // that can replace values, or an array of strings that will select the keys. - // A default replacer method can be provided. Use of the space parameter can - // produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - - // If the space parameter is a number, make an indent string containing that - // many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - - // If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - - // If there is a replacer, it must be a function or an array. - // Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - - // Make a fake root object containing our value under the key of ''. - // Return the result of stringifying the value. - - return str('', {'': value}); - }; - - // If the JSON object does not yet have a parse method, give it one. - - JSON.parse = function (text, reviver) { - // The parse method takes a text and an optional reviver function, and returns - // a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - - // The walk method is used to recursively walk the resulting structure so - // that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - - // Parsing happens in four stages. In the first stage, we replace certain - // Unicode characters with escape sequences. JavaScript handles many characters - // incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - - // In the second stage, we run the text against regular expressions that look - // for non-JSON patterns. We are especially concerned with '()' and 'new' - // because they can cause invocation, and '=' because it can cause mutation. - // But just to be safe, we want to reject all unexpected forms. - - // We split the second stage into 4 regexp operations in order to work around - // crippling inefficiencies in IE's and Safari's regexp engines. First we - // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we - // replace all simple value tokens with ']' characters. Third, we delete all - // open brackets that follow a colon or comma or that begin the text. Finally, - // we look to see that the remaining characters are only whitespace or ']' or - // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - - // In the third stage we use the eval function to compile the text into a - // JavaScript structure. The '{' operator is subject to a syntactic ambiguity - // in JavaScript: it can begin a block or an object literal. We wrap the text - // in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - - // In the optional fourth stage, we recursively walk the new structure, passing - // each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } - - // If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - - return JSON; - })(); - - if ('undefined' != typeof window) { - window.expect = module.exports; - } - -})( - this - , 'undefined' != typeof module ? module : {} - , 'undefined' != typeof exports ? exports : {} -); diff --git a/test/index.html b/test/index.html index a4afad38d8..09681d5542 100644 --- a/test/index.html +++ b/test/index.html @@ -10,8 +10,7 @@
- - + diff --git a/test/spec/ol/expect.test.js b/test/spec/ol/expect.test.js index dee09b79c6..98378d4b3a 100644 --- a/test/spec/ol/expect.test.js +++ b/test/spec/ol/expect.test.js @@ -3,6 +3,21 @@ goog.provide('ol.test.expect.js'); describe('expect.js', function() { + describe('arreqlNaN', function() { + + it('considers NaN in array to be equal', function() { + expect([1, NaN, 2]).to.arreqlNaN([1, NaN, 2]); + expect([1, NaN, 2]).not.to.arreqlNaN([1, 1.5, 2]); + }); + + it('allows a mix of number and string', function() { + expect([1, NaN, 'foo']).to.arreqlNaN([1, NaN, 'foo']); + expect([1, NaN, 'foo']).not.to.arreqlNaN([1, NaN, 'bar']); + expect([1, NaN]).not.to.arreqlNaN([1, 'foo']); + }); + + }); + describe('roughlyEqual', function() { it('can tell the difference between 1 and 3', function() { diff --git a/test/spec/ol/structs/buffer.test.js b/test/spec/ol/structs/buffer.test.js index d91e37a4d7..94d24cec86 100644 --- a/test/spec/ol/structs/buffer.test.js +++ b/test/spec/ol/structs/buffer.test.js @@ -260,31 +260,31 @@ describe('ol.structs.Buffer', function() { it('allows multiple adds and removes', function() { var b = new ol.structs.Buffer(new Array(8), 0); expect(b.add([0, 1])).to.be(0); - expect(b.getArray()).to.eql([0, 1, NaN, NaN, NaN, NaN, NaN, NaN]); + expect(b.getArray()).to.arreqlNaN([0, 1, NaN, NaN, NaN, NaN, NaN, NaN]); expect(b.getCount()).to.be(2); expect(b.add([2, 3, 4, 5])).to.be(2); - expect(b.getArray()).to.eql([0, 1, 2, 3, 4, 5, NaN, NaN]); + expect(b.getArray()).to.arreqlNaN([0, 1, 2, 3, 4, 5, NaN, NaN]); expect(b.getCount()).to.be(6); expect(b.add([6, 7])).to.be(6); expect(b.getArray()).to.eql([0, 1, 2, 3, 4, 5, 6, 7]); expect(b.getCount()).to.be(8); b.remove(2, 2); - expect(b.getArray()).to.eql([0, 1, NaN, NaN, 4, 5, 6, 7]); + expect(b.getArray()).to.arreqlNaN([0, 1, NaN, NaN, 4, 5, 6, 7]); expect(b.getCount()).to.be(6); expect(b.add([8, 9])).to.be(2); expect(b.getArray()).to.eql([0, 1, 8, 9, 4, 5, 6, 7]); expect(b.getCount()).to.be(8); b.remove(1, 1); - expect(b.getArray()).to.eql([0, NaN, 8, 9, 4, 5, 6, 7]); + expect(b.getArray()).to.arreqlNaN([0, NaN, 8, 9, 4, 5, 6, 7]); expect(b.getCount()).to.be(7); b.remove(4, 4); - expect(b.getArray()).to.eql([0, NaN, 8, 9, NaN, NaN, NaN, NaN]); + expect(b.getArray()).to.arreqlNaN([0, NaN, 8, 9, NaN, NaN, NaN, NaN]); expect(b.getCount()).to.be(3); expect(b.add([10, 11, 12])).to.be(4); - expect(b.getArray()).to.eql([0, NaN, 8, 9, 10, 11, 12, NaN]); + expect(b.getArray()).to.arreqlNaN([0, NaN, 8, 9, 10, 11, 12, NaN]); expect(b.getCount()).to.be(6); expect(b.add([13])).to.be(1); - expect(b.getArray()).to.eql([0, 13, 8, 9, 10, 11, 12, NaN]); + expect(b.getArray()).to.arreqlNaN([0, 13, 8, 9, 10, 11, 12, NaN]); expect(b.getCount()).to.be(7); }); diff --git a/test/test-extensions.js b/test/test-extensions.js index 156f307752..bde60a7384 100644 --- a/test/test-extensions.js +++ b/test/test-extensions.js @@ -50,4 +50,261 @@ afterLoad('xml', path, next); }; + + // extensions to expect.js + var expect = global.expect; + + + /** + * Assert value is within some tolerance of a number. + * @param {Number} n Number. + * @param {Number} tol Tolerance. + */ + expect.Assertion.prototype.roughlyEqual = + expect.Assertion.prototype.kindaEqual = function(n, tol) { + this.assert( + Math.abs(this.obj - n) <= tol, + function() { + return 'expected ' + expect.stringify(this.obj) + ' to be within ' + + tol + ' of ' + n; + }, + function() { + return 'expected ' + expect.stringify(this.obj) + + ' not to be within ' + tol + ' of ' + n; + }); + return this; + }; + + + /** + * Assert that a sinon spy was called. + */ + expect.Assertion.prototype.called = + expect.Assertion.prototype.totallyWantsToSpeakToYou = function() { + this.assert( + this.obj.called, + function() { + return 'expected ' + expect.stringify(this.obj) + ' to be called'; + }, + function() { + return 'expected ' + expect.stringify(this.obj) + ' not to be called'; + }); + return this; + }; + + + function getChildNodes(node, options) { + // check whitespace + if (options && options.includeWhiteSpace) { + return node.childNodes; + } else { + var nodes = []; + for (var i = 0, ii=node.childNodes.length; i < ii; i++ ) { + var child = node.childNodes[i]; + if (child.nodeType == 1) { + // element node, add it + nodes.push(child); + } else if (child.nodeType == 3) { + // text node, add if non empty + if (child.nodeValue && + child.nodeValue.replace(/^\s*(.*?)\s*$/, '$1') != '') { + nodes.push(child); + } + } + } + return nodes; + } + }; + + function assertElementNodesEqual(node1, node2, options, errors) { + var testPrefix = (options && options.prefix === true); + try { + expect(node1.nodeType).to.equal(node2.nodeType); + } catch(e) { + errors.push('nodeType test failed for: ' + node1.nodeName + ' | ' + node2.nodeName + ' | ' + e.message); + } + if (testPrefix) { + try { + expect(node1.nodeName).to.equal(node2.nodeName); + } catch(e) { + errors.push('nodeName test failed for: ' + node1.nodeName + ' | ' + node2.nodeName + ' | ' + e.message); + } + } else { + try { + expect(node1.nodeName.split(':').pop()).to.equal(node2.nodeName.split(':').pop()); + } catch(e) { + errors.push('nodeName test failed for: ' + node1.nodeName + ' | ' + node2.nodeName + ' | ' + e.message); + } + } + // for text nodes compare value + if (node1.nodeType === 3) { + try { + // TODO should we make this optional? + expect(node1.nodeValue.replace(/\s/g, '')).to.equal(node2.nodeValue.replace(/\s/g, '')); + } catch(e) { + errors.push('nodeValue test failed | ' + e.message); + } + } + // for element type nodes compare namespace, attributes, and children + else if (node1.nodeType === 1) { + // test namespace alias and uri + if (node1.prefix || node2.prefix) { + if (testPrefix) { + try { + expect(node1.prefix).to.equal(node2.prefix); + } catch(e) { + errors.push('Prefix test failed for: ' + node1.nodeName + ' | ' + e.message); + } + } + } + if (node1.namespaceURI || node2.namespaceURI) { + try { + expect(node1.namespaceURI).to.equal(node2.namespaceURI); + } catch(e) { + errors.push('namespaceURI test failed for: ' + node1.nodeName + ' | ' + e.message); + } + } + // compare attributes - disregard xmlns given namespace handling above + var node1AttrLen = 0; + var node1Attr = {}; + var node2AttrLen = 0; + var node2Attr = {}; + var ga, ea, gn, en; + var i, ii; + if (node1.attributes) { + for (i=0, ii=node1.attributes.length; i