Files
tubestation/addon-sdk/source/lib/sdk/test/assert.js
2013-08-20 18:24:12 -07:00

358 lines
9.4 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.metadata = {
"stability": "unstable"
};
const { isFunction, isNull, isObject, isString,
isRegExp, isArray, isDate, isPrimitive,
isUndefined, instanceOf, source } = require("../lang/type");
/**
* The `AssertionError` is defined in assert.
* @extends Error
* @example
* new assert.AssertionError({
* message: message,
* actual: actual,
* expected: expected
* })
*/
function AssertionError(options) {
let assertionError = Object.create(AssertionError.prototype);
if (isString(options))
options = { message: options };
if ("actual" in options)
assertionError.actual = options.actual;
if ("expected" in options)
assertionError.expected = options.expected;
if ("operator" in options)
assertionError.operator = options.operator;
assertionError.message = options.message;
assertionError.stack = new Error().stack;
return assertionError;
}
AssertionError.prototype = Object.create(Error.prototype, {
constructor: { value: AssertionError },
name: { value: "AssertionError", enumerable: true },
toString: { value: function toString() {
let value;
if (this.message) {
value = this.name + " : " + this.message;
}
else {
value = [
this.name + " : ",
source(this.expected),
this.operator,
source(this.actual)
].join(" ");
}
return value;
}}
});
exports.AssertionError = AssertionError;
function Assert(logger) {
let assert = Object.create(Assert.prototype, { _log: { value: logger }});
assert.fail = assert.fail.bind(assert);
assert.pass = assert.pass.bind(assert);
return assert;
}
Assert.prototype = {
fail: function fail(e) {
if (!e || typeof(e) !== 'object') {
this._log.fail(e);
return;
}
let message = e.message;
try {
if ('operator' in e) {
message += [
" -",
source(e.expected),
e.operator,
source(e.actual)
].join(" ");
}
}
catch(e) {}
this._log.fail(message);
},
pass: function pass(message) {
this._log.pass(message);
},
error: function error(e) {
this._log.exception(e);
},
ok: function ok(value, message) {
if (!!!value) {
this.fail({
actual: value,
expected: true,
message: message,
operator: "=="
});
}
else {
this.pass(message);
}
},
/**
* The equality assertion tests shallow, coercive equality with `==`.
* @example
* assert.equal(1, 1, "one is one");
*/
equal: function equal(actual, expected, message) {
if (actual == expected) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "=="
});
}
},
/**
* The non-equality assertion tests for whether two objects are not equal
* with `!=`.
* @example
* assert.notEqual(1, 2, "one is not two");
*/
notEqual: function notEqual(actual, expected, message) {
if (actual != expected) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "!=",
});
}
},
/**
* The equivalence assertion tests a deep (with `===`) equality relation.
* @example
* assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects")
*/
deepEqual: function deepEqual(actual, expected, message) {
if (isDeepEqual(actual, expected)) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "deepEqual"
});
}
},
/**
* The non-equivalence assertion tests for any deep (with `===`) inequality.
* @example
* assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }),
* "object's inherit from different prototypes");
*/
notDeepEqual: function notDeepEqual(actual, expected, message) {
if (!isDeepEqual(actual, expected)) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "notDeepEqual"
});
}
},
/**
* The strict equality assertion tests strict equality, as determined by
* `===`.
* @example
* assert.strictEqual(null, null, "`null` is `null`")
*/
strictEqual: function strictEqual(actual, expected, message) {
if (actual === expected) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "==="
});
}
},
/**
* The strict non-equality assertion tests for strict inequality, as
* determined by `!==`.
* @example
* assert.notStrictEqual(null, undefined, "`null` is not `undefined`");
*/
notStrictEqual: function notStrictEqual(actual, expected, message) {
if (actual !== expected) {
this.pass(message);
}
else {
this.fail({
actual: actual,
expected: expected,
message: message,
operator: "!=="
})
}
},
/**
* The assertion whether or not given `block` throws an exception. If optional
* `Error` argument is provided and it's type of function thrown error is
* asserted to be an instance of it, if type of `Error` is string then message
* of throw exception is asserted to contain it.
* @param {Function} block
* Function that is expected to throw.
* @param {Error|RegExp} [Error]
* Error constructor that is expected to be thrown or a string that
* must be contained by a message of the thrown exception, or a RegExp
* matching a message of the thrown exception.
* @param {String} message
* Description message
*
* @examples
*
* assert.throws(function block() {
* doSomething(4)
* }, "Object is expected", "Incorrect argument is passed");
*
* assert.throws(function block() {
* Object.create(5)
* }, TypeError, "TypeError is thrown");
*/
throws: function throws(block, Error, message) {
let threw = false;
let exception = null;
// If third argument is not provided and second argument is a string it
// means that optional `Error` argument was not passed, so we shift
// arguments.
if (isString(Error) && isUndefined(message)) {
message = Error;
Error = undefined;
}
// Executing given `block`.
try {
block();
}
catch (e) {
threw = true;
exception = e;
}
// If exception was thrown and `Error` argument was not passed assert is
// passed.
if (threw && (isUndefined(Error) ||
// If passed `Error` is RegExp using it's test method to
// assert thrown exception message.
(isRegExp(Error) && Error.test(exception.message)) ||
// If passed `Error` is a constructor function testing if
// thrown exception is an instance of it.
(isFunction(Error) && instanceOf(exception, Error))))
{
this.pass(message);
}
// Otherwise we report assertion failure.
else {
let failure = {
message: message,
operator: "throws"
};
if (exception)
failure.actual = exception;
if (Error)
failure.expected = Error;
this.fail(failure);
}
}
};
exports.Assert = Assert;
function isDeepEqual(actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
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 (isDate(actual) && isDate(expected)) {
return actual.getTime() === expected.getTime();
}
// XXX specification bug: this should be specified
else if (isPrimitive(actual) || isPrimitive(expected)) {
return expected === actual;
}
// 7.3. Other pairs that do not both pass typeof value == "object",
// equivalence is determined by ==.
else if (!isObject(actual) && !isObject(expected)) {
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 actual.prototype === expected.prototype &&
isEquivalent(actual, expected);
}
}
function isEquivalent(a, b, stack) {
let aKeys = Object.keys(a);
let bKeys = Object.keys(b);
return aKeys.length === bKeys.length &&
isArrayEquivalent(aKeys.sort(), bKeys.sort()) &&
aKeys.every(function(key) {
return isDeepEqual(a[key], b[key], stack)
});
}
function isArrayEquivalent(a, b, stack) {
return isArray(a) && isArray(b) &&
a.every(function(value, index) {
return isDeepEqual(value, b[index]);
});
}