From d869a5fb62888887178a75bfa295787e6f70eadd Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Wed, 27 Aug 2014 21:21:49 +0300 Subject: [PATCH] Update QUnit to v1.15.0. --- js/tests/vendor/qunit.css | 14 +- js/tests/vendor/qunit.js | 2059 ++++++++++++++++++++----------------- 2 files changed, 1140 insertions(+), 933 deletions(-) diff --git a/js/tests/vendor/qunit.css b/js/tests/vendor/qunit.css index 93026e3ba3..9437b4b60c 100644 --- a/js/tests/vendor/qunit.css +++ b/js/tests/vendor/qunit.css @@ -1,12 +1,12 @@ /*! - * QUnit 1.14.0 + * QUnit 1.15.0 * http://qunitjs.com/ * - * Copyright 2013 jQuery Foundation and other contributors + * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: 2014-01-31T16:40Z + * Date: 2014-08-08T16:00Z */ /** Font Family and Sizes */ @@ -62,14 +62,14 @@ } #qunit-testrunner-toolbar { - padding: 0.5em 0 0.5em 2em; + padding: 0.5em 1em 0.5em 1em; color: #5E740B; background-color: #EEE; overflow: hidden; } #qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; + padding: 0.5em 1em 0.5em 1em; background-color: #2B81AF; color: #FFF; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; @@ -86,7 +86,7 @@ } #qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; + padding: 0.4em 1em 0.4em 1em; border-bottom: 1px solid #FFF; list-style-position: inside; } @@ -215,7 +215,7 @@ /** Result */ #qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; + padding: 0.5em 1em 0.5em 1em; color: #2B81AF; background-color: #D2E0E6; diff --git a/js/tests/vendor/qunit.js b/js/tests/vendor/qunit.js index 0e279fde17..474cfe55f3 100644 --- a/js/tests/vendor/qunit.js +++ b/js/tests/vendor/qunit.js @@ -1,26 +1,27 @@ /*! - * QUnit 1.14.0 + * QUnit 1.15.0 * http://qunitjs.com/ * - * Copyright 2013 jQuery Foundation and other contributors + * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: 2014-01-31T16:40Z + * Date: 2014-08-08T16:00Z */ (function( window ) { var QUnit, - assert, config, onErrorFnPrev, - testId = 0, - fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), + fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ), toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, // Keep a local reference to Date (GH-283) Date = window.Date, + now = Date.now || function() { + return new Date().getTime(); + }, setTimeout = window.setTimeout, clearTimeout = window.clearTimeout, defined = { @@ -32,7 +33,7 @@ var QUnit, sessionStorage.setItem( x, x ); sessionStorage.removeItem( x ); return true; - } catch( e ) { + } catch ( e ) { return false; } }()) @@ -74,20 +75,17 @@ var QUnit, * @return {Object} New object with only the own properties (recursively). */ objectValues = function( obj ) { - // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. - /*jshint newcap: false */ var key, val, vals = QUnit.is( "array", obj ) ? [] : {}; for ( key in obj ) { if ( hasOwn.call( obj, key ) ) { - val = obj[key]; - vals[key] = val === Object(val) ? objectValues(val) : val; + val = obj[ key ]; + vals[ key ] = val === Object( val ) ? objectValues( val ) : val; } } return vals; }; - // Root QUnit object. // `QUnit` initialized at top of scope QUnit = { @@ -96,7 +94,7 @@ QUnit = { module: function( name, testEnvironment ) { config.currentModule = name; config.currentModuleTestEnvironment = testEnvironment; - config.modules[name] = true; + config.modules[ name ] = true; }, asyncTest: function( testName, expected, callback ) { @@ -109,20 +107,14 @@ QUnit = { }, test: function( testName, expected, callback, async ) { - var test, - nameHtml = "" + escapeText( testName ) + ""; + var test; if ( arguments.length === 2 ) { callback = expected; expected = null; } - if ( config.currentModule ) { - nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; - } - test = new Test({ - nameHtml: nameHtml, testName: testName, expected: expected, async: async, @@ -139,16 +131,9 @@ QUnit = { test.queue(); }, - // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if (arguments.length === 1) { - config.current.expected = asserts; - } else { - return config.current.expected; - } - }, - start: function( count ) { + var message; + // QUnit hasn't been initialized yet. // Note: RequireJS (et al) may delay onLoad if ( config.semaphore === undefined ) { @@ -166,10 +151,21 @@ QUnit = { if ( config.semaphore > 0 ) { return; } + + // Set the starting time when the first test is run + QUnit.config.started = QUnit.config.started || now(); // ignore if start is called more often then stop if ( config.semaphore < 0 ) { config.semaphore = 0; - QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); + + message = "Called start() while already started (QUnit.config.semaphore was 0 already)"; + + if ( config.current ) { + QUnit.pushFailure( message, sourceFromStacktrace( 2 ) ); + } else { + throw new Error( message ); + } + return; } // A slight delay, to avoid any current callbacks @@ -184,7 +180,7 @@ QUnit = { config.blocking = false; process( true ); - }, 13); + }, 13 ); } else { config.blocking = false; process( true ); @@ -212,6 +208,7 @@ QUnit = { function F() {} F.prototype = QUnit; QUnit = new F(); + // Make F QUnit's constructor so that we can add to the prototype later QUnit.constructor = F; }()); @@ -263,14 +260,7 @@ config = { // Set of all modules. modules: {}, - // logging callback queues - begin: [], - done: [], - log: [], - testStart: [], - testDone: [], - moduleStart: [], - moduleDone: [] + callbacks: {} }; // Initialize more QUnit.config and QUnit.urlParams @@ -323,71 +313,6 @@ extend( QUnit, { config: config, - // Initialize the configuration options - init: function() { - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date(), - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 1 - }); - - var tests, banner, result, - qunit = id( "qunit" ); - - if ( qunit ) { - qunit.innerHTML = - "

" + escapeText( document.title ) + "

" + - "

" + - "
" + - "

" + - "
    "; - } - - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
     "; - } - }, - - // Resets the test setup. Useful for tests that modify the DOM. - /* - DEPRECATED: Use multiple tests instead of resetting inside a test. - Use testStart or testDone for custom cleanup. - This method will throw an error in 2.0, and will be removed in 2.1 - */ - reset: function() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } - }, - // Safe object type checking is: function( type, obj ) { return QUnit.objectType( obj ) === type; @@ -403,12 +328,12 @@ extend( QUnit, { return "null"; } - var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), - type = match && match[1] || ""; + var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), + type = match && match[ 1 ] || ""; switch ( type ) { case "Number": - if ( isNaN(obj) ) { + if ( isNaN( obj ) ) { return "nan"; } return "number"; @@ -426,91 +351,6 @@ extend( QUnit, { return undefined; }, - push: function( result, actual, expected, message ) { - if ( !config.current ) { - throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); - } - - var output, source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeText( message ) || ( result ? "okay" : "failed" ); - message = "" + message + ""; - output = message; - - if ( !result ) { - expected = escapeText( QUnit.jsDump.parse(expected) ); - actual = escapeText( QUnit.jsDump.parse(actual) ); - output += ""; - - if ( actual !== expected ) { - output += ""; - output += ""; - } - - source = sourceFromStacktrace(); - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeText( source ) + "
    "; - } - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: !!result, - message: output - }); - }, - - pushFailure: function( message, source, actual ) { - if ( !config.current ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - - var output, - details = { - module: config.current.module, - name: config.current.testName, - result: false, - message: message - }; - - message = escapeText( message ) || "error"; - message = "" + message + ""; - output = message; - - output += ""; - - if ( actual ) { - output += ""; - } - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
    Result:
    " + escapeText( actual ) + "
    Source:
    " + escapeText( source ) + "
    "; - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: false, - message: output - }); - }, - url: function( params ) { params = extend( extend( {}, QUnit.urlParams ), params ); var key, @@ -526,13 +366,7 @@ extend( QUnit, { window.location.pathname + querystring.slice( 0, -1 ); }, - extend: extend, - id: id, - addEvent: addEvent, - addClass: addClass, - hasClass: hasClass, - removeClass: removeClass - // load, equiv, jsDump, diff: Attached later + extend: extend }); /** @@ -567,215 +401,29 @@ extend( QUnit.constructor.prototype, { moduleDone: registerLoggingCallback( "moduleDone" ) }); -if ( !defined.document || document.readyState === "complete" ) { - config.autorun = true; -} - QUnit.load = function() { - runLoggingCallbacks( "begin", QUnit, {} ); + runLoggingCallbacks( "begin", { + totalTests: Test.count + }); - // Initialize the config, saving the execution queue - var banner, filter, i, j, label, len, main, ol, toolbar, val, selection, - urlConfigContainer, moduleFilter, userAgent, - numModules = 0, - moduleNames = [], - moduleFilterHtml = "", - urlConfigHtml = "", - oldconfig = extend( {}, config ); - - QUnit.init(); - extend(config, oldconfig); + // Initialize the configuration options + extend( config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: 0, + updateRate: 1000, + autostart: true, + filter: "", + semaphore: 1 + }, true ); config.blocking = false; - len = config.urlConfig.length; - - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[i]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val - }; - } - config[ val.id ] = QUnit.urlParams[ val.id ]; - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - for ( i in config.modules ) { - if ( config.modules.hasOwnProperty( i ) ) { - moduleNames.push(i); - } - } - numModules = moduleNames.length; - moduleNames.sort( function( a, b ) { - return a.localeCompare( b ); - }); - moduleFilterHtml += ""; - - // `userAgent` initialized at top of scope - userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } - - // `banner` initialized at top of scope - banner = id( "qunit-header" ); - if ( banner ) { - banner.innerHTML = "" + banner.innerHTML + " "; - } - - // `toolbar` initialized at top of scope - toolbar = id( "qunit-testrunner-toolbar" ); - if ( toolbar ) { - // `filter` initialized at top of scope - filter = document.createElement( "input" ); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - var tmp, - ol = id( "qunit-tests" ); - - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace( / hidepass /, " " ); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } else { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } - } - }); - - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - // `ol` initialized at top of scope - ol = id( "qunit-tests" ); - ol.className = ol.className + " hidepass"; - } - toolbar.appendChild( filter ); - - // `label` initialized at top of scope - label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - - urlConfigContainer = document.createElement("span"); - urlConfigContainer.innerHTML = urlConfigHtml; - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" for checkboxes - // * Fallback from event.target to event.srcElement - addEvents( urlConfigContainer.getElementsByTagName("input"), "click", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.checked ? - target.defaultValue || true : - undefined; - window.location = QUnit.url( params ); - }); - addEvents( urlConfigContainer.getElementsByTagName("select"), "change", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; - window.location = QUnit.url( params ); - }); - toolbar.appendChild( urlConfigContainer ); - - if (numModules > 1) { - moduleFilter = document.createElement( "span" ); - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter.lastChild, "change", function() { - var selectBox = moduleFilter.getElementsByTagName("select")[0], - selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); - - window.location = QUnit.url({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - // Remove any existing filters - filter: undefined, - testNumber: undefined - }); - }); - toolbar.appendChild(moduleFilter); - } - } - - // `main` initialized at top of scope - main = id( "qunit-fixture" ); - if ( main ) { - config.fixture = main.innerHTML; - } - if ( config.autostart ) { QUnit.start(); } }; -if ( defined.document ) { - addEvent( window, "load", QUnit.load ); -} - // `onErrorFnPrev` initialized at top of scope // Preserve other handlers onErrorFnPrev = window.onerror; @@ -783,7 +431,7 @@ onErrorFnPrev = window.onerror; // Cover uncaught exceptions // Returning true will suppress the default browser handler, // returning false will let it run. -window.onerror = function ( error, filePath, linerNr ) { +window.onerror = function( error, filePath, linerNr ) { var ret = false; if ( onErrorFnPrev ) { ret = onErrorFnPrev( error, filePath, linerNr ); @@ -798,7 +446,7 @@ window.onerror = function ( error, filePath, linerNr ) { } QUnit.pushFailure( error, filePath + ":" + linerNr ); } else { - QUnit.test( "global failure", extend( function() { + QUnit.test( "global failure", extend(function() { QUnit.pushFailure( error, filePath + ":" + linerNr ); }, { validTest: validTest } ) ); } @@ -813,7 +461,7 @@ function done() { // Log the last module results if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { + runLoggingCallbacks( "moduleDone", { name: config.previousModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, @@ -822,58 +470,10 @@ function done() { } delete config.previousModule; - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - runtime = +new Date() - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - "Tests completed in ", - runtime, - " milliseconds.
    ", - "", - passed, - " assertions of ", - config.stats.all, - " passed, ", - config.stats.bad, - " failed." - ].join( "" ); + var runtime = now() - config.started, + passed = config.stats.all - config.stats.bad; - if ( banner ) { - banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && defined.document && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( config.stats.bad ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { - // `key` & `i` initialized at top of scope - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( config.scrolltop && window.scrollTo ) { - window.scrollTo(0, 0); - } - - runLoggingCallbacks( "done", QUnit, { + runLoggingCallbacks( "done", { failed: config.stats.bad, passed: passed, total: config.stats.all, @@ -922,21 +522,22 @@ function validTest( test ) { return !include; } -// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) -// Later Safari and IE10 are supposed to support error.stack as well +// Doesn't support IE6 to IE9 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack function extractStacktrace( e, offset ) { - offset = offset === undefined ? 3 : offset; + offset = offset === undefined ? 4 : offset; var stack, include, i; if ( e.stacktrace ) { - // Opera + + // Opera 12.x return e.stacktrace.split( "\n" )[ offset + 3 ]; } else if ( e.stack ) { - // Firefox, Chrome + + // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node stack = e.stack.split( "\n" ); - if (/^error$/i.test( stack[0] ) ) { + if ( /^error$/i.test( stack[ 0 ] ) ) { stack.shift(); } if ( fileName ) { @@ -953,12 +554,13 @@ function extractStacktrace( e, offset ) { } return stack[ offset ]; } else if ( e.sourceURL ) { - // Safari, PhantomJS - // hopefully one day Safari provides actual stacktraces + + // Safari < 6 // exclude useless self-reference for generated Error objects if ( /qunit.js$/.test( e.sourceURL ) ) { return; } + // for actual exceptions, this is useful return e.sourceURL + ":" + e.line; } @@ -971,31 +573,6 @@ function sourceFromStacktrace( offset ) { } } -/** - * Escape text for attribute or text content. - */ -function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - }); -} - function synchronize( callback, last ) { config.queue.push( callback ); @@ -1008,11 +585,11 @@ function process( last ) { function next() { process( last ); } - var start = new Date().getTime(); + var start = now(); config.depth = config.depth ? config.depth + 1 : 1; while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { + if ( !defined.setTimeout || config.updateRate <= 0 || ( ( now() - start ) < config.updateRate ) ) { config.queue.shift()(); } else { setTimeout( next, 13 ); @@ -1050,12 +627,12 @@ function checkPollution() { newGlobals = diff( config.pollution, old ); if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); + QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); } deletedGlobals = diff( old, config.pollution ); if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); + QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); } } @@ -1066,7 +643,7 @@ function diff( a, b ) { for ( i = 0; i < result.length; i++ ) { for ( j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { + if ( result[ i ] === b[ j ] ) { result.splice( i, 1 ); i--; break; @@ -1076,14 +653,15 @@ function diff( a, b ) { return result; } -function extend( a, b ) { +function extend( a, b, undefOnly ) { for ( var prop in b ) { if ( hasOwn.call( b, prop ) ) { + // Avoid "Member not found" error in IE8 caused by messing with window.constructor if ( !( prop === "constructor" && a === window ) ) { if ( b[ prop ] === undefined ) { delete a[ prop ]; - } else { + } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { a[ prop ] = b[ prop ]; } } @@ -1093,79 +671,24 @@ function extend( a, b ) { return a; } -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ -function addEvent( elem, type, fn ) { - if ( elem.addEventListener ) { - - // Standards-based browsers - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - - // support: IE <9 - elem.attachEvent( "on" + type, fn ); - } else { - - // Caller must ensure support for event listeners is present - throw new Error( "addEvent() was called in a context without event listener support" ); - } -} - -/** - * @param {Array|NodeList} elems - * @param {string} type - * @param {Function} fn - */ -function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[i], type, fn ); - } -} - -function hasClass( elem, name ) { - return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; -} - -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += (elem.className ? " " : "") + name; - } -} - -function removeClass( elem, name ) { - var set = " " + elem.className + " "; - // Class name may appear multiple times - while ( set.indexOf(" " + name + " ") > -1 ) { - set = set.replace(" " + name + " " , " "); - } - // If possible, trim it for prettiness, but not necessarily - elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); -} - -function id( name ) { - return defined.document && document.getElementById && document.getElementById( name ); -} - function registerLoggingCallback( key ) { + + // Initialize key collection of logging callback + if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) { + config.callbacks[ key ] = []; + } + return function( callback ) { - config[key].push( callback ); + config.callbacks[ key ].push( callback ); }; } -// Supports deprecated method of completely overwriting logging callbacks -function runLoggingCallbacks( key, scope, args ) { - var i, callbacks; - if ( QUnit.hasOwnProperty( key ) ) { - QUnit[ key ].call(scope, args ); - } else { - callbacks = config[ key ]; - for ( i = 0; i < callbacks.length; i++ ) { - callbacks[ i ].call( scope, args ); - } +function runLoggingCallbacks( key, args ) { + var i, l, callbacks; + + callbacks = config.callbacks[ key ]; + for ( i = 0, l = callbacks.length; i < l; i++ ) { + callbacks[ i ]( args ); } } @@ -1186,6 +709,7 @@ function inArray( elem, array ) { function Test( settings ) { extend( this, settings ); + this.assert = new Assert( this ); this.assertions = []; this.testNumber = ++Test.count; } @@ -1193,32 +717,12 @@ function Test( settings ) { Test.count = 0; Test.prototype = { - init: function() { - var a, b, li, - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml; - - // `a` initialized at top of scope - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: this.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = this.id = "qunit-test-output" + testId++; - - tests.appendChild( li ); - } - }, setup: function() { if ( + // Emit moduleStart when we're switching from one module to another this.module !== config.previousModule || + // They could be equal (both undefined) but if the previousModule property doesn't // yet exist it means this is the first test in a suite that isn't wrapped in a // module, in which case we'll just emit a moduleStart event for 'undefined'. @@ -1226,7 +730,7 @@ Test.prototype = { !hasOwn.call( config, "previousModule" ) ) { if ( hasOwn.call( config, "previousModule" ) ) { - runLoggingCallbacks( "moduleDone", QUnit, { + runLoggingCallbacks( "moduleDone", { name: config.previousModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, @@ -1235,7 +739,7 @@ Test.prototype = { } config.previousModule = this.module; config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( "moduleStart", QUnit, { + runLoggingCallbacks( "moduleStart", { name: this.module }); } @@ -1247,65 +751,49 @@ Test.prototype = { teardown: function() {} }, this.moduleTestEnvironment ); - this.started = +new Date(); - runLoggingCallbacks( "testStart", QUnit, { + this.started = now(); + runLoggingCallbacks( "testStart", { name: this.testName, - module: this.module + module: this.module, + testNumber: this.testNumber }); - /*jshint camelcase:false */ - - - /** - * Expose the current test environment. - * - * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. - */ - QUnit.current_testEnvironment = this.testEnvironment; - - /*jshint camelcase:true */ - if ( !config.pollution ) { saveGlobal(); } if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); + this.testEnvironment.setup.call( this.testEnvironment, this.assert ); return; } try { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); + this.testEnvironment.setup.call( this.testEnvironment, this.assert ); + } catch ( e ) { + this.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); } }, run: function() { config.current = this; - var running = id( "qunit-testresult" ); - - if ( running ) { - running.innerHTML = "Running:
    " + this.nameHtml; - } - if ( this.async ) { QUnit.stop(); } - this.callbackStarted = +new Date(); + this.callbackStarted = now(); if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; + this.callback.call( this.testEnvironment, this.assert ); + this.callbackRuntime = now() - this.callbackStarted; return; } try { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - } catch( e ) { - this.callbackRuntime = +new Date() - this.callbackStarted; + this.callback.call( this.testEnvironment, this.assert ); + this.callbackRuntime = now() - this.callbackStarted; + } catch ( e ) { + this.callbackRuntime = now() - this.callbackStarted; + + this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); // else next test will carry the responsibility saveGlobal(); @@ -1319,15 +807,15 @@ Test.prototype = { config.current = this; if ( config.notrycatch ) { if ( typeof this.callbackRuntime === "undefined" ) { - this.callbackRuntime = +new Date() - this.callbackStarted; + this.callbackRuntime = now() - this.callbackStarted; } - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); + this.testEnvironment.teardown.call( this.testEnvironment, this.assert ); return; } else { try { - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); + this.testEnvironment.teardown.call( this.testEnvironment, this.assert ); + } catch ( e ) { + this.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); } } checkPollution(); @@ -1335,115 +823,44 @@ Test.prototype = { finish: function() { config.current = this; if ( config.requireExpects && this.expected === null ) { - QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); + this.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); + this.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); } else if ( this.expected === null && !this.assertions.length ) { - QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); + this.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); } - var i, assertion, a, b, time, li, ol, - test = this, - good = 0, - bad = 0, - tests = id( "qunit-tests" ); + var i, + bad = 0; - this.runtime = +new Date() - this.started; + this.runtime = now() - this.started; config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; - if ( tests ) { - ol = document.createElement( "ol" ); - ol.className = "qunit-assert-list"; - - for ( i = 0; i < this.assertions.length; i++ ) { - assertion = this.assertions[i]; - - li = document.createElement( "li" ); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); - } - } - - if ( bad === 0 ) { - addClass( ol, "qunit-collapsed" ); - } - - // `b` initialized at top of scope - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.parentNode.lastChild, - collapsed = hasClass( next, "qunit-collapsed" ); - ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); - }); - - addEvent(b, "dblclick", function( e ) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ testNumber: test.testNumber }); - } - }); - - // `time` initialized at top of scope - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = this.runtime + " ms"; - - // `li` initialized at top of scope - li = id( this.id ); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - a = li.firstChild; - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( time ); - li.appendChild( ol ); - - } else { - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } + for ( i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[ i ].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; } } - runLoggingCallbacks( "testDone", QUnit, { + runLoggingCallbacks( "testDone", { name: this.testName, module: this.module, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length, runtime: this.runtime, + + // HTML Reporter use + assertions: this.assertions, + testNumber: this.testNumber, + // DEPRECATED: this property will be removed in 2.0.0, use runtime instead duration: this.runtime }); - QUnit.reset(); - config.current = undefined; }, @@ -1451,9 +868,6 @@ Test.prototype = { var bad, test = this; - synchronize(function() { - test.init(); - }); function run() { // each of these can by async synchronize(function() { @@ -1473,59 +887,129 @@ Test.prototype = { // `bad` initialized at top of scope // defer when previous test run passed, if storage is available bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); + +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); if ( bad ) { run(); } else { synchronize( run, true ); } + }, + + push: function( result, actual, expected, message ) { + var source, + details = { + module: this.module, + name: this.testName, + result: result, + message: message, + actual: actual, + expected: expected, + testNumber: this.testNumber + }; + + if ( !result ) { + source = sourceFromStacktrace(); + + if ( source ) { + details.source = source; + } + } + + runLoggingCallbacks( "log", details ); + + this.assertions.push({ + result: !!result, + message: message + }); + }, + + pushFailure: function( message, source, actual ) { + if ( !this instanceof Test ) { + throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace( 2 ) ); + } + + var details = { + module: this.module, + name: this.testName, + result: false, + message: message || "error", + actual: actual || null, + testNumber: this.testNumber + }; + + if ( source ) { + details.source = source; + } + + runLoggingCallbacks( "log", details ); + + this.assertions.push({ + result: false, + message: message + }); } }; -// `assert` initialized at top of scope +QUnit.pushFailure = function() { + if ( !QUnit.config.current ) { + throw new Error( "pushFailure() assertion outside test context, in " + sourceFromStacktrace( 2 ) ); + } + + // Gets current test obj + var currentTest = QUnit.config.current.assert.test; + + return currentTest.pushFailure.apply( currentTest, arguments ); +}; + +function Assert( testContext ) { + this.test = testContext; +} + // Assert helpers -// All of these must either call QUnit.push() or manually do: -// - runLoggingCallbacks( "log", .. ); -// - config.current.assertions.push({ .. }); -assert = QUnit.assert = { +QUnit.assert = Assert.prototype = { + + // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. + expect: function( asserts ) { + if ( arguments.length === 1 ) { + this.test.expected = asserts; + } else { + return this.test.expected; + } + }, + + // Exports test.push() to the user API + push: function() { + var assert = this; + + // Backwards compatibility fix. + // Allows the direct use of global exported assertions and QUnit.assert.* + // Although, it's use is not recommended as it can leak assertions + // to other tests from async tests, because we only get a reference to the current test, + // not exactly the test where assertion were intended to be called. + if ( !QUnit.config.current ) { + throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); + } + if ( !( assert instanceof Assert ) ) { + assert = QUnit.config.current.assert; + } + return assert.test.push.apply( assert.test, arguments ); + }, + /** * Asserts rough true-ish result. * @name ok * @function * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ - ok: function( result, msg ) { - if ( !config.current ) { - throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); + ok: function( result, message ) { + message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + + QUnit.dump.parse( result ) ); + if ( !!result ) { + this.push( true, result, true, message ); + } else { + this.test.pushFailure( message, null, result ); } - result = !!result; - msg = msg || ( result ? "okay" : "failed" ); - - var source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: msg - }; - - msg = "" + escapeText( msg ) + ""; - - if ( !result ) { - source = sourceFromStacktrace( 2 ); - if ( source ) { - details.source = source; - msg += "
    Source:
    " +
    -					escapeText( source ) +
    -					"
    "; - } - } - runLoggingCallbacks( "log", QUnit, details ); - config.current.assertions.push({ - result: result, - message: msg - }); }, /** @@ -1537,7 +1021,7 @@ assert = QUnit.assert = { */ equal: function( actual, expected, message ) { /*jshint eqeqeq:false */ - QUnit.push( expected == actual, actual, expected, message ); + this.push( expected == actual, actual, expected, message ); }, /** @@ -1546,7 +1030,7 @@ assert = QUnit.assert = { */ notEqual: function( actual, expected, message ) { /*jshint eqeqeq:false */ - QUnit.push( expected != actual, actual, expected, message ); + this.push( expected != actual, actual, expected, message ); }, /** @@ -1554,9 +1038,9 @@ assert = QUnit.assert = { * @function */ propEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + actual = objectValues( actual ); + expected = objectValues( expected ); + this.push( QUnit.equiv( actual, expected ), actual, expected, message ); }, /** @@ -1564,9 +1048,9 @@ assert = QUnit.assert = { * @function */ notPropEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + actual = objectValues( actual ); + expected = objectValues( expected ); + this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); }, /** @@ -1574,7 +1058,7 @@ assert = QUnit.assert = { * @function */ deepEqual: function( actual, expected, message ) { - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + this.push( QUnit.equiv( actual, expected ), actual, expected, message ); }, /** @@ -1582,7 +1066,7 @@ assert = QUnit.assert = { * @function */ notDeepEqual: function( actual, expected, message ) { - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); }, /** @@ -1590,7 +1074,7 @@ assert = QUnit.assert = { * @function */ strictEqual: function( actual, expected, message ) { - QUnit.push( expected === actual, actual, expected, message ); + this.push( expected === actual, actual, expected, message ); }, /** @@ -1598,91 +1082,67 @@ assert = QUnit.assert = { * @function */ notStrictEqual: function( actual, expected, message ) { - QUnit.push( expected !== actual, actual, expected, message ); + this.push( expected !== actual, actual, expected, message ); }, "throws": function( block, expected, message ) { - var actual, + var actual, expectedType, expectedOutput = expected, ok = false; - // 'expected' is optional - if ( !message && typeof expected === "string" ) { + // 'expected' is optional unless doing string comparison + if ( message == null && typeof expected === "string" ) { message = expected; expected = null; } - config.current.ignoreGlobalErrors = true; + this.test.ignoreGlobalErrors = true; try { - block.call( config.current.testEnvironment ); + block.call( this.test.testEnvironment ); } catch (e) { actual = e; } - config.current.ignoreGlobalErrors = false; + this.test.ignoreGlobalErrors = false; if ( actual ) { + expectedType = QUnit.objectType( expected ); // we don't want to validate thrown error if ( !expected ) { ok = true; expectedOutput = null; - // expected is an Error object - } else if ( expected instanceof Error ) { - ok = actual instanceof Error && - actual.name === expected.name && - actual.message === expected.message; - // expected is a regexp - } else if ( QUnit.objectType( expected ) === "regexp" ) { + } else if ( expectedType === "regexp" ) { ok = expected.test( errorString( actual ) ); // expected is a string - } else if ( QUnit.objectType( expected ) === "string" ) { + } else if ( expectedType === "string" ) { ok = expected === errorString( actual ); - // expected is a constructor - } else if ( actual instanceof expected ) { + // expected is a constructor, maybe an Error constructor + } else if ( expectedType === "function" && actual instanceof expected ) { ok = true; - // expected is a validation function which returns true is validation passed - } else if ( expected.call( {}, actual ) === true ) { + // expected is an Error object + } else if ( expectedType === "object" ) { + ok = actual instanceof expected.constructor && + actual.name === expected.name && + actual.message === expected.message; + + // expected is a validation function which returns true if validation passed + } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { expectedOutput = null; ok = true; } - QUnit.push( ok, actual, expectedOutput, message ); + this.push( ok, actual, expectedOutput, message ); } else { - QUnit.pushFailure( message, null, "No exception was thrown." ); + this.test.pushFailure( message, null, "No exception was thrown." ); } } }; -/** - * @deprecated since 1.8.0 - * Kept assertion helpers in root for backwards compatibility. - */ -extend( QUnit.constructor.prototype, assert ); - -/** - * @deprecated since 1.9.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.constructor.prototype.raises = function() { - QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" ); -}; - -/** - * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.constructor.prototype.equals = function() { - QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); -}; -QUnit.constructor.prototype.same = function() { - QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); -}; - // Test for equality any JavaScript type. // Author: Philippe Rathé QUnit.equiv = (function() { @@ -1701,22 +1161,26 @@ QUnit.equiv = (function() { // the real equiv function var innerEquiv, + // stack to decide between skip/abort functions callers = [], + // stack to avoiding loops from circular referencing parents = [], parentsB = [], - getProto = Object.getPrototypeOf || function ( obj ) { - /*jshint camelcase:false */ + getProto = Object.getPrototypeOf || function( obj ) { + /* jshint camelcase: false, proto: true */ return obj.__proto__; }, - callbacks = (function () { + callbacks = (function() { // for string, boolean, number and null function useStrictEquality( b, a ) { + /*jshint eqeqeq:false */ if ( b instanceof a.constructor || a instanceof b.constructor ) { + // to catch short annotation VS 'new' annotation of a // declaration // e.g. var i = 1; @@ -1744,10 +1208,13 @@ QUnit.equiv = (function() { "regexp": function( b, a ) { return QUnit.objectType( b ) === "regexp" && + // the regex itself a.source === b.source && + // and its modifiers a.global === b.global && + // (gmi) ... a.ignoreCase === b.ignoreCase && a.multiline === b.multiline && @@ -1758,7 +1225,7 @@ QUnit.equiv = (function() { // - abort otherwise, // initial === would have catch identical references anyway "function": function() { - var caller = callers[callers.length - 1]; + var caller = callers[ callers.length - 1 ]; return caller !== Object && typeof caller !== "undefined"; }, @@ -1782,10 +1249,10 @@ QUnit.equiv = (function() { for ( i = 0; i < len; i++ ) { loop = false; for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { loop = true; } else { parents.pop(); @@ -1794,7 +1261,7 @@ QUnit.equiv = (function() { } } } - if ( !loop && !innerEquiv(a[i], b[i]) ) { + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { parents.pop(); parentsB.pop(); return false; @@ -1806,6 +1273,7 @@ QUnit.equiv = (function() { }, "object": function( b, a ) { + /*jshint forin:false */ var i, j, loop, aCircular, bCircular, // Default to true @@ -1816,11 +1284,12 @@ QUnit.equiv = (function() { // comparing constructors is more strict than using // instanceof if ( a.constructor !== b.constructor ) { + // Allow objects with no prototype to be equivalent to // objects with Object as their constructor. - if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || - ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { - return false; + if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) || + ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) { + return false; } } @@ -1835,10 +1304,10 @@ QUnit.equiv = (function() { for ( i in a ) { loop = false; for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { loop = true; } else { eq = false; @@ -1846,8 +1315,8 @@ QUnit.equiv = (function() { } } } - aProperties.push(i); - if ( !loop && !innerEquiv(a[i], b[i]) ) { + aProperties.push( i ); + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { eq = false; break; } @@ -1873,35 +1342,29 @@ QUnit.equiv = (function() { return true; // end transition } - return (function( a, b ) { + return ( (function( a, b ) { if ( a === b ) { return true; // catch the most you can } else if ( a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || - QUnit.objectType(a) !== QUnit.objectType(b) ) { - return false; // don't lose time with error prone cases + QUnit.objectType( a ) !== QUnit.objectType( b ) ) { + + // don't lose time with error prone cases + return false; } else { - return bindCallbacks(a, callbacks, [ b, a ]); + return bindCallbacks( a, callbacks, [ b, a ] ); } // apply transition with (1..n) arguments - }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); + }( args[ 0 ], args[ 1 ] ) ) && innerEquiv.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() { +// Based on jsDump by Ariel Flesler +// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html +QUnit.dump = (function() { function quote( str ) { return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; } @@ -1909,42 +1372,43 @@ QUnit.jsDump = (function() { return o + ""; } function join( pre, arr, post ) { - var s = jsDump.separator(), - base = jsDump.indent(), - inner = jsDump.indent(1); + var s = dump.separator(), + base = dump.indent(), + inner = dump.indent( 1 ); if ( arr.join ) { arr = arr.join( "," + s + inner ); } if ( !arr ) { return pre + post; } - return [ pre, inner + arr, base + post ].join(s); + return [ pre, inner + arr, base + post ].join( s ); } function array( arr, stack ) { - var i = arr.length, ret = new Array(i); + var i = arr.length, + ret = new Array( i ); this.up(); while ( i-- ) { - ret[i] = this.parse( arr[i] , undefined , stack); + ret[ i ] = this.parse( arr[ i ], undefined, stack ); } this.down(); return join( "[", ret, "]" ); } var reName = /^function (\w+)/, - jsDump = { + dump = { // type is used mostly internally, you can fix a (custom)type in advance parse: function( obj, type, stack ) { - stack = stack || [ ]; + stack = stack || []; var inStack, res, - parser = this.parsers[ type || this.typeOf(obj) ]; + parser = this.parsers[ type || this.typeOf( obj ) ]; type = typeof parser; inStack = inArray( obj, stack ); if ( inStack !== -1 ) { - return "recursion(" + (inStack - stack.length) + ")"; + return "recursion(" + ( inStack - stack.length ) + ")"; } - if ( type === "function" ) { + if ( type === "function" ) { stack.push( obj ); res = parser.call( this, obj, stack ); stack.pop(); @@ -1958,11 +1422,11 @@ QUnit.jsDump = (function() { type = "null"; } else if ( typeof obj === "undefined" ) { type = "undefined"; - } else if ( QUnit.is( "regexp", obj) ) { + } else if ( QUnit.is( "regexp", obj ) ) { type = "regexp"; - } else if ( QUnit.is( "date", obj) ) { + } else if ( QUnit.is( "date", obj ) ) { type = "date"; - } else if ( QUnit.is( "function", obj) ) { + } else if ( QUnit.is( "function", obj ) ) { type = "function"; } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { type = "window"; @@ -1971,10 +1435,12 @@ QUnit.jsDump = (function() { } else if ( obj.nodeType ) { type = "node"; } else if ( + // native arrays toString.call( obj ) === "[object Array]" || + // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) + ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && typeof obj[ 0 ] === "undefined" ) ) ) ) { type = "array"; } else if ( obj.constructor === Error.prototype.constructor ) { @@ -1985,7 +1451,7 @@ QUnit.jsDump = (function() { return type; }, separator: function() { - return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; + return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; }, // extra can be a number, shortcut for increasing-calling-decreasing indent: function( extra ) { @@ -1996,7 +1462,7 @@ QUnit.jsDump = (function() { if ( this.HTML ) { chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); } - return new Array( this.depth + ( extra || 0 ) ).join(chr); + return new Array( this.depth + ( extra || 0 ) ).join( chr ); }, up: function( a ) { this.depth += a || 1; @@ -2005,7 +1471,7 @@ QUnit.jsDump = (function() { this.depth -= a || 1; }, setParser: function( name, parser ) { - this.parsers[name] = parser; + this.parsers[ name ] = parser; }, // The next 3 are exposed so you can use them quote: quote, @@ -2013,11 +1479,11 @@ QUnit.jsDump = (function() { join: join, // depth: 1, - // This is the list of parsers, to modify them, use jsDump.setParser + // This is the list of parsers, to modify them, use dump.setParser parsers: { window: "[Window]", document: "[Document]", - error: function(error) { + error: function( error ) { return "Error(\"" + error.message + "\")"; }, unknown: "[Unknown]", @@ -2026,51 +1492,61 @@ QUnit.jsDump = (function() { "function": function( fn ) { var ret = "function", // functions never have name in IE - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; + name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; if ( name ) { ret += " " + name; } ret += "( "; - ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); + ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); + return join( ret, dump.parse( fn, "functionCode" ), "}" ); }, array: array, nodelist: array, "arguments": array, object: function( map, stack ) { /*jshint forin:false */ - var ret = [ ], keys, key, val, i; - QUnit.jsDump.up(); + var ret = [], keys, key, val, i, nonEnumerableProperties; + dump.up(); keys = []; for ( key in map ) { keys.push( key ); } + + // Some properties are not always enumerable on Error objects. + nonEnumerableProperties = [ "message", "name" ]; + for ( i in nonEnumerableProperties ) { + key = nonEnumerableProperties[ i ]; + if ( key in map && !( key in keys ) ) { + keys.push( key ); + } + } keys.sort(); for ( i = 0; i < keys.length; i++ ) { key = keys[ i ]; val = map[ key ]; - ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); + ret.push( dump.parse( key, "key" ) + ": " + dump.parse( val, undefined, stack ) ); } - QUnit.jsDump.down(); + dump.down(); return join( "{", ret, "}" ); }, node: function( node ) { var len, i, val, - open = QUnit.jsDump.HTML ? "<" : "<", - close = QUnit.jsDump.HTML ? ">" : ">", + open = dump.HTML ? "<" : "<", + close = dump.HTML ? ">" : ">", tag = node.nodeName.toLowerCase(), ret = open + tag, attrs = node.attributes; if ( attrs ) { for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[i].nodeValue; + val = attrs[ i ].nodeValue; + // IE6 includes all attributes in .attributes, even ones not explicitly set. // Those have values like undefined, null, 0, false, "" or "inherit". if ( val && val !== "inherit" ) { - ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); + ret += " " + attrs[ i ].nodeName + "=" + dump.parse( val, "attribute" ); } } } @@ -2083,6 +1559,7 @@ QUnit.jsDump = (function() { return ret + open + "/" + tag + close; }, + // function calls it internally, it's the arguments part of the function functionArgs: function( fn ) { var args, @@ -2092,10 +1569,11 @@ QUnit.jsDump = (function() { return ""; } - args = new Array(l); + args = new Array( l ); while ( l-- ) { + // 97 is 'a' - args[l] = String.fromCharCode(97+l); + args[ l ] = String.fromCharCode( 97 + l ); } return " " + args.join( ", " ) + " "; }, @@ -2119,9 +1597,73 @@ QUnit.jsDump = (function() { multiline: true }; - return jsDump; + return dump; }()); +// back compat +QUnit.jsDump = QUnit.dump; + +// For browser, export only select globals +if ( typeof window !== "undefined" ) { + + // Deprecated + // Extend assert methods to QUnit and Global scope through Backwards compatibility + (function() { + var i, + assertions = Assert.prototype; + + function applyCurrent( current ) { + return function() { + var assert = new Assert( QUnit.config.current ); + current.apply( assert, arguments ); + }; + } + + for ( i in assertions ) { + QUnit[ i ] = applyCurrent( assertions[ i ] ); + } + })(); + + (function() { + var i, l, + keys = [ + "test", + "module", + "expect", + "asyncTest", + "start", + "stop", + "ok", + "equal", + "notEqual", + "propEqual", + "notPropEqual", + "deepEqual", + "notDeepEqual", + "strictEqual", + "notStrictEqual", + "throws" + ]; + + for ( i = 0, l = keys.length; i < l; i++ ) { + window[ keys[ i ] ] = QUnit[ keys[ i ] ]; + } + })(); + + window.QUnit = QUnit; +} + +// For CommonJS environments, export everything +if ( typeof module !== "undefined" && module.exports ) { + module.exports = QUnit; +} + +// Get a reference to the global object, like window in browsers +}( (function() { + return this; +})() )); + +/*istanbul ignore next */ /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) @@ -2137,6 +1679,8 @@ QUnit.jsDump = (function() { * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" */ QUnit.diff = (function() { + var hasOwn = Object.prototype.hasOwnProperty; + /*jshint eqeqeq:false, eqnull:true */ function diff( o, n ) { var i, @@ -2144,65 +1688,65 @@ QUnit.diff = (function() { os = {}; for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[i] ) ) { - ns[ n[i] ] = { + if ( !hasOwn.call( ns, n[ i ] ) ) { + ns[ n[ i ] ] = { rows: [], o: null }; } - ns[ n[i] ].rows.push( i ); + ns[ n[ i ] ].rows.push( i ); } for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[i] ) ) { - os[ o[i] ] = { + if ( !hasOwn.call( os, o[ i ] ) ) { + os[ o[ i ] ] = { rows: [], n: null }; } - os[ o[i] ].rows.push( i ); + os[ o[ i ] ].rows.push( i ); } for ( i in ns ) { if ( hasOwn.call( ns, i ) ) { - if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { - n[ ns[i].rows[0] ] = { - text: n[ ns[i].rows[0] ], - row: os[i].rows[0] + if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) { + n[ ns[ i ].rows[ 0 ] ] = { + text: n[ ns[ i ].rows[ 0 ] ], + row: os[ i ].rows[ 0 ] }; - o[ os[i].rows[0] ] = { - text: o[ os[i].rows[0] ], - row: ns[i].rows[0] + o[ os[ i ].rows[ 0 ] ] = { + text: o[ os[ i ].rows[ 0 ] ], + row: ns[ i ].rows[ 0 ] }; } } } for ( i = 0; i < n.length - 1; i++ ) { - if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && - n[ i + 1 ] == o[ n[i].row + 1 ] ) { + if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null && + n[ i + 1 ] == o[ n[ i ].row + 1 ] ) { n[ i + 1 ] = { text: n[ i + 1 ], - row: n[i].row + 1 + row: n[ i ].row + 1 }; - o[ n[i].row + 1 ] = { - text: o[ n[i].row + 1 ], + o[ n[ i ].row + 1 ] = { + text: o[ n[ i ].row + 1 ], row: i + 1 }; } } for ( i = n.length - 1; i > 0; i-- ) { - if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && - n[ i - 1 ] == o[ n[i].row - 1 ]) { + if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null && + n[ i - 1 ] == o[ n[ i ].row - 1 ] ) { n[ i - 1 ] = { text: n[ i - 1 ], - row: n[i].row - 1 + row: n[ i ].row - 1 }; - o[ n[i].row - 1 ] = { - text: o[ n[i].row - 1 ], + o[ n[ i ].row - 1 ] = { + text: o[ n[ i ].row - 1 ], row: i - 1 }; } @@ -2220,48 +1764,45 @@ QUnit.diff = (function() { var i, pre, str = "", - out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), - oSpace = o.match(/\s+/g), - nSpace = n.match(/\s+/g); + out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ), + oSpace = o.match( /\s+/g ), + nSpace = n.match( /\s+/g ); if ( oSpace == null ) { oSpace = [ " " ]; - } - else { + } else { oSpace.push( " " ); } if ( nSpace == null ) { nSpace = [ " " ]; - } - else { + } else { nSpace.push( " " ); } if ( out.n.length === 0 ) { for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[i] + oSpace[i] + ""; + str += "" + out.o[ i ] + oSpace[ i ] + ""; } - } - else { - if ( out.n[0].text == null ) { - for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { - str += "" + out.o[n] + oSpace[n] + ""; + } else { + if ( out.n[ 0 ].text == null ) { + for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) { + str += "" + out.o[ n ] + oSpace[ n ] + ""; } } for ( i = 0; i < out.n.length; i++ ) { - if (out.n[i].text == null) { - str += "" + out.n[i] + nSpace[i] + ""; - } - else { + if ( out.n[ i ].text == null ) { + str += "" + out.n[ i ] + nSpace[ i ] + ""; + } else { + // `pre` initialized at top of scope pre = ""; - for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { - pre += "" + out.o[n] + oSpace[n] + ""; + for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) { + pre += "" + out.o[ n ] + oSpace[ n ] + ""; } - str += " " + out.n[i].text + nSpace[i] + pre; + str += " " + out.n[ i ].text + nSpace[ i ] + pre; } } } @@ -2270,19 +1811,685 @@ QUnit.diff = (function() { }; }()); -// For browser, export only select globals -if ( typeof window !== "undefined" ) { - extend( window, QUnit.constructor.prototype ); - window.QUnit = QUnit; +(function() { + +// Deprecated QUnit.init - Ref #530 +// Re-initialize the configuration options +QUnit.init = function() { + var tests, banner, result, qunit, + config = QUnit.config; + + config.stats = { all: 0, bad: 0 }; + config.moduleStats = { all: 0, bad: 0 }; + config.started = 0; + config.updateRate = 1000; + config.blocking = false; + config.autostart = true; + config.autorun = false; + config.filter = ""; + config.queue = []; + config.semaphore = 1; + + // Return on non-browser environments + // This is necessary to not break on node tests + if ( typeof window === "undefined" ) { + return; + } + + qunit = id( "qunit" ); + if ( qunit ) { + qunit.innerHTML = + "

    " + escapeText( document.title ) + "

    " + + "

    " + + "
    " + + "

    " + + "
      "; + } + + tests = id( "qunit-tests" ); + banner = id( "qunit-banner" ); + result = id( "qunit-testresult" ); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
       "; + } +}; + +// Resets the test setup. Useful for tests that modify the DOM. +/* +DEPRECATED: Use multiple tests instead of resetting inside a test. +Use testStart or testDone for custom cleanup. +This method will throw an error in 2.0, and will be removed in 2.1 +*/ +QUnit.reset = function() { + + // Return on non-browser environments + // This is necessary to not break on node tests + if ( typeof window === "undefined" ) { + return; + } + + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; + } +}; + +// Don't load the HTML Reporter on non-Browser environments +if ( typeof window === "undefined" ) { + return; } -// For CommonJS environments, export everything -if ( typeof module !== "undefined" && module.exports ) { - module.exports = QUnit; +var config = QUnit.config, + hasOwn = Object.prototype.hasOwnProperty, + defined = { + document: typeof window.document !== "undefined", + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }()) + }; + +/** +* Escape text for attribute or text content. +*/ +function escapeText( s ) { + if ( !s ) { + return ""; + } + s = s + ""; + + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { + switch ( s ) { + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + } + }); } +/** + * @param {HTMLElement} elem + * @param {string} type + * @param {Function} fn + */ +function addEvent( elem, type, fn ) { + if ( elem.addEventListener ) { -// Get a reference to the global object, like window in browsers -}( (function() { - return this; -})() )); + // Standards-based browsers + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + + // support: IE <9 + elem.attachEvent( "on" + type, fn ); + } +} + +/** + * @param {Array|NodeList} elems + * @param {string} type + * @param {Function} fn + */ +function addEvents( elems, type, fn ) { + var i = elems.length; + while ( i-- ) { + addEvent( elems[ i ], type, fn ); + } +} + +function hasClass( elem, name ) { + return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; +} + +function addClass( elem, name ) { + if ( !hasClass( elem, name ) ) { + elem.className += ( elem.className ? " " : "" ) + name; + } +} + +function toggleClass( elem, name ) { + if ( hasClass( elem, name ) ) { + removeClass( elem, name ); + } else { + addClass( elem, name ); + } +} + +function removeClass( elem, name ) { + var set = " " + elem.className + " "; + + // Class name may appear multiple times + while ( set.indexOf( " " + name + " " ) >= 0 ) { + set = set.replace( " " + name + " ", " " ); + } + + // trim for prettiness + elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); +} + +function id( name ) { + return defined.document && document.getElementById && document.getElementById( name ); +} + +function getUrlConfigHtml() { + var i, j, val, + escaped, escapedTooltip, + selection = false, + len = config.urlConfig.length, + urlConfigHtml = ""; + + for ( i = 0; i < len; i++ ) { + val = config.urlConfig[ i ]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val + }; + } + + escaped = escapeText( val.id ); + escapedTooltip = escapeText( val.tooltip ); + + config[ val.id ] = QUnit.urlParams[ val.id ]; + if ( !val.value || typeof val.value === "string" ) { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; + } + } + + return urlConfigHtml; +} + +function toolbarUrlConfigContainer() { + var urlConfigContainer = document.createElement( "span" ); + + urlConfigContainer.innerHTML = getUrlConfigHtml(); + + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" for checkboxes + // * Fallback from event.target to event.srcElement + addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", function( event ) { + var params = {}, + target = event.target || event.srcElement; + params[ target.name ] = target.checked ? + target.defaultValue || true : + undefined; + window.location = QUnit.url( params ); + }); + addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", function( event ) { + var params = {}, + target = event.target || event.srcElement; + params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; + window.location = QUnit.url( params ); + }); + + return urlConfigContainer; +} + +function getModuleNames() { + var i, + moduleNames = []; + + for ( i in config.modules ) { + if ( config.modules.hasOwnProperty( i ) ) { + moduleNames.push( i ); + } + } + + moduleNames.sort(function( a, b ) { + return a.localeCompare( b ); + }); + + return moduleNames; +} + +function toolbarModuleFilterHtml() { + var i, + moduleFilterHtml = "", + moduleNames = getModuleNames(); + + if ( moduleNames.length <= 1 ) { + return false; + } + + moduleFilterHtml += "" + + ""; + + return moduleFilterHtml; +} + +function toolbarModuleFilter() { + var moduleFilter = document.createElement( "span" ), + moduleFilterHtml = toolbarModuleFilterHtml(); + + if ( !moduleFilterHtml ) { + return false; + } + + moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); + moduleFilter.innerHTML = moduleFilterHtml; + + addEvent( moduleFilter.lastChild, "change", function() { + var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ], + selectedModule = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ); + + window.location = QUnit.url({ + module: ( selectedModule === "" ) ? undefined : selectedModule, + + // Remove any existing filters + filter: undefined, + testNumber: undefined + }); + }); + + return moduleFilter; +} + +function toolbarFilter() { + var testList = id( "qunit-tests" ), + filter = document.createElement( "input" ); + + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + + addEvent( filter, "click", function() { + if ( filter.checked ) { + addClass( testList, "hidepass" ); + if ( defined.sessionStorage ) { + sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); + } + } else { + removeClass( testList, "hidepass" ); + if ( defined.sessionStorage ) { + sessionStorage.removeItem( "qunit-filter-passed-tests" ); + } + } + }); + + if ( config.hidepassed || defined.sessionStorage && + sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { + filter.checked = true; + + addClass( testList, "hidepass" ); + } + + return filter; +} + +function toolbarLabel() { + var label = document.createElement( "label" ); + label.setAttribute( "for", "qunit-filter-pass" ); + label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); + label.innerHTML = "Hide passed tests"; + + return label; +} + +function appendToolbar() { + var moduleFilter, + toolbar = id( "qunit-testrunner-toolbar" ); + + if ( toolbar ) { + toolbar.appendChild( toolbarFilter() ); + toolbar.appendChild( toolbarLabel() ); + toolbar.appendChild( toolbarUrlConfigContainer() ); + + moduleFilter = toolbarModuleFilter(); + if ( moduleFilter ) { + toolbar.appendChild( moduleFilter ); + } + } +} + +function appendBanner() { + var banner = id( "qunit-banner" ); + + if ( banner ) { + banner.className = ""; + banner.innerHTML = "" + banner.innerHTML + " "; + } +} + +function appendTestResults() { + var tests = id( "qunit-tests" ), + result = id( "qunit-testresult" ); + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + tests.innerHTML = ""; + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
       "; + } +} + +function storeFixture() { + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + config.fixture = fixture.innerHTML; + } +} + +function appendUserAgent() { + var userAgent = id( "qunit-userAgent" ); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } +} + +// HTML Reporter initialization and load +QUnit.begin(function() { + var qunit = id( "qunit" ); + + if ( qunit ) { + qunit.innerHTML = + "

      " + escapeText( document.title ) + "

      " + + "

      " + + "
      " + + "

      " + + "
        "; + } + + appendBanner(); + appendTestResults(); + appendUserAgent(); + appendToolbar(); + storeFixture(); +}); + +QUnit.done(function( details ) { + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + html = [ + "Tests completed in ", + details.runtime, + " milliseconds.
        ", + "", + details.passed, + " assertions of ", + details.total, + " passed, ", + details.failed, + " failed." + ].join( "" ); + + if ( banner ) { + banner.className = details.failed ? "qunit-fail" : "qunit-pass"; + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + if ( config.altertitle && defined.document && document.title ) { + + // show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document.title = [ + ( details.failed ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); + } + + // clear own sessionStorage items if all tests passed + if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); + } + } + } + + // scroll back to top to show results + if ( config.scrolltop && window.scrollTo ) { + window.scrollTo( 0, 0 ); + } +}); + +function getNameHtml( name, module ) { + var nameHtml = ""; + + if ( module ) { + nameHtml = "" + escapeText( module ) + ": "; + } + + nameHtml += "" + escapeText( name ) + ""; + + return nameHtml; +} + +QUnit.testStart(function( details ) { + var a, b, li, running, assertList, + name = getNameHtml( details.name, details.module ), + tests = id( "qunit-tests" ); + + if ( tests ) { + b = document.createElement( "strong" ); + b.innerHTML = name; + + a = document.createElement( "a" ); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ testNumber: details.testNumber }); + + li = document.createElement( "li" ); + li.appendChild( b ); + li.appendChild( a ); + li.className = "running"; + li.id = "qunit-test-output" + details.testNumber; + + assertList = document.createElement( "ol" ); + assertList.className = "qunit-assert-list"; + + li.appendChild( assertList ); + + tests.appendChild( li ); + } + + running = id( "qunit-testresult" ); + if ( running ) { + running.innerHTML = "Running:
        " + name; + } + +}); + +QUnit.log(function( details ) { + var assertList, assertLi, + message, expected, actual, + testItem = id( "qunit-test-output" + details.testNumber ); + + if ( !testItem ) { + return; + } + + message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); + message = "" + message + ""; + + // pushFailure doesn't provide details.expected + // when it calls, it's implicit to also not show expected and diff stuff + // Also, we need to check details.expected existence, as it can exist and be undefined + if ( !details.result && hasOwn.call( details, "expected" ) ) { + expected = escapeText( QUnit.dump.parse( details.expected ) ); + actual = escapeText( QUnit.dump.parse( details.actual ) ); + message += ""; + + if ( actual !== expected ) { + message += "" + + ""; + } + + if ( details.source ) { + message += ""; + } + + message += "
        Expected:
        " +
        +			expected +
        +			"
        Result:
        " +
        +				actual + "
        Diff:
        " +
        +				QUnit.diff( expected, actual ) + "
        Source:
        " +
        +				escapeText( details.source ) + "
        "; + + // this occours when pushFailure is set and we have an extracted stack trace + } else if ( !details.result && details.source ) { + message += "" + + "" + + "
        Source:
        " +
        +			escapeText( details.source ) + "
        "; + } + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + assertLi = document.createElement( "li" ); + assertLi.className = details.result ? "pass" : "fail"; + assertLi.innerHTML = message; + assertList.appendChild( assertLi ); +}); + +QUnit.testDone(function( details ) { + var testTitle, time, testItem, assertList, + good, bad, testCounts, + tests = id( "qunit-tests" ); + + // QUnit.reset() is deprecated and will be replaced for a new + // fixture reset function on QUnit 2.0/2.1. + // It's still called here for backwards compatibility handling + QUnit.reset(); + + if ( !tests ) { + return; + } + + testItem = id( "qunit-test-output" + details.testNumber ); + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + good = details.passed; + bad = details.failed; + + // store result when possible + if ( config.reorder && defined.sessionStorage ) { + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); + } else { + sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); + } + } + + if ( bad === 0 ) { + addClass( assertList, "qunit-collapsed" ); + } + + // testItem.firstChild is the test name + testTitle = testItem.firstChild; + + testCounts = bad ? + "" + bad + ", " + "" + good + ", " : + ""; + + testTitle.innerHTML += " (" + testCounts + + details.assertions.length + ")"; + + addEvent( testTitle, "click", function() { + toggleClass( assertList, "qunit-collapsed" ); + }); + + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = details.runtime + " ms"; + + testItem.className = bad ? "fail" : "pass"; + + testItem.insertBefore( time, assertList ); +}); + +if ( !defined.document || document.readyState === "complete" ) { + config.autorun = true; +} + +if ( defined.document ) { + addEvent( window, "load", QUnit.load ); +} + +})();