|
|
/* * QUnit - A JavaScript Unit Testing Framework * * http://docs.jquery.com/QUnit
* * Copyright (c) 2009 John Resig, Jörn Zaefferer * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. */
(function(window) {
var QUnit = {
// Initialize the configuration options
init: function() { config = { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, started: +new Date, blocking: false, autorun: false, assertions: [], filters: [], queue: [] };
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 ); } }, // call on start of module test to prepend name to all tests
module: function(name, testEnvironment) { config.currentModule = name;
synchronize(function() { if ( config.currentModule ) { QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); }
config.currentModule = name; config.moduleTestEnvironment = testEnvironment; config.moduleStats = { all: 0, bad: 0 };
QUnit.moduleStart( name, 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, testEnvironment, 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 + " module: " + name; }
if ( !validTest(name) ) { return; }
synchronize(function() { QUnit.testStart( testName );
testEnvironment = extend({ setup: function() {}, teardown: function() {} }, config.moduleTestEnvironment); if (testEnvironmentArg) { extend(testEnvironment,testEnvironmentArg); }
// allow utility functions to access the current test environment
QUnit.current_testEnvironment = testEnvironment; config.assertions = []; config.expected = expected;
try { if ( !config.pollution ) { saveGlobal(); }
testEnvironment.setup.call(testEnvironment); } catch(e) { QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); }
if ( async ) { QUnit.stop(); }
try { callback.call(testEnvironment); } catch(e) { fail("Test " + name + " died, exception and test follows", e, callback); QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); // else next test will carry the responsibility
saveGlobal();
// Restart the tests if they're blocking
if ( config.blocking ) { start(); } } });
synchronize(function() { try { checkPollution(); testEnvironment.teardown.call(testEnvironment); } catch(e) { QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); }
try { QUnit.reset(); } catch(e) { fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); }
if ( config.expected && config.expected != config.assertions.length ) { QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); }
var good = 0, bad = 0, tests = id("qunit-tests");
config.stats.all += config.assertions.length; config.moduleStats.all += config.assertions.length;
if ( tests ) { var ol = document.createElement("ol"); ol.style.display = "none";
for ( var i = 0; i < config.assertions.length; i++ ) { var assertion = config.assertions[i];
var li = document.createElement("li"); li.className = assertion.result ? "pass" : "fail"; li.appendChild(document.createTextNode(assertion.message || "(no message)")); ol.appendChild( li );
if ( assertion.result ) { good++; } else { bad++; config.stats.bad++; config.moduleStats.bad++; } }
var b = document.createElement("strong"); b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>"; addEvent(b, "click", function() { var next = b.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() === "strong" ) { var text = "", node = target.firstChild;
while ( node.nodeType === 3 ) { text += node.nodeValue; node = node.nextSibling; }
text = text.replace(/(^\s*|\s*$)/g, "");
if ( window.location ) { window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); } } });
var li = document.createElement("li"); li.className = bad ? "fail" : "pass"; li.appendChild( b ); li.appendChild( ol ); tests.appendChild( li );
if ( bad ) { var toolbar = id("qunit-testrunner-toolbar"); if ( toolbar ) { toolbar.style.display = "block"; id("qunit-filter-pass").disabled = null; id("qunit-filter-missing").disabled = null; } }
} else { for ( var i = 0; i < config.assertions.length; i++ ) { if ( !config.assertions[i].result ) { bad++; config.stats.bad++; config.moduleStats.bad++; } } }
QUnit.testDone( testName, bad, config.assertions.length );
if ( !window.setTimeout && !config.queue.length ) { done(); } });
if ( window.setTimeout && !config.doneTimer ) { config.doneTimer = window.setTimeout(function(){ if ( !config.queue.length ) { done(); } else { synchronize( done ); } }, 13); } }, /** * 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.expected = asserts; },
/** * Asserts true. * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ ok: function(a, msg) { QUnit.log(a, msg);
config.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) { push(expected == actual, actual, expected, message); },
notEqual: function(actual, expected, message) { push(expected != actual, actual, expected, message); }, deepEqual: function(a, b, message) { push(QUnit.equiv(a, b), a, b, message); },
notDeepEqual: function(a, b, message) { push(!QUnit.equiv(a, b), a, b, message); },
strictEqual: function(actual, expected, message) { push(expected === actual, actual, expected, message); },
notStrictEqual: function(actual, expected, message) { push(expected !== actual, actual, expected, message); }, start: function() { // A slight delay, to avoid any current callbacks
if ( window.setTimeout ) { window.setTimeout(function() { if ( config.timeout ) { clearTimeout(config.timeout); }
config.blocking = false; process(); }, 13); } else { config.blocking = false; process(); } }, stop: function(timeout) { config.blocking = true;
if ( timeout && window.setTimeout ) { config.timeout = window.setTimeout(function() { QUnit.ok( false, "Test timed out" ); QUnit.start(); }, timeout); } }, /** * Resets the test setup. Useful for tests that modify the DOM. */ reset: function() { if ( window.jQuery ) { jQuery("#main").html( config.fixture ); jQuery.event.global = {}; jQuery.ajaxSettings = extend({}, config.ajaxSettings); } }, /** * 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 Object.prototype.toString.call( obj ) === "[object "+ type +"]"; }, // Logging callbacks
done: function(failures, total) {}, log: function(result, message) {}, testStart: function(name) {}, testDone: function(name, failures, total) {}, moduleStart: function(name, testEnvironment) {}, moduleDone: function(name, failures, total) {} };
// 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 };
// Load paramaters
(function() { var location = window.location || { search: "", protocol: "file:" }, GETParams = location.search.slice(1).split('&');
for ( var i = 0; i < GETParams.length; i++ ) { GETParams[i] = decodeURIComponent( GETParams[i] ); if ( GETParams[i] === "noglobals" ) { GETParams.splice( i, 1 ); i--; config.noglobals = true; } else if ( GETParams[i].search('=') > -1 ) { GETParams.splice( i, 1 ); i--; } } // restrict modules/tests by get parameters
config.filters = GETParams; // 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; }
if ( typeof document === "undefined" || document.readyState === "complete" ) { config.autorun = true; }
addEvent(window, "load", function() { // Initialize the config, saving the execution queue
var oldconfig = extend({}, config); QUnit.init(); extend(config, oldconfig);
config.blocking = false;
var userAgent = id("qunit-userAgent"); if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } var toolbar = id("qunit-testrunner-toolbar"); if ( toolbar ) { toolbar.style.display = "none"; var filter = document.createElement("input"); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; filter.disabled = true; addEvent( filter, "click", function() { var li = document.getElementsByTagName("li"); for ( var i = 0; i < li.length; i++ ) { if ( li[i].className.indexOf("pass") > -1 ) { li[i].style.display = filter.checked ? "none" : ""; } } }); toolbar.appendChild( filter );
var label = document.createElement("label"); label.setAttribute("for", "qunit-filter-pass"); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label );
var missing = document.createElement("input"); missing.type = "checkbox"; missing.id = "qunit-filter-missing"; missing.disabled = true; addEvent( missing, "click", function() { var li = document.getElementsByTagName("li"); for ( var i = 0; i < li.length; i++ ) { if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; } } }); toolbar.appendChild( missing );
label = document.createElement("label"); label.setAttribute("for", "qunit-filter-missing"); label.innerHTML = "Hide missing tests (untested code is broken code)"; toolbar.appendChild( label ); }
var main = id('main'); if ( main ) { config.fixture = main.innerHTML; }
if ( window.jQuery ) { config.ajaxSettings = window.jQuery.ajaxSettings; }
QUnit.start(); });
function done() { if ( config.doneTimer && window.clearTimeout ) { window.clearTimeout( config.doneTimer ); config.doneTimer = null; }
if ( config.queue.length ) { config.doneTimer = window.setTimeout(function(){ if ( !config.queue.length ) { done(); } else { synchronize( done ); } }, 13);
return; }
config.autorun = true;
// Log the last module results
if ( config.currentModule ) { QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); }
var banner = id("qunit-banner"), tests = id("qunit-tests"), html = ['Tests completed in ', +new Date - config.started, ' milliseconds.<br/>', '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join('');
if ( banner ) { banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); }
if ( tests ) { var result = id("qunit-testresult");
if ( !result ) { result = document.createElement("p"); result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests.nextSibling ); }
result.innerHTML = html; }
QUnit.done( config.stats.bad, config.stats.all ); }
function validTest( name ) { var i = config.filters.length, run = false;
if ( !i ) { return true; } while ( i-- ) { var filter = config.filters[i], not = filter.charAt(0) == '!';
if ( not ) { filter = filter.slice(1); }
if ( name.indexOf(filter) !== -1 ) { return !not; }
if ( not ) { run = true; } }
return run; }
function push(result, actual, expected, message) { message = message || (result ? "okay" : "failed"); QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); }
function synchronize( callback ) { config.queue.push( callback );
if ( config.autorun && !config.blocking ) { process(); } }
function process() { while ( config.queue.length && !config.blocking ) { config.queue.shift()(); } }
function saveGlobal() { config.pollution = []; if ( config.noglobals ) { for ( var key in window ) { config.pollution.push( key ); } } }
function checkPollution( name ) { var old = config.pollution; saveGlobal(); var newGlobals = diff( old, config.pollution ); if ( newGlobals.length > 0 ) { ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); config.expected++; }
var deletedGlobals = diff( config.pollution, old ); if ( deletedGlobals.length > 0 ) { ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); config.expected++; } }
// returns a new Array with the elements that are in a but not in b
function diff( a, b ) { var result = a.slice(); for ( var i = 0; i < result.length; i++ ) { for ( var j = 0; j < b.length; j++ ) { if ( result[i] === b[j] ) { result.splice(i, 1); i--; break; } } } return result; }
function fail(message, exception, callback) { if ( typeof console !== "undefined" && console.error && console.warn ) { console.error(message); console.error(exception); console.warn(callback.toString());
} else if ( window.opera && opera.postError ) { opera.postError(message, exception, callback.toString); } }
function extend(a, b) { for ( var prop in b ) { a[prop] = b[prop]; }
return a; }
function addEvent(elem, type, fn) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, fn ); } else { fn(); } }
function id(name) { return !!(typeof document !== "undefined" && document && document.getElementById) && document.getElementById( name ); }
// 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
// Determine what is o.
function hoozit(o) { if (QUnit.is("String", o)) { return "string"; } else if (QUnit.is("Boolean", o)) { return "boolean";
} else if (QUnit.is("Number", o)) {
if (isNaN(o)) { return "nan"; } else { return "number"; }
} else if (typeof o === "undefined") { return "undefined";
// consider: typeof null === object
} else if (o === null) { return "null";
// consider: typeof [] === object
} else if (QUnit.is( "Array", o)) { return "array"; // consider: typeof new Date() === object
} else if (QUnit.is( "Date", o)) { return "date";
// consider: /./ instanceof Object;
// /./ instanceof RegExp;
// typeof /./ === "function"; // => false in IE and Opera,
// true in FF and Safari
} else if (QUnit.is( "RegExp", o)) { return "regexp";
} else if (typeof o === "object") { return "object";
} else if (QUnit.is( "Function", o)) { return "function"; } else { return undefined; } }
// Call the o related callback with the given arguments.
function bindCallbacks(o, callbacks, args) { var prop = hoozit(o); if (prop) { if (hoozit(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 hoozit(b) === "date" && a.valueOf() === b.valueOf(); },
"regexp": function (b, a) { return hoozit(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; var len;
// b could be an object literal here
if ( ! (hoozit(b) === "array")) { return false; }
len = a.length; if (len !== b.length) { // safe and faster
return false; } for (i = 0; i < len; i++) { if ( ! innerEquiv(a[i], b[i])) { return false; } } return true; },
"object": function (b, a) { var i; 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);
for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
aProperties.push(i); // collect a's properties
if ( ! innerEquiv(a[i], b[i])) { eq = false; } }
callers.pop(); // unstack, we are done
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" || hoozit(a) !== hoozit(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;
}();
/** * jsDump * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
* Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
* Date: 5/15/2008 * @projectDescription Advanced and extensible data dumping for Javascript. * @version 1.0.0 * @author Ariel Flesler * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
*/ QUnit.jsDump = (function() { function quote( str ) { return '"' + str.toString().replace(/"/g, '\\"') + '"'; }; function literal( o ) { return o + ''; }; function join( pre, arr, post ) { var s = jsDump.separator(), base = jsDump.indent(), inner = jsDump.indent(1); if ( arr.join ) arr = arr.join( ',' + s + inner ); if ( !arr ) return pre + post; return [ pre, inner + arr, base + post ].join(s); }; function array( arr ) { var i = arr.length, ret = Array(i); this.up(); while ( i-- ) ret[i] = this.parse( arr[i] ); this.down(); return join( '[', ret, ']' ); }; var reName = /^function (\w+)/; var jsDump = { parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
var parser = this.parsers[ type || this.typeOf(obj) ]; type = typeof parser; return type == 'function' ? parser.call( this, obj ) : type == 'string' ? parser : this.parsers.error; }, typeOf:function( obj ) { var type; if ( obj === null ) { type = "null"; } else if (typeof obj === "undefined") { type = "undefined"; } else if (QUnit.is("RegExp", obj)) { type = "regexp"; } else if (QUnit.is("Date", obj)) { type = "date"; } else if (QUnit.is("Function", obj)) { type = "function"; } else if (QUnit.is("Array", obj)) { type = "array"; } else if (QUnit.is("Window", obj) || QUnit.is("global", obj)) { type = "window"; } else if (QUnit.is("HTMLDocument", obj)) { type = "document"; } else if (QUnit.is("HTMLCollection", obj) || QUnit.is("NodeList", obj)) { type = "nodelist"; } else if (/^\[object HTML/.test(Object.prototype.toString.call( obj ))) { type = "node"; } else { type = typeof obj; } return type; }, separator:function() { return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; }, indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
if ( !this.multiline ) return ''; var chr = this.indentChar; if ( this.HTML ) chr = chr.replace(/\t/g,' ').replace(/ /g,' '); return Array( this._depth_ + (extra||0) ).join(chr); }, up:function( a ) { this._depth_ += a || 1; }, down:function( a ) { this._depth_ -= a || 1; }, setParser:function( name, parser ) { this.parsers[name] = parser; }, // The next 3 are exposed so you can use them
quote:quote, literal:literal, join:join, //
_depth_: 1, // This is the list of parsers, to modify them, use jsDump.setParser
parsers:{ window: '[Window]', document: '[Document]', error:'[ERROR]', //when no parser is found, shouldn't happen
unknown: '[Unknown]', 'null':'null', undefined:'undefined', 'function':function( fn ) { var ret = 'function', name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
if ( name ) ret += ' ' + name; ret += '('; ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); return join( ret, this.parse(fn,'functionCode'), '}' ); }, array: array, nodelist: array, arguments: array, object:function( map ) { var ret = [ ]; this.up(); for ( var key in map ) ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); this.down(); return join( '{', ret, '}' ); }, node:function( node ) { var open = this.HTML ? '<' : '<', close = this.HTML ? '>' : '>'; var tag = node.nodeName.toLowerCase(), ret = open + tag; for ( var a in this.DOMAttrs ) { var val = node[this.DOMAttrs[a]]; if ( val ) ret += ' ' + a + '=' + this.parse( val, 'attribute' ); } return ret + close + open + '/' + tag + close; }, functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
var l = fn.length; if ( !l ) return ''; var args = Array(l); while ( l-- ) args[l] = String.fromCharCode(97+l);//97 is 'a'
return ' ' + args.join(', ') + ' '; }, key:quote, //object calls it internally, the key part of an item in a map
functionCode:'[code]', //function calls it internally, it's the content of the function
attribute:quote, //node calls it internally, it's an html attribute value
string:quote, date:quote, regexp:literal, //regex
number:literal, 'boolean':literal }, DOMAttrs:{//attributes to dump from nodes, name=>realName
id:'id', name:'name', 'class':'className' }, HTML:true,//if true, entities are escaped ( <, >, \t, space and \n )
indentChar:' ',//indentation unit
multiline:true //if true, items in a collection, are separated by a \n, else just a space.
};
return jsDump; })();
})(this);
|