/* * 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); }, // calltest:function(testEnvironment,callback){ // clearInterval(config.timer); // callback.call(testEnvironment); // // }, 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.stopCount = 0; QUnit.startCount = 0; 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 { if (QUnit.readyFlag == 0) { config.timer = setInterval(function () { if (QUnit.readyFlag == 1) { callback.call(testEnvironment); if (QUnit.stopCount == 1) { start(); } clearInterval(config.timer); } }, 100); } else { 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 + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; 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); 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) { detailForArrert += a ? '' : 'massage:' + msg + '\\n'; 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 () { QUnit.startCount++; QUnit.stopCount--; // 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; QUnit.stopCount++; 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, detail) { }, log:function (result, message) { }, testStart:function (name) { }, testDone:function (name, failures, totalDetail) { }, moduleStart:function (name, testEnvironment) { }, moduleDone:function (name, failures, total) { } }; //record detail testName, bad, config.assertions QUnit.testDone = function () { if (arguments[0][1]) { failedDetail += 'testName:' + arguments[0][0] + ' failNum:' + arguments[0][1] + "\\n" + detailForArrert; detailForArrert = ''; } }; // Backwards compatibility, deprecated QUnit.equals = QUnit.equal; QUnit.same = QUnit.deepEqual; QUnit.stopCount = 0; QUnit.startCount = 0; QUnit.readyFlag = 1; // Maintain internal state var config = { // The queue of tests to run queue:[], // block until document ready blocking:true, timer:"" }; //failed detail for each test done var failedDetail = ''; var detailForArrert = ''; // 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.
', '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad, ' 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, failedDetail); } 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é 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 ? '
' : '\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);