1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/test/assert.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,357 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 +"use strict"; 1.8 + 1.9 +module.metadata = { 1.10 + "stability": "unstable" 1.11 +}; 1.12 + 1.13 +const { isFunction, isNull, isObject, isString, 1.14 + isRegExp, isArray, isDate, isPrimitive, 1.15 + isUndefined, instanceOf, source } = require("../lang/type"); 1.16 + 1.17 +/** 1.18 + * The `AssertionError` is defined in assert. 1.19 + * @extends Error 1.20 + * @example 1.21 + * new assert.AssertionError({ 1.22 + * message: message, 1.23 + * actual: actual, 1.24 + * expected: expected 1.25 + * }) 1.26 + */ 1.27 +function AssertionError(options) { 1.28 + let assertionError = Object.create(AssertionError.prototype); 1.29 + 1.30 + if (isString(options)) 1.31 + options = { message: options }; 1.32 + if ("actual" in options) 1.33 + assertionError.actual = options.actual; 1.34 + if ("expected" in options) 1.35 + assertionError.expected = options.expected; 1.36 + if ("operator" in options) 1.37 + assertionError.operator = options.operator; 1.38 + 1.39 + assertionError.message = options.message; 1.40 + assertionError.stack = new Error().stack; 1.41 + return assertionError; 1.42 +} 1.43 +AssertionError.prototype = Object.create(Error.prototype, { 1.44 + constructor: { value: AssertionError }, 1.45 + name: { value: "AssertionError", enumerable: true }, 1.46 + toString: { value: function toString() { 1.47 + let value; 1.48 + if (this.message) { 1.49 + value = this.name + " : " + this.message; 1.50 + } 1.51 + else { 1.52 + value = [ 1.53 + this.name + " : ", 1.54 + source(this.expected), 1.55 + this.operator, 1.56 + source(this.actual) 1.57 + ].join(" "); 1.58 + } 1.59 + return value; 1.60 + }} 1.61 +}); 1.62 +exports.AssertionError = AssertionError; 1.63 + 1.64 +function Assert(logger) { 1.65 + let assert = Object.create(Assert.prototype, { _log: { value: logger }}); 1.66 + 1.67 + assert.fail = assert.fail.bind(assert); 1.68 + assert.pass = assert.pass.bind(assert); 1.69 + 1.70 + return assert; 1.71 +} 1.72 + 1.73 +Assert.prototype = { 1.74 + fail: function fail(e) { 1.75 + if (!e || typeof(e) !== 'object') { 1.76 + this._log.fail(e); 1.77 + return; 1.78 + } 1.79 + let message = e.message; 1.80 + try { 1.81 + if ('operator' in e) { 1.82 + message += [ 1.83 + " -", 1.84 + source(e.expected), 1.85 + e.operator, 1.86 + source(e.actual) 1.87 + ].join(" "); 1.88 + } 1.89 + } 1.90 + catch(e) {} 1.91 + this._log.fail(message); 1.92 + }, 1.93 + pass: function pass(message) { 1.94 + this._log.pass(message); 1.95 + }, 1.96 + error: function error(e) { 1.97 + this._log.exception(e); 1.98 + }, 1.99 + ok: function ok(value, message) { 1.100 + if (!!!value) { 1.101 + this.fail({ 1.102 + actual: value, 1.103 + expected: true, 1.104 + message: message, 1.105 + operator: "==" 1.106 + }); 1.107 + } 1.108 + else { 1.109 + this.pass(message); 1.110 + } 1.111 + }, 1.112 + 1.113 + /** 1.114 + * The equality assertion tests shallow, coercive equality with `==`. 1.115 + * @example 1.116 + * assert.equal(1, 1, "one is one"); 1.117 + */ 1.118 + equal: function equal(actual, expected, message) { 1.119 + if (actual == expected) { 1.120 + this.pass(message); 1.121 + } 1.122 + else { 1.123 + this.fail({ 1.124 + actual: actual, 1.125 + expected: expected, 1.126 + message: message, 1.127 + operator: "==" 1.128 + }); 1.129 + } 1.130 + }, 1.131 + 1.132 + /** 1.133 + * The non-equality assertion tests for whether two objects are not equal 1.134 + * with `!=`. 1.135 + * @example 1.136 + * assert.notEqual(1, 2, "one is not two"); 1.137 + */ 1.138 + notEqual: function notEqual(actual, expected, message) { 1.139 + if (actual != expected) { 1.140 + this.pass(message); 1.141 + } 1.142 + else { 1.143 + this.fail({ 1.144 + actual: actual, 1.145 + expected: expected, 1.146 + message: message, 1.147 + operator: "!=", 1.148 + }); 1.149 + } 1.150 + }, 1.151 + 1.152 + /** 1.153 + * The equivalence assertion tests a deep (with `===`) equality relation. 1.154 + * @example 1.155 + * assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects") 1.156 + */ 1.157 + deepEqual: function deepEqual(actual, expected, message) { 1.158 + if (isDeepEqual(actual, expected)) { 1.159 + this.pass(message); 1.160 + } 1.161 + else { 1.162 + this.fail({ 1.163 + actual: actual, 1.164 + expected: expected, 1.165 + message: message, 1.166 + operator: "deepEqual" 1.167 + }); 1.168 + } 1.169 + }, 1.170 + 1.171 + /** 1.172 + * The non-equivalence assertion tests for any deep (with `===`) inequality. 1.173 + * @example 1.174 + * assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }), 1.175 + * "object's inherit from different prototypes"); 1.176 + */ 1.177 + notDeepEqual: function notDeepEqual(actual, expected, message) { 1.178 + if (!isDeepEqual(actual, expected)) { 1.179 + this.pass(message); 1.180 + } 1.181 + else { 1.182 + this.fail({ 1.183 + actual: actual, 1.184 + expected: expected, 1.185 + message: message, 1.186 + operator: "notDeepEqual" 1.187 + }); 1.188 + } 1.189 + }, 1.190 + 1.191 + /** 1.192 + * The strict equality assertion tests strict equality, as determined by 1.193 + * `===`. 1.194 + * @example 1.195 + * assert.strictEqual(null, null, "`null` is `null`") 1.196 + */ 1.197 + strictEqual: function strictEqual(actual, expected, message) { 1.198 + if (actual === expected) { 1.199 + this.pass(message); 1.200 + } 1.201 + else { 1.202 + this.fail({ 1.203 + actual: actual, 1.204 + expected: expected, 1.205 + message: message, 1.206 + operator: "===" 1.207 + }); 1.208 + } 1.209 + }, 1.210 + 1.211 + /** 1.212 + * The strict non-equality assertion tests for strict inequality, as 1.213 + * determined by `!==`. 1.214 + * @example 1.215 + * assert.notStrictEqual(null, undefined, "`null` is not `undefined`"); 1.216 + */ 1.217 + notStrictEqual: function notStrictEqual(actual, expected, message) { 1.218 + if (actual !== expected) { 1.219 + this.pass(message); 1.220 + } 1.221 + else { 1.222 + this.fail({ 1.223 + actual: actual, 1.224 + expected: expected, 1.225 + message: message, 1.226 + operator: "!==" 1.227 + }) 1.228 + } 1.229 + }, 1.230 + 1.231 + /** 1.232 + * The assertion whether or not given `block` throws an exception. If optional 1.233 + * `Error` argument is provided and it's type of function thrown error is 1.234 + * asserted to be an instance of it, if type of `Error` is string then message 1.235 + * of throw exception is asserted to contain it. 1.236 + * @param {Function} block 1.237 + * Function that is expected to throw. 1.238 + * @param {Error|RegExp} [Error] 1.239 + * Error constructor that is expected to be thrown or a string that 1.240 + * must be contained by a message of the thrown exception, or a RegExp 1.241 + * matching a message of the thrown exception. 1.242 + * @param {String} message 1.243 + * Description message 1.244 + * 1.245 + * @examples 1.246 + * 1.247 + * assert.throws(function block() { 1.248 + * doSomething(4) 1.249 + * }, "Object is expected", "Incorrect argument is passed"); 1.250 + * 1.251 + * assert.throws(function block() { 1.252 + * Object.create(5) 1.253 + * }, TypeError, "TypeError is thrown"); 1.254 + */ 1.255 + throws: function throws(block, Error, message) { 1.256 + let threw = false; 1.257 + let exception = null; 1.258 + 1.259 + // If third argument is not provided and second argument is a string it 1.260 + // means that optional `Error` argument was not passed, so we shift 1.261 + // arguments. 1.262 + if (isString(Error) && isUndefined(message)) { 1.263 + message = Error; 1.264 + Error = undefined; 1.265 + } 1.266 + 1.267 + // Executing given `block`. 1.268 + try { 1.269 + block(); 1.270 + } 1.271 + catch (e) { 1.272 + threw = true; 1.273 + exception = e; 1.274 + } 1.275 + 1.276 + // If exception was thrown and `Error` argument was not passed assert is 1.277 + // passed. 1.278 + if (threw && (isUndefined(Error) || 1.279 + // If passed `Error` is RegExp using it's test method to 1.280 + // assert thrown exception message. 1.281 + (isRegExp(Error) && Error.test(exception.message)) || 1.282 + // If passed `Error` is a constructor function testing if 1.283 + // thrown exception is an instance of it. 1.284 + (isFunction(Error) && instanceOf(exception, Error)))) 1.285 + { 1.286 + this.pass(message); 1.287 + } 1.288 + 1.289 + // Otherwise we report assertion failure. 1.290 + else { 1.291 + let failure = { 1.292 + message: message, 1.293 + operator: "throws" 1.294 + }; 1.295 + 1.296 + if (exception) 1.297 + failure.actual = exception; 1.298 + 1.299 + if (Error) 1.300 + failure.expected = Error; 1.301 + 1.302 + this.fail(failure); 1.303 + } 1.304 + } 1.305 +}; 1.306 +exports.Assert = Assert; 1.307 + 1.308 +function isDeepEqual(actual, expected) { 1.309 + 1.310 + // 7.1. All identical values are equivalent, as determined by ===. 1.311 + if (actual === expected) { 1.312 + return true; 1.313 + } 1.314 + 1.315 + // 7.2. If the expected value is a Date object, the actual value is 1.316 + // equivalent if it is also a Date object that refers to the same time. 1.317 + else if (isDate(actual) && isDate(expected)) { 1.318 + return actual.getTime() === expected.getTime(); 1.319 + } 1.320 + 1.321 + // XXX specification bug: this should be specified 1.322 + else if (isPrimitive(actual) || isPrimitive(expected)) { 1.323 + return expected === actual; 1.324 + } 1.325 + 1.326 + // 7.3. Other pairs that do not both pass typeof value == "object", 1.327 + // equivalence is determined by ==. 1.328 + else if (!isObject(actual) && !isObject(expected)) { 1.329 + return actual == expected; 1.330 + } 1.331 + 1.332 + // 7.4. For all other Object pairs, including Array objects, equivalence is 1.333 + // determined by having the same number of owned properties (as verified 1.334 + // with Object.prototype.hasOwnProperty.call), the same set of keys 1.335 + // (although not necessarily the same order), equivalent values for every 1.336 + // corresponding key, and an identical "prototype" property. Note: this 1.337 + // accounts for both named and indexed properties on Arrays. 1.338 + else { 1.339 + return actual.prototype === expected.prototype && 1.340 + isEquivalent(actual, expected); 1.341 + } 1.342 +} 1.343 + 1.344 +function isEquivalent(a, b, stack) { 1.345 + let aKeys = Object.keys(a); 1.346 + let bKeys = Object.keys(b); 1.347 + 1.348 + return aKeys.length === bKeys.length && 1.349 + isArrayEquivalent(aKeys.sort(), bKeys.sort()) && 1.350 + aKeys.every(function(key) { 1.351 + return isDeepEqual(a[key], b[key], stack) 1.352 + }); 1.353 +} 1.354 + 1.355 +function isArrayEquivalent(a, b, stack) { 1.356 + return isArray(a) && isArray(b) && 1.357 + a.every(function(value, index) { 1.358 + return isDeepEqual(value, b[index]); 1.359 + }); 1.360 +}