From afe2727c5610ba9cd5ffbec5c34f15d38538b05f Mon Sep 17 00:00:00 2001 From: Oleg Gaidarenko Date: Thu, 19 May 2016 22:24:46 +0300 Subject: [PATCH] Build: Bump qunit version So it would correspond to one in master --- external/qunit/LICENSE.txt | 7 +- external/qunit/qunit.css | 41 +- external/qunit/qunit.js | 3841 +++++++++++++++++++++++++----------- package.json | 2 +- 4 files changed, 2687 insertions(+), 1204 deletions(-) diff --git a/external/qunit/LICENSE.txt b/external/qunit/LICENSE.txt index fb928a543..a3a9b5120 100644 --- a/external/qunit/LICENSE.txt +++ b/external/qunit/LICENSE.txt @@ -30,7 +30,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ==== -All files located in the node_modules and external directories are -externally maintained libraries used by this software which have their -own licenses; we recommend you read them, as their terms may differ from -the terms above. +All files located in the node_modules directory are externally maintained +libraries used by this software which have their own licenses; we +recommend you read them, as their terms may differ from the terms above. diff --git a/external/qunit/qunit.css b/external/qunit/qunit.css index 0eb0b0171..ae68fc412 100644 --- a/external/qunit/qunit.css +++ b/external/qunit/qunit.css @@ -1,27 +1,27 @@ /*! - * QUnit 1.17.1 - * http://qunitjs.com/ + * QUnit 1.23.1 + * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://jquery.org/license * - * Date: 2015-01-20T19:39Z + * Date: 2016-04-12T17:29Z */ /** Font Family and Sizes */ -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult { font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; } -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } +#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } #qunit-tests { font-size: smaller; } /** Resets */ -#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { margin: 0; padding: 0; } @@ -68,6 +68,12 @@ overflow: hidden; } +#qunit-filteredTest { + padding: 0.5em 1em 0.5em 1em; + background-color: #F4FF77; + color: #366097; +} + #qunit-userAgent { padding: 0.5em 1em 0.5em 1em; background-color: #2B81AF; @@ -114,9 +120,19 @@ display: list-item; } +#qunit-tests.hidepass { + position: relative; +} + #qunit-tests.hidepass li.running, #qunit-tests.hidepass li.pass { - display: none; + visibility: hidden; + position: absolute; + width: 0; + height: 0; + padding: 0; + border: 0; + margin: 0; } #qunit-tests li strong { @@ -132,6 +148,11 @@ color: #C2CCD1; text-decoration: none; } + +#qunit-tests li p a { + padding: 0.25em; + color: #6B6464; +} #qunit-tests li a:hover, #qunit-tests li a:focus { color: #000; @@ -151,6 +172,10 @@ border-radius: 5px; } +.qunit-source { + margin: 0.6em 0 0.3em; +} + .qunit-collapsed { display: none; } diff --git a/external/qunit/qunit.js b/external/qunit/qunit.js index 006ca4747..5df0822ea 100644 --- a/external/qunit/qunit.js +++ b/external/qunit/qunit.js @@ -1,146 +1,259 @@ /*! - * QUnit 1.17.1 - * http://qunitjs.com/ + * QUnit 1.23.1 + * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://jquery.org/license * - * Date: 2015-01-20T19:39Z + * Date: 2016-04-12T17:29Z */ -(function( window ) { +( function( global ) { -var QUnit, - config, - onErrorFnPrev, - loggingCallbacks = {}, - 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(); - }, - globalStartCalled = false, - runStarted = false, - setTimeout = window.setTimeout, - clearTimeout = window.clearTimeout, - defined = { - document: window.document !== undefined, - setTimeout: window.setTimeout !== undefined, - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch ( e ) { - return false; +var QUnit = {}; + +var Date = global.Date; +var now = Date.now || function() { + return new Date().getTime(); +}; + +var setTimeout = global.setTimeout; +var clearTimeout = global.clearTimeout; + +// Store a local window from the global to allow direct references. +var window = global.window; + +var defined = { + document: window && window.document !== undefined, + setTimeout: setTimeout !== undefined, + sessionStorage: ( function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }() ) +}; + +var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ); +var globalStartCalled = false; +var runStarted = false; + +var toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty; + +// Returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var i, j, + result = a.slice(); + + for ( i = 0; i < result.length; i++ ) { + for ( j = 0; j < b.length; j++ ) { + if ( result[ i ] === b[ j ] ) { + result.splice( i, 1 ); + i--; + break; } - }()) - }, - /** - * Provides a normalized error string, correcting an issue - * with IE 7 (and prior) where Error.prototype.toString is - * not properly implemented - * - * Based on http://es5.github.com/#x15.11.4.4 - * - * @param {String|Error} error - * @return {String} error message - */ - errorString = function( error ) { - var name, message, - errorString = error.toString(); - if ( errorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; + } + } + return result; +} + +// From jquery.js +function inArray( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; +} + +/** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ +function objectValues ( obj ) { + 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; + } + } + return vals; +} + +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 + // This block runs on every environment, so `global` is being used instead of `window` + // to avoid errors on node. + if ( prop !== "constructor" || a !== global ) { + if ( b[ prop ] === undefined ) { + delete a[ prop ]; + } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { + a[ prop ] = b[ prop ]; + } } - } else { - return errorString; } - }, - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - objectValues = function( obj ) { - 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; + } + + return a; +} + +function objectType( obj ) { + if ( typeof obj === "undefined" ) { + return "undefined"; + } + + // Consider: typeof null === object + if ( obj === null ) { + return "null"; + } + + var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), + type = match && match[ 1 ]; + + switch ( type ) { + case "Number": + if ( isNaN( obj ) ) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Set": + case "Map": + case "Date": + case "RegExp": + case "Function": + case "Symbol": + return type.toLowerCase(); + } + if ( typeof obj === "object" ) { + return "object"; + } +} + +// Safe object type checking +function is( type, obj ) { + return QUnit.objectType( obj ) === type; +} + +// Doesn't support IE6 to IE9, it will return undefined on these browsers +// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack +function extractStacktrace( e, offset ) { + offset = offset === undefined ? 4 : offset; + + var stack, include, i; + + if ( e.stack ) { + stack = e.stack.split( "\n" ); + if ( /^error$/i.test( stack[ 0 ] ) ) { + stack.shift(); + } + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { + break; + } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); } } - return vals; - }; + return stack[ offset ]; + + // Support: Safari <=6 only + } else if ( e.sourceURL ) { + + // 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; + } +} + +function sourceFromStacktrace( offset ) { + var error = new Error(); + + // Support: Safari <=7 only, IE <=10 - 11 only + // Not all browsers generate the `stack` property for `new Error()`, see also #636 + if ( !error.stack ) { + try { + throw error; + } catch ( err ) { + error = err; + } + } -QUnit = {}; + return extractStacktrace( error, offset ); +} /** * Config object: Maintain internal state * Later exposed as QUnit.config * `config` initialized at top of scope */ -config = { +var config = { + // The queue of tests to run queue: [], - // block until document ready + // Block until document ready blocking: true, - // by default, run previously failed tests first + // By default, run previously failed tests first // very useful in combination with "Hide passed tests" checked reorder: true, - // by default, modify document.title when suite is done + // By default, modify document.title when suite is done altertitle: true, - // by default, scroll to top of the page when suite is done + // HTML Reporter: collapse every test except the first failing test + // If false, all failing tests will be expanded + collapse: true, + + // By default, scroll to top of the page when suite is done scrolltop: true, - // when enabled, all tests must call expect() + // Depth up-to which object will be dumped + maxDepth: 5, + + // When enabled, all tests must call expect() requireExpects: false, - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "hidepassed", - label: "Hide passed tests", - tooltip: "Only show tests and assertions that fail. Stored as query-strings." - }, - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the " + - "`window` object. Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + - "exceptions in IE reasonable. Stored as query-strings." - } - ], + // Placeholder for user-configurable form-exposed URL parameters + urlConfig: [], // Set of all modules. modules: [], + // Stack of nested modules + moduleStack: [], + // The first unnamed module currentModule: { name: "", @@ -153,63 +266,139 @@ config = { // Push a loose unnamed module to the modules collection config.modules.push( config.currentModule ); -// Initialize more QUnit.config and QUnit.urlParams -(function() { - var i, current, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - if ( urlParams[ current[ 0 ] ] ) { - urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); - } else { - urlParams[ current[ 0 ] ] = current[ 1 ]; +var loggingCallbacks = {}; + +// Register logging callbacks +function registerLoggingCallbacks( obj ) { + var i, l, key, + callbackNames = [ "begin", "done", "log", "testStart", "testDone", + "moduleStart", "moduleDone" ]; + + function registerLoggingCallback( key ) { + var loggingCallback = function( callback ) { + if ( objectType( callback ) !== "function" ) { + throw new Error( + "QUnit logging methods require a callback function as their first parameters." + ); } + + config.callbacks[ key ].push( callback ); + }; + + // DEPRECATED: This will be removed on QUnit 2.0.0+ + // Stores the registered functions allowing restoring + // at verifyLoggingCallbacks() if modified + loggingCallbacks[ key ] = loggingCallback; + + return loggingCallback; + } + + for ( i = 0, l = callbackNames.length; i < l; i++ ) { + key = callbackNames[ i ]; + + // Initialize key collection of logging callback + if ( objectType( config.callbacks[ key ] ) === "undefined" ) { + config.callbacks[ key ] = []; } + + obj[ key ] = registerLoggingCallback( key ); } +} + +function runLoggingCallbacks( key, args ) { + var i, l, callbacks; - if ( urlParams.filter === true ) { - delete urlParams.filter; + callbacks = config.callbacks[ key ]; + for ( i = 0, l = callbacks.length; i < l; i++ ) { + callbacks[ i ]( args ); } +} + +// DEPRECATED: This will be removed on 2.0.0+ +// This function verifies if the loggingCallbacks were modified by the user +// If so, it will restore it, assign the given callback and print a console warning +function verifyLoggingCallbacks() { + var loggingCallback, userCallback; + + for ( loggingCallback in loggingCallbacks ) { + if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { - QUnit.urlParams = urlParams; + userCallback = QUnit[ loggingCallback ]; - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; + // Restore the callback function + QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; - config.testId = []; - if ( urlParams.testId ) { + // Assign the deprecated given callback + QUnit[ loggingCallback ]( userCallback ); - // Ensure that urlParams.testId is an array - urlParams.testId = [].concat( urlParams.testId ); - for ( i = 0; i < urlParams.testId.length; i++ ) { - config.testId.push( urlParams.testId[ i ] ); + if ( global.console && global.console.warn ) { + global.console.warn( + "QUnit." + loggingCallback + " was replaced with a new value.\n" + + "Please, check out the documentation on how to apply logging callbacks.\n" + + "Reference: https://api.qunitjs.com/category/callbacks/" + ); + } } } +} + +( function() { + if ( !defined.document ) { + return; + } + + // `onErrorFnPrev` initialized at top of scope + // Preserve other handlers + var 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 ) { + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); + } + + // Treat return value as window.onerror itself does, + // Only do our handling if not suppressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", extend( function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }, { validTest: true } ) ); + } + return false; + } + + return ret; + }; +}() ); + +// Figure out if we're running the tests from a server or not +QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" ); - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; -}()); +// Expose the current QUnit version +QUnit.version = "1.23.1"; -// Root QUnit object. -// `QUnit` initialized at top of scope extend( QUnit, { - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - var currentModule = { - name: name, - testEnvironment: testEnvironment, - tests: [] - }; + // Call on start of module test to prepend name to all tests + module: function( name, testEnvironment, executeNow ) { + var module, moduleFns; + var currentModule = config.currentModule; + + if ( arguments.length === 2 ) { + if ( objectType( testEnvironment ) === "function" ) { + executeNow = testEnvironment; + testEnvironment = undefined; + } + } // DEPRECATED: handles setup/teardown functions, // beforeEach and afterEach should be used instead @@ -222,46 +411,62 @@ extend( QUnit, { delete testEnvironment.teardown; } - config.modules.push( currentModule ); - config.currentModule = currentModule; - }, + module = createModule(); - // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; + moduleFns = { + beforeEach: setHook( module, "beforeEach" ), + afterEach: setHook( module, "afterEach" ) + }; + + if ( objectType( executeNow ) === "function" ) { + config.moduleStack.push( module ); + setCurrentModule( module ); + executeNow.call( module.testEnvironment, moduleFns ); + config.moduleStack.pop(); + module = module.parentModule || currentModule; } - QUnit.test( testName, expected, callback, true ); - }, + setCurrentModule( module ); + + function createModule() { + var parentModule = config.moduleStack.length ? + config.moduleStack.slice( -1 )[ 0 ] : null; + var moduleName = parentModule !== null ? + [ parentModule.name, name ].join( " > " ) : name; + var module = { + name: moduleName, + parentModule: parentModule, + tests: [], + moduleId: generateHash( moduleName ) + }; - test: function( testName, expected, callback, async ) { - var test; + var env = {}; + if ( parentModule ) { + extend( env, parentModule.testEnvironment ); + delete env.beforeEach; + delete env.afterEach; + } + extend( env, testEnvironment ); + module.testEnvironment = env; - if ( arguments.length === 2 ) { - callback = expected; - expected = null; + config.modules.push( module ); + return module; } - test = new Test({ - testName: testName, - expected: expected, - async: async, - callback: callback - }); + function setCurrentModule( module ) { + config.currentModule = module; + } - test.queue(); }, - skip: function( testName ) { - var test = new Test({ - testName: testName, - skip: true - }); + // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. + asyncTest: asyncTest, - test.queue(); - }, + test: test, + + skip: skip, + + only: only, // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. @@ -289,12 +494,23 @@ extend( QUnit, { // If a test is running, adjust its semaphore config.current.semaphore -= count || 1; + // If semaphore is non-numeric, throw error + if ( isNaN( config.current.semaphore ) ) { + config.current.semaphore = 0; + + QUnit.pushFailure( + "Called start() with a non-numeric decrement.", + sourceFromStacktrace( 2 ) + ); + return; + } + // Don't start until equal number of stop-calls if ( config.current.semaphore > 0 ) { return; } - // throw an Error if start is called more often than stop + // Throw an Error if start is called more often than stop if ( config.current.semaphore < 0 ) { config.current.semaphore = 0; @@ -325,43 +541,9 @@ extend( QUnit, { config: config, - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) === type; - }, - - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - } - - // Consider: typeof null === object - if ( obj === null ) { - return "null"; - } - - var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), - type = match && match[ 1 ] || ""; + is: is, - switch ( type ) { - case "Number": - if ( isNaN( obj ) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, + objectType: objectType, extend: extend, @@ -383,176 +565,50 @@ extend( QUnit, { if ( config.autostart ) { resumeProcessing(); } + }, + + stack: function( offset ) { + offset = ( offset || 0 ) + 2; + return sourceFromStacktrace( offset ); } -}); +} ); -// Register logging callbacks -(function() { - var i, l, key, - callbacks = [ "begin", "done", "log", "testStart", "testDone", - "moduleStart", "moduleDone" ]; +registerLoggingCallbacks( QUnit ); - function registerLoggingCallback( key ) { - var loggingCallback = function( callback ) { - if ( QUnit.objectType( callback ) !== "function" ) { - throw new Error( - "QUnit logging methods require a callback function as their first parameters." - ); - } - - config.callbacks[ key ].push( callback ); - }; - - // DEPRECATED: This will be removed on QUnit 2.0.0+ - // Stores the registered functions allowing restoring - // at verifyLoggingCallbacks() if modified - loggingCallbacks[ key ] = loggingCallback; - - return loggingCallback; - } - - for ( i = 0, l = callbacks.length; i < l; i++ ) { - key = callbacks[ i ]; - - // Initialize key collection of logging callback - if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) { - config.callbacks[ key ] = []; - } - - QUnit[ key ] = registerLoggingCallback( key ); - } -})(); - -// `onErrorFnPrev` initialized at top of scope -// Preserve other handlers -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 ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend(function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: true } ) ); - } - return false; - } - - return ret; -}; - -function done() { - var runtime, passed; - - config.autorun = true; - - // Log the last module results - if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", { - name: config.previousModule.name, - tests: config.previousModule.tests, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all, - runtime: now() - config.moduleStats.started - }); - } - delete config.previousModule; - - runtime = now() - config.started; - passed = config.stats.all - config.stats.bad; - - runLoggingCallbacks( "done", { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); -} - -// 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 ? 4 : offset; - - var stack, include, i; +function begin() { + var i, l, + modulesLog = []; - if ( e.stacktrace ) { + // If the test run hasn't officially begun yet + if ( !config.started ) { - // Opera 12.x - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { + // Record the time of the test run's beginning + config.started = now(); - // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node - stack = e.stack.split( "\n" ); - if ( /^error$/i.test( stack[ 0 ] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { + verifyLoggingCallbacks(); - // Safari < 6 - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; + // Delete the loose unnamed module if unused. + if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { + config.modules.shift(); } - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } -} - -function sourceFromStacktrace( offset ) { - var e = new Error(); - if ( !e.stack ) { - try { - throw e; - } catch ( err ) { - // This should already be true in most browsers - e = err; + // Avoid unnecessary information by not logging modules' test environments + for ( i = 0, l = config.modules.length; i < l; i++ ) { + modulesLog.push( { + name: config.modules[ i ].name, + tests: config.modules[ i ].tests + } ); } - } - return extractStacktrace( e, offset ); -} -function synchronize( callback, last ) { - if ( QUnit.objectType( callback ) === "array" ) { - while ( callback.length ) { - synchronize( callback.shift() ); - } - return; + // The test run is officially beginning now + runLoggingCallbacks( "begin", { + totalTests: Test.count, + modules: modulesLog + } ); } - config.queue.push( callback ); - if ( config.autorun && !config.blocking ) { - process( last ); - } + config.blocking = false; + process( true ); } function process( last ) { @@ -582,40 +638,21 @@ function process( last ) { } } -function begin() { - var i, l, - modulesLog = []; - - // If the test run hasn't officially begun yet - if ( !config.started ) { - - // Record the time of the test run's beginning - config.started = now(); - - verifyLoggingCallbacks(); - - // Delete the loose unnamed module if unused. - if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { - config.modules.shift(); - } - - // Avoid unnecessary information by not logging modules' test environments - for ( i = 0, l = config.modules.length; i < l; i++ ) { - modulesLog.push({ - name: config.modules[ i ].name, - tests: config.modules[ i ].tests - }); - } +function pauseProcessing() { + config.blocking = true; - // The test run is officially beginning now - runLoggingCallbacks( "begin", { - totalTests: Test.count, - modules: modulesLog - }); + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = setTimeout( function() { + if ( config.current ) { + config.current.semaphore = 0; + QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); + } else { + throw new Error( "Test timed out" ); + } + resumeProcessing(); + }, config.testTimeout ); } - - config.blocking = false; - process( true ); } function resumeProcessing() { @@ -623,7 +660,7 @@ function resumeProcessing() { // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) if ( defined.setTimeout ) { - setTimeout(function() { + setTimeout( function() { if ( config.current && config.current.semaphore > 0 ) { return; } @@ -638,169 +675,74 @@ function resumeProcessing() { } } -function pauseProcessing() { - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { - if ( config.current ) { - config.current.semaphore = 0; - QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); - } else { - throw new Error( "Test timed out" ); - } - resumeProcessing(); - }, config.testTimeout ); - } -} +function done() { + var runtime, passed; -function saveGlobal() { - config.pollution = []; + config.autorun = true; - if ( config.noglobals ) { - for ( var key in window ) { - if ( hasOwn.call( window, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } + // Log the last module results + if ( config.previousModule ) { + runLoggingCallbacks( "moduleDone", { + name: config.previousModule.name, + tests: config.previousModule.tests, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started + } ); } -} + delete config.previousModule; -function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; + runtime = now() - config.started; + passed = config.stats.all - config.stats.bad; - saveGlobal(); + runLoggingCallbacks( "done", { + failed: config.stats.bad, + passed: passed, + total: config.stats.all, + runtime: runtime + } ); +} - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); +function setHook( module, hookName ) { + if ( module.testEnvironment === undefined ) { + module.testEnvironment = {}; } - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); - } + return function( callback ) { + module.testEnvironment[ hookName ] = callback; + }; } -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); +var focused = false; +var priorityCount = 0; +var unitSampler; - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[ i ] === b[ j ] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; -} - -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 if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { - a[ prop ] = b[ prop ]; - } - } - } - } - - return a; -} - -function runLoggingCallbacks( key, args ) { - var i, l, callbacks; - - callbacks = config.callbacks[ key ]; - for ( i = 0, l = callbacks.length; i < l; i++ ) { - callbacks[ i ]( args ); - } -} - -// DEPRECATED: This will be removed on 2.0.0+ -// This function verifies if the loggingCallbacks were modified by the user -// If so, it will restore it, assign the given callback and print a console warning -function verifyLoggingCallbacks() { - var loggingCallback, userCallback; - - for ( loggingCallback in loggingCallbacks ) { - if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { - - userCallback = QUnit[ loggingCallback ]; - - // Restore the callback function - QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; - - // Assign the deprecated given callback - QUnit[ loggingCallback ]( userCallback ); - - if ( window.console && window.console.warn ) { - window.console.warn( - "QUnit." + loggingCallback + " was replaced with a new value.\n" + - "Please, check out the documentation on how to apply logging callbacks.\n" + - "Reference: http://api.qunitjs.com/category/callbacks/" - ); - } - } - } -} - -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - -function Test( settings ) { - var i, l; - - ++Test.count; - - extend( this, settings ); - this.assertions = []; - this.semaphore = 0; - this.usedAsync = false; - this.module = config.currentModule; - this.stack = sourceFromStacktrace( 3 ); - - // Register unique strings - for ( i = 0, l = this.module.tests; i < l.length; i++ ) { - if ( this.module.tests[ i ].name === this.testName ) { - this.testName += " "; +function Test( settings ) { + var i, l; + + ++Test.count; + + extend( this, settings ); + this.assertions = []; + this.semaphore = 0; + this.usedAsync = false; + this.module = config.currentModule; + this.stack = sourceFromStacktrace( 3 ); + + // Register unique strings + for ( i = 0, l = this.module.tests; i < l.length; i++ ) { + if ( this.module.tests[ i ].name === this.testName ) { + this.testName += " "; } } this.testId = generateHash( this.module.name, this.testName ); - this.module.tests.push({ + this.module.tests.push( { name: this.testName, testId: this.testId - }); + } ); if ( settings.skip ) { @@ -836,28 +778,30 @@ Test.prototype = { passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all, runtime: now() - config.moduleStats.started - }); + } ); } config.previousModule = this.module; config.moduleStats = { all: 0, bad: 0, started: now() }; runLoggingCallbacks( "moduleStart", { name: this.module.name, tests: this.module.tests - }); + } ); } config.current = this; + if ( this.module.testEnvironment ) { + delete this.module.testEnvironment.beforeEach; + delete this.module.testEnvironment.afterEach; + } this.testEnvironment = extend( {}, this.module.testEnvironment ); - delete this.testEnvironment.beforeEach; - delete this.testEnvironment.afterEach; this.started = now(); runLoggingCallbacks( "testStart", { name: this.testName, module: this.module.name, testId: this.testId - }); + } ); if ( !config.pollution ) { saveGlobal(); @@ -876,19 +820,17 @@ Test.prototype = { this.callbackStarted = now(); if ( config.notrycatch ) { - promise = this.callback.call( this.testEnvironment, this.assert ); - this.resolvePromise( promise ); + runTest( this ); return; } try { - promise = this.callback.call( this.testEnvironment, this.assert ); - this.resolvePromise( promise ); + runTest( this ); } catch ( e ) { this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - // else next test will carry the responsibility + // Else next test will carry the responsibility saveGlobal(); // Restart the tests if they're blocking @@ -896,6 +838,11 @@ Test.prototype = { QUnit.start(); } } + + function runTest( test ) { + promise = test.callback.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise ); + } }, after: function() { @@ -908,16 +855,19 @@ Test.prototype = { return function runHook() { config.current = test; if ( config.notrycatch ) { - promise = hook.call( test.testEnvironment, test.assert ); - test.resolvePromise( promise, hookName ); + callHook(); return; } try { - promise = hook.call( test.testEnvironment, test.assert ); - test.resolvePromise( promise, hookName ); + callHook(); } catch ( error ) { test.pushFailure( hookName + " failed on " + test.testName + ": " + - ( error.message || error ), extractStacktrace( error, 0 ) ); + ( error.message || error ), extractStacktrace( error, 0 ) ); + } + + function callHook() { + promise = hook.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise, hookName ); } }; }, @@ -926,16 +876,20 @@ Test.prototype = { hooks: function( handler ) { var hooks = []; - // Hooks are ignored on skipped tests - if ( this.skip ) { - return hooks; + function processHooks( test, module ) { + if ( module.parentModule ) { + processHooks( test, module.parentModule ); + } + if ( module.testEnvironment && + QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) { + hooks.push( test.queueHook( module.testEnvironment[ handler ], handler ) ); + } } - if ( this.module.testEnvironment && - QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) { - hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) ); + // Hooks are ignored on skipped tests + if ( !this.skip ) { + processHooks( this, this.module ); } - return hooks; }, @@ -980,9 +934,12 @@ Test.prototype = { assertions: this.assertions, testId: this.testId, + // Source of Test + source: this.stack, + // DEPRECATED: this property will be removed in 2.0.0, use runtime instead duration: this.runtime - }); + } ); // QUnit.reset() is deprecated and will be replaced for a new // fixture reset function on QUnit 2.0/2.1. @@ -993,7 +950,7 @@ Test.prototype = { }, queue: function() { - var bad, + var priority, test = this; if ( !this.valid() ) { @@ -1002,14 +959,13 @@ Test.prototype = { function run() { - // each of these can by async - synchronize([ + // Each of these can by async + synchronize( [ function() { test.before(); }, test.hooks( "beforeEach" ), - function() { test.run(); }, @@ -1022,35 +978,33 @@ Test.prototype = { function() { test.finish(); } - ]); + ] ); } - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && + // Prioritize previously failed tests, detected from sessionStorage + priority = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); - if ( bad ) { - run(); - } else { - synchronize( run, true ); - } + return synchronize( run, priority, config.seed ); }, - push: function( result, actual, expected, message ) { + pushResult: function( resultInfo ) { + + // Destructure of resultInfo = { result, actual, expected, message, negative } var source, details = { module: this.module.name, name: this.testName, - result: result, - message: message, - actual: actual, - expected: expected, + result: resultInfo.result, + message: resultInfo.message, + actual: resultInfo.actual, + expected: resultInfo.expected, testId: this.testId, + negative: resultInfo.negative || false, runtime: now() - this.started }; - if ( !result ) { + if ( !resultInfo.result ) { source = sourceFromStacktrace(); if ( source ) { @@ -1060,14 +1014,14 @@ Test.prototype = { runLoggingCallbacks( "log", details ); - this.assertions.push({ - result: !!result, - message: message - }); + this.assertions.push( { + result: !!resultInfo.result, + message: resultInfo.message + } ); }, pushFailure: function( message, source, actual ) { - if ( !this instanceof Test ) { + if ( !( this instanceof Test ) ) { throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace( 2 ) ); } @@ -1088,10 +1042,10 @@ Test.prototype = { runLoggingCallbacks( "log", details ); - this.assertions.push({ + this.assertions.push( { result: false, message: message - }); + } ); }, resolvePromise: function( promise, phase ) { @@ -1103,14 +1057,14 @@ Test.prototype = { QUnit.stop(); then.call( promise, - QUnit.start, + function() { QUnit.start(); }, function( error ) { message = "Promise rejected " + ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + " " + test.testName + ": " + ( error.message || error ); test.pushFailure( message, extractStacktrace( error, 0 ) ); - // else next test will carry the responsibility + // Else next test will carry the responsibility saveGlobal(); // Unblock @@ -1122,21 +1076,45 @@ Test.prototype = { }, valid: function() { - var include, - filter = config.filter, - module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), - fullName = ( this.module.name + ": " + this.testName ).toLowerCase(); + var filter = config.filter, + regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ), + module = config.module && config.module.toLowerCase(), + fullName = ( this.module.name + ": " + this.testName ); + + function moduleChainNameMatch( testModule ) { + var testModuleName = testModule.name ? testModule.name.toLowerCase() : null; + if ( testModuleName === module ) { + return true; + } else if ( testModule.parentModule ) { + return moduleChainNameMatch( testModule.parentModule ); + } else { + return false; + } + } + + function moduleChainIdMatch( testModule ) { + return inArray( testModule.moduleId, config.moduleId ) > -1 || + testModule.parentModule && moduleChainIdMatch( testModule.parentModule ); + } // Internally-generated tests are always valid if ( this.callback && this.callback.validTest ) { return true; } - if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) { + if ( config.moduleId && config.moduleId.length > 0 && + !moduleChainIdMatch( this.module ) ) { + + return false; + } + + if ( config.testId && config.testId.length > 0 && + inArray( this.testId, config.testId ) < 0 ) { + return false; } - if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) { + if ( module && !moduleChainNameMatch( this.module ) ) { return false; } @@ -1144,9 +1122,25 @@ Test.prototype = { return true; } - include = filter.charAt( 0 ) !== "!"; + return regexFilter ? + this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) : + this.stringFilter( filter, fullName ); + }, + + regexFilter: function( exclude, pattern, flags, fullName ) { + var regex = new RegExp( pattern, flags ); + var match = regex.test( fullName ); + + return match !== exclude; + }, + + stringFilter: function( filter, fullName ) { + filter = filter.toLowerCase(); + fullName = fullName.toLowerCase(); + + var include = filter.charAt( 0 ) !== "!"; if ( !include ) { - filter = filter.toLowerCase().slice( 1 ); + filter = filter.slice( 1 ); } // If the filter matches, we need to honour include @@ -1157,7 +1151,6 @@ Test.prototype = { // Otherwise, do the opposite return !include; } - }; // Resets the test setup. Useful for tests that modify the DOM. @@ -1170,7 +1163,7 @@ QUnit.reset = function() { // Return on non-browser environments // This is necessary to not break on node tests - if ( typeof window === "undefined" ) { + if ( !defined.document ) { return; } @@ -1218,6 +1211,157 @@ function generateHash( module, testName ) { return hex.slice( -8 ); } +function synchronize( callback, priority, seed ) { + var last = !priority, + index; + + if ( QUnit.objectType( callback ) === "array" ) { + while ( callback.length ) { + synchronize( callback.shift() ); + } + return; + } + + if ( priority ) { + config.queue.splice( priorityCount++, 0, callback ); + } else if ( seed ) { + if ( !unitSampler ) { + unitSampler = unitSamplerGenerator( seed ); + } + + // Insert into a random position after all priority items + index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) ); + config.queue.splice( priorityCount + index, 0, callback ); + } else { + config.queue.push( callback ); + } + + if ( config.autorun && !config.blocking ) { + process( last ); + } +} + +function unitSamplerGenerator( seed ) { + + // 32-bit xorshift, requires only a nonzero seed + // http://excamera.com/sphinx/article-xorshift.html + var sample = parseInt( generateHash( seed ), 16 ) || -1; + return function() { + sample ^= sample << 13; + sample ^= sample >>> 17; + sample ^= sample << 5; + + // ECMAScript has no unsigned number type + if ( sample < 0 ) { + sample += 0x100000000; + } + + return sample / 0x100000000; + }; +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in global ) { + if ( hasOwn.call( global, key ) ) { + + // In Opera sometimes DOM element ids show up here, ignore them + if ( /^qunit-test-output/.test( key ) ) { + continue; + } + config.pollution.push( key ); + } + } + } +} + +function checkPollution() { + var newGlobals, + deletedGlobals, + old = config.pollution; + + saveGlobal(); + + newGlobals = diff( config.pollution, old ); + if ( newGlobals.length > 0 ) { + 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( ", " ) ); + } +} + +// Will be exposed as QUnit.asyncTest +function asyncTest( testName, expected, callback ) { + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + QUnit.test( testName, expected, callback, true ); +} + +// Will be exposed as QUnit.test +function test( testName, expected, callback, async ) { + if ( focused ) { return; } + + var newTest; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + newTest = new Test( { + testName: testName, + expected: expected, + async: async, + callback: callback + } ); + + newTest.queue(); +} + +// Will be exposed as QUnit.skip +function skip( testName ) { + if ( focused ) { return; } + + var test = new Test( { + testName: testName, + skip: true + } ); + + test.queue(); +} + +// Will be exposed as QUnit.only +function only( testName, expected, callback, async ) { + var newTest; + + if ( focused ) { return; } + + QUnit.config.queue.length = 0; + focused = true; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + newTest = new Test( { + testName: testName, + expected: expected, + async: async, + callback: callback + } ); + + newTest.queue(); +} + function Assert( testContext ) { this.test = testContext; } @@ -1235,30 +1379,55 @@ QUnit.assert = Assert.prototype = { } }, - // Increment this Test's semaphore counter, then return a single-use function that + // Increment this Test's semaphore counter, then return a function that // decrements that counter a maximum of once. - async: function() { + async: function( count ) { var test = this.test, - popped = false; + popped = false, + acceptCallCount = count; + + if ( typeof acceptCallCount === "undefined" ) { + acceptCallCount = 1; + } test.semaphore += 1; test.usedAsync = true; pauseProcessing(); return function done() { - if ( !popped ) { - test.semaphore -= 1; - popped = true; - resumeProcessing(); - } else { - test.pushFailure( "Called the callback returned from `assert.async` more than once", + + if ( popped ) { + test.pushFailure( "Too many calls to the `assert.async` callback", sourceFromStacktrace( 2 ) ); + return; + } + acceptCallCount -= 1; + if ( acceptCallCount > 0 ) { + return; } + + test.semaphore -= 1; + popped = true; + resumeProcessing(); }; }, // Exports test.push() to the user API - push: function( /* result, actual, expected, message */ ) { + // Alias of pushResult. + push: function( result, actual, expected, message, negative ) { + var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert; + return currentAssert.pushResult( { + result: result, + actual: actual, + expected: expected, + message: message, + negative: negative + } ); + }, + + pushResult: function( resultInfo ) { + + // Destructure of resultInfo = { result, actual, expected, message, negative } var assert = this, currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; @@ -1281,98 +1450,119 @@ QUnit.assert = Assert.prototype = { if ( !( assert instanceof Assert ) ) { assert = currentTest.assert; } - return assert.test.push.apply( assert.test, arguments ); + + return assert.test.pushResult( resultInfo ); }, - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ ok: function( result, message ) { message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + QUnit.dump.parse( result ) ); - this.push( !!result, result, true, message ); + this.pushResult( { + result: !!result, + actual: result, + expected: true, + message: message + } ); + }, + + notOk: function( result, message ) { + message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " + + QUnit.dump.parse( result ) ); + this.pushResult( { + result: !result, + actual: result, + expected: false, + message: message + } ); }, - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" ); - */ equal: function( actual, expected, message ) { /*jshint eqeqeq:false */ - this.push( expected == actual, actual, expected, message ); + this.pushResult( { + result: expected == actual, + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notEqual - * @function - */ notEqual: function( actual, expected, message ) { /*jshint eqeqeq:false */ - this.push( expected != actual, actual, expected, message ); + this.pushResult( { + result: expected != actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, - /** - * @name propEqual - * @function - */ propEqual: function( actual, expected, message ) { actual = objectValues( actual ); expected = objectValues( expected ); - this.push( QUnit.equiv( actual, expected ), actual, expected, message ); + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notPropEqual - * @function - */ notPropEqual: function( actual, expected, message ) { actual = objectValues( actual ); expected = objectValues( expected ); - this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, - /** - * @name deepEqual - * @function - */ deepEqual: function( actual, expected, message ) { - this.push( QUnit.equiv( actual, expected ), actual, expected, message ); + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notDeepEqual - * @function - */ notDeepEqual: function( actual, expected, message ) { - this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, - /** - * @name strictEqual - * @function - */ strictEqual: function( actual, expected, message ) { - this.push( expected === actual, actual, expected, message ); + this.pushResult( { + result: expected === actual, + actual: actual, + expected: expected, + message: message + } ); }, - /** - * @name notStrictEqual - * @function - */ notStrictEqual: function( actual, expected, message ) { - this.push( expected !== actual, actual, expected, message ); + this.pushResult( { + result: expected !== actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, "throws": function( block, expected, message ) { var actual, expectedType, expectedOutput = expected, - ok = false; + ok = false, + currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current; // 'expected' is optional unless doing string comparison if ( message == null && typeof expected === "string" ) { @@ -1380,285 +1570,351 @@ QUnit.assert = Assert.prototype = { expected = null; } - this.test.ignoreGlobalErrors = true; + currentTest.ignoreGlobalErrors = true; try { - block.call( this.test.testEnvironment ); - } catch (e) { + block.call( currentTest.testEnvironment ); + } catch ( e ) { actual = e; } - this.test.ignoreGlobalErrors = false; + currentTest.ignoreGlobalErrors = false; if ( actual ) { expectedType = QUnit.objectType( expected ); - // we don't want to validate thrown error + // We don't want to validate thrown error if ( !expected ) { ok = true; expectedOutput = null; - // expected is a regexp + // Expected is a regexp } else if ( expectedType === "regexp" ) { ok = expected.test( errorString( actual ) ); - // expected is a string + // Expected is a string } else if ( expectedType === "string" ) { ok = expected === errorString( actual ); - // expected is a constructor, maybe an Error constructor + // Expected is a constructor, maybe an Error constructor } else if ( expectedType === "function" && actual instanceof expected ) { ok = true; - // expected is an Error object + // 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 + // Expected is a validation function which returns true if validation passed } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { expectedOutput = null; ok = true; } - - this.push( ok, actual, expectedOutput, message ); - } else { - this.test.pushFailure( message, null, "No exception was thrown." ); } + + currentTest.assert.pushResult( { + result: ok, + actual: actual, + expected: expectedOutput, + message: message + } ); } }; -// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word +// Provide an alternative to assert.throws(), for environments that consider throws a reserved word // Known to us are: Closure Compiler, Narwhal -(function() { +( function() { /*jshint sub:true */ - Assert.prototype.raises = Assert.prototype[ "throws" ]; -}()); + Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation +}() ); + +function errorString( error ) { + var name, message, + resultErrorString = error.toString(); + if ( resultErrorString.substring( 0, 7 ) === "[object" ) { + name = error.name ? error.name.toString() : "Error"; + message = error.message ? error.message.toString() : ""; + if ( name && message ) { + return name + ": " + message; + } else if ( name ) { + return name; + } else if ( message ) { + return message; + } else { + return "Error"; + } + } else { + return resultErrorString; + } +} // Test for equality any JavaScript type. // Author: Philippe Rathé -QUnit.equiv = (function() { - - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } - } - } +QUnit.equiv = ( function() { - // the real equiv function - var innerEquiv, + // Stack to decide between skip/abort functions + var callers = []; - // stack to decide between skip/abort functions - callers = [], + // Stack to avoiding loops from circular referencing + var parents = []; + var parentsB = []; - // stack to avoiding loops from circular referencing - parents = [], - parentsB = [], + var getProto = Object.getPrototypeOf || function( obj ) { - getProto = Object.getPrototypeOf || function( obj ) { - /* jshint camelcase: false, proto: true */ - return obj.__proto__; - }, - callbacks = (function() { + /*jshint proto: true */ + return obj.__proto__; + }; - // for string, boolean, number and null - function useStrictEquality( b, a ) { + 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;` + // `var j = new Number(1);` + if ( typeof a === "object" ) { + a = a.valueOf(); + } + if ( typeof b === "object" ) { + b = b.valueOf(); + } - // to catch short annotation VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } + return a === b; + } - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, + function compareConstructors( a, b ) { + var protoA = getProto( a ); + var protoB = getProto( b ); - "nan": function( b ) { - return isNaN( b ); - }, + // Comparing constructors is more strict than using `instanceof` + if ( a.constructor === b.constructor ) { + return true; + } - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, + // Ref #851 + // If the obj prototype descends from a null constructor, treat it + // as a null prototype. + if ( protoA && protoA.constructor === null ) { + protoA = null; + } + if ( protoB && protoB.constructor === null ) { + protoB = null; + } + + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( ( protoA === null && protoB === Object.prototype ) || + ( protoB === null && protoA === Object.prototype ) ) { + return true; + } - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && + return false; + } - // the regex itself - a.source === b.source && + function getRegExpFlags( regexp ) { + return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ]; + } - // and its modifiers - a.global === b.global && + var callbacks = { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + "symbol": useStrictEquality, + "date": useStrictEquality, - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, + "nan": function() { + return true; + }, - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[ callers.length - 1 ]; - return caller !== Object && typeof caller !== "undefined"; - }, + "regexp": function( b, a ) { + return a.source === b.source && - "array": function( b, a ) { - var i, j, len, loop, aCircular, bCircular; + // Include flags in the comparison + getRegExpFlags( a ) === getRegExpFlags( b ); + }, - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function() { + var caller = callers[ callers.length - 1 ]; + return caller !== Object && typeof caller !== "undefined"; + }, - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } + "array": function( b, a ) { + var i, j, len, loop, aCircular, bCircular; - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - 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 ]; - if ( aCircular || bCircular ) { - if ( a[ i ] === b[ i ] || aCircular && bCircular ) { - loop = true; - } else { - parents.pop(); - parentsB.pop(); - return false; - } - } - } - if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { + len = a.length; + if ( len !== b.length ) { + + // Safe and faster + return false; + } + + // Track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + 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 ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { parents.pop(); parentsB.pop(); return false; } } + } + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { parents.pop(); parentsB.pop(); - return true; - }, + return false; + } + } + parents.pop(); + parentsB.pop(); + return true; + }, - "object": function( b, a ) { + "set": function( b, a ) { + var innerEq, + outerEq = true; - /*jshint forin:false */ - var i, j, loop, aCircular, bCircular, - // Default to true - eq = true, - aProperties = [], - bProperties = []; + if ( a.size !== b.size ) { + return false; + } - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { + a.forEach( function( aVal ) { + innerEq = false; - // 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; - } + b.forEach( function( bVal ) { + if ( innerEquiv( bVal, aVal ) ) { + innerEq = true; } + } ); - // stack constructor before traversing properties - callers.push( a.constructor ); - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - - // be strict: don't ensure hasOwnProperty and go deep - for ( i in a ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[ j ] === a[ i ]; - bCircular = parentsB[ j ] === b[ i ]; - if ( aCircular || bCircular ) { - if ( a[ i ] === b[ i ] || aCircular && bCircular ) { - loop = true; - } else { - eq = false; - break; - } - } - } - aProperties.push( i ); - if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { - eq = false; - break; - } - } + if ( !innerEq ) { + outerEq = false; + } + } ); - parents.pop(); - parentsB.pop(); - callers.pop(); // unstack, we are done + return outerEq; + }, + + "map": function( b, a ) { + var innerEq, + outerEq = true; - for ( i in b ) { - bProperties.push( i ); // collect b's properties + if ( a.size !== b.size ) { + return false; + } + + a.forEach( function( aVal, aKey ) { + innerEq = false; + + b.forEach( function( bVal, bKey ) { + if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) { + innerEq = true; } + } ); - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + if ( !innerEq ) { + outerEq = false; } - }; - }()); + } ); - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } + return outerEq; + }, + + "object": function( b, a ) { + var i, j, loop, aCircular, bCircular; - 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 ) ) { + // Default to true + var eq = true; + var aProperties = []; + var bProperties = []; - // don't lose time with error prone cases + if ( compareConstructors( a, b ) === false ) { return false; - } else { - 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 ) ) ); + // Stack constructor before traversing properties + callers.push( a.constructor ); + + // Track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + + // Be strict: don't ensure hasOwnProperty and go deep + for ( i in a ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[ j ] === a[ i ]; + bCircular = parentsB[ j ] === b[ i ]; + if ( aCircular || bCircular ) { + if ( a[ i ] === b[ i ] || aCircular && bCircular ) { + loop = true; + } else { + eq = false; + break; + } + } + } + aProperties.push( i ); + if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { + eq = false; + break; + } + } + + parents.pop(); + parentsB.pop(); + + // Unstack, we are done + callers.pop(); + + for ( i in b ) { + + // Collect b's properties + bProperties.push( i ); + } + + // Ensures identical properties name + return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + } }; + function typeEquiv( a, b ) { + var type = QUnit.objectType( a ); + return QUnit.objectType( b ) === type && callbacks[ type ]( b, a ); + } + + // The real equiv function + function innerEquiv( a, b ) { + + // We're done when there's nothing more to compare + if ( arguments.length < 2 ) { + return true; + } + + // Require type-specific equality + return ( a === b || typeEquiv( a, b ) ) && + + // ...across all consecutive argument pairs + ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) ); + } + return innerEquiv; -}()); +}() ); // Based on jsDump by Ariel Flesler // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html -QUnit.dump = (function() { +QUnit.dump = ( function() { function quote( str ) { - return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; + return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; } function literal( o ) { return o + ""; @@ -1694,7 +1950,7 @@ QUnit.dump = (function() { var reName = /^function (\w+)/, dump = { - // objType is used mostly internally, you can fix a (custom) type in advance + // The objType is used mostly internally, you can fix a (custom) type in advance parse: function( obj, objType, stack ) { stack = stack || []; var res, parser, parserType, @@ -1738,7 +1994,7 @@ QUnit.dump = (function() { type = "node"; } else if ( - // native arrays + // Native arrays toString.call( obj ) === "[object Array]" || // NodeList objects @@ -1754,10 +2010,12 @@ QUnit.dump = (function() { } return type; }, + separator: function() { return this.multiline ? this.HTML ? "
" : "\n" : this.HTML ? " " : " "; }, - // extra can be a number, shortcut for increasing-calling-decreasing + + // Extra can be a number, shortcut for increasing-calling-decreasing indent: function( extra ) { if ( !this.multiline ) { return ""; @@ -1777,13 +2035,13 @@ QUnit.dump = (function() { setParser: function( name, parser ) { this.parsers[ name ] = parser; }, + // The next 3 are exposed so you can use them quote: quote, literal: literal, join: join, - // depth: 1, - maxDepth: 5, + maxDepth: QUnit.config.maxDepth, // This is the list of parsers, to modify them, use dump.setParser parsers: { @@ -1798,13 +2056,13 @@ QUnit.dump = (function() { "function": function( fn ) { var ret = "function", - // functions never have name in IE + // Functions never have name in IE name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; if ( name ) { ret += " " + name; } - ret += "( "; + ret += "("; ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); return join( ret, dump.parse( fn, "functionCode" ), "}" ); @@ -1830,7 +2088,7 @@ QUnit.dump = (function() { nonEnumerableProperties = [ "message", "name" ]; for ( i in nonEnumerableProperties ) { key = nonEnumerableProperties[ i ]; - if ( key in map && !( key in keys ) ) { + if ( key in map && inArray( key, keys ) < 0 ) { keys.push( key ); } } @@ -1875,7 +2133,7 @@ QUnit.dump = (function() { return ret + open + "/" + tag + close; }, - // function calls it internally, it's the arguments part of the function + // Function calls it internally, it's the arguments part of the function functionArgs: function( fn ) { var args, l = fn.length; @@ -1892,11 +2150,14 @@ QUnit.dump = (function() { } return " " + args.join( ", " ) + " "; }, - // object calls it internally, the key part of an item in a map + + // Object calls it internally, the key part of an item in a map key: quote, - // function calls it internally, it's the content of the function + + // Function calls it internally, it's the content of the function functionCode: "[code]", - // node calls it internally, it's an html attribute value + + // Node calls it internally, it's a html attribute value attribute: quote, string: quote, date: quote, @@ -1904,42 +2165,45 @@ QUnit.dump = (function() { number: literal, "boolean": literal }, - // if true, entities are escaped ( <, >, \t, space and \n ) + + // If true, entities are escaped ( <, >, \t, space and \n ) HTML: false, - // indentation unit + + // Indentation unit indentChar: " ", - // if true, items in a collection, are separated by a \n, else just a space. + + // If true, items in a collection, are separated by a \n, else just a space. multiline: true }; return dump; -}()); +}() ); -// back compat +// Back compat QUnit.jsDump = QUnit.dump; -// For browser, export only select globals -if ( typeof window !== "undefined" ) { +// Deprecated +// Extend assert methods to QUnit for Backwards compatibility +( function() { + var i, + assertions = Assert.prototype; - // 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 ); + }; + } - 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 ] ); + } +}() ); - for ( i in assertions ) { - QUnit[ i ] = applyCurrent( assertions[ i ] ); - } - })(); +// For browser, export only select globals +if ( defined.document ) { - (function() { + ( function() { var i, l, keys = [ "test", @@ -1949,6 +2213,7 @@ if ( typeof window !== "undefined" ) { "start", "stop", "ok", + "notOk", "equal", "notEqual", "propEqual", @@ -1957,13 +2222,14 @@ if ( typeof window !== "undefined" ) { "notDeepEqual", "strictEqual", "notStrictEqual", - "throws" + "throws", + "raises" ]; for ( i = 0, l = keys.length; i < l; i++ ) { window[ keys[ i ] ] = QUnit[ keys[ i ] ]; } - })(); + }() ); window.QUnit = QUnit; } @@ -1981,168 +2247,122 @@ if ( typeof exports !== "undefined" && exports ) { exports.QUnit = QUnit; } +if ( typeof define === "function" && define.amd ) { + define( function() { + return QUnit; + } ); + QUnit.config.autostart = false; +} + // Get a reference to the global object, like window in browsers -}( (function() { +}( ( function() { return this; -})() )); - -/*istanbul ignore next */ -// jscs:disable maximumLineLength -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * 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, - ns = {}, - os = {}; - - for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[ i ] ) ) { - ns[ n[ i ] ] = { - rows: [], - o: null - }; - } - ns[ n[ i ] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[ i ] ) ) { - os[ o[ i ] ] = { - rows: [], - n: null - }; - } - 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 ] - }; - o[ os[ i ].rows[ 0 ] ] = { - text: o[ os[ i ].rows[ 0 ] ], - row: ns[ i ].rows[ 0 ] - }; - } - } - } +( function() { - 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 ] ) { +// Only interact with URLs via window.location +var location = typeof window !== "undefined" && window.location; +if ( !location ) { + return; +} - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[ i ].row + 1 - }; - o[ n[ i ].row + 1 ] = { - text: o[ n[ i ].row + 1 ], - row: i + 1 - }; - } - } +var urlParams = getUrlParams(); - 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 ] ) { +QUnit.urlParams = urlParams; - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[ i ].row - 1 - }; - o[ n[ i ].row - 1 ] = { - text: o[ n[ i ].row - 1 ], - row: i - 1 - }; - } - } +// Match module/test by inclusion in an array +QUnit.config.moduleId = [].concat( urlParams.moduleId || [] ); +QUnit.config.testId = [].concat( urlParams.testId || [] ); - return { - o: o, - n: n - }; - } +// Exact case-insensitive match of the module name +QUnit.config.module = urlParams.module; - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); +// Regular expression or case-insenstive substring match against "moduleName: testName" +QUnit.config.filter = urlParams.filter; - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ), - oSpace = o.match( /\s+/g ), - nSpace = n.match( /\s+/g ); +// Test order randomization +if ( urlParams.seed === true ) { - if ( oSpace == null ) { - oSpace = [ " " ]; - } else { - oSpace.push( " " ); - } + // Generate a random seed if the option is specified without a value + QUnit.config.seed = Math.random().toString( 36 ).slice( 2 ); +} else if ( urlParams.seed ) { + QUnit.config.seed = urlParams.seed; +} - if ( nSpace == null ) { - nSpace = [ " " ]; - } else { - nSpace.push( " " ); - } +// Add URL-parameter-mapped config values with UI form rendering data +QUnit.config.urlConfig.push( + { + id: "hidepassed", + label: "Hide passed tests", + tooltip: "Only show tests and assertions that fail. Stored as query-strings." + }, + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the " + + "global object (`window` in Browsers). Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + + "exceptions in IE reasonable. Stored as query-strings." + } +); - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; 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 ] + ""; - } - } +QUnit.begin( function() { + var i, option, + urlConfig = QUnit.config.urlConfig; - for ( i = 0; i < out.n.length; i++ ) { - if ( out.n[ i ].text == null ) { - str += "" + out.n[ i ] + nSpace[ i ] + ""; - } else { + for ( i = 0; i < urlConfig.length; i++ ) { - // `pre` initialized at top of scope - pre = ""; + // Options can be either strings or objects with nonempty "id" properties + option = QUnit.config.urlConfig[ i ]; + if ( typeof option !== "string" ) { + option = option.id; + } - 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; - } + if ( QUnit.config[ option ] === undefined ) { + QUnit.config[ option ] = urlParams[ option ]; + } + } +} ); + +function getUrlParams() { + var i, param, name, value; + var urlParams = {}; + var params = location.search.slice( 1 ).split( "&" ); + var length = params.length; + + for ( i = 0; i < length; i++ ) { + if ( params[ i ] ) { + param = params[ i ].split( "=" ); + name = decodeURIComponent( param[ 0 ] ); + + // Allow just a key to turn on a flag, e.g., test.html?noglobals + value = param.length === 1 || + decodeURIComponent( param.slice( 1 ).join( "=" ) ) ; + if ( urlParams[ name ] ) { + urlParams[ name ] = [].concat( urlParams[ name ], value ); + } else { + urlParams[ name ] = value; } } + } - return str; - }; -}()); -// jscs:enable + return urlParams; +} -(function() { +// Don't load the HTML Reporter on non-browser environments +if ( typeof window === "undefined" || !window.document ) { + return; +} // Deprecated QUnit.init - Ref #530 // Re-initialize the configuration options QUnit.init = function() { - var tests, banner, result, qunit, - config = QUnit.config; + var config = QUnit.config; config.stats = { all: 0, bad: 0 }; config.moduleStats = { all: 0, bad: 0 }; @@ -2154,57 +2374,17 @@ QUnit.init = function() { config.filter = ""; config.queue = []; - // 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...
     "; - } + appendInterface(); }; -// Don't load the HTML Reporter on non-Browser environments -if ( typeof window === "undefined" ) { - return; -} - var config = QUnit.config, + document = window.document, + collapseNext = false, hasOwn = Object.prototype.hasOwnProperty, + unfilteredUrl = setUrl( { filter: undefined, module: undefined, + moduleId: undefined, testId: undefined } ), defined = { - document: window.document !== undefined, - sessionStorage: (function() { + sessionStorage: ( function() { var x = "qunit-test-string"; try { sessionStorage.setItem( x, x ); @@ -2213,7 +2393,7 @@ var config = QUnit.config, } catch ( e ) { return false; } - }()) + }() ) }, modulesList = []; @@ -2240,7 +2420,7 @@ function escapeText( s ) { case "&": return "&"; } - }); + } ); } /** @@ -2255,8 +2435,15 @@ function addEvent( elem, type, fn ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { - // support: IE <9 - elem.attachEvent( "on" + type, fn ); + // Support: IE <9 + elem.attachEvent( "on" + type, function() { + var event = window.event; + if ( !event.target ) { + event.target = event.srcElement || document; + } + + fn.call( elem, event ); + } ); } } @@ -2282,11 +2469,11 @@ function addClass( elem, name ) { } } -function toggleClass( elem, name ) { - if ( hasClass( elem, name ) ) { - removeClass( elem, name ); - } else { +function toggleClass( elem, name, force ) { + if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) { addClass( elem, name ); + } else { + removeClass( elem, name ); } } @@ -2298,22 +2485,24 @@ function removeClass( elem, name ) { set = set.replace( " " + name + " ", " " ); } - // trim for prettiness + // 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 ); + return document.getElementById && document.getElementById( name ); } function getUrlConfigHtml() { var i, j, val, escaped, escapedTooltip, selection = false, - len = config.urlConfig.length, + urlConfig = config.urlConfig, urlConfigHtml = ""; - for ( i = 0; i < len; i++ ) { + for ( i = 0; i < urlConfig.length; i++ ) { + + // Options can be either strings or objects with nonempty "id" properties val = config.urlConfig[ i ]; if ( typeof val === "string" ) { val = { @@ -2325,10 +2514,6 @@ function getUrlConfigHtml() { escaped = escapeText( val.id ); escapedTooltip = escapeText( val.tooltip ); - if ( config[ val.id ] === undefined ) { - config[ val.id ] = QUnit.urlParams[ val.id ]; - } - if ( !val.value || typeof val.value === "string" ) { urlConfigHtml += "