0
0
mirror of https://github.com/twbs/bootstrap.git synced 2025-02-20 17:54:23 +01:00

tests: bump QUnit to v1.20.0.

[skip validator]
This commit is contained in:
XhmikosR 2015-10-26 23:54:22 +02:00
parent 713e53f105
commit e55fcbcdfe
2 changed files with 2199 additions and 1859 deletions

View File

@ -1,27 +1,27 @@
/*!
* QUnit 1.18.0
* QUnit 1.20.0
* http://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2015-04-03T10:23Z
* Date: 2015-10-27T17:53Z
*/
/** 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;
@ -118,8 +124,8 @@
#qunit-tests.hidepass li.pass {
visibility: hidden;
position: absolute;
width: 0px;
height: 0px;
width: 0;
height: 0;
padding: 0;
border: 0;
margin: 0;
@ -162,6 +168,10 @@
border-radius: 5px;
}
.qunit-source {
margin: 0.6em 0 0.3em;
}
.qunit-collapsed {
display: none;
}

1868
js/tests/vendor/qunit.js vendored
View File

@ -1,35 +1,32 @@
/*!
* QUnit 1.18.0
* QUnit 1.20.0
* http://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2015-04-03T10:23Z
* Date: 2015-10-27T17:53Z
*/
(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() {
var QUnit = {};
var Date = global.Date;
var 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,
};
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 {
@ -40,36 +37,47 @@ var QUnit,
return false;
}
}() )
},
/**
* 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";
};
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;
}
} else {
return errorString;
}
},
}
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.
@ -77,7 +85,7 @@ var QUnit,
* @param {Object} obj
* @return {Object} New object with only the own properties (recursively).
*/
objectValues = function( obj ) {
function objectValues ( obj ) {
var key, val,
vals = QUnit.is( "array", obj ) ? [] : {};
for ( key in obj ) {
@ -87,82 +95,74 @@ var QUnit,
}
}
return vals;
};
QUnit = {};
/**
* Config object: Maintain internal state
* Later exposed as QUnit.config
* `config` initialized at top of scope
*/
config = {
// The queue of tests to run
queue: [],
// block until document ready
blocking: true,
// 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
altertitle: true,
// by default, scroll to top of the page when suite is done
scrolltop: true,
// when enabled, all tests must call expect()
requireExpects: false,
// depth up-to which object will be dumped
maxDepth: 5,
// 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."
}
],
// Set of all modules.
modules: [],
function extend( a, b, undefOnly ) {
for ( var prop in b ) {
if ( hasOwn.call( b, prop ) ) {
// The first unnamed module
currentModule: {
name: "",
tests: []
},
// 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 ];
}
}
}
}
callbacks: {}
};
return a;
}
// Push a loose unnamed module to the modules collection
config.modules.push( config.currentModule );
function objectType( obj ) {
if ( typeof obj === "undefined" ) {
return "undefined";
}
// 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 = {};
// 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;
}
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++ ) {
@ -179,323 +179,9 @@ config.modules.push( config.currentModule );
}
}
if ( urlParams.filter === true ) {
delete urlParams.filter;
}
QUnit.urlParams = urlParams;
// String search anywhere in moduleName+testName
config.filter = urlParams.filter;
if ( urlParams.maxDepth ) {
config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?
Number.POSITIVE_INFINITY :
urlParams.maxDepth;
}
config.testId = [];
if ( urlParams.testId ) {
// Ensure that urlParams.testId is an array
urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
for ( i = 0; i < urlParams.testId.length; i++ ) {
config.testId.push( urlParams.testId[ i ] );
}
}
// 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.18.0";
}());
// 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: []
return urlParams;
};
// DEPRECATED: handles setup/teardown functions,
// beforeEach and afterEach should be used instead
if ( testEnvironment && testEnvironment.setup ) {
testEnvironment.beforeEach = testEnvironment.setup;
delete testEnvironment.setup;
}
if ( testEnvironment && testEnvironment.teardown ) {
testEnvironment.afterEach = testEnvironment.teardown;
delete testEnvironment.teardown;
}
config.modules.push( currentModule );
config.currentModule = currentModule;
},
// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
asyncTest: function( testName, expected, callback ) {
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
QUnit.test( testName, expected, callback, true );
},
test: function( testName, expected, callback, async ) {
var test;
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
test = new Test({
testName: testName,
expected: expected,
async: async,
callback: callback
});
test.queue();
},
skip: function( testName ) {
var test = new Test({
testName: testName,
skip: true
});
test.queue();
},
// 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.
start: function( count ) {
var globalStartAlreadyCalled = globalStartCalled;
if ( !config.current ) {
globalStartCalled = true;
if ( runStarted ) {
throw new Error( "Called start() outside of a test context while already started" );
} else if ( globalStartAlreadyCalled || count > 1 ) {
throw new Error( "Called start() outside of a test context too many times" );
} else if ( config.autostart ) {
throw new Error( "Called start() outside of a test context when " +
"QUnit.config.autostart was true" );
} else if ( !config.pageLoaded ) {
// The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
config.autostart = true;
return;
}
} else {
// If a test is running, adjust its semaphore
config.current.semaphore -= count || 1;
// 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
if ( config.current.semaphore < 0 ) {
config.current.semaphore = 0;
QUnit.pushFailure(
"Called start() while already started (test's semaphore was 0 already)",
sourceFromStacktrace( 2 )
);
return;
}
}
resumeProcessing();
},
// DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
stop: function( count ) {
// If there isn't a test running, don't allow QUnit.stop() to be called
if ( !config.current ) {
throw new Error( "Called stop() outside of a test context" );
}
// If a test is running, adjust its semaphore
config.current.semaphore += count || 1;
pauseProcessing();
},
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 ] || "";
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;
},
extend: extend,
load: function() {
config.pageLoaded = true;
// Initialize the configuration options
extend( config, {
stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 },
started: 0,
updateRate: 1000,
autostart: true,
filter: ""
}, true );
config.blocking = false;
if ( config.autostart ) {
resumeProcessing();
}
}
});
// Register logging callbacks
(function() {
var i, l, key,
callbacks = [ "begin", "done", "log", "testStart", "testDone",
"moduleStart", "moduleDone" ];
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, it will return undefined on these browsers
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
function extractStacktrace( e, offset ) {
@ -551,47 +237,406 @@ function sourceFromStacktrace( offset ) {
return extractStacktrace( error, offset );
}
function synchronize( callback, last ) {
if ( QUnit.objectType( callback ) === "array" ) {
while ( callback.length ) {
synchronize( callback.shift() );
/**
* Config object: Maintain internal state
* Later exposed as QUnit.config
* `config` initialized at top of scope
*/
var config = {
// The queue of tests to run
queue: [],
// block until document ready
blocking: true,
// 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
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
scrolltop: true,
// 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 " +
"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."
}
],
// Set of all modules.
modules: [],
// Stack of nested modules
moduleStack: [],
// The first unnamed module
currentModule: {
name: "",
tests: []
},
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
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;
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 ( 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: http://api.qunitjs.com/category/callbacks/"
);
}
}
}
}
( function() {
if ( !defined.document ) {
return;
}
config.queue.push( callback );
if ( config.autorun && !config.blocking ) {
process( last );
}
// `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 );
}
function process( last ) {
function next() {
process( last );
// 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;
}
var start = now();
config.depth = ( config.depth || 0 ) + 1;
while ( config.queue.length && !config.blocking ) {
if ( !defined.setTimeout || config.updateRate <= 0 ||
( ( now() - start ) < config.updateRate ) ) {
if ( config.current ) {
// Reset async tracking for each phase of the Test lifecycle
config.current.usedAsync = false;
}
config.queue.shift()();
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
setTimeout( next, 13 );
break;
QUnit.test( "global failure", extend(function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
}, { validTest: true } ) );
}
return false;
}
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";
extend( QUnit, {
// 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 ) {
executeNow = testEnvironment;
testEnvironment = undefined;
}
}
config.depth--;
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
done();
// DEPRECATED: handles setup/teardown functions,
// beforeEach and afterEach should be used instead
if ( testEnvironment && testEnvironment.setup ) {
testEnvironment.beforeEach = testEnvironment.setup;
delete testEnvironment.setup;
}
if ( testEnvironment && testEnvironment.teardown ) {
testEnvironment.afterEach = testEnvironment.teardown;
delete testEnvironment.teardown;
}
module = createModule();
moduleFns = {
beforeEach: setHook( module, "beforeEach" ),
afterEach: setHook( module, "afterEach" )
};
if ( executeNow instanceof Function ) {
config.moduleStack.push( module );
setCurrentModule( module );
executeNow.call( module.testEnvironment, moduleFns );
config.moduleStack.pop();
module = module.parentModule || currentModule;
}
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: []
};
var env = {};
if ( parentModule ) {
extend( env, parentModule.testEnvironment );
delete env.beforeEach;
delete env.afterEach;
}
extend( env, testEnvironment );
module.testEnvironment = env;
config.modules.push( module );
return module;
}
function setCurrentModule( module ) {
config.currentModule = module;
}
},
// DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
asyncTest: asyncTest,
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.
start: function( count ) {
var globalStartAlreadyCalled = globalStartCalled;
if ( !config.current ) {
globalStartCalled = true;
if ( runStarted ) {
throw new Error( "Called start() outside of a test context while already started" );
} else if ( globalStartAlreadyCalled || count > 1 ) {
throw new Error( "Called start() outside of a test context too many times" );
} else if ( config.autostart ) {
throw new Error( "Called start() outside of a test context when " +
"QUnit.config.autostart was true" );
} else if ( !config.pageLoaded ) {
// The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
config.autostart = true;
return;
}
} else {
// 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
if ( config.current.semaphore < 0 ) {
config.current.semaphore = 0;
QUnit.pushFailure(
"Called start() while already started (test's semaphore was 0 already)",
sourceFromStacktrace( 2 )
);
return;
}
}
resumeProcessing();
},
// DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
stop: function( count ) {
// If there isn't a test running, don't allow QUnit.stop() to be called
if ( !config.current ) {
throw new Error( "Called stop() outside of a test context" );
}
// If a test is running, adjust its semaphore
config.current.semaphore += count || 1;
pauseProcessing();
},
config: config,
is: is,
objectType: objectType,
extend: extend,
load: function() {
config.pageLoaded = true;
// Initialize the configuration options
extend( config, {
stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 },
started: 0,
updateRate: 1000,
autostart: true,
filter: ""
}, true );
config.blocking = false;
if ( config.autostart ) {
resumeProcessing();
}
},
stack: function( offset ) {
offset = ( offset || 0 ) + 2;
return sourceFromStacktrace( offset );
}
});
registerLoggingCallbacks( QUnit );
function begin() {
var i, l,
modulesLog = [];
@ -628,23 +673,30 @@ function begin() {
process( true );
}
function resumeProcessing() {
runStarted = true;
// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
if ( defined.setTimeout ) {
setTimeout(function() {
if ( config.current && config.current.semaphore > 0 ) {
return;
}
if ( config.timeout ) {
clearTimeout( config.timeout );
function process( last ) {
function next() {
process( last );
}
var start = now();
config.depth = ( config.depth || 0 ) + 1;
begin();
}, 13 );
while ( config.queue.length && !config.blocking ) {
if ( !defined.setTimeout || config.updateRate <= 0 ||
( ( now() - start ) < config.updateRate ) ) {
if ( config.current ) {
// Reset async tracking for each phase of the Test lifecycle
config.current.usedAsync = false;
}
config.queue.shift()();
} else {
begin();
setTimeout( next, 13 );
break;
}
}
config.depth--;
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
done();
}
}
@ -665,126 +717,66 @@ function pauseProcessing() {
}
}
function saveGlobal() {
config.pollution = [];
function resumeProcessing() {
runStarted = 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 );
// A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
if ( defined.setTimeout ) {
setTimeout(function() {
if ( config.current && config.current.semaphore > 0 ) {
return;
}
if ( config.timeout ) {
clearTimeout( config.timeout );
}
begin();
}, 13 );
} else {
begin();
}
}
function checkPollution() {
var newGlobals,
deletedGlobals,
old = config.pollution;
function done() {
var runtime, passed;
saveGlobal();
config.autorun = true;
newGlobals = diff( config.pollution, old );
if ( newGlobals.length > 0 ) {
QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
// 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
});
}
deletedGlobals = diff( old, config.pollution );
if ( deletedGlobals.length > 0 ) {
QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
}
function setHook( module, hookName ) {
if ( module.testEnvironment === undefined ) {
module.testEnvironment = {};
}
// 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;
}
}
}
return result;
return function( callback ) {
module.testEnvironment[ hookName ] = callback;
};
}
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;
}
var focused = false;
function Test( settings ) {
var i, l;
@ -858,9 +850,11 @@ Test.prototype = {
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", {
@ -886,14 +880,12 @@ 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 ) );
@ -906,6 +898,11 @@ Test.prototype = {
QUnit.start();
}
}
function runTest( test ) {
promise = test.callback.call( test.testEnvironment, test.assert );
test.resolvePromise( promise );
}
},
after: function() {
@ -918,17 +915,20 @@ 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 ) );
}
function callHook() {
promise = hook.call( test.testEnvironment, test.assert );
test.resolvePromise( promise, hookName );
}
};
},
@ -936,16 +936,20 @@ Test.prototype = {
hooks: function( handler ) {
var 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 ) );
}
}
// Hooks are ignored on skipped tests
if ( this.skip ) {
return hooks;
if ( !this.skip ) {
processHooks( this, this.module );
}
if ( this.module.testEnvironment &&
QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
}
return hooks;
},
@ -990,6 +994,9 @@ 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
});
@ -1003,7 +1010,7 @@ Test.prototype = {
},
queue: function() {
var bad,
var priority,
test = this;
if ( !this.valid() ) {
@ -1019,7 +1026,6 @@ Test.prototype = {
},
test.hooks( "beforeEach" ),
function() {
test.run();
},
@ -1035,19 +1041,14 @@ Test.prototype = {
]);
}
// `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 );
},
push: function( result, actual, expected, message ) {
push: function( result, actual, expected, message, negative ) {
var source,
details = {
module: this.module.name,
@ -1057,6 +1058,7 @@ Test.prototype = {
actual: actual,
expected: expected,
testId: this.testId,
negative: negative || false,
runtime: now() - this.started
};
@ -1077,7 +1079,7 @@ Test.prototype = {
},
pushFailure: function( message, source, actual ) {
if ( !this instanceof Test ) {
if ( !( this instanceof Test ) ) {
throw new Error( "pushFailure() assertion outside test context, was " +
sourceFromStacktrace( 2 ) );
}
@ -1113,7 +1115,7 @@ Test.prototype = {
QUnit.stop();
then.call(
promise,
QUnit.start,
function() { QUnit.start(); },
function( error ) {
message = "Promise rejected " +
( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
@ -1137,6 +1139,17 @@ Test.prototype = {
module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
function testInModuleChain( testModule ) {
var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
if ( testModuleName === module ) {
return true;
} else if ( testModule.parentModule ) {
return testInModuleChain( testModule.parentModule );
} else {
return false;
}
}
// Internally-generated tests are always valid
if ( this.callback && this.callback.validTest ) {
return true;
@ -1146,7 +1159,7 @@ Test.prototype = {
return false;
}
if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
if ( module && !testInModuleChain( this.module ) ) {
return false;
}
@ -1167,7 +1180,6 @@ Test.prototype = {
// Otherwise, do the opposite
return !include;
}
};
// Resets the test setup. Useful for tests that modify the DOM.
@ -1180,7 +1192,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;
}
@ -1228,6 +1240,145 @@ function generateHash( module, testName ) {
return hex.slice( -8 );
}
function synchronize( callback, priority ) {
var last = !priority;
if ( QUnit.objectType( callback ) === "array" ) {
while ( callback.length ) {
synchronize( callback.shift() );
}
return;
}
if ( priority ) {
priorityFill( callback );
} else {
config.queue.push( callback );
}
if ( config.autorun && !config.blocking ) {
process( last );
}
}
// Place previously failed tests on a queue priority line, respecting the order they get assigned.
function priorityFill( callback ) {
var queue, prioritizedQueue;
queue = config.queue.slice( priorityFill.pos );
prioritizedQueue = config.queue.slice( 0, -config.queue.length + priorityFill.pos );
queue.unshift( callback );
queue.unshift.apply( queue, prioritizedQueue );
config.queue = queue;
priorityFill.pos += 1;
}
priorityFill.pos = 0;
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;
}
@ -1245,30 +1396,41 @@ 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 ) {
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();
} else {
test.pushFailure( "Called the callback returned from `assert.async` more than once",
sourceFromStacktrace( 2 ) );
}
};
},
// Exports test.push() to the user API
push: function( /* result, actual, expected, message */ ) {
push: function( /* result, actual, expected, message, negative */ ) {
var assert = this,
currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
@ -1303,7 +1465,7 @@ QUnit.assert = Assert.prototype = {
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 );
this.push( !result, result, false, message, true );
},
equal: function( actual, expected, message ) {
@ -1313,7 +1475,7 @@ QUnit.assert = Assert.prototype = {
notEqual: function( actual, expected, message ) {
/*jshint eqeqeq:false */
this.push( expected != actual, actual, expected, message );
this.push( expected != actual, actual, expected, message, true );
},
propEqual: function( actual, expected, message ) {
@ -1325,7 +1487,7 @@ QUnit.assert = Assert.prototype = {
notPropEqual: function( actual, expected, message ) {
actual = objectValues( actual );
expected = objectValues( expected );
this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true );
},
deepEqual: function( actual, expected, message ) {
@ -1333,7 +1495,7 @@ QUnit.assert = Assert.prototype = {
},
notDeepEqual: function( actual, expected, message ) {
this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true );
},
strictEqual: function( actual, expected, message ) {
@ -1341,7 +1503,7 @@ QUnit.assert = Assert.prototype = {
},
notStrictEqual: function( actual, expected, message ) {
this.push( expected !== actual, actual, expected, message );
this.push( expected !== actual, actual, expected, message, true );
},
"throws": function( block, expected, message ) {
@ -1401,67 +1563,99 @@ QUnit.assert = Assert.prototype = {
}
};
// 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() {
/*jshint sub:true */
Assert.prototype.raises = Assert.prototype[ "throws" ];
}());
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é <prathe@gmail.com>
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
}
}
}
// Stack to decide between skip/abort functions
var callers = [];
// the real equiv function
var innerEquiv,
// Stack to avoiding loops from circular referencing
var parents = [];
var parentsB = [];
// stack to decide between skip/abort functions
callers = [],
// stack to avoiding loops from circular referencing
parents = [],
parentsB = [],
getProto = Object.getPrototypeOf || function( obj ) {
/* jshint camelcase: false, proto: true */
return obj.__proto__;
},
callbacks = (function() {
// for string, boolean, number and null
function useStrictEquality( b, a ) {
/*jshint eqeqeq:false */
if ( b instanceof a.constructor || a instanceof b.constructor ) {
// to catch short annotation VS 'new' annotation of a
// declaration
// e.g. var i = 1;
// var j = new Number(1);
// 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 {
function compareConstructors( a, b ) {
var getProto = Object.getPrototypeOf || function( obj ) {
/*jshint proto: true */
return obj.__proto__;
};
var protoA = getProto( a );
var protoB = getProto( b );
// Comparing constructors is more strict than using `instanceof`
if ( a.constructor === b.constructor ) {
return true;
}
// 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;
}
return false;
}
var callbacks = {
"string": useStrictEquality,
"boolean": useStrictEquality,
"number": useStrictEquality,
"null": useStrictEquality,
"undefined": useStrictEquality,
"symbol": useStrictEquality,
"nan": function( b ) {
return isNaN( b );
@ -1474,10 +1668,10 @@ QUnit.equiv = (function() {
"regexp": function( b, a ) {
return QUnit.objectType( b ) === "regexp" &&
// the regex itself
// The regex itself
a.source === b.source &&
// and its modifiers
// And its modifiers
a.global === b.global &&
// (gmi) ...
@ -1508,7 +1702,7 @@ QUnit.equiv = (function() {
return false;
}
// track reference to avoid circular references
// Track reference to avoid circular references
parents.push( a );
parentsB.push( b );
for ( i = 0; i < len; i++ ) {
@ -1537,35 +1731,66 @@ QUnit.equiv = (function() {
return true;
},
"object": function( b, a ) {
"set": function( b, a ) {
var aArray, bArray;
/*jshint forin:false */
var i, j, loop, aCircular, bCircular,
// Default to true
eq = true,
aProperties = [],
bProperties = [];
// comparing constructors is more strict than using
// instanceof
if ( a.constructor !== b.constructor ) {
// Allow objects with no prototype to be equivalent to
// objects with Object as their constructor.
if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
// `b` could be any object here
if ( QUnit.objectType( b ) !== "set" ) {
return false;
}
aArray = [];
a.forEach( function( v ) {
aArray.push( v );
});
bArray = [];
b.forEach( function( v ) {
bArray.push( v );
});
return innerEquiv( bArray, aArray );
},
"map": function( b, a ) {
var aArray, bArray;
// `b` could be any object here
if ( QUnit.objectType( b ) !== "map" ) {
return false;
}
// stack constructor before traversing properties
aArray = [];
a.forEach( function( v, k ) {
aArray.push( [ k, v ] );
});
bArray = [];
b.forEach( function( v, k ) {
bArray.push( [ k, v ] );
});
return innerEquiv( bArray, aArray );
},
"object": function( b, a ) {
var i, j, loop, aCircular, bCircular;
// Default to true
var eq = true;
var aProperties = [];
var bProperties = [];
if ( compareConstructors( a, b ) === false ) {
return false;
}
// Stack constructor before traversing properties
callers.push( a.constructor );
// track reference to avoid circular references
// Track reference to avoid circular references
parents.push( a );
parentsB.push( b );
// be strict: don't ensure hasOwnProperty and go deep
// Be strict: don't ensure hasOwnProperty and go deep
for ( i in a ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
@ -1589,41 +1814,54 @@ QUnit.equiv = (function() {
parents.pop();
parentsB.pop();
callers.pop(); // unstack, we are done
// Unstack, we are done
callers.pop();
for ( i in b ) {
bProperties.push( i ); // collect b's properties
// Collect b's properties
bProperties.push( i );
}
// Ensures identical properties name
return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
}
};
}());
innerEquiv = function() { // can take multiple arguments
function typeEquiv( a, b ) {
var prop = QUnit.objectType( a );
return callbacks[ prop ]( b, a );
}
// The real equiv function
function innerEquiv() {
var args = [].slice.apply( arguments );
if ( args.length < 2 ) {
return true; // end transition
// End transition
return true;
}
return ( (function( a, b ) {
if ( a === b ) {
return true; // catch the most you can
// 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
// Don't lose time with error prone cases
return false;
} else {
return bindCallbacks( a, callbacks, [ b, a ] );
return typeEquiv( a, b );
}
// apply transition with (1..n) arguments
// Apply transition with (1..n) arguments
}( args[ 0 ], args[ 1 ] ) ) &&
innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
};
}
return innerEquiv;
}());
@ -1632,7 +1870,7 @@ QUnit.equiv = (function() {
// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
QUnit.dump = (function() {
function quote( str ) {
return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
}
function literal( o ) {
return o + "";
@ -1893,7 +2131,7 @@ QUnit.dump = (function() {
QUnit.jsDump = QUnit.dump;
// For browser, export only select globals
if ( typeof window !== "undefined" ) {
if ( defined.document ) {
// Deprecated
// Extend assert methods to QUnit and Global scope through Backwards compatibility
@ -1932,7 +2170,8 @@ if ( typeof window !== "undefined" ) {
"notDeepEqual",
"strictEqual",
"notStrictEqual",
"throws"
"throws",
"raises"
];
for ( i = 0, l = keys.length; i < l; i++ ) {
@ -1963,13 +2202,6 @@ if ( typeof define === "function" && define.amd ) {
QUnit.config.autostart = false;
}
// Get a reference to the global object, like window in browsers
}( (function() {
return this;
})() ));
/*istanbul ignore next */
// jscs:disable maximumLineLength
/*
* 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),
@ -1997,19 +2229,9 @@ if ( typeof define === "function" && define.amd ) {
*
* Usage: QUnit.diff(expected, actual)
*
* QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over"
*/
QUnit.diff = ( function() {
function DiffMatchPatch() {
// Defaults.
// Redefine these in your program to override the defaults.
// Number of seconds to map a diff before giving up (0 for infinity).
this.DiffTimeout = 1.0;
// Cost of an empty edit operation in terms of edit characters.
this.DiffEditCost = 4;
}
// DIFF FUNCTIONS
@ -2031,23 +2253,14 @@ QUnit.diff = (function() {
* @param {boolean=} optChecklines Optional speedup flag. If present and false,
* then don't run a line-level diff first to identify the changed areas.
* Defaults to true, which does a faster, slightly less optimal diff.
* @param {number} optDeadline Optional time when the diff should be complete
* by. Used internally for recursive calls. Users should set DiffTimeout
* instead.
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
*/
DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
var deadline, checklines, commonlength,
commonprefix, commonsuffix, diffs;
// Set a deadline by which time the diff must be complete.
if ( typeof optDeadline === "undefined" ) {
if ( this.DiffTimeout <= 0 ) {
optDeadline = Number.MAX_VALUE;
} else {
optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
}
}
deadline = optDeadline;
// The diff must be complete in up to 1 second.
deadline = ( new Date() ).getTime() + 1000;
// Check for null inputs.
if ( text1 === null || text2 === null ) {
@ -2077,7 +2290,6 @@ QUnit.diff = (function() {
text2 = text2.substring( commonlength );
// Trim off common suffix (speedup).
/////////
commonlength = this.diffCommonSuffix( text1, text2 );
commonsuffix = text1.substring( text1.length - commonlength );
text1 = text1.substring( 0, text1.length - commonlength );
@ -2120,25 +2332,33 @@ QUnit.diff = (function() {
// Is there a deletion operation after the last equality.
postDel = false;
while ( pointer < diffs.length ) {
if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {
// Equality found.
if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
// Candidate found.
equalities[ equalitiesLength++ ] = pointer;
preIns = postIns;
preDel = postDel;
lastequality = diffs[ pointer ][ 1 ];
} else {
// Not a candidate, and can never become one.
equalitiesLength = 0;
lastequality = null;
}
postIns = postDel = false;
} else { // An insertion or deletion.
// An insertion or deletion.
} else {
if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
postDel = true;
} else {
postIns = true;
}
/*
* Five types to be split:
* <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
@ -2148,10 +2368,16 @@ QUnit.diff = (function() {
* <ins>A</ins><del>B</del>X<del>C</del>
*/
if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
( ( lastequality.length < this.DiffEditCost / 2 ) &&
( ( lastequality.length < 2 ) &&
( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
// Duplicate record.
diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );
diffs.splice(
equalities[ equalitiesLength - 1 ],
0,
[ DIFF_DELETE, lastequality ]
);
// Change second copy to insert.
diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
equalitiesLength--; // Throw away the equality we just deleted;
@ -2183,7 +2409,8 @@ QUnit.diff = (function() {
* @return {string} HTML representation.
*/
DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
var op, data, x, html = [];
var op, data, x,
html = [];
for ( x = 0; x < diffs.length; x++ ) {
op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
data = diffs[ x ][ 1 ]; // Text of change.
@ -2222,7 +2449,8 @@ QUnit.diff = (function() {
pointermid = pointermax;
pointerstart = 0;
while ( pointermin < pointermid ) {
if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {
if ( text1.substring( pointerstart, pointermid ) ===
text2.substring( pointerstart, pointermid ) ) {
pointermin = pointermid;
pointerstart = pointermin;
} else {
@ -2242,7 +2470,9 @@ 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)) {
if ( !text1 ||
!text2 ||
text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
return 0;
}
// Binary search.
@ -2361,10 +2591,7 @@ QUnit.diff = (function() {
var longtext, shorttext, dmp,
text1A, text2B, text2A, text1B, midCommon,
hm1, hm2, hm;
if (this.DiffTimeout <= 0) {
// Don't risk returning a non-optimal diff if we have unlimited time.
return null;
}
longtext = text1.length > text2.length ? text1 : text2;
shorttext = text1.length > text2.length ? text2 : text1;
if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
@ -2709,16 +2936,26 @@ QUnit.diff = (function() {
Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
( lastequality.length <= Math.max( lengthInsertions2,
lengthDeletions2 ) ) ) {
// Duplicate record.
diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );
diffs.splice(
equalities[ equalitiesLength - 1 ],
0,
[ DIFF_DELETE, lastequality ]
);
// Change second copy to insert.
diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
// Throw away the equality we just deleted.
equalitiesLength--;
// Throw away the previous equality (it needs to be reevaluated).
equalitiesLength--;
pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
lengthInsertions1 = 0; // Reset the counters.
// Reset the counters.
lengthInsertions1 = 0;
lengthDeletions1 = 0;
lengthInsertions2 = 0;
lengthDeletions2 = 0;
@ -2752,7 +2989,11 @@ QUnit.diff = (function() {
if ( overlapLength1 >= deletion.length / 2 ||
overlapLength1 >= insertion.length / 2 ) {
// Overlap found. Insert an equality and trim the surrounding edits.
diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );
diffs.splice(
pointer,
0,
[ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
);
diffs[ pointer - 1 ][ 1 ] =
deletion.substring( 0, deletion.length - overlapLength1 );
diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
@ -2761,9 +3002,15 @@ QUnit.diff = (function() {
} else {
if ( overlapLength2 >= deletion.length / 2 ||
overlapLength2 >= insertion.length / 2 ) {
// Reverse overlap found.
// Insert an equality and swap and trim the surrounding edits.
diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );
diffs.splice(
pointer,
0,
[ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
);
diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
diffs[ pointer - 1 ][ 1 ] =
insertion.substring( 0, insertion.length - overlapLength2 );
@ -2922,7 +3169,7 @@ QUnit.diff = (function() {
*/
DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
var pointer, countDelete, countInsert, textInsert, textDelete,
commonlength, changes;
commonlength, changes, diffPointer, position;
diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
pointer = 0;
countDelete = 0;
@ -2982,12 +3229,16 @@ QUnit.diff = (function() {
diffs.splice( pointer - countDelete,
countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
} else {
diffs.splice( pointer - countDelete - countInsert,
countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );
diffs.splice(
pointer - countDelete - countInsert,
countDelete + countInsert,
[ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
);
}
pointer = pointer - countDelete - countInsert +
( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
} else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
// Merge this equality with the previous one.
diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
diffs.splice( pointer, 1 );
@ -3010,22 +3261,31 @@ QUnit.diff = (function() {
// e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
changes = false;
pointer = 1;
// Intentionally ignore the first and last element (don't need checking).
while ( pointer < diffs.length - 1 ) {
if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
diffPointer = diffs[ pointer ][ 1 ];
position = diffPointer.substring(
diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
);
// This is a single edit surrounded by equalities.
if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {
if ( position === diffs[ pointer - 1 ][ 1 ] ) {
// Shift the edit over the previous equality.
diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
diffs[ pointer - 1 ][ 1 ].length );
diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
diffs[ pointer + 1 ][ 1 ] =
diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
diffs.splice( pointer - 1, 1 );
changes = true;
} else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
} else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
diffs[ pointer + 1 ][ 1 ] ) {
// Shift the edit over the next equality.
diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
diffs[ pointer ][ 1 ] =
@ -3047,17 +3307,25 @@ QUnit.diff = (function() {
var diff, output, text;
diff = new DiffMatchPatch();
output = diff.DiffMain( o, n );
//console.log(output);
diff.diffCleanupEfficiency( output );
text = diff.diffPrettyHtml( output );
return text;
};
}() );
// jscs:enable
// 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() {
@ -3115,12 +3383,8 @@ QUnit.init = function() {
}
};
// Don't load the HTML Reporter on non-Browser environments
if ( typeof window === "undefined" ) {
return;
}
var config = QUnit.config,
collapseNext = false,
hasOwn = Object.prototype.hasOwnProperty,
defined = {
document: window.document !== undefined,
@ -3517,6 +3781,17 @@ function storeFixture() {
}
}
function appendFilteredTest() {
var testId = QUnit.config.testId;
if ( !testId || testId.length <= 0 ) {
return "";
}
return "<div id='qunit-filteredTest'>Rerunning selected tests: " + testId.join(", ") +
" <a id='qunit-clearFilter' href='" +
setUrl({ filter: undefined, module: undefined, testId: undefined }) +
"'>" + "Run all tests" + "</a></div>";
}
function appendUserAgent() {
var userAgent = id( "qunit-userAgent" );
@ -3588,6 +3863,7 @@ QUnit.begin(function( details ) {
"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
"<h2 id='qunit-banner'></h2>" +
"<div id='qunit-testrunner-toolbar'></div>" +
appendFilteredTest() +
"<h2 id='qunit-userAgent'></h2>" +
"<ol id='qunit-tests'></ol>";
}
@ -3693,9 +3969,15 @@ QUnit.testStart(function( details ) {
});
function stripHtml( string ) {
// strip tags, html entity and whitespaces
return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\&quot;/g, "").replace(/\s+/g, "");
}
QUnit.log(function( details ) {
var assertList, assertLi,
message, expected, actual,
message, expected, actual, diff,
showDiff = false,
testItem = id( "qunit-test-output-" + details.testId );
if ( !testItem ) {
@ -3710,19 +3992,37 @@ QUnit.log(function( details ) {
// 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 += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
expected +
"</pre></td></tr>";
if ( actual !== expected ) {
message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
actual + "</pre></td></tr>" +
"<tr class='test-diff'><th>Diff: </th><td><pre>" +
QUnit.diff( expected, actual ) + "</pre></td></tr>";
} else {
if ( expected.indexOf( "[object Array]" ) !== -1 ||
actual + "</pre></td></tr>";
// 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 += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
diff + "</pre></td></tr>";
}
} else if ( expected.indexOf( "[object Array]" ) !== -1 ||
expected.indexOf( "[object Object]" ) !== -1 ) {
message += "<tr class='test-message'><th>Message: </th><td>" +
"Diff suppressed as the depth of object is more than current max depth (" +
@ -3730,7 +4030,6 @@ QUnit.log(function( details ) {
" run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" +
"Rerun</a> without max depth.</p></td></tr>";
}
}
if ( details.source ) {
message += "<tr class='test-source'><th>Source: </th><td><pre>" +
@ -3757,7 +4056,7 @@ QUnit.log(function( details ) {
QUnit.testDone(function( details ) {
var testTitle, time, testItem, assertList,
good, bad, testCounts, skipped,
good, bad, testCounts, skipped, sourceName,
tests = id( "qunit-tests" );
if ( !tests ) {
@ -3781,6 +4080,16 @@ QUnit.testDone(function( details ) {
}
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" );
}
@ -3812,10 +4121,31 @@ QUnit.testDone(function( details ) {
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 = "<strong>Source: </strong>" + 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 ) {
if ( document.readyState === "complete" ) {
// 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 );