Upgrade QUnit to 1.11.0. Fixes #8969 - Global test failure in Opera 12.12.

This commit is contained in:
Scott González 2013-01-31 10:19:48 -05:00
parent a36012ad97
commit d36d116d36
4 changed files with 368 additions and 174 deletions

15
external/qunit.css vendored
View File

@ -1,5 +1,5 @@
/** /**
* QUnit v1.10.0 - A JavaScript Unit Testing Framework * QUnit v1.11.0 - A JavaScript Unit Testing Framework
* *
* http://qunitjs.com * http://qunitjs.com
* *
@ -20,7 +20,7 @@
/** Resets */ /** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@ -111,7 +111,12 @@
color: #000; color: #000;
} }
#qunit-tests ol { #qunit-tests li .runtime {
float: right;
font-size: smaller;
}
.qunit-assert-list {
margin-top: 0.5em; margin-top: 0.5em;
padding: 0.5em; padding: 0.5em;
@ -122,6 +127,10 @@
-webkit-border-radius: 5px; -webkit-border-radius: 5px;
} }
.qunit-collapsed {
display: none;
}
#qunit-tests table { #qunit-tests table {
border-collapse: collapse; border-collapse: collapse;
margin-top: .2em; margin-top: .2em;

491
external/qunit.js vendored
View File

@ -1,5 +1,5 @@
/** /**
* QUnit v1.10.0 - A JavaScript Unit Testing Framework * QUnit v1.11.0 - A JavaScript Unit Testing Framework
* *
* http://qunitjs.com * http://qunitjs.com
* *
@ -11,6 +11,7 @@
(function( window ) { (function( window ) {
var QUnit, var QUnit,
assert,
config, config,
onErrorFnPrev, onErrorFnPrev,
testId = 0, testId = 0,
@ -20,18 +21,67 @@ var QUnit,
// Keep a local reference to Date (GH-283) // Keep a local reference to Date (GH-283)
Date = window.Date, Date = window.Date,
defined = { defined = {
setTimeout: typeof window.setTimeout !== "undefined", setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() { sessionStorage: (function() {
var x = "qunit-test-string"; var x = "qunit-test-string";
try { try {
sessionStorage.setItem( x, x ); sessionStorage.setItem( x, x );
sessionStorage.removeItem( x ); sessionStorage.removeItem( x );
return true; return true;
} catch( e ) { } catch( e ) {
return false; 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";
}
} else {
return errorString;
} }
}()) },
}; /**
* Makes a clone of an object using only Array or Object as base,
* and copies over the own enumerable properties.
*
* @param {Object} obj
* @return {Object} New object with only the own properties (recursively).
*/
objectValues = function( obj ) {
// Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
/*jshint newcap: false */
var key, val,
vals = QUnit.is( "array", obj ) ? [] : {};
for ( key in obj ) {
if ( hasOwn.call( obj, key ) ) {
val = obj[key];
vals[key] = val === Object(val) ? objectValues(val) : val;
}
}
return vals;
};
function Test( settings ) { function Test( settings ) {
extend( this, settings ); extend( this, settings );
@ -44,11 +94,11 @@ Test.count = 0;
Test.prototype = { Test.prototype = {
init: function() { init: function() {
var a, b, li, var a, b, li,
tests = id( "qunit-tests" ); tests = id( "qunit-tests" );
if ( tests ) { if ( tests ) {
b = document.createElement( "strong" ); b = document.createElement( "strong" );
b.innerHTML = this.name; b.innerHTML = this.nameHtml;
// `a` initialized at top of scope // `a` initialized at top of scope
a = document.createElement( "a" ); a = document.createElement( "a" );
@ -92,6 +142,7 @@ Test.prototype = {
teardown: function() {} teardown: function() {}
}, this.moduleTestEnvironment ); }, this.moduleTestEnvironment );
this.started = +new Date();
runLoggingCallbacks( "testStart", QUnit, { runLoggingCallbacks( "testStart", QUnit, {
name: this.testName, name: this.testName,
module: this.module module: this.module
@ -111,7 +162,7 @@ Test.prototype = {
try { try {
this.testEnvironment.setup.call( this.testEnvironment ); this.testEnvironment.setup.call( this.testEnvironment );
} catch( e ) { } catch( e ) {
QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
} }
}, },
run: function() { run: function() {
@ -120,22 +171,28 @@ Test.prototype = {
var running = id( "qunit-testresult" ); var running = id( "qunit-testresult" );
if ( running ) { if ( running ) {
running.innerHTML = "Running: <br/>" + this.name; running.innerHTML = "Running: <br/>" + this.nameHtml;
} }
if ( this.async ) { if ( this.async ) {
QUnit.stop(); QUnit.stop();
} }
this.callbackStarted = +new Date();
if ( config.notrycatch ) { if ( config.notrycatch ) {
this.callback.call( this.testEnvironment, QUnit.assert ); this.callback.call( this.testEnvironment, QUnit.assert );
this.callbackRuntime = +new Date() - this.callbackStarted;
return; return;
} }
try { try {
this.callback.call( this.testEnvironment, QUnit.assert ); this.callback.call( this.testEnvironment, QUnit.assert );
this.callbackRuntime = +new Date() - this.callbackStarted;
} catch( e ) { } catch( e ) {
QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); this.callbackRuntime = +new Date() - this.callbackStarted;
QUnit.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(); saveGlobal();
@ -148,38 +205,43 @@ Test.prototype = {
teardown: function() { teardown: function() {
config.current = this; config.current = this;
if ( config.notrycatch ) { if ( config.notrycatch ) {
if ( typeof this.callbackRuntime === "undefined" ) {
this.callbackRuntime = +new Date() - this.callbackStarted;
}
this.testEnvironment.teardown.call( this.testEnvironment ); this.testEnvironment.teardown.call( this.testEnvironment );
return; return;
} else { } else {
try { try {
this.testEnvironment.teardown.call( this.testEnvironment ); this.testEnvironment.teardown.call( this.testEnvironment );
} catch( e ) { } catch( e ) {
QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
} }
} }
checkPollution(); checkPollution();
}, },
finish: function() { finish: function() {
config.current = this; config.current = this;
if ( config.requireExpects && this.expected == null ) { if ( config.requireExpects && this.expected === null ) {
QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
} else if ( this.expected != null && this.expected != this.assertions.length ) { } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
} else if ( this.expected == null && !this.assertions.length ) { } else if ( this.expected === null && !this.assertions.length ) {
QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
} }
var assertion, a, b, i, li, ol, var i, assertion, a, b, time, li, ol,
test = this, test = this,
good = 0, good = 0,
bad = 0, bad = 0,
tests = id( "qunit-tests" ); tests = id( "qunit-tests" );
this.runtime = +new Date() - this.started;
config.stats.all += this.assertions.length; config.stats.all += this.assertions.length;
config.moduleStats.all += this.assertions.length; config.moduleStats.all += this.assertions.length;
if ( tests ) { if ( tests ) {
ol = document.createElement( "ol" ); ol = document.createElement( "ol" );
ol.className = "qunit-assert-list";
for ( i = 0; i < this.assertions.length; i++ ) { for ( i = 0; i < this.assertions.length; i++ ) {
assertion = this.assertions[i]; assertion = this.assertions[i];
@ -208,22 +270,22 @@ Test.prototype = {
} }
if ( bad === 0 ) { if ( bad === 0 ) {
ol.style.display = "none"; addClass( ol, "qunit-collapsed" );
} }
// `b` initialized at top of scope // `b` initialized at top of scope
b = document.createElement( "strong" ); b = document.createElement( "strong" );
b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
addEvent(b, "click", function() { addEvent(b, "click", function() {
var next = b.nextSibling.nextSibling, var next = b.parentNode.lastChild,
display = next.style.display; collapsed = hasClass( next, "qunit-collapsed" );
next.style.display = display === "none" ? "block" : "none"; ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
}); });
addEvent(b, "dblclick", function( e ) { addEvent(b, "dblclick", function( e ) {
var target = e && e.target ? e.target : window.event.srcElement; var target = e && e.target ? e.target : window.event.srcElement;
if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
target = target.parentNode; target = target.parentNode;
} }
if ( window.location && target.nodeName.toLowerCase() === "strong" ) { if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
@ -231,13 +293,19 @@ Test.prototype = {
} }
}); });
// `time` initialized at top of scope
time = document.createElement( "span" );
time.className = "runtime";
time.innerHTML = this.runtime + " ms";
// `li` initialized at top of scope // `li` initialized at top of scope
li = id( this.id ); li = id( this.id );
li.className = bad ? "fail" : "pass"; li.className = bad ? "fail" : "pass";
li.removeChild( li.firstChild ); li.removeChild( li.firstChild );
a = li.firstChild; a = li.firstChild;
li.appendChild( b ); li.appendChild( b );
li.appendChild ( a ); li.appendChild( a );
li.appendChild( time );
li.appendChild( ol ); li.appendChild( ol );
} else { } else {
@ -255,7 +323,8 @@ Test.prototype = {
module: this.module, module: this.module,
failed: bad, failed: bad,
passed: this.assertions.length - bad, passed: this.assertions.length - bad,
total: this.assertions.length total: this.assertions.length,
duration: this.runtime
}); });
QUnit.reset(); QUnit.reset();
@ -321,7 +390,7 @@ QUnit = {
test: function( testName, expected, callback, async ) { test: function( testName, expected, callback, async ) {
var test, var test,
name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>"; nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
if ( arguments.length === 2 ) { if ( arguments.length === 2 ) {
callback = expected; callback = expected;
@ -329,11 +398,11 @@ QUnit = {
} }
if ( config.currentModule ) { if ( config.currentModule ) {
name = "<span class='module-name'>" + config.currentModule + "</span>: " + name; nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
} }
test = new Test({ test = new Test({
name: name, nameHtml: nameHtml,
testName: testName, testName: testName,
expected: expected, expected: expected,
async: async, async: async,
@ -360,6 +429,18 @@ QUnit = {
}, },
start: function( count ) { start: function( count ) {
// QUnit hasn't been initialized yet.
// Note: RequireJS (et al) may delay onLoad
if ( config.semaphore === undefined ) {
QUnit.begin(function() {
// This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
setTimeout(function() {
QUnit.start( count );
});
});
return;
}
config.semaphore -= count || 1; config.semaphore -= count || 1;
// don't start until equal number of stop-calls // don't start until equal number of stop-calls
if ( config.semaphore > 0 ) { if ( config.semaphore > 0 ) {
@ -368,6 +449,8 @@ QUnit = {
// ignore if start is called more often then stop // ignore if start is called more often then stop
if ( config.semaphore < 0 ) { if ( config.semaphore < 0 ) {
config.semaphore = 0; config.semaphore = 0;
QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
return;
} }
// A slight delay, to avoid any current callbacks // A slight delay, to avoid any current callbacks
if ( defined.setTimeout ) { if ( defined.setTimeout ) {
@ -403,11 +486,14 @@ QUnit = {
} }
}; };
// `assert` initialized at top of scope
// Asssert helpers // Asssert helpers
// All of these must call either QUnit.push() or manually do: // All of these must either call QUnit.push() or manually do:
// - runLoggingCallbacks( "log", .. ); // - runLoggingCallbacks( "log", .. );
// - config.current.assertions.push({ .. }); // - config.current.assertions.push({ .. });
QUnit.assert = { // We attach it to the QUnit object *after* we expose the public API,
// otherwise `assert` will become a global variable in browsers (#341).
assert = {
/** /**
* Asserts rough true-ish result. * Asserts rough true-ish result.
* @name ok * @name ok
@ -428,14 +514,14 @@ QUnit.assert = {
message: msg message: msg
}; };
msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); msg = escapeText( msg || (result ? "okay" : "failed" ) );
msg = "<span class='test-message'>" + msg + "</span>"; msg = "<span class='test-message'>" + msg + "</span>";
if ( !result ) { if ( !result ) {
source = sourceFromStacktrace( 2 ); source = sourceFromStacktrace( 2 );
if ( source ) { if ( source ) {
details.source = source; details.source = source;
msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>"; msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>";
} }
} }
runLoggingCallbacks( "log", QUnit, details ); runLoggingCallbacks( "log", QUnit, details );
@ -453,6 +539,7 @@ QUnit.assert = {
* @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
*/ */
equal: function( actual, expected, message ) { equal: function( actual, expected, message ) {
/*jshint eqeqeq:false */
QUnit.push( expected == actual, actual, expected, message ); QUnit.push( expected == actual, actual, expected, message );
}, },
@ -461,9 +548,30 @@ QUnit.assert = {
* @function * @function
*/ */
notEqual: function( actual, expected, message ) { notEqual: function( actual, expected, message ) {
/*jshint eqeqeq:false */
QUnit.push( expected != actual, actual, expected, message ); QUnit.push( expected != actual, actual, expected, message );
}, },
/**
* @name propEqual
* @function
*/
propEqual: function( actual, expected, message ) {
actual = objectValues(actual);
expected = objectValues(expected);
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name notPropEqual
* @function
*/
notPropEqual: function( actual, expected, message ) {
actual = objectValues(actual);
expected = objectValues(expected);
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
},
/** /**
* @name deepEqual * @name deepEqual
* @function * @function
@ -496,8 +604,9 @@ QUnit.assert = {
QUnit.push( expected !== actual, actual, expected, message ); QUnit.push( expected !== actual, actual, expected, message );
}, },
throws: function( block, expected, message ) { "throws": function( block, expected, message ) {
var actual, var actual,
expectedOutput = expected,
ok = false; ok = false;
// 'expected' is optional // 'expected' is optional
@ -518,18 +627,20 @@ QUnit.assert = {
// we don't want to validate thrown error // we don't want to validate thrown error
if ( !expected ) { if ( !expected ) {
ok = true; ok = true;
expectedOutput = null;
// expected is a regexp // expected is a regexp
} else if ( QUnit.objectType( expected ) === "regexp" ) { } else if ( QUnit.objectType( expected ) === "regexp" ) {
ok = expected.test( actual ); ok = expected.test( errorString( actual ) );
// expected is a constructor // expected is a constructor
} else if ( actual instanceof expected ) { } else if ( actual instanceof expected ) {
ok = true; ok = true;
// expected is a validation function which returns true is validation passed // expected is a validation function which returns true is validation passed
} else if ( expected.call( {}, actual ) === true ) { } else if ( expected.call( {}, actual ) === true ) {
expectedOutput = null;
ok = true; ok = true;
} }
QUnit.push( ok, actual, null, message ); QUnit.push( ok, actual, expectedOutput, message );
} else { } else {
QUnit.pushFailure( message, null, 'No exception was thrown.' ); QUnit.pushFailure( message, null, 'No exception was thrown.' );
} }
@ -538,15 +649,16 @@ QUnit.assert = {
/** /**
* @deprecate since 1.8.0 * @deprecate since 1.8.0
* Kept assertion helpers in root for backwards compatibility * Kept assertion helpers in root for backwards compatibility.
*/ */
extend( QUnit, QUnit.assert ); extend( QUnit, assert );
/** /**
* @deprecated since 1.9.0 * @deprecated since 1.9.0
* Kept global "raises()" for backwards compatibility * Kept root "raises()" for backwards compatibility.
* (Note that we don't introduce assert.raises).
*/ */
QUnit.raises = QUnit.assert.throws; QUnit.raises = assert[ "throws" ];
/** /**
* @deprecated since 1.0.0, replaced with error pushes since 1.3.0 * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
@ -622,6 +734,15 @@ config = {
moduleDone: [] moduleDone: []
}; };
// Export global variables, unless an 'exports' object exists,
// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
if ( typeof exports === "undefined" ) {
extend( window, QUnit );
// Expose QUnit object
window.QUnit = QUnit;
}
// Initialize more QUnit.config and QUnit.urlParams // Initialize more QUnit.config and QUnit.urlParams
(function() { (function() {
var i, var i,
@ -655,18 +776,11 @@ config = {
QUnit.isLocal = location.protocol === "file:"; QUnit.isLocal = location.protocol === "file:";
}()); }());
// Export global variables, unless an 'exports' object exists,
// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
if ( typeof exports === "undefined" ) {
extend( window, QUnit );
// Expose QUnit object
window.QUnit = QUnit;
}
// Extend QUnit object, // Extend QUnit object,
// these after set here because they should not be exposed as global functions // these after set here because they should not be exposed as global functions
extend( QUnit, { extend( QUnit, {
assert: assert,
config: config, config: config,
// Initialize the configuration options // Initialize the configuration options
@ -681,7 +795,7 @@ extend( QUnit, {
autorun: false, autorun: false,
filter: "", filter: "",
queue: [], queue: [],
semaphore: 0 semaphore: 1
}); });
var tests, banner, result, var tests, banner, result,
@ -689,7 +803,7 @@ extend( QUnit, {
if ( qunit ) { if ( qunit ) {
qunit.innerHTML = qunit.innerHTML =
"<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" + "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
"<h2 id='qunit-banner'></h2>" + "<h2 id='qunit-banner'></h2>" +
"<div id='qunit-testrunner-toolbar'></div>" + "<div id='qunit-testrunner-toolbar'></div>" +
"<h2 id='qunit-userAgent'></h2>" + "<h2 id='qunit-userAgent'></h2>" +
@ -745,7 +859,7 @@ extend( QUnit, {
// Safe object type checking // Safe object type checking
is: function( type, obj ) { is: function( type, obj ) {
return QUnit.objectType( obj ) == type; return QUnit.objectType( obj ) === type;
}, },
objectType: function( obj ) { objectType: function( obj ) {
@ -757,7 +871,8 @@ extend( QUnit, {
return "null"; return "null";
} }
var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
type = match && match[1] || "";
switch ( type ) { switch ( type ) {
case "Number": case "Number":
@ -794,16 +909,16 @@ extend( QUnit, {
expected: expected expected: expected
}; };
message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); message = escapeText( message ) || ( result ? "okay" : "failed" );
message = "<span class='test-message'>" + message + "</span>"; message = "<span class='test-message'>" + message + "</span>";
output = message; output = message;
if ( !result ) { if ( !result ) {
expected = escapeInnerText( QUnit.jsDump.parse(expected) ); expected = escapeText( QUnit.jsDump.parse(expected) );
actual = escapeInnerText( QUnit.jsDump.parse(actual) ); actual = escapeText( QUnit.jsDump.parse(actual) );
output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>"; output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
if ( actual != expected ) { if ( actual !== expected ) {
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>"; output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>"; output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
} }
@ -812,7 +927,7 @@ extend( QUnit, {
if ( source ) { if ( source ) {
details.source = source; details.source = source;
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>"; output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
} }
output += "</table>"; output += "</table>";
@ -839,19 +954,19 @@ extend( QUnit, {
message: message message: message
}; };
message = escapeInnerText( message ) || "error"; message = escapeText( message ) || "error";
message = "<span class='test-message'>" + message + "</span>"; message = "<span class='test-message'>" + message + "</span>";
output = message; output = message;
output += "<table>"; output += "<table>";
if ( actual ) { if ( actual ) {
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>"; output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
} }
if ( source ) { if ( source ) {
details.source = source; details.source = source;
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>"; output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
} }
output += "</table>"; output += "</table>";
@ -876,7 +991,8 @@ extend( QUnit, {
querystring += encodeURIComponent( key ) + "=" + querystring += encodeURIComponent( key ) + "=" +
encodeURIComponent( params[ key ] ) + "&"; encodeURIComponent( params[ key ] ) + "&";
} }
return window.location.pathname + querystring.slice( 0, -1 ); return window.location.protocol + "//" + window.location.host +
window.location.pathname + querystring.slice( 0, -1 );
}, },
extend: extend, extend: extend,
@ -907,7 +1023,7 @@ extend( QUnit.constructor.prototype, {
// testStart: { name } // testStart: { name }
testStart: registerLoggingCallback( "testStart" ), testStart: registerLoggingCallback( "testStart" ),
// testDone: { name, failed, passed, total } // testDone: { name, failed, passed, total, duration }
testDone: registerLoggingCallback( "testDone" ), testDone: registerLoggingCallback( "testDone" ),
// moduleStart: { name } // moduleStart: { name }
@ -925,9 +1041,10 @@ QUnit.load = function() {
runLoggingCallbacks( "begin", QUnit, {} ); runLoggingCallbacks( "begin", QUnit, {} );
// Initialize the config, saving the execution queue // Initialize the config, saving the execution queue
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter, var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
numModules = 0, urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
moduleFilterHtml = "", numModules = 0,
moduleFilterHtml = "",
urlConfigHtml = "", urlConfigHtml = "",
oldconfig = extend( {}, config ); oldconfig = extend( {}, config );
@ -948,14 +1065,24 @@ QUnit.load = function() {
}; };
} }
config[ val.id ] = QUnit.urlParams[ val.id ]; config[ val.id ] = QUnit.urlParams[ val.id ];
urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>"; urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
"' name='" + escapeText( val.id ) +
"' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
" title='" + escapeText( val.tooltip ) +
"'><label for='qunit-urlconfig-" + escapeText( val.id ) +
"' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
} }
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined ? "selected" : "" ) + ">< All Modules ></option>"; moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
( config.module === undefined ? "selected='selected'" : "" ) +
">< All Modules ></option>";
for ( i in config.modules ) { for ( i in config.modules ) {
if ( config.modules.hasOwnProperty( i ) ) { if ( config.modules.hasOwnProperty( i ) ) {
numModules += 1; numModules += 1;
moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>"; moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(i) ) + "' " +
( config.module === i ? "selected='selected'" : "" ) +
">" + escapeText(i) + "</option>";
} }
} }
moduleFilterHtml += "</select>"; moduleFilterHtml += "</select>";
@ -1014,22 +1141,28 @@ QUnit.load = function() {
label.innerHTML = "Hide passed tests"; label.innerHTML = "Hide passed tests";
toolbar.appendChild( label ); toolbar.appendChild( label );
urlConfigCheckboxes = document.createElement( 'span' ); urlConfigCheckboxesContainer = document.createElement("span");
urlConfigCheckboxes.innerHTML = urlConfigHtml; urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
addEvent( urlConfigCheckboxes, "change", function( event ) { urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
var params = {}; // For oldIE support:
params[ event.target.name ] = event.target.checked ? true : undefined; // * Add handlers to the individual elements instead of the container
// * Use "click" instead of "change"
// * Fallback from event.target to event.srcElement
addEvents( urlConfigCheckboxes, "click", function( event ) {
var params = {},
target = event.target || event.srcElement;
params[ target.name ] = target.checked ? true : undefined;
window.location = QUnit.url( params ); window.location = QUnit.url( params );
}); });
toolbar.appendChild( urlConfigCheckboxes ); toolbar.appendChild( urlConfigCheckboxesContainer );
if (numModules > 1) { if (numModules > 1) {
moduleFilter = document.createElement( 'span' ); moduleFilter = document.createElement( 'span' );
moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' ); moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
moduleFilter.innerHTML = moduleFilterHtml; moduleFilter.innerHTML = moduleFilterHtml;
addEvent( moduleFilter, "change", function() { addEvent( moduleFilter.lastChild, "change", function() {
var selectBox = moduleFilter.getElementsByTagName("select")[0], var selectBox = moduleFilter.getElementsByTagName("select")[0],
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
}); });
@ -1106,7 +1239,7 @@ function done() {
" milliseconds.<br/>", " milliseconds.<br/>",
"<span class='passed'>", "<span class='passed'>",
passed, passed,
"</span> tests of <span class='total'>", "</span> assertions of <span class='total'>",
config.stats.all, config.stats.all,
"</span> passed, <span class='failed'>", "</span> passed, <span class='failed'>",
config.stats.bad, config.stats.bad,
@ -1199,7 +1332,7 @@ function validTest( test ) {
function extractStacktrace( e, offset ) { function extractStacktrace( e, offset ) {
offset = offset === undefined ? 3 : offset; offset = offset === undefined ? 3 : offset;
var stack, include, i, regex; var stack, include, i;
if ( e.stacktrace ) { if ( e.stacktrace ) {
// Opera // Opera
@ -1213,7 +1346,7 @@ function extractStacktrace( e, offset ) {
if ( fileName ) { if ( fileName ) {
include = []; include = [];
for ( i = offset; i < stack.length; i++ ) { for ( i = offset; i < stack.length; i++ ) {
if ( stack[ i ].indexOf( fileName ) != -1 ) { if ( stack[ i ].indexOf( fileName ) !== -1 ) {
break; break;
} }
include.push( stack[ i ] ); include.push( stack[ i ] );
@ -1242,17 +1375,27 @@ function sourceFromStacktrace( offset ) {
} }
} }
function escapeInnerText( s ) { /**
* Escape text for attribute or text content.
*/
function escapeText( s ) {
if ( !s ) { if ( !s ) {
return ""; return "";
} }
s = s + ""; s = s + "";
return s.replace( /[\&<>]/g, function( s ) { // Both single quotes and double quotes (for attributes)
return s.replace( /['"<>&]/g, function( s ) {
switch( s ) { switch( s ) {
case "&": return "&amp;"; case '\'':
case "<": return "&lt;"; return '&#039;';
case ">": return "&gt;"; case '"':
default: return s; return '&quot;';
case '<':
return '&lt;';
case '>':
return '&gt;';
case '&':
return '&amp;';
} }
}); });
} }
@ -1300,7 +1443,7 @@ function saveGlobal() {
} }
} }
function checkPollution( name ) { function checkPollution() {
var newGlobals, var newGlobals,
deletedGlobals, deletedGlobals,
old = config.pollution; old = config.pollution;
@ -1349,16 +1492,53 @@ function extend( a, b ) {
return a; return a;
} }
/**
* @param {HTMLElement} elem
* @param {string} type
* @param {Function} fn
*/
function addEvent( elem, type, fn ) { function addEvent( elem, type, fn ) {
// Standards-based browsers
if ( elem.addEventListener ) { if ( elem.addEventListener ) {
elem.addEventListener( type, fn, false ); elem.addEventListener( type, fn, false );
} else if ( elem.attachEvent ) { // IE
elem.attachEvent( "on" + type, fn );
} else { } else {
fn(); elem.attachEvent( "on" + type, fn );
} }
} }
/**
* @param {Array|NodeList} elems
* @param {string} type
* @param {Function} fn
*/
function addEvents( elems, type, fn ) {
var i = elems.length;
while ( i-- ) {
addEvent( elems[i], type, fn );
}
}
function hasClass( elem, name ) {
return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
}
function addClass( elem, name ) {
if ( !hasClass( elem, name ) ) {
elem.className += (elem.className ? " " : "") + name;
}
}
function removeClass( elem, name ) {
var set = " " + elem.className + " ";
// Class name may appear multiple times
while ( set.indexOf(" " + name + " ") > -1 ) {
set = set.replace(" " + name + " " , " ");
}
// If possible, trim it for prettiness, but not neccecarily
elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set );
}
function id( name ) { function id( name ) {
return !!( typeof document !== "undefined" && document && document.getElementById ) && return !!( typeof document !== "undefined" && document && document.getElementById ) &&
document.getElementById( name ); document.getElementById( name );
@ -1372,7 +1552,6 @@ function registerLoggingCallback( key ) {
// Supports deprecated method of completely overwriting logging callbacks // Supports deprecated method of completely overwriting logging callbacks
function runLoggingCallbacks( key, scope, args ) { function runLoggingCallbacks( key, scope, args ) {
//debugger;
var i, callbacks; var i, callbacks;
if ( QUnit.hasOwnProperty( key ) ) { if ( QUnit.hasOwnProperty( key ) ) {
QUnit[ key ].call(scope, args ); QUnit[ key ].call(scope, args );
@ -1414,6 +1593,7 @@ QUnit.equiv = (function() {
// for string, boolean, number and null // for string, boolean, number and null
function useStrictEquality( b, a ) { function useStrictEquality( b, a ) {
/*jshint eqeqeq:false */
if ( b instanceof a.constructor || a instanceof b.constructor ) { if ( b instanceof a.constructor || a instanceof b.constructor ) {
// to catch short annotaion VS 'new' annotation of a // to catch short annotaion VS 'new' annotation of a
// declaration // declaration
@ -1610,7 +1790,8 @@ QUnit.jsDump = (function() {
var reName = /^function (\w+)/, var reName = /^function (\w+)/,
jsDump = { jsDump = {
parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance // type is used mostly internally, you can fix a (custom)type in advance
parse: function( obj, type, stack ) {
stack = stack || [ ]; stack = stack || [ ];
var inStack, res, var inStack, res,
parser = this.parsers[ type || this.typeOf(obj) ]; parser = this.parsers[ type || this.typeOf(obj) ];
@ -1618,18 +1799,16 @@ QUnit.jsDump = (function() {
type = typeof parser; type = typeof parser;
inStack = inArray( obj, stack ); inStack = inArray( obj, stack );
if ( inStack != -1 ) { if ( inStack !== -1 ) {
return "recursion(" + (inStack - stack.length) + ")"; return "recursion(" + (inStack - stack.length) + ")";
} }
//else if ( type === "function" ) {
if ( type == "function" ) {
stack.push( obj ); stack.push( obj );
res = parser.call( this, obj, stack ); res = parser.call( this, obj, stack );
stack.pop(); stack.pop();
return res; return res;
} }
// else return ( type === "string" ) ? parser : this.parsers.error;
return ( type == "string" ) ? parser : this.parsers.error;
}, },
typeOf: function( obj ) { typeOf: function( obj ) {
var type; var type;
@ -1656,6 +1835,8 @@ QUnit.jsDump = (function() {
( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
) { ) {
type = "array"; type = "array";
} else if ( obj.constructor === Error.prototype.constructor ) {
type = "error";
} else { } else {
type = typeof obj; type = typeof obj;
} }
@ -1664,7 +1845,8 @@ QUnit.jsDump = (function() {
separator: function() { separator: function() {
return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " "; return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
}, },
indent: function( extra ) {// 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 ) { if ( !this.multiline ) {
return ""; return "";
} }
@ -1693,13 +1875,16 @@ QUnit.jsDump = (function() {
parsers: { parsers: {
window: "[Window]", window: "[Window]",
document: "[Document]", document: "[Document]",
error: "[ERROR]", //when no parser is found, shouldn"t happen error: function(error) {
return "Error(\"" + error.message + "\")";
},
unknown: "[Unknown]", unknown: "[Unknown]",
"null": "null", "null": "null",
"undefined": "undefined", "undefined": "undefined",
"function": function( fn ) { "function": function( fn ) {
var ret = "function", var ret = "function",
name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE // functions never have name in IE
name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
if ( name ) { if ( name ) {
ret += " " + name; ret += " " + name;
@ -1715,13 +1900,9 @@ QUnit.jsDump = (function() {
object: function( map, stack ) { object: function( map, stack ) {
var ret = [ ], keys, key, val, i; var ret = [ ], keys, key, val, i;
QUnit.jsDump.up(); QUnit.jsDump.up();
if ( Object.keys ) { keys = [];
keys = Object.keys( map ); for ( key in map ) {
} else { keys.push( key );
keys = [];
for ( key in map ) {
keys.push( key );
}
} }
keys.sort(); keys.sort();
for ( i = 0; i < keys.length; i++ ) { for ( i = 0; i < keys.length; i++ ) {
@ -1733,21 +1914,34 @@ QUnit.jsDump = (function() {
return join( "{", ret, "}" ); return join( "{", ret, "}" );
}, },
node: function( node ) { node: function( node ) {
var a, val, var len, i, val,
open = QUnit.jsDump.HTML ? "&lt;" : "<", open = QUnit.jsDump.HTML ? "&lt;" : "<",
close = QUnit.jsDump.HTML ? "&gt;" : ">", close = QUnit.jsDump.HTML ? "&gt;" : ">",
tag = node.nodeName.toLowerCase(), tag = node.nodeName.toLowerCase(),
ret = open + tag; ret = open + tag,
attrs = node.attributes;
for ( a in QUnit.jsDump.DOMAttrs ) { if ( attrs ) {
val = node[ QUnit.jsDump.DOMAttrs[a] ]; for ( i = 0, len = attrs.length; i < len; i++ ) {
if ( val ) { val = attrs[i].nodeValue;
ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); // IE6 includes all attributes in .attributes, even ones not explicitly set.
// Those have values like undefined, null, 0, false, "" or "inherit".
if ( val && val !== "inherit" ) {
ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
}
} }
} }
return ret + close + open + "/" + tag + close; ret += close;
// Show content of TextNode or CDATASection
if ( node.nodeType === 3 || node.nodeType === 4 ) {
ret += node.nodeValue;
}
return ret + open + "/" + tag + close;
}, },
functionArgs: function( fn ) {//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, var args,
l = fn.length; l = fn.length;
@ -1757,54 +1951,34 @@ QUnit.jsDump = (function() {
args = new Array(l); args = new Array(l);
while ( l-- ) { while ( l-- ) {
args[l] = String.fromCharCode(97+l);//97 is 'a' // 97 is 'a'
args[l] = String.fromCharCode(97+l);
} }
return " " + args.join( ", " ) + " "; return " " + args.join( ", " ) + " ";
}, },
key: quote, //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
functionCode: "[code]", //function calls it internally, it's the content of the function key: quote,
attribute: quote, //node calls it internally, it's an html attribute value // function calls it internally, it's the content of the function
functionCode: "[code]",
// node calls it internally, it's an html attribute value
attribute: quote,
string: quote, string: quote,
date: quote, date: quote,
regexp: literal, //regex regexp: literal,
number: literal, number: literal,
"boolean": literal "boolean": literal
}, },
DOMAttrs: { // if true, entities are escaped ( <, >, \t, space and \n )
//attributes to dump from nodes, name=>realName HTML: false,
id: "id", // indentation unit
name: "name", indentChar: " ",
"class": "className" // if true, items in a collection, are separated by a \n, else just a space.
}, multiline: true
HTML: false,//if true, entities are escaped ( <, >, \t, space and \n )
indentChar: " ",//indentation unit
multiline: true //if true, items in a collection, are separated by a \n, else just a space.
}; };
return jsDump; return jsDump;
}()); }());
// from Sizzle.js
function getText( elems ) {
var i, elem,
ret = "";
for ( i = 0; elems[i]; i++ ) {
elem = elems[i];
// Get the text from text nodes and CDATA nodes
if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
ret += elem.nodeValue;
// Traverse everything else, except comment nodes
} else if ( elem.nodeType !== 8 ) {
ret += getText( elem.childNodes );
}
}
return ret;
}
// from jquery.js // from jquery.js
function inArray( elem, array ) { function inArray( elem, array ) {
if ( array.indexOf ) { if ( array.indexOf ) {
@ -1835,13 +2009,14 @@ function inArray( elem, array ) {
* QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
*/ */
QUnit.diff = (function() { QUnit.diff = (function() {
/*jshint eqeqeq:false, eqnull:true */
function diff( o, n ) { function diff( o, n ) {
var i, var i,
ns = {}, ns = {},
os = {}; os = {};
for ( i = 0; i < n.length; i++ ) { for ( i = 0; i < n.length; i++ ) {
if ( ns[ n[i] ] == null ) { if ( !hasOwn.call( ns, n[i] ) ) {
ns[ n[i] ] = { ns[ n[i] ] = {
rows: [], rows: [],
o: null o: null
@ -1851,7 +2026,7 @@ QUnit.diff = (function() {
} }
for ( i = 0; i < o.length; i++ ) { for ( i = 0; i < o.length; i++ ) {
if ( os[ o[i] ] == null ) { if ( !hasOwn.call( os, o[i] ) ) {
os[ o[i] ] = { os[ o[i] ] = {
rows: [], rows: [],
n: null n: null
@ -1864,7 +2039,7 @@ QUnit.diff = (function() {
if ( !hasOwn.call( ns, i ) ) { if ( !hasOwn.call( ns, i ) ) {
continue; continue;
} }
if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
n[ ns[i].rows[0] ] = { n[ ns[i].rows[0] ] = {
text: n[ ns[i].rows[0] ], text: n[ ns[i].rows[0] ],
row: os[i].rows[0] row: os[i].rows[0]
@ -1970,7 +2145,7 @@ QUnit.diff = (function() {
// for CommonJS enviroments, export everything // for CommonJS enviroments, export everything
if ( typeof exports !== "undefined" ) { if ( typeof exports !== "undefined" ) {
extend(exports, QUnit); extend( exports, QUnit );
} }
// get at whatever the global object is, like window in browsers // get at whatever the global object is, like window in browsers

View File

@ -1,13 +1,13 @@
iframe.qunit-subsuite{ iframe.qunit-subsuite {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
margin: 0;
padding: 0;
border-width: 1px 0 0;
height: 45%;
width: 100%;
background: #fff; margin: 0;
} padding: 0;
border-width: 1px 0 0;
height: 45%;
width: 100%;
background: #fff;
}

View File

@ -16,8 +16,15 @@ QUnit.extend( QUnit, {
}, },
runSuite: function( suite ) { runSuite: function( suite ) {
var path = suite;
if ( QUnit.is( "object", suite ) ) {
path = suite.path;
suite = suite.name;
}
asyncTest( suite, function() { asyncTest( suite, function() {
QUnit.iframe.setAttribute( "src", suite ); QUnit.iframe.setAttribute( "src", path );
}); });
}, },
@ -33,6 +40,9 @@ QUnit.extend( QUnit, {
var module, test, var module, test,
count = 0; count = 0;
if (iframe.src === "") {
return;
}
iframeWin.QUnit.moduleStart(function( data ) { iframeWin.QUnit.moduleStart(function( data ) {
// capture module name for messages // capture module name for messages