From 2265ff3710ae244a6f01640972571979543282c6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 16 May 2016 13:28:16 -0400 Subject: [PATCH] Updated qunit to 1.23.1. --- js_tests/qunit/qunit.css | 12 +- js_tests/qunit/qunit.js | 2480 ++++++++++++++++++++------------------ 2 files changed, 1336 insertions(+), 1156 deletions(-) diff --git a/js_tests/qunit/qunit.css b/js_tests/qunit/qunit.css index a59e2824ce..ae68fc412e 100644 --- a/js_tests/qunit/qunit.css +++ b/js_tests/qunit/qunit.css @@ -1,12 +1,12 @@ /*! - * QUnit 1.20.0 - * 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-10-27T17:53Z + * Date: 2016-04-12T17:29Z */ /** Font Family and Sizes */ @@ -120,6 +120,10 @@ display: list-item; } +#qunit-tests.hidepass { + position: relative; +} + #qunit-tests.hidepass li.running, #qunit-tests.hidepass li.pass { visibility: hidden; diff --git a/js_tests/qunit/qunit.js b/js_tests/qunit/qunit.js index 904943f088..5df0822ea4 100644 --- a/js_tests/qunit/qunit.js +++ b/js_tests/qunit/qunit.js @@ -1,15 +1,15 @@ /*! - * QUnit 1.20.0 - * 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-10-27T17:53Z + * Date: 2016-04-12T17:29Z */ -(function( global ) { +( function( global ) { var QUnit = {}; @@ -27,7 +27,7 @@ var window = global.window; var defined = { document: window && window.document !== undefined, setTimeout: setTimeout !== undefined, - sessionStorage: (function() { + sessionStorage: ( function() { var x = "qunit-test-string"; try { sessionStorage.setItem( x, x ); @@ -46,7 +46,7 @@ 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 +// 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(); @@ -63,7 +63,7 @@ function diff( a, b ) { return result; } -// from jquery.js +// From jquery.js function inArray( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); @@ -157,31 +157,6 @@ function is( type, obj ) { return QUnit.objectType( obj ) === type; } -var getUrlParams = function() { - var i, current; - var urlParams = {}; - var location = window.location; - var params = location.search.slice( 1 ).split( "&" ); - var length = params.length; - - 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 ]; - } - } - } - - return urlParams; -}; - // 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 ) { @@ -211,12 +186,12 @@ function extractStacktrace( e, offset ) { // Support: Safari <=6 only } else if ( e.sourceURL ) { - // exclude useless self-reference for generated Error objects + // Exclude useless self-reference for generated Error objects if ( /qunit.js$/.test( e.sourceURL ) ) { return; } - // for actual exceptions, this is useful + // For actual exceptions, this is useful return e.sourceURL + ":" + e.line; } } @@ -243,53 +218,35 @@ function sourceFromStacktrace( offset ) { * `config` initialized at top of scope */ 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, // 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 + // By default, scroll to top of the page when suite is done scrolltop: true, - // depth up-to which object will be dumped + // Depth up-to which object will be dumped maxDepth: 5, - // when enabled, all tests must call expect() + // 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 " + - "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." - } - ], + // Placeholder for user-configurable form-exposed URL parameters + urlConfig: [], // Set of all modules. modules: [], @@ -306,27 +263,9 @@ var config = { callbacks: {} }; -var urlParams = defined.document ? getUrlParams() : {}; - // Push a loose unnamed module to the modules collection config.modules.push( config.currentModule ); -if ( urlParams.filter === true ) { - delete urlParams.filter; -} - -// String search anywhere in moduleName+testName -config.filter = urlParams.filter; - -config.testId = []; -if ( urlParams.testId ) { - // Ensure that urlParams.testId is an array - urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," ); - for (var i = 0; i < urlParams.testId.length; i++ ) { - config.testId.push( urlParams.testId[ i ] ); - } -} - var loggingCallbacks = {}; // Register logging callbacks @@ -396,7 +335,7 @@ function verifyLoggingCallbacks() { 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: http://api.qunitjs.com/category/callbacks/" + "Reference: https://api.qunitjs.com/category/callbacks/" ); } } @@ -430,7 +369,7 @@ function verifyLoggingCallbacks() { } QUnit.pushFailure( error, filePath + ":" + linerNr ); } else { - QUnit.test( "global failure", extend(function() { + QUnit.test( "global failure", extend( function() { QUnit.pushFailure( error, filePath + ":" + linerNr ); }, { validTest: true } ) ); } @@ -439,25 +378,23 @@ function verifyLoggingCallbacks() { return ret; }; -} )(); - -QUnit.urlParams = urlParams; +}() ); // Figure out if we're running the tests from a server or not QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" ); // Expose the current QUnit version -QUnit.version = "1.20.0"; +QUnit.version = "1.23.1"; extend( QUnit, { - // call on start of module test to prepend name to all 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 ( testEnvironment instanceof Function ) { + if ( objectType( testEnvironment ) === "function" ) { executeNow = testEnvironment; testEnvironment = undefined; } @@ -481,7 +418,7 @@ extend( QUnit, { afterEach: setHook( module, "afterEach" ) }; - if ( executeNow instanceof Function ) { + if ( objectType( executeNow ) === "function" ) { config.moduleStack.push( module ); setCurrentModule( module ); executeNow.call( module.testEnvironment, moduleFns ); @@ -499,7 +436,8 @@ extend( QUnit, { var module = { name: moduleName, parentModule: parentModule, - tests: [] + tests: [], + moduleId: generateHash( moduleName ) }; var env = {}; @@ -572,7 +510,7 @@ extend( QUnit, { 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; @@ -633,7 +571,7 @@ extend( QUnit, { offset = ( offset || 0 ) + 2; return sourceFromStacktrace( offset ); } -}); +} ); registerLoggingCallbacks( QUnit ); @@ -656,17 +594,17 @@ function begin() { // Avoid unnecessary information by not logging modules' test environments for ( i = 0, l = config.modules.length; i < l; i++ ) { - modulesLog.push({ + modulesLog.push( { name: config.modules[ i ].name, tests: config.modules[ i ].tests - }); + } ); } // The test run is officially beginning now runLoggingCallbacks( "begin", { totalTests: Test.count, modules: modulesLog - }); + } ); } config.blocking = false; @@ -705,7 +643,7 @@ function pauseProcessing() { if ( config.testTimeout && defined.setTimeout ) { clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { + config.timeout = setTimeout( function() { if ( config.current ) { config.current.semaphore = 0; QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); @@ -722,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; } @@ -751,7 +689,7 @@ function done() { passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all, runtime: now() - config.moduleStats.started - }); + } ); } delete config.previousModule; @@ -763,7 +701,7 @@ function done() { passed: passed, total: config.stats.all, runtime: runtime - }); + } ); } function setHook( module, hookName ) { @@ -777,6 +715,8 @@ function setHook( module, hookName ) { } var focused = false; +var priorityCount = 0; +var unitSampler; function Test( settings ) { var i, l; @@ -799,10 +739,10 @@ function Test( settings ) { 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 ) { @@ -838,14 +778,14 @@ 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; @@ -861,7 +801,7 @@ Test.prototype = { name: this.testName, module: this.module.name, testId: this.testId - }); + } ); if ( !config.pollution ) { saveGlobal(); @@ -890,7 +830,7 @@ Test.prototype = { 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 @@ -999,7 +939,7 @@ Test.prototype = { // 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. @@ -1019,8 +959,8 @@ Test.prototype = { function run() { - // each of these can by async - synchronize([ + // Each of these can by async + synchronize( [ function() { test.before(); }, @@ -1038,31 +978,33 @@ Test.prototype = { function() { test.finish(); } - ]); + ] ); } // Prioritize previously failed tests, detected from sessionStorage priority = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); - return synchronize( run, priority ); + return synchronize( run, priority, config.seed ); }, - push: function( result, actual, expected, message, negative ) { + 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: negative || false, + negative: resultInfo.negative || false, runtime: now() - this.started }; - if ( !result ) { + if ( !resultInfo.result ) { source = sourceFromStacktrace(); if ( source ) { @@ -1072,10 +1014,10 @@ 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 ) { @@ -1100,10 +1042,10 @@ Test.prototype = { runLoggingCallbacks( "log", details ); - this.assertions.push({ + this.assertions.push( { result: false, message: message - }); + } ); }, resolvePromise: function( promise, phase ) { @@ -1122,7 +1064,7 @@ Test.prototype = { " " + 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 @@ -1134,32 +1076,45 @@ Test.prototype = { }, valid: function() { - var include, - filter = config.filter && config.filter.toLowerCase(), - 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 testInModuleChain( testModule ) { + function moduleChainNameMatch( testModule ) { var testModuleName = testModule.name ? testModule.name.toLowerCase() : null; if ( testModuleName === module ) { return true; } else if ( testModule.parentModule ) { - return testInModuleChain( 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 ( module && !testInModuleChain( this.module ) ) { + if ( config.testId && config.testId.length > 0 && + inArray( this.testId, config.testId ) < 0 ) { + + return false; + } + + if ( module && !moduleChainNameMatch( this.module ) ) { return false; } @@ -1167,7 +1122,23 @@ 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.slice( 1 ); } @@ -1240,8 +1211,9 @@ function generateHash( module, testName ) { return hex.slice( -8 ); } -function synchronize( callback, priority ) { - var last = !priority; +function synchronize( callback, priority, seed ) { + var last = !priority, + index; if ( QUnit.objectType( callback ) === "array" ) { while ( callback.length ) { @@ -1251,7 +1223,15 @@ function synchronize( callback, priority ) { } if ( priority ) { - priorityFill( callback ); + 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 ); } @@ -1261,21 +1241,24 @@ function synchronize( callback, priority ) { } } -// Place previously failed tests on a queue priority line, respecting the order they get assigned. -function priorityFill( callback ) { - var queue, prioritizedQueue; +function unitSamplerGenerator( seed ) { - queue = config.queue.slice( priorityFill.pos ); - prioritizedQueue = config.queue.slice( 0, -config.queue.length + priorityFill.pos ); + // 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; - queue.unshift( callback ); - queue.unshift.apply( queue, prioritizedQueue ); + // ECMAScript has no unsigned number type + if ( sample < 0 ) { + sample += 0x100000000; + } - config.queue = queue; - - priorityFill.pos += 1; + return sample / 0x100000000; + }; } -priorityFill.pos = 0; function saveGlobal() { config.pollution = []; @@ -1284,7 +1267,7 @@ function saveGlobal() { for ( var key in global ) { if ( hasOwn.call( global, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them + // In Opera sometimes DOM element ids show up here, ignore them if ( /^qunit-test-output/.test( key ) ) { continue; } @@ -1333,12 +1316,12 @@ function test( testName, expected, callback, async ) { expected = null; } - newTest = new Test({ + newTest = new Test( { testName: testName, expected: expected, async: async, callback: callback - }); + } ); newTest.queue(); } @@ -1347,10 +1330,10 @@ function test( testName, expected, callback, async ) { function skip( testName ) { if ( focused ) { return; } - var test = new Test({ + var test = new Test( { testName: testName, skip: true - }); + } ); test.queue(); } @@ -1369,12 +1352,12 @@ function only( testName, expected, callback, async ) { expected = null; } - newTest = new Test({ + newTest = new Test( { testName: testName, expected: expected, async: async, callback: callback - }); + } ); newTest.queue(); } @@ -1430,7 +1413,21 @@ QUnit.assert = Assert.prototype = { }, // Exports test.push() to the user API - push: function( /* result, actual, expected, message, negative */ ) { + // 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; @@ -1453,57 +1450,112 @@ QUnit.assert = Assert.prototype = { if ( !( assert instanceof Assert ) ) { assert = currentTest.assert; } - return assert.test.push.apply( assert.test, arguments ); + + return assert.test.pushResult( resultInfo ); }, 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.push( !result, result, false, message, true ); + this.pushResult( { + result: !result, + actual: result, + expected: false, + message: message + } ); }, 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 + } ); }, notEqual: function( actual, expected, message ) { /*jshint eqeqeq:false */ - this.push( expected != actual, actual, expected, message, true ); + this.pushResult( { + result: expected != actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, 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 + } ); }, notPropEqual: function( actual, expected, message ) { actual = objectValues( actual ); expected = objectValues( expected ); - this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, 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 + } ); }, notDeepEqual: function( actual, expected, message ) { - this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, strictEqual: function( actual, expected, message ) { - this.push( expected === actual, actual, expected, message ); + this.pushResult( { + result: expected === actual, + actual: actual, + expected: expected, + message: message + } ); }, notStrictEqual: function( actual, expected, message ) { - this.push( expected !== actual, actual, expected, message, true ); + this.pushResult( { + result: expected !== actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, "throws": function( block, expected, message ) { @@ -1521,7 +1573,7 @@ QUnit.assert = Assert.prototype = { currentTest.ignoreGlobalErrors = true; try { block.call( currentTest.testEnvironment ); - } catch (e) { + } catch ( e ) { actual = e; } currentTest.ignoreGlobalErrors = false; @@ -1529,46 +1581,51 @@ QUnit.assert = Assert.prototype = { 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; } } - currentTest.assert.push( ok, actual, expectedOutput, message ); + currentTest.assert.pushResult( { + result: ok, + actual: actual, + expected: expectedOutput, + message: message + } ); } }; // 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, @@ -1592,7 +1649,7 @@ function errorString( error ) { // Test for equality any JavaScript type. // Author: Philippe Rathé -QUnit.equiv = (function() { +QUnit.equiv = ( function() { // Stack to decide between skip/abort functions var callers = []; @@ -1601,26 +1658,28 @@ QUnit.equiv = (function() { var parents = []; var parentsB = []; + var getProto = Object.getPrototypeOf || function( obj ) { + + /*jshint proto: true */ + return obj.__proto__; + }; + 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);` - return a == b; - } else { - return a === b; + // 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(); + } + + return a === b; } function compareConstructors( a, b ) { - var getProto = Object.getPrototypeOf || function( obj ) { - - /*jshint proto: true */ - return obj.__proto__; - }; var protoA = getProto( a ); var protoB = getProto( b ); @@ -1649,6 +1708,10 @@ QUnit.equiv = (function() { return false; } + function getRegExpFlags( regexp ) { + return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ]; + } + var callbacks = { "string": useStrictEquality, "boolean": useStrictEquality, @@ -1656,28 +1719,17 @@ QUnit.equiv = (function() { "null": useStrictEquality, "undefined": useStrictEquality, "symbol": useStrictEquality, + "date": useStrictEquality, - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); + "nan": function() { + return true; }, "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && + return a.source === b.source && - // The regex itself - a.source === b.source && - - // And its modifiers - a.global === b.global && - - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; + // Include flags in the comparison + getRegExpFlags( a ) === getRegExpFlags( b ); }, // - skip when the property is a method of an instance (OOP) @@ -1691,14 +1743,10 @@ QUnit.equiv = (function() { "array": function( b, a ) { var i, j, len, loop, aCircular, bCircular; - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - len = a.length; if ( len !== b.length ) { - // safe and faster + + // Safe and faster return false; } @@ -1732,43 +1780,53 @@ QUnit.equiv = (function() { }, "set": function( b, a ) { - var aArray, bArray; + var innerEq, + outerEq = true; - // `b` could be any object here - if ( QUnit.objectType( b ) !== "set" ) { + if ( a.size !== b.size ) { return false; } - aArray = []; - a.forEach( function( v ) { - aArray.push( v ); - }); - bArray = []; - b.forEach( function( v ) { - bArray.push( v ); - }); + a.forEach( function( aVal ) { + innerEq = false; - return innerEquiv( bArray, aArray ); + b.forEach( function( bVal ) { + if ( innerEquiv( bVal, aVal ) ) { + innerEq = true; + } + } ); + + if ( !innerEq ) { + outerEq = false; + } + } ); + + return outerEq; }, "map": function( b, a ) { - var aArray, bArray; + var innerEq, + outerEq = true; - // `b` could be any object here - if ( QUnit.objectType( b ) !== "map" ) { + if ( a.size !== b.size ) { return false; } - aArray = []; - a.forEach( function( v, k ) { - aArray.push( [ k, v ] ); - }); - bArray = []; - b.forEach( function( v, k ) { - bArray.push( [ k, v ] ); - }); + a.forEach( function( aVal, aKey ) { + innerEq = false; - return innerEquiv( bArray, aArray ); + b.forEach( function( bVal, bKey ) { + if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) { + innerEq = true; + } + } ); + + if ( !innerEq ) { + outerEq = false; + } + } ); + + return outerEq; }, "object": function( b, a ) { @@ -1830,45 +1888,31 @@ QUnit.equiv = (function() { }; function typeEquiv( a, b ) { - var prop = QUnit.objectType( a ); - return callbacks[ prop ]( b, a ); + var type = QUnit.objectType( a ); + return QUnit.objectType( b ) === type && callbacks[ type ]( b, a ); } // The real equiv function - function innerEquiv() { - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { + function innerEquiv( a, b ) { - // End transition + // We're done when there's nothing more to compare + if ( arguments.length < 2 ) { return true; } - return ( (function( a, b ) { - if ( a === b ) { + // Require type-specific equality + return ( a === b || typeEquiv( a, b ) ) && - // Catch the most you can - return true; - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType( a ) !== QUnit.objectType( b ) ) { - - // Don't lose time with error prone cases - return false; - } else { - return typeEquiv( a, b ); - } - - // Apply transition with (1..n) arguments - }( args[ 0 ], args[ 1 ] ) ) && - innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) ); + // ...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, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; } @@ -1906,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, @@ -1950,7 +1994,7 @@ QUnit.dump = (function() { type = "node"; } else if ( - // native arrays + // Native arrays toString.call( obj ) === "[object Array]" || // NodeList objects @@ -1966,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 ""; @@ -1989,11 +2035,11 @@ 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: QUnit.config.maxDepth, @@ -2010,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" ), "}" ); @@ -2087,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; @@ -2104,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, @@ -2116,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; +// Deprecated +// Extend assert methods to QUnit for 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 ] ); + } +}() ); + // For browser, export only select globals if ( defined.document ) { - // 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() { + ( function() { var i, l, keys = [ "test", @@ -2177,7 +2229,7 @@ if ( defined.document ) { for ( i = 0, l = keys.length; i < l; i++ ) { window[ keys[ i ] ] = QUnit[ keys[ i ] ]; } - })(); + }() ); window.QUnit = QUnit; } @@ -2202,6 +2254,916 @@ if ( typeof define === "function" && define.amd ) { QUnit.config.autostart = false; } +// Get a reference to the global object, like window in browsers +}( ( function() { + return this; +}() ) ) ); + +( function() { + +// Only interact with URLs via window.location +var location = typeof window !== "undefined" && window.location; +if ( !location ) { + return; +} + +var urlParams = getUrlParams(); + +QUnit.urlParams = urlParams; + +// Match module/test by inclusion in an array +QUnit.config.moduleId = [].concat( urlParams.moduleId || [] ); +QUnit.config.testId = [].concat( urlParams.testId || [] ); + +// Exact case-insensitive match of the module name +QUnit.config.module = urlParams.module; + +// Regular expression or case-insenstive substring match against "moduleName: testName" +QUnit.config.filter = urlParams.filter; + +// Test order randomization +if ( urlParams.seed === true ) { + + // 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; +} + +// 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." + } +); + +QUnit.begin( function() { + var i, option, + urlConfig = QUnit.config.urlConfig; + + for ( i = 0; i < urlConfig.length; i++ ) { + + // Options can be either strings or objects with nonempty "id" properties + option = QUnit.config.urlConfig[ i ]; + if ( typeof option !== "string" ) { + option = option.id; + } + + 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 urlParams; +} + +// 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 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 = []; + + appendInterface(); +}; + +var config = QUnit.config, + document = window.document, + collapseNext = false, + hasOwn = Object.prototype.hasOwnProperty, + unfilteredUrl = setUrl( { filter: undefined, module: undefined, + moduleId: undefined, testId: undefined } ), + defined = { + sessionStorage: ( function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; + } + }() ) + }, + modulesList = []; + +/** +* 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 ) { + + // Standards-based browsers + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + + // Support: IE <9 + elem.attachEvent( "on" + type, function() { + var event = window.event; + if ( !event.target ) { + event.target = event.srcElement || document; + } + + fn.call( elem, event ); + } ); + } +} + +/** + * @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, force ) { + if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) { + addClass( elem, name ); + } else { + removeClass( 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 document.getElementById && document.getElementById( name ); +} + +function getUrlConfigHtml() { + var i, j, val, + escaped, escapedTooltip, + selection = false, + urlConfig = config.urlConfig, + urlConfigHtml = ""; + + 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 = { + id: val, + label: val + }; + } + + escaped = escapeText( val.id ); + escapedTooltip = escapeText( val.tooltip ); + + if ( !val.value || typeof val.value === "string" ) { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; + } + } + + return urlConfigHtml; +} + +// Handle "click" events on toolbar checkboxes and "change" for select menus. +// Updates the URL with the new state of `config.urlConfig` values. +function toolbarChanged() { + var updatedUrl, value, tests, + field = this, + params = {}; + + // Detect if field is a select menu or a checkbox + if ( "selectedIndex" in field ) { + value = field.options[ field.selectedIndex ].value || undefined; + } else { + value = field.checked ? ( field.defaultValue || true ) : undefined; + } + + params[ field.name ] = value; + updatedUrl = setUrl( params ); + + // Check if we can apply the change without a page refresh + if ( "hidepassed" === field.name && "replaceState" in window.history ) { + QUnit.urlParams[ field.name ] = value; + config[ field.name ] = value || false; + tests = id( "qunit-tests" ); + if ( tests ) { + toggleClass( tests, "hidepass", value || false ); + } + window.history.replaceState( null, "", updatedUrl ); + } else { + window.location = updatedUrl; + } +} + +function setUrl( params ) { + var key, arrValue, i, + querystring = "?", + location = window.location; + + params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); + + for ( key in params ) { + + // Skip inherited or undefined properties + if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) { + + // Output a parameter for each value of this key (but usually just one) + arrValue = [].concat( params[ key ] ); + for ( i = 0; i < arrValue.length; i++ ) { + querystring += encodeURIComponent( key ); + if ( arrValue[ i ] !== true ) { + querystring += "=" + encodeURIComponent( arrValue[ i ] ); + } + querystring += "&"; + } + } + } + return location.protocol + "//" + location.host + + location.pathname + querystring.slice( 0, -1 ); +} + +function applyUrlParams() { + var selectedModule, + modulesList = id( "qunit-modulefilter" ), + filter = id( "qunit-filter-input" ).value; + + selectedModule = modulesList ? + decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) : + undefined; + + window.location = setUrl( { + module: ( selectedModule === "" ) ? undefined : selectedModule, + filter: ( filter === "" ) ? undefined : filter, + + // Remove moduleId and testId filters + moduleId: undefined, + testId: undefined + } ); +} + +function toolbarUrlConfigContainer() { + var urlConfigContainer = document.createElement( "span" ); + + urlConfigContainer.innerHTML = getUrlConfigHtml(); + addClass( urlConfigContainer, "qunit-url-config" ); + + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" for checkboxes + addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); + addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); + + return urlConfigContainer; +} + +function toolbarLooseFilter() { + var filter = document.createElement( "form" ), + label = document.createElement( "label" ), + input = document.createElement( "input" ), + button = document.createElement( "button" ); + + addClass( filter, "qunit-filter" ); + + label.innerHTML = "Filter: "; + + input.type = "text"; + input.value = config.filter || ""; + input.name = "filter"; + input.id = "qunit-filter-input"; + + button.innerHTML = "Go"; + + label.appendChild( input ); + + filter.appendChild( label ); + filter.appendChild( button ); + addEvent( filter, "submit", function( ev ) { + applyUrlParams(); + + if ( ev && ev.preventDefault ) { + ev.preventDefault(); + } + + return false; + } ); + + return filter; +} + +function toolbarModuleFilterHtml() { + var i, + moduleFilterHtml = ""; + + if ( !modulesList.length ) { + return false; + } + + moduleFilterHtml += "" + + ""; + + return moduleFilterHtml; +} + +function toolbarModuleFilter() { + var toolbar = id( "qunit-testrunner-toolbar" ), + moduleFilter = document.createElement( "span" ), + moduleFilterHtml = toolbarModuleFilterHtml(); + + if ( !toolbar || !moduleFilterHtml ) { + return false; + } + + moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); + moduleFilter.innerHTML = moduleFilterHtml; + + addEvent( moduleFilter.lastChild, "change", applyUrlParams ); + + toolbar.appendChild( moduleFilter ); +} + +function appendToolbar() { + var toolbar = id( "qunit-testrunner-toolbar" ); + + if ( toolbar ) { + toolbar.appendChild( toolbarUrlConfigContainer() ); + toolbar.appendChild( toolbarLooseFilter() ); + toolbarModuleFilter(); + } +} + +function appendHeader() { + var header = id( "qunit-header" ); + + if ( header ) { + header.innerHTML = "" + header.innerHTML + + " "; + } +} + +function appendBanner() { + var banner = id( "qunit-banner" ); + + if ( banner ) { + banner.className = ""; + } +} + +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 appendFilteredTest() { + var testId = QUnit.config.testId; + if ( !testId || testId.length <= 0 ) { + return ""; + } + return "
Rerunning selected tests: " + + escapeText( testId.join( ", " ) ) + + " Run all tests
"; +} + +function appendUserAgent() { + var userAgent = id( "qunit-userAgent" ); + + if ( userAgent ) { + userAgent.innerHTML = ""; + userAgent.appendChild( + document.createTextNode( + "QUnit " + QUnit.version + "; " + navigator.userAgent + ) + ); + } +} + +function appendInterface() { + var qunit = id( "qunit" ); + + if ( qunit ) { + qunit.innerHTML = + "

" + escapeText( document.title ) + "

" + + "

" + + "
" + + appendFilteredTest() + + "

" + + "
    "; + } + + appendHeader(); + appendBanner(); + appendTestResults(); + appendUserAgent(); + appendToolbar(); +} + +function appendTestsList( modules ) { + var i, l, x, z, test, moduleObj; + + for ( i = 0, l = modules.length; i < l; i++ ) { + moduleObj = modules[ i ]; + + for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { + test = moduleObj.tests[ x ]; + + appendTest( test.name, test.testId, moduleObj.name ); + } + } +} + +function appendTest( name, testId, moduleName ) { + var title, rerunTrigger, testBlock, assertList, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + title = document.createElement( "strong" ); + title.innerHTML = getNameHtml( name, moduleName ); + + rerunTrigger = document.createElement( "a" ); + rerunTrigger.innerHTML = "Rerun"; + rerunTrigger.href = setUrl( { testId: testId } ); + + testBlock = document.createElement( "li" ); + testBlock.appendChild( title ); + testBlock.appendChild( rerunTrigger ); + testBlock.id = "qunit-test-output-" + testId; + + assertList = document.createElement( "ol" ); + assertList.className = "qunit-assert-list"; + + testBlock.appendChild( assertList ); + + tests.appendChild( testBlock ); +} + +// HTML Reporter initialization and load +QUnit.begin( function( details ) { + var i, moduleObj, tests; + + // Sort modules by name for the picker + for ( i = 0; i < details.modules.length; i++ ) { + moduleObj = details.modules[ i ]; + if ( moduleObj.name ) { + modulesList.push( moduleObj.name ); + } + } + modulesList.sort( function( a, b ) { + return a.localeCompare( b ); + } ); + + // Capture fixture HTML from the page + storeFixture(); + + // Initialize QUnit elements + appendInterface(); + appendTestsList( details.modules ); + tests = id( "qunit-tests" ); + if ( tests && config.hidepassed ) { + addClass( tests, "hidepass" ); + } +} ); + +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 && 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 running, testBlock, bad; + + testBlock = id( "qunit-test-output-" + details.testId ); + if ( testBlock ) { + testBlock.className = "running"; + } else { + + // Report later registered tests + appendTest( details.name, details.testId, details.module ); + } + + running = id( "qunit-testresult" ); + if ( running ) { + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); + + running.innerHTML = ( bad ? + "Rerunning previously failed test:
    " : + "Running:
    " ) + + getNameHtml( details.name, details.module ); + } + +} ); + +function stripHtml( string ) { + + // Strip tags, html entity and whitespaces + return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\"/g, "" ).replace( /\s+/g, "" ); +} + +QUnit.log( function( details ) { + var assertList, assertLi, + message, expected, actual, diff, + showDiff = false, + testItem = id( "qunit-test-output-" + details.testId ); + + if ( !testItem ) { + return; + } + + message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); + message = "" + message + ""; + message += "@ " + details.runtime + " ms"; + + // The 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" ) ) { + if ( details.negative ) { + expected = "NOT " + QUnit.dump.parse( details.expected ); + } else { + expected = QUnit.dump.parse( details.expected ); + } + + actual = QUnit.dump.parse( details.actual ); + message += ""; + + if ( actual !== expected ) { + + message += ""; + + // Don't show diff if actual or expected are booleans + if ( !( /^(true|false)$/.test( actual ) ) && + !( /^(true|false)$/.test( expected ) ) ) { + diff = QUnit.diff( expected, actual ); + showDiff = stripHtml( diff ).length !== + stripHtml( expected ).length + + stripHtml( actual ).length; + } + + // Don't show diff if expected and actual are totally different + if ( showDiff ) { + message += ""; + } + } else if ( expected.indexOf( "[object Array]" ) !== -1 || + expected.indexOf( "[object Object]" ) !== -1 ) { + message += ""; + } else { + message += ""; + } + + if ( details.source ) { + message += ""; + } + + message += "
    Expected:
    " +
    +			escapeText( expected ) +
    +			"
    Result:
    " +
    +				escapeText( actual ) + "
    Diff:
    " +
    +					diff + "
    Message: " + + "Diff suppressed as the depth of object is more than current max depth (" + + QUnit.config.maxDepth + ").

    Hint: Use QUnit.dump.maxDepth to " + + " run with a higher max depth or " + + "Rerun without max depth.

    Message: " + + "Diff suppressed as the expected and actual results have an equivalent" + + " serialization
    Source:
    " +
    +				escapeText( details.source ) + "
    "; + + // This occurs 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, skipped, sourceName, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + testItem = id( "qunit-test-output-" + details.testId ); + + 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 ) { + + // Collapse the passing tests + addClass( assertList, "qunit-collapsed" ); + } else if ( bad && config.collapse && !collapseNext ) { + + // Skip collapsing the first failing test + collapseNext = true; + } else { + + // Collapse remaining tests + addClass( assertList, "qunit-collapsed" ); + } + + // The testItem.firstChild is the test name + testTitle = testItem.firstChild; + + testCounts = bad ? + "" + bad + ", " + "" + good + ", " : + ""; + + testTitle.innerHTML += " (" + testCounts + + details.assertions.length + ")"; + + if ( details.skipped ) { + testItem.className = "skipped"; + skipped = document.createElement( "em" ); + skipped.className = "qunit-skipped-label"; + skipped.innerHTML = "skipped"; + testItem.insertBefore( skipped, testTitle ); + } else { + addEvent( testTitle, "click", function() { + toggleClass( assertList, "qunit-collapsed" ); + } ); + + testItem.className = bad ? "fail" : "pass"; + + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = details.runtime + " ms"; + testItem.insertBefore( time, assertList ); + } + + // Show the source of the test when showing assertions + if ( details.source ) { + sourceName = document.createElement( "p" ); + sourceName.innerHTML = "Source: " + details.source; + addClass( sourceName, "qunit-source" ); + if ( bad === 0 ) { + addClass( sourceName, "qunit-collapsed" ); + } + addEvent( testTitle, "click", function() { + toggleClass( sourceName, "qunit-collapsed" ); + } ); + testItem.appendChild( sourceName ); + } +} ); + +// Avoid readyState issue with phantomjs +// Ref: #818 +var notPhantom = ( function( p ) { + return !( p && p.version && p.version.major > 0 ); +} )( window.phantom ); + +if ( notPhantom && document.readyState === "complete" ) { + QUnit.load(); +} else { + addEvent( window, "load", QUnit.load ); +} + /* * This file is a modified version of google-diff-match-patch's JavaScript implementation * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js), @@ -2210,13 +3172,13 @@ if ( typeof define === "function" && define.amd ) { * The original source of google-diff-match-patch is attributable and licensed as follows: * * Copyright 2006 Google Inc. - * http://code.google.com/p/google-diff-match-patch/ + * https://code.google.com/p/google-diff-match-patch/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -2321,14 +3283,19 @@ QUnit.diff = ( function() { equalitiesLength = 0; // Keeping our own length var is faster in JS. /** @type {?string} */ lastequality = null; + // Always equal to diffs[equalities[equalitiesLength - 1]][1] pointer = 0; // Index of current position. + // Is there an insertion operation before the last equality. preIns = false; + // Is there a deletion operation before the last equality. preDel = false; + // Is there an insertion operation after the last equality. postIns = false; + // Is there a deletion operation after the last equality. postDel = false; while ( pointer < diffs.length ) { @@ -2383,6 +3350,7 @@ QUnit.diff = ( function() { equalitiesLength--; // Throw away the equality we just deleted; lastequality = null; if ( preIns && preDel ) { + // No changes made which could affect previous entry, keep going. postIns = postDel = true; equalitiesLength = 0; @@ -2416,13 +3384,13 @@ QUnit.diff = ( function() { data = diffs[ x ][ 1 ]; // Text of change. switch ( op ) { case DIFF_INSERT: - html[ x ] = "" + data + ""; + html[ x ] = "" + escapeText( data ) + ""; break; case DIFF_DELETE: - html[ x ] = "" + data + ""; + html[ x ] = "" + escapeText( data ) + ""; break; case DIFF_EQUAL: - html[ x ] = "" + data + ""; + html[ x ] = "" + escapeText( data ) + ""; break; } } @@ -2438,12 +3406,14 @@ QUnit.diff = ( function() { */ DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { var pointermid, pointermax, pointermin, pointerstart; + // Quick check for common null cases. if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) { return 0; } + // Binary search. - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ pointermin = 0; pointermax = Math.min( text1.length, text2.length ); pointermid = pointermax; @@ -2469,14 +3439,16 @@ QUnit.diff = ( function() { */ DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { var pointermid, pointermax, pointermin, pointerend; + // Quick check for common null cases. if ( !text1 || !text2 || text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) { return 0; } + // Binary search. - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ pointermin = 0; pointermax = Math.min( text1.length, text2.length ); pointermid = pointermax; @@ -2512,6 +3484,7 @@ QUnit.diff = ( function() { midCommon, diffsA, diffsB; if ( !text1 ) { + // Just add some text (speedup). return [ [ DIFF_INSERT, text2 ] @@ -2519,6 +3492,7 @@ QUnit.diff = ( function() { } if ( !text2 ) { + // Just delete some text (speedup). return [ [ DIFF_DELETE, text1 ] @@ -2529,12 +3503,14 @@ QUnit.diff = ( function() { shorttext = text1.length > text2.length ? text2 : text1; i = longtext.indexOf( shorttext ); if ( i !== -1 ) { + // Shorter text is inside the longer text (speedup). diffs = [ [ DIFF_INSERT, longtext.substring( 0, i ) ], [ DIFF_EQUAL, shorttext ], [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] ]; + // Swap insertions for deletions if diff is reversed. if ( text1.length > text2.length ) { diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE; @@ -2543,6 +3519,7 @@ QUnit.diff = ( function() { } if ( shorttext.length === 1 ) { + // Single character string. // After the previous speedup, the character can't be an equality. return [ @@ -2554,15 +3531,18 @@ QUnit.diff = ( function() { // Check to see if the problem can be split in two. hm = this.diffHalfMatch( text1, text2 ); if ( hm ) { + // A half-match was found, sort out the return data. text1A = hm[ 0 ]; text1B = hm[ 1 ]; text2A = hm[ 2 ]; text2B = hm[ 3 ]; midCommon = hm[ 4 ]; + // Send both pairs off for separate processing. diffsA = this.DiffMain( text1A, text2A, checklines, deadline ); diffsB = this.DiffMain( text1B, text2B, checklines, deadline ); + // Merge the results. return diffsA.concat( [ [ DIFF_EQUAL, midCommon ] @@ -2614,6 +3594,7 @@ QUnit.diff = ( function() { function diffHalfMatchI( longtext, shorttext, i ) { var seed, j, bestCommon, prefixLength, suffixLength, bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; + // Start with a 1/4 length substring at position i as a seed. seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) ); j = -1; @@ -2644,6 +3625,7 @@ QUnit.diff = ( function() { // First check if the second quarter is the seed for a half-match. hm1 = diffHalfMatchI( longtext, shorttext, Math.ceil( longtext.length / 4 ) ); + // Check again based on the third quarter. hm2 = diffHalfMatchI( longtext, shorttext, Math.ceil( longtext.length / 2 ) ); @@ -2654,6 +3636,7 @@ QUnit.diff = ( function() { } else if ( !hm1 ) { hm = hm2; } else { + // Both matched. Select the longest. hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2; } @@ -2688,6 +3671,7 @@ QUnit.diff = ( function() { DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) { var a, diffs, linearray, pointer, countInsert, countDelete, textInsert, textDelete, j; + // Scan the text on a line-by-line basis first. a = this.diffLinesToChars( text1, text2 ); text1 = a.chars1; @@ -2698,6 +3682,7 @@ QUnit.diff = ( function() { // Convert the diff back to original text. this.diffCharsToLines( diffs, linearray ); + // Eliminate freak matches (e.g. blank lines) this.diffCleanupSemantic( diffs ); @@ -2720,8 +3705,10 @@ QUnit.diff = ( function() { textDelete += diffs[ pointer ][ 1 ]; break; case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. if ( countDelete >= 1 && countInsert >= 1 ) { + // Delete the offending records and add the merged ones. diffs.splice( pointer - countDelete - countInsert, countDelete + countInsert ); @@ -2759,6 +3746,7 @@ QUnit.diff = ( function() { var text1Length, text2Length, maxD, vOffset, vLength, v1, v2, x, delta, front, k1start, k1end, k2start, k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; + // Cache the text lengths to prevent multiple calls. text1Length = text1.length; text2Length = text2.length; @@ -2767,6 +3755,7 @@ QUnit.diff = ( function() { vLength = 2 * maxD; v1 = new Array( vLength ); v2 = new Array( vLength ); + // Setting all elements to -1 is faster in Chrome & Firefox than mixing // integers and undefined. for ( x = 0; x < vLength; x++ ) { @@ -2776,9 +3765,11 @@ QUnit.diff = ( function() { v1[ vOffset + 1 ] = 0; v2[ vOffset + 1 ] = 0; delta = text1Length - text2Length; + // If the total number of characters is odd, then the front path will collide // with the reverse path. front = ( delta % 2 !== 0 ); + // Offsets for start and end of k loop. // Prevents mapping of space beyond the grid. k1start = 0; @@ -2786,6 +3777,7 @@ QUnit.diff = ( function() { k2start = 0; k2end = 0; for ( d = 0; d < maxD; d++ ) { + // Bail out if deadline is reached. if ( ( new Date() ).getTime() > deadline ) { break; @@ -2807,17 +3799,21 @@ QUnit.diff = ( function() { } v1[ k1Offset ] = x1; if ( x1 > text1Length ) { + // Ran off the right of the graph. k1end += 2; } else if ( y1 > text2Length ) { + // Ran off the bottom of the graph. k1start += 2; } else if ( front ) { k2Offset = vOffset + delta - k1; if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) { + // Mirror x2 onto top-left coordinate system. x2 = text1Length - v2[ k2Offset ]; if ( x1 >= x2 ) { + // Overlap detected. return this.diffBisectSplit( text1, text2, x1, y1, deadline ); } @@ -2842,9 +3838,11 @@ QUnit.diff = ( function() { } v2[ k2Offset ] = x2; if ( x2 > text1Length ) { + // Ran off the left of the graph. k2end += 2; } else if ( y2 > text2Length ) { + // Ran off the top of the graph. k2start += 2; } else if ( !front ) { @@ -2852,9 +3850,11 @@ QUnit.diff = ( function() { if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) { x1 = v1[ k1Offset ]; y1 = vOffset + x1 - k1Offset; + // Mirror x2 onto top-left coordinate system. x2 = text1Length - x2; if ( x1 >= x2 ) { + // Overlap detected. return this.diffBisectSplit( text1, text2, x1, y1, deadline ); } @@ -2862,6 +3862,7 @@ QUnit.diff = ( function() { } } } + // Diff took too long and hit the deadline or // number of diffs equals number of characters, no commonality at all. return [ @@ -2908,11 +3909,14 @@ QUnit.diff = ( function() { equalitiesLength = 0; // Keeping our own length var is faster in JS. /** @type {?string} */ lastequality = null; + // Always equal to diffs[equalities[equalitiesLength - 1]][1] pointer = 0; // Index of current position. + // Number of characters that changed prior to the equality. lengthInsertions1 = 0; lengthDeletions1 = 0; + // Number of characters that changed after the equality. lengthInsertions2 = 0; lengthDeletions2 = 0; @@ -2930,6 +3934,7 @@ QUnit.diff = ( function() { } else { lengthDeletions2 += diffs[ pointer ][ 1 ].length; } + // Eliminate an equality that is smaller or equal to the edits on both // sides of it. if ( lastequality && ( lastequality.length <= @@ -2988,6 +3993,7 @@ QUnit.diff = ( function() { if ( overlapLength1 >= overlapLength2 ) { if ( overlapLength1 >= deletion.length / 2 || overlapLength1 >= insertion.length / 2 ) { + // Overlap found. Insert an equality and trim the surrounding edits. diffs.splice( pointer, @@ -3037,13 +4043,16 @@ QUnit.diff = ( function() { DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) { var text1Length, text2Length, textLength, best, length, pattern, found; + // Cache the text lengths to prevent multiple calls. text1Length = text1.length; text2Length = text2.length; + // Eliminate the null case. if ( text1Length === 0 || text2Length === 0 ) { return 0; } + // Truncate the longer string. if ( text1Length > text2Length ) { text1 = text1.substring( text1Length - text2Length ); @@ -3051,6 +4060,7 @@ QUnit.diff = ( function() { text2 = text2.substring( 0, text1Length ); } textLength = Math.min( text1Length, text2Length ); + // Quick check for the worst case. if ( text1 === text2 ) { return textLength; @@ -3058,7 +4068,7 @@ QUnit.diff = ( function() { // Start by looking for a single character match // and increase length until no match is found. - // Performance analysis: http://neil.fraser.name/news/2010/11/04/ + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ best = 0; length = 1; while ( true ) { @@ -3089,8 +4099,8 @@ QUnit.diff = ( function() { */ DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) { var lineArray, lineHash, chars1, chars2; - lineArray = []; // e.g. lineArray[4] === 'Hello\n' - lineHash = {}; // e.g. lineHash['Hello\n'] === 4 + lineArray = []; // E.g. lineArray[4] === 'Hello\n' + lineHash = {}; // E.g. lineHash['Hello\n'] === 4 // '\x00' is a valid character, but various debuggers don't like it. // So we'll insert a junk entry to avoid generating a null character. @@ -3107,11 +4117,13 @@ QUnit.diff = ( function() { function diffLinesToCharsMunge( text ) { var chars, lineStart, lineEnd, lineArrayLength, line; chars = ""; + // Walk the text, pulling out a substring for each line. // text.split('\n') would would temporarily double our memory footprint. // Modifying text would create many large strings to garbage collect. lineStart = 0; lineEnd = -1; + // Keeping our own length variable is faster than looking it up. lineArrayLength = lineArray.length; while ( lineEnd < text.length - 1 ) { @@ -3190,10 +4202,12 @@ QUnit.diff = ( function() { pointer++; break; case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. if ( countDelete + countInsert > 1 ) { if ( countDelete !== 0 && countInsert !== 0 ) { - // Factor out any common prefixies. + + // Factor out any common prefixes. commonlength = this.diffCommonPrefix( textInsert, textDelete ); if ( commonlength !== 0 ) { if ( ( pointer - countDelete - countInsert ) > 0 && @@ -3210,6 +4224,7 @@ QUnit.diff = ( function() { textInsert = textInsert.substring( commonlength ); textDelete = textDelete.substring( commonlength ); } + // Factor out any common suffixies. commonlength = this.diffCommonSuffix( textInsert, textDelete ); if ( commonlength !== 0 ) { @@ -3221,6 +4236,7 @@ QUnit.diff = ( function() { commonlength ); } } + // Delete the offending records and add the merged ones. if ( countDelete === 0 ) { diffs.splice( pointer - countInsert, @@ -3297,6 +4313,7 @@ QUnit.diff = ( function() { } pointer++; } + // If shifts were made, the diff needs reordering and another shift sweep. if ( changes ) { this.diffCleanupMerge( diffs ); @@ -3314,845 +4331,4 @@ QUnit.diff = ( function() { }; }() ); -// Get a reference to the global object, like window in browsers -}( (function() { - return this; -})() )); - -(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; - - 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 = []; - - // 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...
       "; - } -}; - -var config = QUnit.config, - collapseNext = false, - hasOwn = Object.prototype.hasOwnProperty, - defined = { - document: window.document !== undefined, - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch ( e ) { - return false; - } - }()) - }, - modulesList = []; - -/** -* 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 ) { - - // Standards-based browsers - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - - // support: IE <9 - elem.attachEvent( "on" + type, function() { - var event = window.event; - if ( !event.target ) { - event.target = event.srcElement || document; - } - - fn.call( elem, event ); - }); - } -} - -/** - * @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 ); - - if ( config[ val.id ] === undefined ) { - config[ val.id ] = QUnit.urlParams[ val.id ]; - } - - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - - return urlConfigHtml; -} - -// Handle "click" events on toolbar checkboxes and "change" for select menus. -// Updates the URL with the new state of `config.urlConfig` values. -function toolbarChanged() { - var updatedUrl, value, - field = this, - params = {}; - - // Detect if field is a select menu or a checkbox - if ( "selectedIndex" in field ) { - value = field.options[ field.selectedIndex ].value || undefined; - } else { - value = field.checked ? ( field.defaultValue || true ) : undefined; - } - - params[ field.name ] = value; - updatedUrl = setUrl( params ); - - if ( "hidepassed" === field.name && "replaceState" in window.history ) { - config[ field.name ] = value || false; - if ( value ) { - addClass( id( "qunit-tests" ), "hidepass" ); - } else { - removeClass( id( "qunit-tests" ), "hidepass" ); - } - - // It is not necessary to refresh the whole page - window.history.replaceState( null, "", updatedUrl ); - } else { - window.location = updatedUrl; - } -} - -function setUrl( params ) { - var key, - querystring = "?"; - - params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); - - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - if ( params[ key ] === undefined ) { - continue; - } - querystring += encodeURIComponent( key ); - if ( params[ key ] !== true ) { - querystring += "=" + encodeURIComponent( params[ key ] ); - } - querystring += "&"; - } - } - return location.protocol + "//" + location.host + - location.pathname + querystring.slice( 0, -1 ); -} - -function applyUrlParams() { - var selectedModule, - modulesList = id( "qunit-modulefilter" ), - filter = id( "qunit-filter-input" ).value; - - selectedModule = modulesList ? - decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) : - undefined; - - window.location = setUrl({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - filter: ( filter === "" ) ? undefined : filter, - - // Remove testId filter - testId: undefined - }); -} - -function toolbarUrlConfigContainer() { - var urlConfigContainer = document.createElement( "span" ); - - urlConfigContainer.innerHTML = getUrlConfigHtml(); - addClass( urlConfigContainer, "qunit-url-config" ); - - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" for checkboxes - addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); - addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); - - return urlConfigContainer; -} - -function toolbarLooseFilter() { - var filter = document.createElement( "form" ), - label = document.createElement( "label" ), - input = document.createElement( "input" ), - button = document.createElement( "button" ); - - addClass( filter, "qunit-filter" ); - - label.innerHTML = "Filter: "; - - input.type = "text"; - input.value = config.filter || ""; - input.name = "filter"; - input.id = "qunit-filter-input"; - - button.innerHTML = "Go"; - - label.appendChild( input ); - - filter.appendChild( label ); - filter.appendChild( button ); - addEvent( filter, "submit", function( ev ) { - applyUrlParams(); - - if ( ev && ev.preventDefault ) { - ev.preventDefault(); - } - - return false; - }); - - return filter; -} - -function toolbarModuleFilterHtml() { - var i, - moduleFilterHtml = ""; - - if ( !modulesList.length ) { - return false; - } - - modulesList.sort(function( a, b ) { - return a.localeCompare( b ); - }); - - moduleFilterHtml += "" + - ""; - - return moduleFilterHtml; -} - -function toolbarModuleFilter() { - var toolbar = id( "qunit-testrunner-toolbar" ), - moduleFilter = document.createElement( "span" ), - moduleFilterHtml = toolbarModuleFilterHtml(); - - if ( !toolbar || !moduleFilterHtml ) { - return false; - } - - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; - - addEvent( moduleFilter.lastChild, "change", applyUrlParams ); - - toolbar.appendChild( moduleFilter ); -} - -function appendToolbar() { - var toolbar = id( "qunit-testrunner-toolbar" ); - - if ( toolbar ) { - toolbar.appendChild( toolbarUrlConfigContainer() ); - toolbar.appendChild( toolbarLooseFilter() ); - } -} - -function appendHeader() { - var header = id( "qunit-header" ); - - if ( header ) { - header.innerHTML = "" + header.innerHTML + " "; - } -} - -function appendBanner() { - var banner = id( "qunit-banner" ); - - if ( banner ) { - banner.className = ""; - } -} - -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 appendFilteredTest() { - var testId = QUnit.config.testId; - if ( !testId || testId.length <= 0 ) { - return ""; - } - return "
      Rerunning selected tests: " + testId.join(", ") + - " " + "Run all tests" + "
      "; -} - -function appendUserAgent() { - var userAgent = id( "qunit-userAgent" ); - - if ( userAgent ) { - userAgent.innerHTML = ""; - userAgent.appendChild( - document.createTextNode( - "QUnit " + QUnit.version + "; " + navigator.userAgent - ) - ); - } -} - -function appendTestsList( modules ) { - var i, l, x, z, test, moduleObj; - - for ( i = 0, l = modules.length; i < l; i++ ) { - moduleObj = modules[ i ]; - - if ( moduleObj.name ) { - modulesList.push( moduleObj.name ); - } - - for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { - test = moduleObj.tests[ x ]; - - appendTest( test.name, test.testId, moduleObj.name ); - } - } -} - -function appendTest( name, testId, moduleName ) { - var title, rerunTrigger, testBlock, assertList, - tests = id( "qunit-tests" ); - - if ( !tests ) { - return; - } - - title = document.createElement( "strong" ); - title.innerHTML = getNameHtml( name, moduleName ); - - rerunTrigger = document.createElement( "a" ); - rerunTrigger.innerHTML = "Rerun"; - rerunTrigger.href = setUrl({ testId: testId }); - - testBlock = document.createElement( "li" ); - testBlock.appendChild( title ); - testBlock.appendChild( rerunTrigger ); - testBlock.id = "qunit-test-output-" + testId; - - assertList = document.createElement( "ol" ); - assertList.className = "qunit-assert-list"; - - testBlock.appendChild( assertList ); - - tests.appendChild( testBlock ); -} - -// HTML Reporter initialization and load -QUnit.begin(function( details ) { - var qunit = id( "qunit" ); - - // Fixture is the only one necessary to run without the #qunit element - storeFixture(); - - if ( qunit ) { - qunit.innerHTML = - "

      " + escapeText( document.title ) + "

      " + - "

      " + - "
      " + - appendFilteredTest() + - "

      " + - "
        "; - } - - appendHeader(); - appendBanner(); - appendTestResults(); - appendUserAgent(); - appendToolbar(); - appendTestsList( details.modules ); - toolbarModuleFilter(); - - if ( qunit && config.hidepassed ) { - addClass( qunit.lastChild, "hidepass" ); - } -}); - -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 running, testBlock, bad; - - testBlock = id( "qunit-test-output-" + details.testId ); - if ( testBlock ) { - testBlock.className = "running"; - } else { - - // Report later registered tests - appendTest( details.name, details.testId, details.module ); - } - - running = id( "qunit-testresult" ); - if ( running ) { - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); - - running.innerHTML = ( bad ? - "Rerunning previously failed test:
        " : - "Running:
        " ) + - getNameHtml( details.name, details.module ); - } - -}); - -function stripHtml( string ) { - // strip tags, html entity and whitespaces - return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, ""); -} - -QUnit.log(function( details ) { - var assertList, assertLi, - message, expected, actual, diff, - showDiff = false, - testItem = id( "qunit-test-output-" + details.testId ); - - if ( !testItem ) { - return; - } - - message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); - message = "" + message + ""; - message += "@ " + details.runtime + " ms"; - - // 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" ) ) { - if ( details.negative ) { - expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) ); - } else { - expected = escapeText( QUnit.dump.parse( details.expected ) ); - } - - actual = escapeText( QUnit.dump.parse( details.actual ) ); - message += ""; - - if ( actual !== expected ) { - - message += ""; - - // Don't show diff if actual or expected are booleans - if ( !( /^(true|false)$/.test( actual ) ) && - !( /^(true|false)$/.test( expected ) ) ) { - diff = QUnit.diff( expected, actual ); - showDiff = stripHtml( diff ).length !== - stripHtml( expected ).length + - stripHtml( actual ).length; - } - - // Don't show diff if expected and actual are totally different - if ( showDiff ) { - message += ""; - } - } else if ( expected.indexOf( "[object Array]" ) !== -1 || - expected.indexOf( "[object Object]" ) !== -1 ) { - message += ""; - } - - if ( details.source ) { - message += ""; - } - - message += "
        Expected:
        " +
        -			expected +
        -			"
        Result:
        " +
        -				actual + "
        Diff:
        " +
        -					diff + "
        Message: " + - "Diff suppressed as the depth of object is more than current max depth (" + - QUnit.config.maxDepth + ").

        Hint: Use QUnit.dump.maxDepth to " + - " run with a higher max depth or " + - "Rerun without max depth.

        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, skipped, sourceName, - tests = id( "qunit-tests" ); - - if ( !tests ) { - return; - } - - testItem = id( "qunit-test-output-" + details.testId ); - - 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 ) { - - // Collapse the passing tests - addClass( assertList, "qunit-collapsed" ); - } else if ( bad && config.collapse && !collapseNext ) { - - // Skip collapsing the first failing test - collapseNext = true; - } else { - - // Collapse remaining tests - addClass( assertList, "qunit-collapsed" ); - } - - // testItem.firstChild is the test name - testTitle = testItem.firstChild; - - testCounts = bad ? - "" + bad + ", " + "" + good + ", " : - ""; - - testTitle.innerHTML += " (" + testCounts + - details.assertions.length + ")"; - - if ( details.skipped ) { - testItem.className = "skipped"; - skipped = document.createElement( "em" ); - skipped.className = "qunit-skipped-label"; - skipped.innerHTML = "skipped"; - testItem.insertBefore( skipped, testTitle ); - } else { - addEvent( testTitle, "click", function() { - toggleClass( assertList, "qunit-collapsed" ); - }); - - testItem.className = bad ? "fail" : "pass"; - - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = details.runtime + " ms"; - testItem.insertBefore( time, assertList ); - } - - // Show the source of the test when showing assertions - if ( details.source ) { - sourceName = document.createElement( "p" ); - sourceName.innerHTML = "Source: " + details.source; - addClass( sourceName, "qunit-source" ); - if ( bad === 0 ) { - addClass( sourceName, "qunit-collapsed" ); - } - addEvent( testTitle, "click", function() { - toggleClass( sourceName, "qunit-collapsed" ); - }); - testItem.appendChild( sourceName ); - } -}); - -if ( defined.document ) { - - // Avoid readyState issue with phantomjs - // Ref: #818 - var notPhantom = ( function( p ) { - return !( p && p.version && p.version.major > 0 ); - } )( window.phantom ); - - if ( notPhantom && document.readyState === "complete" ) { - QUnit.load(); - } else { - addEvent( window, "load", QUnit.load ); - } -} else { - config.pageLoaded = true; - config.autorun = true; -} - -})(); +}() );