/**
* QUnit - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2011 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
(function(window) {
var defined = {
setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() {
try {
return !!sessionStorage.getItem;
} catch(e){
return false;
}
})()
};
var testId = 0;
var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
this.name = name;
this.testName = testName;
this.expected = expected;
this.testEnvironmentArg = testEnvironmentArg;
this.async = async;
this.callback = callback;
this.assertions = [];
};
Test.prototype = {
init: function() {
var tests = id("qunit-tests");
if (tests) {
var b = document.createElement("strong");
b.innerHTML = "Running " + this.name;
var li = document.createElement("li");
li.appendChild( b );
li.className = "running";
li.id = this.id = "test-output" + testId++;
tests.appendChild( li );
}
},
setup: function() {
if (this.module != config.previousModule) {
if ( config.previousModule ) {
QUnit.moduleDone( {
name: config.previousModule,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all
} );
}
config.previousModule = this.module;
config.moduleStats = { all: 0, bad: 0 };
QUnit.moduleStart( {
name: this.module
} );
}
config.current = this;
this.testEnvironment = extend({
setup: function() {},
teardown: function() {}
}, this.moduleTestEnvironment);
if (this.testEnvironmentArg) {
extend(this.testEnvironment, this.testEnvironmentArg);
}
QUnit.testStart( {
name: this.testName
} );
// allow utility functions to access the current test environment
// TODO why??
QUnit.current_testEnvironment = this.testEnvironment;
try {
if ( !config.pollution ) {
saveGlobal();
}
this.testEnvironment.setup.call(this.testEnvironment);
} catch(e) {
QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
}
},
run: function() {
if ( this.async ) {
QUnit.stop();
}
if ( config.notrycatch ) {
this.callback.call(this.testEnvironment);
return;
}
try {
this.callback.call(this.testEnvironment);
} catch(e) {
fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
// else next test will carry the responsibility
saveGlobal();
// Restart the tests if they're blocking
if ( config.blocking ) {
start();
}
}
},
teardown: function() {
try {
this.testEnvironment.teardown.call(this.testEnvironment);
checkPollution();
} catch(e) {
QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
}
},
finish: function() {
if ( this.expected && this.expected != this.assertions.length ) {
QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
}
var good = 0, bad = 0,
tests = id("qunit-tests");
config.stats.all += this.assertions.length;
config.moduleStats.all += this.assertions.length;
if ( tests ) {
var ol = document.createElement("ol");
for ( var i = 0; i < this.assertions.length; i++ ) {
var assertion = this.assertions[i];
var li = document.createElement("li");
li.className = assertion.result ? "pass" : "fail";
li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
ol.appendChild( li );
if ( assertion.result ) {
good++;
} else {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
// store result when possible
if ( QUnit.config.reorder && defined.sessionStorage ) {
if (bad) {
sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
} else {
sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
}
}
if (bad == 0) {
ol.style.display = "none";
}
var b = document.createElement("strong");
b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")";
var a = document.createElement("a");
a.innerHTML = "Rerun";
a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
addEvent(b, "click", function() {
var next = b.nextSibling.nextSibling,
display = next.style.display;
next.style.display = display === "none" ? "block" : "none";
});
addEvent(b, "dblclick", function(e) {
var target = e && e.target ? e.target : window.event.srcElement;
if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
target = target.parentNode;
}
if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
}
});
var li = id(this.id);
li.className = bad ? "fail" : "pass";
li.removeChild( li.firstChild );
li.appendChild( b );
li.appendChild( a );
li.appendChild( ol );
} else {
for ( var i = 0; i < this.assertions.length; i++ ) {
if ( !this.assertions[i].result ) {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
}
try {
QUnit.reset();
} catch(e) {
fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
}
QUnit.testDone( {
name: this.testName,
failed: bad,
passed: this.assertions.length - bad,
total: this.assertions.length
} );
},
queue: function() {
var test = this;
synchronize(function() {
test.init();
});
function run() {
// each of these can by async
synchronize(function() {
test.setup();
});
synchronize(function() {
test.run();
});
synchronize(function() {
test.teardown();
});
synchronize(function() {
test.finish();
});
}
// defer when previous test run passed, if storage is available
var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
if (bad) {
run();
} else {
synchronize(run);
};
}
};
var QUnit = {
// call on start of module test to prepend name to all tests
module: function(name, testEnvironment) {
config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment;
},
asyncTest: function(testName, expected, callback) {
if ( arguments.length === 2 ) {
callback = expected;
expected = 0;
}
QUnit.test(testName, expected, callback, true);
},
test: function(testName, expected, callback, async) {
var name = '' + testName + '', testEnvironmentArg;
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
// is 2nd argument a testEnvironment?
if ( expected && typeof expected === 'object') {
testEnvironmentArg = expected;
expected = null;
}
if ( config.currentModule ) {
name = '' + config.currentModule + ": " + name;
}
if ( !validTest(config.currentModule + ": " + testName) ) {
return;
}
var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
test.module = config.currentModule;
test.moduleTestEnvironment = config.currentModuleTestEnviroment;
test.queue();
},
/**
* Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
*/
expect: function(asserts) {
config.current.expected = asserts;
},
/**
* Asserts true.
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
ok: function(a, msg) {
a = !!a;
var details = {
result: a,
message: msg
};
msg = escapeHtml(msg);
QUnit.log(details);
config.current.assertions.push({
result: a,
message: msg
});
},
/**
* Checks that the first two arguments are equal, with an optional message.
* Prints out both actual and expected values.
*
* Prefered to ok( actual == expected, message )
*
* @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
*
* @param Object actual
* @param Object expected
* @param String message (optional)
*/
equal: function(actual, expected, message) {
QUnit.push(expected == actual, actual, expected, message);
},
notEqual: function(actual, expected, message) {
QUnit.push(expected != actual, actual, expected, message);
},
deepEqual: function(actual, expected, message) {
QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
},
notDeepEqual: function(actual, expected, message) {
QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
},
strictEqual: function(actual, expected, message) {
QUnit.push(expected === actual, actual, expected, message);
},
notStrictEqual: function(actual, expected, message) {
QUnit.push(expected !== actual, actual, expected, message);
},
raises: function(block, expected, message) {
var actual, ok = false;
if (typeof expected === 'string') {
message = expected;
expected = null;
}
try {
block();
} catch (e) {
actual = e;
}
if (actual) {
// we don't want to validate thrown error
if (!expected) {
ok = true;
// expected is a regexp
} else if (QUnit.objectType(expected) === "regexp") {
ok = expected.test(actual);
// expected is a constructor
} else if (actual instanceof expected) {
ok = true;
// expected is a validation function which returns true is validation passed
} else if (expected.call({}, actual) === true) {
ok = true;
}
}
QUnit.ok(ok, message);
},
start: function() {
config.semaphore--;
if (config.semaphore > 0) {
// don't start until equal number of stop-calls
return;
}
if (config.semaphore < 0) {
// ignore if start is called more often then stop
config.semaphore = 0;
}
// A slight delay, to avoid any current callbacks
if ( defined.setTimeout ) {
window.setTimeout(function() {
if ( config.timeout ) {
clearTimeout(config.timeout);
}
config.blocking = false;
process();
}, 13);
} else {
config.blocking = false;
process();
}
},
stop: function(timeout) {
config.semaphore++;
config.blocking = true;
if ( timeout && defined.setTimeout ) {
clearTimeout(config.timeout);
config.timeout = window.setTimeout(function() {
QUnit.ok( false, "Test timed out" );
QUnit.start();
}, timeout);
}
}
};
// Backwards compatibility, deprecated
QUnit.equals = QUnit.equal;
QUnit.same = QUnit.deepEqual;
// Maintain internal state
var config = {
// The queue of tests to run
queue: [],
// block until document ready
blocking: true,
// by default, run previously failed tests first
// very useful in combination with "Hide passed tests" checked
reorder: true,
noglobals: false,
notrycatch: false
};
// Load paramaters
(function() {
var location = window.location || { search: "", protocol: "file:" },
params = location.search.slice( 1 ).split( "&" ),
length = params.length,
urlParams = {},
current;
if ( params[ 0 ] ) {
for ( var i = 0; i < length; i++ ) {
current = params[ i ].split( "=" );
current[ 0 ] = decodeURIComponent( current[ 0 ] );
// allow just a key to turn on a flag, e.g., test.html?noglobals
current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
urlParams[ current[ 0 ] ] = current[ 1 ];
if ( current[ 0 ] in config ) {
config[ current[ 0 ] ] = current[ 1 ];
}
}
}
QUnit.urlParams = urlParams;
config.filter = urlParams.filter;
// Figure out if we're running the tests from a server or not
QUnit.isLocal = !!(location.protocol === 'file:');
})();
// Expose the API as global variables, unless an 'exports'
// object exists, in that case we assume we're in CommonJS
if ( typeof exports === "undefined" || typeof require === "undefined" ) {
extend(window, QUnit);
window.QUnit = QUnit;
} else {
extend(exports, QUnit);
exports.QUnit = QUnit;
}
// define these after exposing globals to keep them in these QUnit namespace only
extend(QUnit, {
config: config,
// Initialize the configuration options
init: function() {
extend(config, {
stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 },
started: +new Date,
updateRate: 1000,
blocking: false,
autostart: true,
autorun: false,
filter: "",
queue: [],
semaphore: 0
});
var tests = id( "qunit-tests" ),
banner = id( "qunit-banner" ),
result = id( "qunit-testresult" );
if ( tests ) {
tests.innerHTML = "";
}
if ( banner ) {
banner.className = "";
}
if ( result ) {
result.parentNode.removeChild( result );
}
if ( tests ) {
result = document.createElement( "p" );
result.id = "qunit-testresult";
result.className = "result";
tests.parentNode.insertBefore( result, tests );
result.innerHTML = 'Running...
';
}
},
/**
* Resets the test setup. Useful for tests that modify the DOM.
*
* If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
*/
reset: function() {
if ( window.jQuery ) {
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
var main = id( 'qunit-fixture' );
if ( main ) {
main.innerHTML = config.fixture;
}
}
},
/**
* Trigger an event on an element.
*
* @example triggerEvent( document.body, "click" );
*
* @param DOMElement elem
* @param String type
*/
triggerEvent: function( elem, type, event ) {
if ( document.createEvent ) {
event = document.createEvent("MouseEvents");
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
elem.dispatchEvent( event );
} else if ( elem.fireEvent ) {
elem.fireEvent("on"+type);
}
},
// Safe object type checking
is: function( type, obj ) {
return QUnit.objectType( obj ) == type;
},
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;
},
push: function(result, actual, expected, message) {
var details = {
result: result,
message: message,
actual: actual,
expected: expected
};
message = escapeHtml(message) || (result ? "okay" : "failed");
message = ' ";
expected = escapeHtml(QUnit.jsDump.parse(expected));
actual = escapeHtml(QUnit.jsDump.parse(actual));
var output = message + '
Expected: | ' + expected + ' |
---|---|
Result: | ' + actual + ' |
Diff: | ' + QUnit.diff(expected, actual) +' |
Source: | ' + source +' |