postman
Version:
The Postman will help deliver messages around your application
562 lines (462 loc) • 13.3 kB
JavaScript
var util = require( "util" ),
assert = require( "assert" ),
$ = require('sharedjs');
var undefined,
pause = false,
queue = [],
results = [],
test,
module = {},
QUnit = {
api: {}
};
exports.QUnit = QUnit;
/**
* Setup the env, run the test and then run the next one
*/
function run() {
// all tests are done
if ( queue.length <= 0 ) {
QUnit.done(results);
return;
}
test = queue[0];
module = test.module;
if ( module.env && module.env.setup && !test.setupDone ) {
module.env.setup();
test.setupDone = true;
}
// run the test
if ( !pause ) {
test.fn.call( QUnit.current_testEnvironment = $.extend( true, {}, module.env , test.env) );
}
if ( test.async ) {
QUnit.api.stop();
} else if ( !pause ) {
testDone();
}
if ( !pause ) {
run();
}
}
/**
* Do teardown and count expected assertions after each test is done
*/
function testDone() {
if ( !test || !module ) {
return;
}
if ( module.env && module.env.teardown && !test.teardownDone ) {
module.env.teardown();
test.teardownDone = true;
}
if ( pause ) {
return;
}
// check if expected assertions count is correct
if ( test.expected !== undefined && test.expected !== test.done ) {
var errorStack;
// trigger an error to get an error stack
try {
throw new Error( "Assertions amount" );
} catch( err ) {
errorStack = err.stack;
}
log({
message: "Expected " + test.expected + " assertions, but " + test.done + " were run",
errorStack: errorStack
});
}
queue.shift();
}
/**
* Collect results
* @param {Object} res test result
*/
function log(res) {
res.message = res.message || "";
res.name = test.name || "";
res.module = module.name || "";
results.push(res);
}
/**
* Begin tests execution
*/
QUnit.begin = function() {
if ( queue.length <= 0 ) {
throw new Error( "There are no tests defined" );
}
this.start();
};
/**
* Callback for tests complete
*/
QUnit.done;
/**
* Define current module name and environment
* @param {String} name
* @param {Object} env
*/
QUnit.api.module = function( name, env ) {
env = env || {};
module = {
name: name,
env: env
};
return this;
};
/**
* Add async test
* @param {String} name
* @param {Number} expect optional
* @param {Function} fn contains the test
* @param {Boolean} async add test as async one
*/
QUnit.api.test = function( name, expect, fn, async ) {
if ( typeof name !== "string" ) {
throw new Error( "Test name should be the first parameter" );
}
var type = typeof expect,
env;
if ( type === "function" ) {
fn = expect;
expect = undefined;
// expect can extend module env
} else if ( type === "object" ) {
env = expect;
expect = undefined;
}
if ( typeof fn !== "function" ) {
throw new Error( "No test function passed" );
}
queue.push({
name: name,
module: module,
fn: fn,
expected: expect,
env: env,
done: 0,
async: async
});
return this;
};
/**
* Add async test
* @param {String} name
* @param {Number} expect optional
* @param {Function} fn contains the test
*/
QUnit.api.asyncTest = function( name, expect, fn ) {
this.test( name, expect, fn, true );
return this;
};
/**
* Start tests execution
*/
QUnit.api.start = function() {
pause = false;
testDone();
run();
return this;
};
/**
* Stop tests execution
*/
QUnit.api.stop = function() {
pause = true;
return this;
};
/**
* Set expected amount of assertions for current test
* @param {Number} amount
*/
QUnit.api.expect = function( amount ) {
test.expected = amount;
return this;
};
/**
* A boolean assertion. Passes if the first argument is truthy.
* @param {Boolean} value
* @param {String} message
*/
QUnit.api.ok = function( value, message ) {
test.done++;
var r = {
message: message
};
try {
assert.ok( value );
} catch (err) {
r.errorStack = err.stack;
}
log(r);
return this;
};
/**
* A comparison assertion, uses ==.
* @param {Object} actual
* @param {Object} expected
* @param {String} message
*/
QUnit.api.equals = QUnit.api.equal = function( actual, expected, message ) {
test.done++;
var r = {
message: message
};
try {
assert.equal( actual, expected );
} catch( err ) {
r.errorStack = err.stack;
}
log( r );
return this;
};
/**
* A comparison assertion, uses ==.
* @param {Object} actual
* @param {Object} expected
* @param {String} message
*/
QUnit.api.notEqual = function( actual, expected, message ) {
test.done++;
var r = {
message: message
};
try {
assert.notEqual( actual, expected );
} catch( err ) {
r.errorStack = err.stack;
}
log( r );
return this;
};
/**
* A comparison assertion, uses ===.
* @param {Object} actual
* @param {Object} expected
* @param {String} message
*/
QUnit.api.strictEqual = function( actual, expected, message ) {
test.done++;
var r = {
message: message
};
try {
assert.strictEqual( actual, expected );
} catch( err ) {
r.errorStack = err.stack;
}
log( r );
return this;
};
/**
* A deep recursive comparison assertion, working on primitive types, arrays and objects.
* @param {Object} actual
* @param {Object} expected
* @param {String} message
*/
QUnit.api.same = QUnit.api.deepEqual = function( actual, expected, message ) {
test.done++;
var r = {
message: message
};
try {
assert.deepEqual( actual, expected );
} catch( err ) {
r.errorStack = err.stack;
}
log( r );
return this;
};
QUnit.api.raises = function( actual, message ) {
test.done++;
var r = {
message: message
};
try {
assert.throws( actual, message );
} catch( err ) {
r.errorStack = err.stack;
}
log( r );
return this;
};
/**
* Original function from QUnit
* @param {Object} obj
* @return {String|undefined}
*/
QUnit.objectType = function( obj ) {
if (typeof obj === "undefined") {
return "undefined";
}
// consider: typeof null === object
if (obj === null) {
return "null";
}
var type = Object.prototype.toString.call( obj )
.match(/^\[object\s(.*)\]$/)[1] || '';
switch (type) {
case 'Number':
if (isNaN(obj)) {
return "nan";
} else {
return "number";
}
case 'String':
case 'Boolean':
case 'Array':
case 'Date':
case 'RegExp':
case 'Function':
return type.toLowerCase();
}
if (typeof obj === "object") {
return "object";
}
return undefined;
}
// Test for equality any JavaScript type.
// Discussions and reference: http://philrathe.com/articles/equiv
// Test suites: http://philrathe.com/tests/equiv
// Author: Philippe Rathé <prathe@gmail.com>
QUnit.equiv = function () {
var innerEquiv; // the real equiv function
var callers = []; // stack to decide between skip/abort functions
var parents = []; // stack to avoiding loops from circular referencing
// Call the o related callback with the given arguments.
function bindCallbacks(o, callbacks, args) {
var prop = QUnit.objectType(o);
if (prop) {
if (QUnit.objectType(callbacks[prop]) === "function") {
return callbacks[prop].apply(callbacks, args);
} else {
return callbacks[prop]; // or undefined
}
}
}
var callbacks = function () {
// for string, boolean, number and null
function useStrictEquality(b, a) {
if (b instanceof a.constructor || a instanceof b.constructor) {
// to catch short annotaion VS 'new' annotation of a declaration
// e.g. var i = 1;
// var j = new Number(1);
return a == b;
} else {
return a === b;
}
}
return {
"string": useStrictEquality,
"boolean": useStrictEquality,
"number": useStrictEquality,
"null": useStrictEquality,
"undefined": useStrictEquality,
"nan": function (b) {
return isNaN(b);
},
"date": function (b, a) {
return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
},
"regexp": function (b, a) {
return QUnit.objectType(b) === "regexp" &&
a.source === b.source && // the regex itself
a.global === b.global && // and its modifers (gmi) ...
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline;
},
// - skip when the property is a method of an instance (OOP)
// - abort otherwise,
// initial === would have catch identical references anyway
"function": function () {
var caller = callers[callers.length - 1];
return caller !== Object &&
typeof caller !== "undefined";
},
"array": function (b, a) {
var i, j, loop;
var len;
// b could be an object literal here
if ( ! (QUnit.objectType(b) === "array")) {
return false;
}
len = a.length;
if (len !== b.length) { // safe and faster
return false;
}
//track reference to avoid circular references
parents.push(a);
for (i = 0; i < len; i++) {
loop = false;
for(j=0;j<parents.length;j++){
if(parents[j] === a[i]){
loop = true;//dont rewalk array
}
}
if (!loop && ! innerEquiv(a[i], b[i])) {
parents.pop();
return false;
}
}
parents.pop();
return true;
},
"object": function (b, a) {
var i, j, loop;
var eq = true; // unless we can proove it
var aProperties = [], bProperties = []; // collection of strings
// comparing constructors is more strict than using instanceof
if ( a.constructor !== b.constructor) {
return false;
}
// stack constructor before traversing properties
callers.push(a.constructor);
//track reference to avoid circular references
parents.push(a);
for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
loop = false;
for(j=0;j<parents.length;j++){
if(parents[j] === a[i])
loop = true; //don't go down the same path twice
}
aProperties.push(i); // collect a's properties
if (!loop && ! innerEquiv(a[i], b[i])) {
eq = false;
break;
}
}
callers.pop(); // unstack, we are done
parents.pop();
for (i in b) {
bProperties.push(i); // collect b's properties
}
// Ensures identical properties name
return eq && innerEquiv(aProperties.sort(), bProperties.sort());
}
};
}();
innerEquiv = function () { // can take multiple arguments
var args = Array.prototype.slice.apply(arguments);
if (args.length < 2) {
return true; // end transition
}
return (function (a, b) {
if (a === b) {
return true; // catch the most you can
} else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
return false; // don't lose time with error prone cases
} else {
return bindCallbacks(a, callbacks, [b, a]);
}
// apply transition with (1..n) arguments
})(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
};
return innerEquiv;
}();
// copy api to QUnit namespace
$.extend( QUnit, QUnit.api );
// make qunit api global
$.extend( global, QUnit.api );
// provide QUnit global namespace
global.QUnit = QUnit;