info@54: /** info@54: * QUnit - A JavaScript Unit Testing Framework info@54: * info@54: * http://docs.jquery.com/QUnit info@54: * info@54: * Copyright (c) 2012 John Resig, Jörn Zaefferer info@54: * Dual licensed under the MIT (MIT-LICENSE.txt) info@54: * or GPL (GPL-LICENSE.txt) licenses. info@54: */ info@54: info@54: (function(window) { info@54: info@54: var defined = { info@54: setTimeout: typeof window.setTimeout !== "undefined", info@54: sessionStorage: (function() { info@54: try { info@54: return !!sessionStorage.getItem; info@54: } catch(e) { info@54: return false; info@54: } info@54: })() info@54: }; info@54: info@54: var testId = 0; info@54: info@54: var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { info@54: this.name = name; info@54: this.testName = testName; info@54: this.expected = expected; info@54: this.testEnvironmentArg = testEnvironmentArg; info@54: this.async = async; info@54: this.callback = callback; info@54: this.assertions = []; info@54: }; info@54: Test.prototype = { info@54: init: function() { info@54: var tests = id("qunit-tests"); info@54: if (tests) { info@54: var b = document.createElement("strong"); info@54: b.innerHTML = "Running " + this.name; info@54: var li = document.createElement("li"); info@54: li.appendChild( b ); info@54: li.className = "running"; info@54: li.id = this.id = "test-output" + testId++; info@54: tests.appendChild( li ); info@54: } info@54: }, info@54: setup: function() { info@54: if (this.module != config.previousModule) { info@54: if ( config.previousModule ) { info@54: QUnit.moduleDone( { info@54: name: config.previousModule, info@54: failed: config.moduleStats.bad, info@54: passed: config.moduleStats.all - config.moduleStats.bad, info@54: total: config.moduleStats.all info@54: } ); info@54: } info@54: config.previousModule = this.module; info@54: config.moduleStats = { all: 0, bad: 0 }; info@54: QUnit.moduleStart( { info@54: name: this.module info@54: } ); info@54: } info@54: info@54: config.current = this; info@54: this.testEnvironment = extend({ info@54: setup: function() {}, info@54: teardown: function() {} info@54: }, this.moduleTestEnvironment); info@54: if (this.testEnvironmentArg) { info@54: extend(this.testEnvironment, this.testEnvironmentArg); info@54: } info@54: info@54: QUnit.testStart( { info@54: name: this.testName info@54: } ); info@54: info@54: // allow utility functions to access the current test environment info@54: // TODO why?? info@54: QUnit.current_testEnvironment = this.testEnvironment; info@54: info@54: try { info@54: if ( !config.pollution ) { info@54: saveGlobal(); info@54: } info@54: info@54: this.testEnvironment.setup.call(this.testEnvironment); info@54: } catch(e) { info@54: QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); info@54: } info@54: }, info@54: run: function() { info@54: if ( this.async ) { info@54: QUnit.stop(); info@54: } info@54: info@54: if ( config.notrycatch ) { info@54: this.callback.call(this.testEnvironment); info@54: return; info@54: } info@54: try { info@54: this.callback.call(this.testEnvironment); info@54: } catch(e) { info@54: fail("Test " + this.testName + " died, exception and test follows", e, this.callback); info@54: QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); info@54: // else next test will carry the responsibility info@54: saveGlobal(); info@54: info@54: // Restart the tests if they're blocking info@54: if ( config.blocking ) { info@54: start(); info@54: } info@54: } info@54: }, info@54: teardown: function() { info@54: try { info@54: this.testEnvironment.teardown.call(this.testEnvironment); info@54: checkPollution(); info@54: } catch(e) { info@54: QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); info@54: } info@54: }, info@54: finish: function() { info@54: if ( this.expected && this.expected != this.assertions.length ) { info@54: QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); info@54: } info@54: info@54: var good = 0, bad = 0, info@54: tests = id("qunit-tests"); info@54: info@54: config.stats.all += this.assertions.length; info@54: config.moduleStats.all += this.assertions.length; info@54: info@54: if ( tests ) { info@54: var ol = document.createElement("ol"); info@54: info@54: for ( var i = 0; i < this.assertions.length; i++ ) { info@54: var assertion = this.assertions[i]; info@54: info@54: var li = document.createElement("li"); info@54: li.className = assertion.result ? "pass" : "fail"; info@54: li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); info@54: ol.appendChild( li ); info@54: info@54: if ( assertion.result ) { info@54: good++; info@54: } else { info@54: bad++; info@54: config.stats.bad++; info@54: config.moduleStats.bad++; info@54: } info@54: } info@54: info@54: // store result when possible info@54: if ( QUnit.config.reorder && defined.sessionStorage ) { info@54: if (bad) { info@54: sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); info@54: } else { info@54: sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); info@54: } info@54: } info@54: info@54: if (bad == 0) { info@54: ol.style.display = "none"; info@54: } info@54: info@54: var b = document.createElement("strong"); info@54: b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; info@54: info@54: var a = document.createElement("a"); info@54: a.innerHTML = "Rerun"; info@54: a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); info@54: info@54: addEvent(b, "click", function() { info@54: var next = b.nextSibling.nextSibling, info@54: display = next.style.display; info@54: next.style.display = display === "none" ? "block" : "none"; info@54: }); info@54: info@54: addEvent(b, "dblclick", function(e) { info@54: var target = e && e.target ? e.target : window.event.srcElement; info@54: if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { info@54: target = target.parentNode; info@54: } info@54: if ( window.location && target.nodeName.toLowerCase() === "strong" ) { info@54: window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); info@54: } info@54: }); info@54: info@54: var li = id(this.id); info@54: li.className = bad ? "fail" : "pass"; info@54: li.removeChild( li.firstChild ); info@54: li.appendChild( b ); info@54: li.appendChild( a ); info@54: li.appendChild( ol ); info@54: info@54: } else { info@54: for ( var i = 0; i < this.assertions.length; i++ ) { info@54: if ( !this.assertions[i].result ) { info@54: bad++; info@54: config.stats.bad++; info@54: config.moduleStats.bad++; info@54: } info@54: } info@54: } info@54: info@54: try { info@54: QUnit.reset(); info@54: } catch(e) { info@54: fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); info@54: } info@54: info@54: QUnit.testDone( { info@54: name: this.testName, info@54: failed: bad, info@54: passed: this.assertions.length - bad, info@54: total: this.assertions.length info@54: } ); info@54: }, info@54: info@54: queue: function() { info@54: var test = this; info@54: synchronize(function() { info@54: test.init(); info@54: }); info@54: function run() { info@54: // each of these can by async info@54: synchronize(function() { info@54: test.setup(); info@54: }); info@54: synchronize(function() { info@54: test.run(); info@54: }); info@54: synchronize(function() { info@54: test.teardown(); info@54: }); info@54: synchronize(function() { info@54: test.finish(); info@54: }); info@54: } info@54: // defer when previous test run passed, if storage is available info@54: var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); info@54: if (bad) { info@54: run(); info@54: } else { info@54: synchronize(run); info@54: }; info@54: } info@54: info@54: }; info@54: info@54: var QUnit = { info@54: info@54: // call on start of module test to prepend name to all tests info@54: module: function(name, testEnvironment) { info@54: config.currentModule = name; info@54: config.currentModuleTestEnviroment = testEnvironment; info@54: }, info@54: info@54: asyncTest: function(testName, expected, callback) { info@54: if ( arguments.length === 2 ) { info@54: callback = expected; info@54: expected = 0; info@54: } info@54: info@54: QUnit.test(testName, expected, callback, true); info@54: }, info@54: info@54: test: function(testName, expected, callback, async) { info@54: var name = '' + testName + '', testEnvironmentArg; info@54: info@54: if ( arguments.length === 2 ) { info@54: callback = expected; info@54: expected = null; info@54: } info@54: // is 2nd argument a testEnvironment? info@54: if ( expected && typeof expected === 'object') { info@54: testEnvironmentArg = expected; info@54: expected = null; info@54: } info@54: info@54: if ( config.currentModule ) { info@54: name = '' + config.currentModule + ": " + name; info@54: } info@54: info@54: if ( !validTest(config.currentModule + ": " + testName) ) { info@54: return; info@54: } info@54: info@54: var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); info@54: test.module = config.currentModule; info@54: test.moduleTestEnvironment = config.currentModuleTestEnviroment; info@54: test.queue(); info@54: }, info@54: info@54: /** info@54: * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. info@54: */ info@54: expect: function(asserts) { info@54: config.current.expected = asserts; info@54: }, info@54: info@54: /** info@54: * Asserts true. info@54: * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); info@54: */ info@54: ok: function(a, msg) { info@54: a = !!a; info@54: var details = { info@54: result: a, info@54: message: msg info@54: }; info@54: msg = escapeHtml(msg); info@54: QUnit.log(details); info@54: config.current.assertions.push({ info@54: result: a, info@54: message: msg info@54: }); info@54: }, info@54: info@54: /** info@54: * Checks that the first two arguments are equal, with an optional message. info@54: * Prints out both actual and expected values. info@54: * info@54: * Prefered to ok( actual == expected, message ) info@54: * info@54: * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); info@54: * info@54: * @param Object actual info@54: * @param Object expected info@54: * @param String message (optional) info@54: */ info@54: equal: function(actual, expected, message) { info@54: QUnit.push(expected == actual, actual, expected, message); info@54: }, info@54: info@54: notEqual: function(actual, expected, message) { info@54: QUnit.push(expected != actual, actual, expected, message); info@54: }, info@54: info@54: deepEqual: function(actual, expected, message) { info@54: QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); info@54: }, info@54: info@54: notDeepEqual: function(actual, expected, message) { info@54: QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); info@54: }, info@54: info@54: strictEqual: function(actual, expected, message) { info@54: QUnit.push(expected === actual, actual, expected, message); info@54: }, info@54: info@54: notStrictEqual: function(actual, expected, message) { info@54: QUnit.push(expected !== actual, actual, expected, message); info@54: }, info@54: info@54: raises: function(block, expected, message) { info@54: var actual, ok = false; info@54: info@54: if (typeof expected === 'string') { info@54: message = expected; info@54: expected = null; info@54: } info@54: info@54: try { info@54: block(); info@54: } catch (e) { info@54: actual = e; info@54: } info@54: info@54: if (actual) { info@54: // we don't want to validate thrown error info@54: if (!expected) { info@54: ok = true; info@54: // expected is a regexp info@54: } else if (QUnit.objectType(expected) === "regexp") { info@54: ok = expected.test(actual); info@54: // expected is a constructor info@54: } else if (actual instanceof expected) { info@54: ok = true; info@54: // expected is a validation function which returns true is validation passed info@54: } else if (expected.call({}, actual) === true) { info@54: ok = true; info@54: } info@54: } info@54: info@54: QUnit.ok(ok, message); info@54: }, info@54: info@54: start: function() { info@54: config.semaphore--; info@54: if (config.semaphore > 0) { info@54: // don't start until equal number of stop-calls info@54: return; info@54: } info@54: if (config.semaphore < 0) { info@54: // ignore if start is called more often then stop info@54: config.semaphore = 0; info@54: } info@54: // A slight delay, to avoid any current callbacks info@54: if ( defined.setTimeout ) { info@54: window.setTimeout(function() { info@54: if (config.semaphore > 0) { info@54: return; info@54: } info@54: if ( config.timeout ) { info@54: clearTimeout(config.timeout); info@54: } info@54: info@54: config.blocking = false; info@54: process(); info@54: }, 13); info@54: } else { info@54: config.blocking = false; info@54: process(); info@54: } info@54: }, info@54: info@54: stop: function(timeout) { info@54: config.semaphore++; info@54: config.blocking = true; info@54: info@54: if ( timeout && defined.setTimeout ) { info@54: clearTimeout(config.timeout); info@54: config.timeout = window.setTimeout(function() { info@54: QUnit.ok( false, "Test timed out" ); info@54: QUnit.start(); info@54: }, timeout); info@54: } info@54: } info@54: }; info@54: info@54: // Backwards compatibility, deprecated info@54: QUnit.equals = QUnit.equal; info@54: QUnit.same = QUnit.deepEqual; info@54: info@54: // Maintain internal state info@54: var config = { info@54: // The queue of tests to run info@54: queue: [], info@54: info@54: // block until document ready info@54: blocking: true, info@54: info@54: // when enabled, show only failing tests info@54: // gets persisted through sessionStorage and can be changed in UI via checkbox info@54: hidepassed: false, info@54: info@54: // by default, run previously failed tests first info@54: // very useful in combination with "Hide passed tests" checked info@54: reorder: true, info@54: info@54: // by default, modify document.title when suite is done info@54: altertitle: true, info@54: info@54: urlConfig: ['noglobals', 'notrycatch'] info@54: }; info@54: info@54: // Load paramaters info@54: (function() { info@54: var location = window.location || { search: "", protocol: "file:" }, info@54: params = location.search.slice( 1 ).split( "&" ), info@54: length = params.length, info@54: urlParams = {}, info@54: current; info@54: info@54: if ( params[ 0 ] ) { info@54: for ( var i = 0; i < length; i++ ) { info@54: current = params[ i ].split( "=" ); info@54: current[ 0 ] = decodeURIComponent( current[ 0 ] ); info@54: // allow just a key to turn on a flag, e.g., test.html?noglobals info@54: current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; info@54: urlParams[ current[ 0 ] ] = current[ 1 ]; info@54: } info@54: } info@54: info@54: QUnit.urlParams = urlParams; info@54: config.filter = urlParams.filter; info@54: info@54: // Figure out if we're running the tests from a server or not info@54: QUnit.isLocal = !!(location.protocol === 'file:'); info@54: })(); info@54: info@54: // Expose the API as global variables, unless an 'exports' info@54: // object exists, in that case we assume we're in CommonJS info@54: if ( typeof exports === "undefined" || typeof require === "undefined" ) { info@54: extend(window, QUnit); info@54: window.QUnit = QUnit; info@54: } else { info@54: extend(exports, QUnit); info@54: exports.QUnit = QUnit; info@54: } info@54: info@54: // define these after exposing globals to keep them in these QUnit namespace only info@54: extend(QUnit, { info@54: config: config, info@54: info@54: // Initialize the configuration options info@54: init: function() { info@54: extend(config, { info@54: stats: { all: 0, bad: 0 }, info@54: moduleStats: { all: 0, bad: 0 }, info@54: started: +new Date, info@54: updateRate: 1000, info@54: blocking: false, info@54: autostart: true, info@54: autorun: false, info@54: filter: "", info@54: queue: [], info@54: semaphore: 0 info@54: }); info@54: info@54: var tests = id( "qunit-tests" ), info@54: banner = id( "qunit-banner" ), info@54: result = id( "qunit-testresult" ); info@54: info@54: if ( tests ) { info@54: tests.innerHTML = ""; info@54: } info@54: info@54: if ( banner ) { info@54: banner.className = ""; info@54: } info@54: info@54: if ( result ) { info@54: result.parentNode.removeChild( result ); info@54: } info@54: info@54: if ( tests ) { info@54: result = document.createElement( "p" ); info@54: result.id = "qunit-testresult"; info@54: result.className = "result"; info@54: tests.parentNode.insertBefore( result, tests ); info@54: result.innerHTML = 'Running...
 '; info@54: } info@54: }, info@54: info@54: /** info@54: * Resets the test setup. Useful for tests that modify the DOM. info@54: * info@54: * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. info@54: */ info@54: reset: function() { info@54: if ( window.jQuery ) { info@54: jQuery( "#qunit-fixture" ).html( config.fixture ); info@54: } else { info@54: var main = id( 'qunit-fixture' ); info@54: if ( main ) { info@54: main.innerHTML = config.fixture; info@54: } info@54: } info@54: }, info@54: info@54: /** info@54: * Trigger an event on an element. info@54: * info@54: * @example triggerEvent( document.body, "click" ); info@54: * info@54: * @param DOMElement elem info@54: * @param String type info@54: */ info@54: triggerEvent: function( elem, type, event ) { info@54: if ( document.createEvent ) { info@54: event = document.createEvent("MouseEvents"); info@54: event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, info@54: 0, 0, 0, 0, 0, false, false, false, false, 0, null); info@54: elem.dispatchEvent( event ); info@54: info@54: } else if ( elem.fireEvent ) { info@54: elem.fireEvent("on"+type); info@54: } info@54: }, info@54: info@54: // Safe object type checking info@54: is: function( type, obj ) { info@54: return QUnit.objectType( obj ) == type; info@54: }, info@54: info@54: objectType: function( obj ) { info@54: if (typeof obj === "undefined") { info@54: return "undefined"; info@54: info@54: // consider: typeof null === object info@54: } info@54: if (obj === null) { info@54: return "null"; info@54: } info@54: info@54: var type = Object.prototype.toString.call( obj ) info@54: .match(/^\[object\s(.*)\]$/)[1] || ''; info@54: info@54: switch (type) { info@54: case 'Number': info@54: if (isNaN(obj)) { info@54: return "nan"; info@54: } else { info@54: return "number"; info@54: } info@54: case 'String': info@54: case 'Boolean': info@54: case 'Array': info@54: case 'Date': info@54: case 'RegExp': info@54: case 'Function': info@54: return type.toLowerCase(); info@54: } info@54: if (typeof obj === "object") { info@54: return "object"; info@54: } info@54: return undefined; info@54: }, info@54: info@54: push: function(result, actual, expected, message) { info@54: var details = { info@54: result: result, info@54: message: message, info@54: actual: actual, info@54: expected: expected info@54: }; info@54: info@54: message = escapeHtml(message) || (result ? "okay" : "failed"); info@54: message = '' + message + ""; info@54: expected = escapeHtml(QUnit.jsDump.parse(expected)); info@54: actual = escapeHtml(QUnit.jsDump.parse(actual)); info@54: var output = message + ''; info@54: if (actual != expected) { info@54: output += ''; info@54: output += ''; info@54: } info@54: if (!result) { info@54: var source = sourceFromStacktrace(); info@54: if (source) { info@54: details.source = source; info@54: output += ''; info@54: } info@54: } info@54: output += "
Expected:
' + expected + '
Result:
' + actual + '
Diff:
' + QUnit.diff(expected, actual) +'
Source:
' + escapeHtml(source) + '
"; info@54: info@54: QUnit.log(details); info@54: info@54: config.current.assertions.push({ info@54: result: !!result, info@54: message: output info@54: }); info@54: }, info@54: info@54: url: function( params ) { info@54: params = extend( extend( {}, QUnit.urlParams ), params ); info@54: var querystring = "?", info@54: key; info@54: for ( key in params ) { info@54: querystring += encodeURIComponent( key ) + "=" + info@54: encodeURIComponent( params[ key ] ) + "&"; info@54: } info@54: return window.location.pathname + querystring.slice( 0, -1 ); info@54: }, info@54: info@54: extend: extend, info@54: id: id, info@54: addEvent: addEvent, info@54: info@54: // Logging callbacks; all receive a single argument with the listed properties info@54: // run test/logs.html for any related changes info@54: begin: function() {}, info@54: // done: { failed, passed, total, runtime } info@54: done: function() {}, info@54: // log: { result, actual, expected, message } info@54: log: function() {}, info@54: // testStart: { name } info@54: testStart: function() {}, info@54: // testDone: { name, failed, passed, total } info@54: testDone: function() {}, info@54: // moduleStart: { name } info@54: moduleStart: function() {}, info@54: // moduleDone: { name, failed, passed, total } info@54: moduleDone: function() {} info@54: }); info@54: info@54: if ( typeof document === "undefined" || document.readyState === "complete" ) { info@54: config.autorun = true; info@54: } info@54: info@54: QUnit.load = function() { info@54: QUnit.begin({}); info@54: info@54: // Initialize the config, saving the execution queue info@54: var oldconfig = extend({}, config); info@54: QUnit.init(); info@54: extend(config, oldconfig); info@54: info@54: config.blocking = false; info@54: info@54: var urlConfigHtml = '', len = config.urlConfig.length; info@54: for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { info@54: config[val] = QUnit.urlParams[val]; info@54: urlConfigHtml += ''; info@54: } info@54: info@54: var userAgent = id("qunit-userAgent"); info@54: if ( userAgent ) { info@54: userAgent.innerHTML = navigator.userAgent; info@54: } info@54: var banner = id("qunit-header"); info@54: if ( banner ) { info@54: banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml; info@54: addEvent( banner, "change", function( event ) { info@54: var params = {}; info@54: params[ event.target.name ] = event.target.checked ? true : undefined; info@54: window.location = QUnit.url( params ); info@54: }); info@54: } info@54: info@54: var toolbar = id("qunit-testrunner-toolbar"); info@54: if ( toolbar ) { info@54: var filter = document.createElement("input"); info@54: filter.type = "checkbox"; info@54: filter.id = "qunit-filter-pass"; info@54: addEvent( filter, "click", function() { info@54: var ol = document.getElementById("qunit-tests"); info@54: if ( filter.checked ) { info@54: ol.className = ol.className + " hidepass"; info@54: } else { info@54: var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; info@54: ol.className = tmp.replace(/ hidepass /, " "); info@54: } info@54: if ( defined.sessionStorage ) { info@54: if (filter.checked) { info@54: sessionStorage.setItem("qunit-filter-passed-tests", "true"); info@54: } else { info@54: sessionStorage.removeItem("qunit-filter-passed-tests"); info@54: } info@54: } info@54: }); info@54: if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { info@54: filter.checked = true; info@54: var ol = document.getElementById("qunit-tests"); info@54: ol.className = ol.className + " hidepass"; info@54: } info@54: toolbar.appendChild( filter ); info@54: info@54: var label = document.createElement("label"); info@54: label.setAttribute("for", "qunit-filter-pass"); info@54: label.innerHTML = "Hide passed tests"; info@54: toolbar.appendChild( label ); info@54: } info@54: info@54: var main = id('qunit-fixture'); info@54: if ( main ) { info@54: config.fixture = main.innerHTML; info@54: } info@54: info@54: if (config.autostart) { info@54: QUnit.start(); info@54: } info@54: }; info@54: info@54: addEvent(window, "load", QUnit.load); info@54: info@54: function done() { info@54: config.autorun = true; info@54: info@54: // Log the last module results info@54: if ( config.currentModule ) { info@54: QUnit.moduleDone( { info@54: name: config.currentModule, info@54: failed: config.moduleStats.bad, info@54: passed: config.moduleStats.all - config.moduleStats.bad, info@54: total: config.moduleStats.all info@54: } ); info@54: } info@54: info@54: var banner = id("qunit-banner"), info@54: tests = id("qunit-tests"), info@54: runtime = +new Date - config.started, info@54: passed = config.stats.all - config.stats.bad, info@54: html = [ info@54: 'Tests completed in ', info@54: runtime, info@54: ' milliseconds.
', info@54: '', info@54: passed, info@54: ' tests of ', info@54: config.stats.all, info@54: ' passed, ', info@54: config.stats.bad, info@54: ' failed.' info@54: ].join(''); info@54: info@54: if ( banner ) { info@54: banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); info@54: } info@54: info@54: if ( tests ) { info@54: id( "qunit-testresult" ).innerHTML = html; info@54: } info@54: info@54: if ( config.altertitle && typeof document !== "undefined" && document.title ) { info@54: // show ✖ for good, ✔ for bad suite result in title info@54: // use escape sequences in case file gets loaded with non-utf-8-charset info@54: document.title = [ info@54: (config.stats.bad ? "\u2716" : "\u2714"), info@54: document.title.replace(/^[\u2714\u2716] /i, "") info@54: ].join(" "); info@54: } info@54: info@54: QUnit.done( { info@54: failed: config.stats.bad, info@54: passed: passed, info@54: total: config.stats.all, info@54: runtime: runtime info@54: } ); info@54: } info@54: info@54: function validTest( name ) { info@54: var filter = config.filter, info@54: run = false; info@54: info@54: if ( !filter ) { info@54: return true; info@54: } info@54: info@54: var not = filter.charAt( 0 ) === "!"; info@54: if ( not ) { info@54: filter = filter.slice( 1 ); info@54: } info@54: info@54: if ( name.indexOf( filter ) !== -1 ) { info@54: return !not; info@54: } info@54: info@54: if ( not ) { info@54: run = true; info@54: } info@54: info@54: return run; info@54: } info@54: info@54: // so far supports only Firefox, Chrome and Opera (buggy) info@54: // could be extended in the future to use something like https://github.com/csnover/TraceKit info@54: function sourceFromStacktrace() { info@54: try { info@54: throw new Error(); info@54: } catch ( e ) { info@54: if (e.stacktrace) { info@54: // Opera info@54: return e.stacktrace.split("\n")[6]; info@54: } else if (e.stack) { info@54: // Firefox, Chrome info@54: return e.stack.split("\n")[4]; info@54: } else if (e.sourceURL) { info@54: // Safari, PhantomJS info@54: // TODO sourceURL points at the 'throw new Error' line above, useless info@54: //return e.sourceURL + ":" + e.line; info@54: } info@54: } info@54: } info@54: info@54: function escapeHtml(s) { info@54: if (!s) { info@54: return ""; info@54: } info@54: s = s + ""; info@54: return s.replace(/[\&"<>\\]/g, function(s) { info@54: switch(s) { info@54: case "&": return "&"; info@54: case "\\": return "\\\\"; info@54: case '"': return '\"'; info@54: case "<": return "<"; info@54: case ">": return ">"; info@54: default: return s; info@54: } info@54: }); info@54: } info@54: info@54: function synchronize( callback ) { info@54: config.queue.push( callback ); info@54: info@54: if ( config.autorun && !config.blocking ) { info@54: process(); info@54: } info@54: } info@54: info@54: function process() { info@54: var start = (new Date()).getTime(); info@54: info@54: while ( config.queue.length && !config.blocking ) { info@54: if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { info@54: config.queue.shift()(); info@54: } else { info@54: window.setTimeout( process, 13 ); info@54: break; info@54: } info@54: } info@54: if (!config.blocking && !config.queue.length) { info@54: done(); info@54: } info@54: } info@54: info@54: function saveGlobal() { info@54: config.pollution = []; info@54: info@54: if ( config.noglobals ) { info@54: for ( var key in window ) { info@54: config.pollution.push( key ); info@54: } info@54: } info@54: } info@54: info@54: function checkPollution( name ) { info@54: var old = config.pollution; info@54: saveGlobal(); info@54: info@54: var newGlobals = diff( config.pollution, old ); info@54: if ( newGlobals.length > 0 ) { info@54: ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); info@54: } info@54: info@54: var deletedGlobals = diff( old, config.pollution ); info@54: if ( deletedGlobals.length > 0 ) { info@54: ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); info@54: } info@54: } info@54: info@54: // returns a new Array with the elements that are in a but not in b info@54: function diff( a, b ) { info@54: var result = a.slice(); info@54: for ( var i = 0; i < result.length; i++ ) { info@54: for ( var j = 0; j < b.length; j++ ) { info@54: if ( result[i] === b[j] ) { info@54: result.splice(i, 1); info@54: i--; info@54: break; info@54: } info@54: } info@54: } info@54: return result; info@54: } info@54: info@54: function fail(message, exception, callback) { info@54: if ( typeof console !== "undefined" && console.error && console.warn ) { info@54: console.error(message); info@54: console.error(exception); info@54: console.warn(callback.toString()); info@54: info@54: } else if ( window.opera && opera.postError ) { info@54: opera.postError(message, exception, callback.toString); info@54: } info@54: } info@54: info@54: function extend(a, b) { info@54: for ( var prop in b ) { info@54: if ( b[prop] === undefined ) { info@54: delete a[prop]; info@54: } else { info@54: a[prop] = b[prop]; info@54: } info@54: } info@54: info@54: return a; info@54: } info@54: info@54: function addEvent(elem, type, fn) { info@54: if ( elem.addEventListener ) { info@54: elem.addEventListener( type, fn, false ); info@54: } else if ( elem.attachEvent ) { info@54: elem.attachEvent( "on" + type, fn ); info@54: } else { info@54: fn(); info@54: } info@54: } info@54: info@54: function id(name) { info@54: return !!(typeof document !== "undefined" && document && document.getElementById) && info@54: document.getElementById( name ); info@54: } info@54: info@54: // Test for equality any JavaScript type. info@54: // Discussions and reference: http://philrathe.com/articles/equiv info@54: // Test suites: http://philrathe.com/tests/equiv info@54: // Author: Philippe Rathé info@54: QUnit.equiv = function () { info@54: info@54: var innerEquiv; // the real equiv function info@54: var callers = []; // stack to decide between skip/abort functions info@54: var parents = []; // stack to avoiding loops from circular referencing info@54: info@54: // Call the o related callback with the given arguments. info@54: function bindCallbacks(o, callbacks, args) { info@54: var prop = QUnit.objectType(o); info@54: if (prop) { info@54: if (QUnit.objectType(callbacks[prop]) === "function") { info@54: return callbacks[prop].apply(callbacks, args); info@54: } else { info@54: return callbacks[prop]; // or undefined info@54: } info@54: } info@54: } info@54: info@54: var callbacks = function () { info@54: info@54: // for string, boolean, number and null info@54: function useStrictEquality(b, a) { info@54: if (b instanceof a.constructor || a instanceof b.constructor) { info@54: // to catch short annotaion VS 'new' annotation of a info@54: // declaration info@54: // e.g. var i = 1; info@54: // var j = new Number(1); info@54: return a == b; info@54: } else { info@54: return a === b; info@54: } info@54: } info@54: info@54: return { info@54: "string" : useStrictEquality, info@54: "boolean" : useStrictEquality, info@54: "number" : useStrictEquality, info@54: "null" : useStrictEquality, info@54: "undefined" : useStrictEquality, info@54: info@54: "nan" : function(b) { info@54: return isNaN(b); info@54: }, info@54: info@54: "date" : function(b, a) { info@54: return QUnit.objectType(b) === "date" info@54: && a.valueOf() === b.valueOf(); info@54: }, info@54: info@54: "regexp" : function(b, a) { info@54: return QUnit.objectType(b) === "regexp" info@54: && a.source === b.source && // the regex itself info@54: a.global === b.global && // and its modifers info@54: // (gmi) ... info@54: a.ignoreCase === b.ignoreCase info@54: && a.multiline === b.multiline; info@54: }, info@54: info@54: // - skip when the property is a method of an instance (OOP) info@54: // - abort otherwise, info@54: // initial === would have catch identical references anyway info@54: "function" : function() { info@54: var caller = callers[callers.length - 1]; info@54: return caller !== Object && typeof caller !== "undefined"; info@54: }, info@54: info@54: "array" : function(b, a) { info@54: var i, j, loop; info@54: var len; info@54: info@54: // b could be an object literal here info@54: if (!(QUnit.objectType(b) === "array")) { info@54: return false; info@54: } info@54: info@54: len = a.length; info@54: if (len !== b.length) { // safe and faster info@54: return false; info@54: } info@54: info@54: // track reference to avoid circular references info@54: parents.push(a); info@54: for (i = 0; i < len; i++) { info@54: loop = false; info@54: for (j = 0; j < parents.length; j++) { info@54: if (parents[j] === a[i]) { info@54: loop = true;// dont rewalk array info@54: } info@54: } info@54: if (!loop && !innerEquiv(a[i], b[i])) { info@54: parents.pop(); info@54: return false; info@54: } info@54: } info@54: parents.pop(); info@54: return true; info@54: }, info@54: info@54: "object" : function(b, a) { info@54: var i, j, loop; info@54: var eq = true; // unless we can proove it info@54: var aProperties = [], bProperties = []; // collection of info@54: // strings info@54: info@54: // comparing constructors is more strict than using info@54: // instanceof info@54: if (a.constructor !== b.constructor) { info@54: return false; info@54: } info@54: info@54: // stack constructor before traversing properties info@54: callers.push(a.constructor); info@54: // track reference to avoid circular references info@54: parents.push(a); info@54: info@54: for (i in a) { // be strict: don't ensures hasOwnProperty info@54: // and go deep info@54: loop = false; info@54: for (j = 0; j < parents.length; j++) { info@54: if (parents[j] === a[i]) info@54: loop = true; // don't go down the same path info@54: // twice info@54: } info@54: aProperties.push(i); // collect a's properties info@54: info@54: if (!loop && !innerEquiv(a[i], b[i])) { info@54: eq = false; info@54: break; info@54: } info@54: } info@54: info@54: callers.pop(); // unstack, we are done info@54: parents.pop(); info@54: info@54: for (i in b) { info@54: bProperties.push(i); // collect b's properties info@54: } info@54: info@54: // Ensures identical properties name info@54: return eq info@54: && innerEquiv(aProperties.sort(), bProperties info@54: .sort()); info@54: } info@54: }; info@54: }(); info@54: info@54: innerEquiv = function() { // can take multiple arguments info@54: var args = Array.prototype.slice.apply(arguments); info@54: if (args.length < 2) { info@54: return true; // end transition info@54: } info@54: info@54: return (function(a, b) { info@54: if (a === b) { info@54: return true; // catch the most you can info@54: } else if (a === null || b === null || typeof a === "undefined" info@54: || typeof b === "undefined" info@54: || QUnit.objectType(a) !== QUnit.objectType(b)) { info@54: return false; // don't lose time with error prone cases info@54: } else { info@54: return bindCallbacks(a, callbacks, [ b, a ]); info@54: } info@54: info@54: // apply transition with (1..n) arguments info@54: })(args[0], args[1]) info@54: && arguments.callee.apply(this, args.splice(1, info@54: args.length - 1)); info@54: }; info@54: info@54: return innerEquiv; info@54: info@54: }(); info@54: info@54: /** info@54: * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | info@54: * http://flesler.blogspot.com Licensed under BSD info@54: * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 info@54: * info@54: * @projectDescription Advanced and extensible data dumping for Javascript. info@54: * @version 1.0.0 info@54: * @author Ariel Flesler info@54: * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} info@54: */ info@54: QUnit.jsDump = (function() { info@54: function quote( str ) { info@54: return '"' + str.toString().replace(/"/g, '\\"') + '"'; info@54: }; info@54: function literal( o ) { info@54: return o + ''; info@54: }; info@54: function join( pre, arr, post ) { info@54: var s = jsDump.separator(), info@54: base = jsDump.indent(), info@54: inner = jsDump.indent(1); info@54: if ( arr.join ) info@54: arr = arr.join( ',' + s + inner ); info@54: if ( !arr ) info@54: return pre + post; info@54: return [ pre, inner + arr, base + post ].join(s); info@54: }; info@54: function array( arr, stack ) { info@54: var i = arr.length, ret = Array(i); info@54: this.up(); info@54: while ( i-- ) info@54: ret[i] = this.parse( arr[i] , undefined , stack); info@54: this.down(); info@54: return join( '[', ret, ']' ); info@54: }; info@54: info@54: var reName = /^function (\w+)/; info@54: info@54: var jsDump = { info@54: parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance info@54: stack = stack || [ ]; info@54: var parser = this.parsers[ type || this.typeOf(obj) ]; info@54: type = typeof parser; info@54: var inStack = inArray(obj, stack); info@54: if (inStack != -1) { info@54: return 'recursion('+(inStack - stack.length)+')'; info@54: } info@54: //else info@54: if (type == 'function') { info@54: stack.push(obj); info@54: var res = parser.call( this, obj, stack ); info@54: stack.pop(); info@54: return res; info@54: } info@54: // else info@54: return (type == 'string') ? parser : this.parsers.error; info@54: }, info@54: typeOf:function( obj ) { info@54: var type; info@54: if ( obj === null ) { info@54: type = "null"; info@54: } else if (typeof obj === "undefined") { info@54: type = "undefined"; info@54: } else if (QUnit.is("RegExp", obj)) { info@54: type = "regexp"; info@54: } else if (QUnit.is("Date", obj)) { info@54: type = "date"; info@54: } else if (QUnit.is("Function", obj)) { info@54: type = "function"; info@54: } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { info@54: type = "window"; info@54: } else if (obj.nodeType === 9) { info@54: type = "document"; info@54: } else if (obj.nodeType) { info@54: type = "node"; info@54: } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { info@54: type = "array"; info@54: } else { info@54: type = typeof obj; info@54: } info@54: return type; info@54: }, info@54: separator:function() { info@54: return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; info@54: }, info@54: indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing info@54: if ( !this.multiline ) info@54: return ''; info@54: var chr = this.indentChar; info@54: if ( this.HTML ) info@54: chr = chr.replace(/\t/g,' ').replace(/ /g,' '); info@54: return Array( this._depth_ + (extra||0) ).join(chr); info@54: }, info@54: up:function( a ) { info@54: this._depth_ += a || 1; info@54: }, info@54: down:function( a ) { info@54: this._depth_ -= a || 1; info@54: }, info@54: setParser:function( name, parser ) { info@54: this.parsers[name] = parser; info@54: }, info@54: // The next 3 are exposed so you can use them info@54: quote:quote, info@54: literal:literal, info@54: join:join, info@54: // info@54: _depth_: 1, info@54: // This is the list of parsers, to modify them, use jsDump.setParser info@54: parsers:{ info@54: window: '[Window]', info@54: document: '[Document]', info@54: error:'[ERROR]', //when no parser is found, shouldn't happen info@54: unknown: '[Unknown]', info@54: 'null':'null', info@54: 'undefined':'undefined', info@54: 'function':function( fn ) { info@54: var ret = 'function', info@54: name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE info@54: if ( name ) info@54: ret += ' ' + name; info@54: ret += '('; info@54: info@54: ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); info@54: return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); info@54: }, info@54: array: array, info@54: nodelist: array, info@54: arguments: array, info@54: object:function( map, stack ) { info@54: var ret = [ ]; info@54: QUnit.jsDump.up(); info@54: for ( var key in map ) { info@54: var val = map[key]; info@54: ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); info@54: } info@54: QUnit.jsDump.down(); info@54: return join( '{', ret, '}' ); info@54: }, info@54: node:function( node ) { info@54: var open = QUnit.jsDump.HTML ? '<' : '<', info@54: close = QUnit.jsDump.HTML ? '>' : '>'; info@54: info@54: var tag = node.nodeName.toLowerCase(), info@54: ret = open + tag; info@54: info@54: for ( var a in QUnit.jsDump.DOMAttrs ) { info@54: var val = node[QUnit.jsDump.DOMAttrs[a]]; info@54: if ( val ) info@54: ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); info@54: } info@54: return ret + close + open + '/' + tag + close; info@54: }, info@54: functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function info@54: var l = fn.length; info@54: if ( !l ) return ''; info@54: info@54: var args = Array(l); info@54: while ( l-- ) info@54: args[l] = String.fromCharCode(97+l);//97 is 'a' info@54: return ' ' + args.join(', ') + ' '; info@54: }, info@54: key:quote, //object calls it internally, the key part of an item in a map info@54: functionCode:'[code]', //function calls it internally, it's the content of the function info@54: attribute:quote, //node calls it internally, it's an html attribute value info@54: string:quote, info@54: date:quote, info@54: regexp:literal, //regex info@54: number:literal, info@54: 'boolean':literal info@54: }, info@54: DOMAttrs:{//attributes to dump from nodes, name=>realName info@54: id:'id', info@54: name:'name', info@54: 'class':'className' info@54: }, info@54: HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) info@54: indentChar:' ',//indentation unit info@54: multiline:true //if true, items in a collection, are separated by a \n, else just a space. info@54: }; info@54: info@54: return jsDump; info@54: })(); info@54: info@54: // from Sizzle.js info@54: function getText( elems ) { info@54: var ret = "", elem; info@54: info@54: for ( var i = 0; elems[i]; i++ ) { info@54: elem = elems[i]; info@54: info@54: // Get the text from text nodes and CDATA nodes info@54: if ( elem.nodeType === 3 || elem.nodeType === 4 ) { info@54: ret += elem.nodeValue; info@54: info@54: // Traverse everything else, except comment nodes info@54: } else if ( elem.nodeType !== 8 ) { info@54: ret += getText( elem.childNodes ); info@54: } info@54: } info@54: info@54: return ret; info@54: }; info@54: info@54: //from jquery.js info@54: function inArray( elem, array ) { info@54: if ( array.indexOf ) { info@54: return array.indexOf( elem ); info@54: } info@54: info@54: for ( var i = 0, length = array.length; i < length; i++ ) { info@54: if ( array[ i ] === elem ) { info@54: return i; info@54: } info@54: } info@54: info@54: return -1; info@54: } info@54: info@54: /* info@54: * Javascript Diff Algorithm info@54: * By John Resig (http://ejohn.org/) info@54: * Modified by Chu Alan "sprite" info@54: * info@54: * Released under the MIT license. info@54: * info@54: * More Info: info@54: * http://ejohn.org/projects/javascript-diff-algorithm/ info@54: * info@54: * Usage: QUnit.diff(expected, actual) info@54: * info@54: * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" info@54: */ info@54: QUnit.diff = (function() { info@54: function diff(o, n) { info@54: var ns = {}; info@54: var os = {}; info@54: info@54: for (var i = 0; i < n.length; i++) { info@54: if (ns[n[i]] == null) info@54: ns[n[i]] = { info@54: rows: [], info@54: o: null info@54: }; info@54: ns[n[i]].rows.push(i); info@54: } info@54: info@54: for (var i = 0; i < o.length; i++) { info@54: if (os[o[i]] == null) info@54: os[o[i]] = { info@54: rows: [], info@54: n: null info@54: }; info@54: os[o[i]].rows.push(i); info@54: } info@54: info@54: for (var i in ns) { info@54: if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { info@54: n[ns[i].rows[0]] = { info@54: text: n[ns[i].rows[0]], info@54: row: os[i].rows[0] info@54: }; info@54: o[os[i].rows[0]] = { info@54: text: o[os[i].rows[0]], info@54: row: ns[i].rows[0] info@54: }; info@54: } info@54: } info@54: info@54: for (var i = 0; i < n.length - 1; i++) { info@54: if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && info@54: n[i + 1] == o[n[i].row + 1]) { info@54: n[i + 1] = { info@54: text: n[i + 1], info@54: row: n[i].row + 1 info@54: }; info@54: o[n[i].row + 1] = { info@54: text: o[n[i].row + 1], info@54: row: i + 1 info@54: }; info@54: } info@54: } info@54: info@54: for (var i = n.length - 1; i > 0; i--) { info@54: if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && info@54: n[i - 1] == o[n[i].row - 1]) { info@54: n[i - 1] = { info@54: text: n[i - 1], info@54: row: n[i].row - 1 info@54: }; info@54: o[n[i].row - 1] = { info@54: text: o[n[i].row - 1], info@54: row: i - 1 info@54: }; info@54: } info@54: } info@54: info@54: return { info@54: o: o, info@54: n: n info@54: }; info@54: } info@54: info@54: return function(o, n) { info@54: o = o.replace(/\s+$/, ''); info@54: n = n.replace(/\s+$/, ''); info@54: var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); info@54: info@54: var str = ""; info@54: info@54: var oSpace = o.match(/\s+/g); info@54: if (oSpace == null) { info@54: oSpace = [" "]; info@54: } info@54: else { info@54: oSpace.push(" "); info@54: } info@54: var nSpace = n.match(/\s+/g); info@54: if (nSpace == null) { info@54: nSpace = [" "]; info@54: } info@54: else { info@54: nSpace.push(" "); info@54: } info@54: info@54: if (out.n.length == 0) { info@54: for (var i = 0; i < out.o.length; i++) { info@54: str += '' + out.o[i] + oSpace[i] + ""; info@54: } info@54: } info@54: else { info@54: if (out.n[0].text == null) { info@54: for (n = 0; n < out.o.length && out.o[n].text == null; n++) { info@54: str += '' + out.o[n] + oSpace[n] + ""; info@54: } info@54: } info@54: info@54: for (var i = 0; i < out.n.length; i++) { info@54: if (out.n[i].text == null) { info@54: str += '' + out.n[i] + nSpace[i] + ""; info@54: } info@54: else { info@54: var pre = ""; info@54: info@54: for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { info@54: pre += '' + out.o[n] + oSpace[n] + ""; info@54: } info@54: str += " " + out.n[i].text + nSpace[i] + pre; info@54: } info@54: } info@54: } info@54: info@54: return str; info@54: }; info@54: })(); info@54: info@54: })(this);