diff --git a/addons/web/static/lib/qunit/qunit.css b/addons/web/static/lib/qunit/qunit.css index 257b224ff44..7ba3f9a30b8 100644 --- a/addons/web/static/lib/qunit/qunit.css +++ b/addons/web/static/lib/qunit/qunit.css @@ -1,11 +1,11 @@ /** - * QUnit v1.9.0 - A JavaScript Unit Testing Framework + * QUnit v1.12.0 - A JavaScript Unit Testing Framework * - * http://docs.jquery.com/QUnit + * http://qunitjs.com * - * Copyright (c) 2012 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license */ /** Font Family and Sizes */ @@ -20,7 +20,7 @@ /** Resets */ -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { +#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { margin: 0; padding: 0; } @@ -67,6 +67,7 @@ padding: 0.5em 0 0.5em 2em; color: #5E740B; background-color: #eee; + overflow: hidden; } #qunit-userAgent { @@ -76,6 +77,9 @@ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } +#qunit-modulefilter-container { + float: right; +} /** Tests: Pass/Fail */ @@ -107,7 +111,12 @@ color: #000; } -#qunit-tests ol { +#qunit-tests li .runtime { + float: right; + font-size: smaller; +} + +.qunit-assert-list { margin-top: 0.5em; padding: 0.5em; @@ -118,6 +127,10 @@ -webkit-border-radius: 5px; } +.qunit-collapsed { + display: none; +} + #qunit-tests table { border-collapse: collapse; margin-top: .2em; diff --git a/addons/web/static/lib/qunit/qunit.js b/addons/web/static/lib/qunit/qunit.js index 65625336453..c36724e33b2 100644 --- a/addons/web/static/lib/qunit/qunit.js +++ b/addons/web/static/lib/qunit/qunit.js @@ -1,35 +1,88 @@ /** - * QUnit v1.9.0 - A JavaScript Unit Testing Framework + * QUnit v1.12.0 - A JavaScript Unit Testing Framework * - * http://docs.jquery.com/QUnit + * http://qunitjs.com * - * Copyright (c) 2012 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * https://jquery.org/license/ */ (function( window ) { var QUnit, + assert, config, onErrorFnPrev, testId = 0, 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, + setTimeout = window.setTimeout, defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; + setTimeout: typeof window.setTimeout !== "undefined", + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch( e ) { + 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 ) { extend( this, settings ); @@ -42,11 +95,11 @@ Test.count = 0; Test.prototype = { init: function() { var a, b, li, - tests = id( "qunit-tests" ); + tests = id( "qunit-tests" ); if ( tests ) { b = document.createElement( "strong" ); - b.innerHTML = this.name; + b.innerHTML = this.nameHtml; // `a` initialized at top of scope a = document.createElement( "a" ); @@ -63,8 +116,16 @@ Test.prototype = { } }, setup: function() { - if ( this.module !== config.previousModule ) { - if ( config.previousModule ) { + if ( + // Emit moduleStart when we're switching from one module to another + this.module !== config.previousModule || + // They could be equal (both undefined) but if the previousModule property doesn't + // yet exist it means this is the first test in a suite that isn't wrapped in a + // module, in which case we'll just emit a moduleStart event for 'undefined'. + // Without this, reporters can get testStart before moduleStart which is a problem. + !hasOwn.call( config, "previousModule" ) + ) { + if ( hasOwn.call( config, "previousModule" ) ) { runLoggingCallbacks( "moduleDone", QUnit, { name: config.previousModule, failed: config.moduleStats.bad, @@ -77,10 +138,6 @@ Test.prototype = { runLoggingCallbacks( "moduleStart", QUnit, { name: this.module }); - } else if ( config.autorun ) { - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module - }); } config.current = this; @@ -90,26 +147,35 @@ Test.prototype = { teardown: function() {} }, this.moduleTestEnvironment ); + this.started = +new Date(); runLoggingCallbacks( "testStart", QUnit, { name: this.testName, module: this.module }); - // allow utility functions to access the current test environment - // TODO why?? + /*jshint camelcase:false */ + + + /** + * Expose the current test environment. + * + * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. + */ QUnit.current_testEnvironment = this.testEnvironment; + /*jshint camelcase:true */ + if ( !config.pollution ) { saveGlobal(); } if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment ); + this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); return; } try { - this.testEnvironment.setup.call( this.testEnvironment ); + this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); } 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() { @@ -118,22 +184,28 @@ Test.prototype = { var running = id( "qunit-testresult" ); if ( running ) { - running.innerHTML = "Running:
" + this.name; + running.innerHTML = "Running:
" + this.nameHtml; } if ( this.async ) { QUnit.stop(); } + this.callbackStarted = +new Date(); + if ( config.notrycatch ) { this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; return; } try { this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; } 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 saveGlobal(); @@ -146,38 +218,43 @@ Test.prototype = { teardown: function() { config.current = this; if ( config.notrycatch ) { - this.testEnvironment.teardown.call( this.testEnvironment ); + if ( typeof this.callbackRuntime === "undefined" ) { + this.callbackRuntime = +new Date() - this.callbackStarted; + } + this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); return; } else { try { - this.testEnvironment.teardown.call( this.testEnvironment ); + this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); } 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(); }, finish: function() { 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 ); - } 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 ); - } 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 ); } - var assertion, a, b, i, li, ol, + var i, assertion, a, b, time, li, ol, test = this, good = 0, bad = 0, tests = id( "qunit-tests" ); + this.runtime = +new Date() - this.started; config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; if ( tests ) { ol = document.createElement( "ol" ); + ol.className = "qunit-assert-list"; for ( i = 0; i < this.assertions.length; i++ ) { assertion = this.assertions[i]; @@ -206,22 +283,22 @@ Test.prototype = { } if ( bad === 0 ) { - ol.style.display = "none"; + addClass( ol, "qunit-collapsed" ); } // `b` initialized at top of scope b = document.createElement( "strong" ); - b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; + b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; addEvent(b, "click", function() { - var next = b.nextSibling.nextSibling, - display = next.style.display; - next.style.display = display === "none" ? "block" : "none"; + var next = b.parentNode.lastChild, + collapsed = hasClass( next, "qunit-collapsed" ); + ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); }); addEvent(b, "dblclick", function( e ) { var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { + if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { target = target.parentNode; } if ( window.location && target.nodeName.toLowerCase() === "strong" ) { @@ -229,13 +306,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 = id( this.id ); li.className = bad ? "fail" : "pass"; li.removeChild( li.firstChild ); a = li.firstChild; li.appendChild( b ); - li.appendChild ( a ); + li.appendChild( a ); + li.appendChild( time ); li.appendChild( ol ); } else { @@ -253,7 +336,8 @@ Test.prototype = { module: this.module, failed: bad, passed: this.assertions.length - bad, - total: this.assertions.length + total: this.assertions.length, + duration: this.runtime }); QUnit.reset(); @@ -304,7 +388,8 @@ QUnit = { // call on start of module test to prepend name to all tests module: function( name, testEnvironment ) { config.currentModule = name; - config.currentModuleTestEnviroment = testEnvironment; + config.currentModuleTestEnvironment = testEnvironment; + config.modules[name] = true; }, asyncTest: function( testName, expected, callback ) { @@ -318,7 +403,7 @@ QUnit = { test: function( testName, expected, callback, async ) { var test, - name = "" + escapeInnerText( testName ) + ""; + nameHtml = "" + escapeText( testName ) + ""; if ( arguments.length === 2 ) { callback = expected; @@ -326,17 +411,17 @@ QUnit = { } if ( config.currentModule ) { - name = "" + config.currentModule + ": " + name; + nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; } test = new Test({ - name: name, + nameHtml: nameHtml, testName: testName, expected: expected, async: async, callback: callback, module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnviroment, + moduleTestEnvironment: config.currentModuleTestEnvironment, stack: sourceFromStacktrace( 2 ) }); @@ -347,12 +432,28 @@ QUnit = { test.queue(); }, - // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. expect: function( asserts ) { - config.current.expected = asserts; + if (arguments.length === 1) { + config.current.expected = asserts; + } else { + return config.current.expected; + } }, 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; // don't start until equal number of stop-calls if ( config.semaphore > 0 ) { @@ -360,11 +461,13 @@ QUnit = { } // ignore if start is called more often then stop 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 if ( defined.setTimeout ) { - window.setTimeout(function() { + setTimeout(function() { if ( config.semaphore > 0 ) { return; } @@ -387,7 +490,7 @@ QUnit = { if ( config.testTimeout && defined.setTimeout ) { clearTimeout( config.timeout ); - config.timeout = window.setTimeout(function() { + config.timeout = setTimeout(function() { QUnit.ok( false, "Test timed out" ); config.semaphore = 1; QUnit.start(); @@ -396,11 +499,14 @@ QUnit = { } }; -// Asssert helpers -// All of these must call either QUnit.push() or manually do: +// `assert` initialized at top of scope +// Assert helpers +// All of these must either call QUnit.push() or manually do: // - runLoggingCallbacks( "log", .. ); // - 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. * @name ok @@ -412,21 +518,23 @@ QUnit.assert = { throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); } result = !!result; + msg = msg || (result ? "okay" : "failed" ); var source, details = { + module: config.current.module, + name: config.current.testName, result: result, message: msg }; - msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); - msg = "" + msg + ""; + msg = "" + escapeText( msg ) + ""; if ( !result ) { source = sourceFromStacktrace( 2 ); if ( source ) { details.source = source; - msg += "
Source:
" + escapeInnerText( source ) + "
"; + msg += "
Source:
" + escapeText( source ) + "
"; } } runLoggingCallbacks( "log", QUnit, details ); @@ -444,6 +552,7 @@ QUnit.assert = { * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); */ equal: function( actual, expected, message ) { + /*jshint eqeqeq:false */ QUnit.push( expected == actual, actual, expected, message ); }, @@ -452,9 +561,30 @@ QUnit.assert = { * @function */ notEqual: function( actual, expected, message ) { + /*jshint eqeqeq:false */ 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 * @function @@ -487,8 +617,9 @@ QUnit.assert = { QUnit.push( expected !== actual, actual, expected, message ); }, - throws: function( block, expected, message ) { + "throws": function( block, expected, message ) { var actual, + expectedOutput = expected, ok = false; // 'expected' is optional @@ -509,35 +640,38 @@ QUnit.assert = { // we don't want to validate thrown error if ( !expected ) { ok = true; + expectedOutput = null; // expected is a regexp } else if ( QUnit.objectType( expected ) === "regexp" ) { - ok = expected.test( actual ); + ok = expected.test( errorString( actual ) ); // expected is a constructor } else if ( actual instanceof expected ) { ok = true; // expected is a validation function which returns true is validation passed } else if ( expected.call( {}, actual ) === true ) { + expectedOutput = null; ok = true; } - QUnit.push( ok, actual, null, message ); + QUnit.push( ok, actual, expectedOutput, message ); } else { - QUnit.pushFailure( message, null, 'No exception was thrown.' ); + QUnit.pushFailure( message, null, "No exception was thrown." ); } } }; /** - * @deprecate since 1.8.0 - * Kept assertion helpers in root for backwards compatibility + * @deprecated since 1.8.0 + * Kept assertion helpers in root for backwards compatibility. */ -extend( QUnit, QUnit.assert ); +extend( QUnit, assert ); /** * @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 @@ -600,6 +734,9 @@ config = { } ], + // Set of all modules. + modules: {}, + // logging callback queues begin: [], done: [], @@ -610,6 +747,15 @@ config = { 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.constructor.prototype ); + + // Expose QUnit object + window.QUnit = QUnit; +} + // Initialize more QUnit.config and QUnit.urlParams (function() { var i, @@ -643,18 +789,11 @@ config = { 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, // these after set here because they should not be exposed as global functions extend( QUnit, { + assert: assert, + config: config, // Initialize the configuration options @@ -669,7 +808,7 @@ extend( QUnit, { autorun: false, filter: "", queue: [], - semaphore: 0 + semaphore: 1 }); var tests, banner, result, @@ -677,7 +816,7 @@ extend( QUnit, { if ( qunit ) { qunit.innerHTML = - "

" + escapeInnerText( document.title ) + "

" + + "

" + escapeText( document.title ) + "

" + "

" + "
" + "

" + @@ -710,17 +849,15 @@ extend( QUnit, { }, // Resets the test setup. Useful for tests that modify the DOM. - // If jQuery is available, uses jQuery's html(), otherwise just innerHTML. + /* + DEPRECATED: Use multiple tests instead of resetting inside a test. + Use testStart or testDone for custom cleanup. + This method will throw an error in 2.0, and will be removed in 2.1 + */ reset: function() { - var fixture; - - if ( window.jQuery ) { - jQuery( "#qunit-fixture" ).html( config.fixture ); - } else { - fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; } }, @@ -740,7 +877,7 @@ extend( QUnit, { // Safe object type checking is: function( type, obj ) { - return QUnit.objectType( obj ) == type; + return QUnit.objectType( obj ) === type; }, objectType: function( obj ) { @@ -752,7 +889,8 @@ extend( QUnit, { 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 ) { case "Number": @@ -781,22 +919,24 @@ extend( QUnit, { var output, source, details = { + module: config.current.module, + name: config.current.testName, result: result, message: message, actual: actual, expected: expected }; - message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); + message = escapeText( message ) || ( result ? "okay" : "failed" ); message = "" + message + ""; output = message; if ( !result ) { - expected = escapeInnerText( QUnit.jsDump.parse(expected) ); - actual = escapeInnerText( QUnit.jsDump.parse(actual) ); + expected = escapeText( QUnit.jsDump.parse(expected) ); + actual = escapeText( QUnit.jsDump.parse(actual) ); output += ""; - if ( actual != expected ) { + if ( actual !== expected ) { output += ""; output += ""; } @@ -805,7 +945,7 @@ extend( QUnit, { if ( source ) { details.source = source; - output += ""; + output += ""; } output += "
Expected:
" + expected + "
Result:
" + actual + "
Diff:
" + QUnit.diff( expected, actual ) + "
Source:
" + escapeInnerText( source ) + "
Source:
" + escapeText( source ) + "
"; @@ -826,23 +966,25 @@ extend( QUnit, { var output, details = { + module: config.current.module, + name: config.current.testName, result: false, message: message }; - message = escapeInnerText( message ) || "error"; + message = escapeText( message ) || "error"; message = "" + message + ""; output = message; output += ""; if ( actual ) { - output += ""; + output += ""; } if ( source ) { details.source = source; - output += ""; + output += ""; } output += "
Result:
" + escapeInnerText( actual ) + "
Result:
" + escapeText( actual ) + "
Source:
" + escapeInnerText( source ) + "
Source:
" + escapeText( source ) + "
"; @@ -861,18 +1003,21 @@ extend( QUnit, { querystring = "?"; for ( key in params ) { - if ( !hasOwn.call( params, key ) ) { - continue; + if ( hasOwn.call( params, key ) ) { + querystring += encodeURIComponent( key ) + "=" + + encodeURIComponent( params[ key ] ) + "&"; } - querystring += encodeURIComponent( 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, id: id, - addEvent: addEvent + addEvent: addEvent, + addClass: addClass, + hasClass: hasClass, + removeClass: removeClass // load, equiv, jsDump, diff: Attached later }); @@ -898,7 +1043,7 @@ extend( QUnit.constructor.prototype, { // testStart: { name } testStart: registerLoggingCallback( "testStart" ), - // testDone: { name, failed, passed, total } + // testDone: { name, failed, passed, total, duration } testDone: registerLoggingCallback( "testDone" ), // moduleStart: { name } @@ -916,7 +1061,11 @@ QUnit.load = function() { runLoggingCallbacks( "begin", QUnit, {} ); // Initialize the config, saving the execution queue - var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, + var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, + urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter, + numModules = 0, + moduleNames = [], + moduleFilterHtml = "", urlConfigHtml = "", oldconfig = extend( {}, config ); @@ -937,8 +1086,33 @@ QUnit.load = function() { }; } config[ val.id ] = QUnit.urlParams[ val.id ]; - urlConfigHtml += ""; + urlConfigHtml += ""; } + for ( i in config.modules ) { + if ( config.modules.hasOwnProperty( i ) ) { + moduleNames.push(i); + } + } + numModules = moduleNames.length; + moduleNames.sort( function( a, b ) { + return a.localeCompare( b ); + }); + moduleFilterHtml += ""; // `userAgent` initialized at top of scope userAgent = id( "qunit-userAgent" ); @@ -990,18 +1164,42 @@ QUnit.load = function() { // `label` initialized at top of scope label = document.createElement( "label" ); label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); + label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); - urlConfigCheckboxes = document.createElement( 'span' ); - urlConfigCheckboxes.innerHTML = urlConfigHtml; - addEvent( urlConfigCheckboxes, "change", function( event ) { - var params = {}; - params[ event.target.name ] = event.target.checked ? true : undefined; + urlConfigCheckboxesContainer = document.createElement("span"); + urlConfigCheckboxesContainer.innerHTML = urlConfigHtml; + urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input"); + // For oldIE support: + // * 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 ); }); - toolbar.appendChild( urlConfigCheckboxes ); + toolbar.appendChild( urlConfigCheckboxesContainer ); + + if (numModules > 1) { + moduleFilter = document.createElement( "span" ); + moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); + moduleFilter.innerHTML = moduleFilterHtml; + addEvent( moduleFilter.lastChild, "change", function() { + var selectBox = moduleFilter.getElementsByTagName("select")[0], + selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); + + window.location = QUnit.url({ + module: ( selectedModule === "" ) ? undefined : selectedModule, + // Remove any existing filters + filter: undefined, + testNumber: undefined + }); + }); + toolbar.appendChild(moduleFilter); + } } // `main` initialized at top of scope @@ -1022,7 +1220,7 @@ addEvent( window, "load", QUnit.load ); onErrorFnPrev = window.onerror; // Cover uncaught exceptions -// Returning true will surpress the default browser handler, +// Returning true will suppress the default browser handler, // returning false will let it run. window.onerror = function ( error, filePath, linerNr ) { var ret = false; @@ -1031,7 +1229,7 @@ window.onerror = function ( error, filePath, linerNr ) { } // Treat return value as window.onerror itself does, - // Only do our handling if not surpressed. + // Only do our handling if not suppressed. if ( ret !== true ) { if ( QUnit.config.current ) { if ( QUnit.config.current.ignoreGlobalErrors ) { @@ -1039,9 +1237,9 @@ window.onerror = function ( error, filePath, linerNr ) { } QUnit.pushFailure( error, filePath + ":" + linerNr ); } else { - QUnit.test( "global failure", function() { + QUnit.test( "global failure", extend( function() { QUnit.pushFailure( error, filePath + ":" + linerNr ); - }); + }, { validTest: validTest } ) ); } return false; } @@ -1061,6 +1259,7 @@ function done() { total: config.moduleStats.all }); } + delete config.previousModule; var i, key, banner = id( "qunit-banner" ), @@ -1073,7 +1272,7 @@ function done() { " milliseconds.
", "", passed, - " tests of ", + " assertions of ", config.stats.all, " passed, ", config.stats.bad, @@ -1108,6 +1307,11 @@ function done() { } } + // scroll back to top to show results + if ( window.scrollTo ) { + window.scrollTo(0, 0); + } + runLoggingCallbacks( "done", QUnit, { failed: config.stats.bad, passed: passed, @@ -1123,6 +1327,12 @@ function validTest( test ) { module = config.module && config.module.toLowerCase(), fullName = (test.module + ": " + test.testName).toLowerCase(); + // Internally-generated tests are always valid + if ( test.callback && test.callback.validTest === validTest ) { + delete test.callback.validTest; + return true; + } + if ( config.testNumber ) { return test.testNumber === config.testNumber; } @@ -1155,7 +1365,7 @@ function validTest( test ) { function extractStacktrace( e, offset ) { offset = offset === undefined ? 3 : offset; - var stack, include, i, regex; + var stack, include, i; if ( e.stacktrace ) { // Opera @@ -1169,7 +1379,7 @@ function extractStacktrace( e, offset ) { if ( fileName ) { include = []; for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) != -1 ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { break; } include.push( stack[ i ] ); @@ -1198,17 +1408,27 @@ function sourceFromStacktrace( offset ) { } } -function escapeInnerText( s ) { +/** + * Escape text for attribute or text content. + */ +function escapeText( s ) { if ( !s ) { return ""; } s = s + ""; - return s.replace( /[\&<>]/g, function( s ) { + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { switch( s ) { - case "&": return "&"; - case "<": return "<"; - case ">": return ">"; - default: return s; + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; } }); } @@ -1232,7 +1452,7 @@ function process( last ) { if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { config.queue.shift()(); } else { - window.setTimeout( next, 13 ); + setTimeout( next, 13 ); break; } } @@ -1247,16 +1467,18 @@ function saveGlobal() { if ( config.noglobals ) { for ( var key in window ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { - continue; + 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 ); } - config.pollution.push( key ); } } } -function checkPollution( name ) { +function checkPollution() { var newGlobals, deletedGlobals, old = config.pollution; @@ -1293,28 +1515,68 @@ function diff( a, b ) { function extend( a, b ) { for ( var prop in b ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - - // Avoid "Member not found" error in IE8 caused by setting window.constructor - } else if ( prop !== "constructor" || a !== window ) { - a[ prop ] = b[ prop ]; + 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 { + a[ prop ] = b[ prop ]; + } + } } } return a; } +/** + * @param {HTMLElement} elem + * @param {string} type + * @param {Function} fn + */ function addEvent( elem, type, fn ) { + // Standards-based browsers if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, fn ); + // IE } 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 necessarily + elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); +} + function id( name ) { return !!( typeof document !== "undefined" && document && document.getElementById ) && document.getElementById( name ); @@ -1328,7 +1590,6 @@ function registerLoggingCallback( key ) { // Supports deprecated method of completely overwriting logging callbacks function runLoggingCallbacks( key, scope, args ) { - //debugger; var i, callbacks; if ( QUnit.hasOwnProperty( key ) ) { QUnit[ key ].call(scope, args ); @@ -1362,16 +1623,19 @@ QUnit.equiv = (function() { callers = [], // stack to avoiding loops from circular referencing parents = [], + parentsB = [], getProto = Object.getPrototypeOf || function ( obj ) { + /*jshint camelcase:false */ 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 annotaion VS 'new' annotation of a + // to catch short annotation VS 'new' annotation of a // declaration // e.g. var i = 1; // var j = new Number(1); @@ -1400,11 +1664,12 @@ QUnit.equiv = (function() { return QUnit.objectType( b ) === "regexp" && // the regex itself a.source === b.source && - // and its modifers + // and its modifiers a.global === b.global && // (gmi) ... a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; + a.multiline === b.multiline && + a.sticky === b.sticky; }, // - skip when the property is a method of an instance (OOP) @@ -1416,7 +1681,7 @@ QUnit.equiv = (function() { }, "array": function( b, a ) { - var i, j, len, loop; + var i, j, len, loop, aCircular, bCircular; // b could be an object literal here if ( QUnit.objectType( b ) !== "array" ) { @@ -1431,24 +1696,36 @@ QUnit.equiv = (function() { // track reference to avoid circular references parents.push( a ); + parentsB.push( b ); for ( i = 0; i < len; i++ ) { loop = false; for ( j = 0; j < parents.length; j++ ) { - if ( parents[j] === a[i] ) { - loop = true;// dont rewalk array + aCircular = parents[j] === a[i]; + bCircular = parentsB[j] === b[i]; + if ( aCircular || bCircular ) { + if ( a[i] === b[i] || aCircular && bCircular ) { + loop = true; + } else { + parents.pop(); + parentsB.pop(); + return false; + } } } if ( !loop && !innerEquiv(a[i], b[i]) ) { parents.pop(); + parentsB.pop(); return false; } } parents.pop(); + parentsB.pop(); return true; }, "object": function( b, a ) { - var i, j, loop, + /*jshint forin:false */ + var i, j, loop, aCircular, bCircular, // Default to true eq = true, aProperties = [], @@ -1467,28 +1744,36 @@ QUnit.equiv = (function() { // stack constructor before traversing properties callers.push( a.constructor ); + // track reference to avoid circular references parents.push( a ); + parentsB.push( b ); - for ( i in a ) { // be strict: don't ensures 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++ ) { - if ( parents[j] === a[i] ) { - // don't go down the same path twice - loop = true; + aCircular = parents[j] === a[i]; + bCircular = parentsB[j] === b[i]; + if ( aCircular || bCircular ) { + if ( a[i] === b[i] || aCircular && bCircular ) { + loop = true; + } else { + eq = false; + break; + } } } - aProperties.push(i); // collect a's properties - - if (!loop && !innerEquiv( a[i], b[i] ) ) { + aProperties.push(i); + if ( !loop && !innerEquiv(a[i], b[i]) ) { eq = false; break; } } - callers.pop(); // unstack, we are done parents.pop(); + parentsB.pop(); + callers.pop(); // unstack, we are done for ( i in b ) { bProperties.push( i ); // collect b's properties @@ -1518,7 +1803,7 @@ QUnit.equiv = (function() { } // apply transition with (1..n) arguments - }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); + }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); }; return innerEquiv; @@ -1536,7 +1821,7 @@ QUnit.equiv = (function() { */ QUnit.jsDump = (function() { function quote( str ) { - return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; + return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; } function literal( o ) { return o + ""; @@ -1565,26 +1850,25 @@ QUnit.jsDump = (function() { var reName = /^function (\w+)/, jsDump = { - parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance - stack = stack || [ ]; + // type is used mostly internally, you can fix a (custom)type in advance + parse: function( obj, type, stack ) { + stack = stack || [{}]; var inStack, res, parser = this.parsers[ type || this.typeOf(obj) ]; type = typeof parser; inStack = inArray( obj, stack ); - if ( inStack != -1 ) { + if ( inStack !== -1 ) { return "recursion(" + (inStack - stack.length) + ")"; } - //else - if ( type == "function" ) { + if ( type === "function" ) { stack.push( obj ); res = parser.call( this, obj, stack ); stack.pop(); return res; } - // else - return ( type == "string" ) ? parser : this.parsers.error; + return ( type === "string" ) ? parser : this.parsers.error; }, typeOf: function( obj ) { var type; @@ -1611,6 +1895,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" ) ) ) ) { type = "array"; + } else if ( obj.constructor === Error.prototype.constructor ) { + type = "error"; } else { type = typeof obj; } @@ -1619,7 +1905,8 @@ QUnit.jsDump = (function() { separator: function() { return this.multiline ? this.HTML ? "
" : "\n" : this.HTML ? " " : " "; }, - 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 ) { return ""; } @@ -1627,13 +1914,13 @@ QUnit.jsDump = (function() { if ( this.HTML ) { chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); } - return new Array( this._depth_ + (extra||0) ).join(chr); + return new Array( this.depth + ( extra || 0 ) ).join(chr); }, up: function( a ) { - this._depth_ += a || 1; + this.depth += a || 1; }, down: function( a ) { - this._depth_ -= a || 1; + this.depth -= a || 1; }, setParser: function( name, parser ) { this.parsers[name] = parser; @@ -1643,18 +1930,21 @@ QUnit.jsDump = (function() { literal: literal, join: join, // - _depth_: 1, + depth: 1, // This is the list of parsers, to modify them, use jsDump.setParser parsers: { window: "[Window]", document: "[Document]", - error: "[ERROR]", //when no parser is found, shouldn"t happen + error: function(error) { + return "Error(\"" + error.message + "\")"; + }, unknown: "[Unknown]", "null": "null", "undefined": "undefined", "function": function( fn ) { 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 ) { ret += " " + name; @@ -1668,15 +1958,12 @@ QUnit.jsDump = (function() { nodelist: array, "arguments": array, object: function( map, stack ) { + /*jshint forin:false */ var ret = [ ], keys, key, val, i; QUnit.jsDump.up(); - if ( Object.keys ) { - keys = Object.keys( map ); - } else { - keys = []; - for ( key in map ) { - keys.push( key ); - } + keys = []; + for ( key in map ) { + keys.push( key ); } keys.sort(); for ( i = 0; i < keys.length; i++ ) { @@ -1688,21 +1975,34 @@ QUnit.jsDump = (function() { return join( "{", ret, "}" ); }, node: function( node ) { - var a, val, + var len, i, val, open = QUnit.jsDump.HTML ? "<" : "<", close = QUnit.jsDump.HTML ? ">" : ">", tag = node.nodeName.toLowerCase(), - ret = open + tag; + ret = open + tag, + attrs = node.attributes; - for ( a in QUnit.jsDump.DOMAttrs ) { - val = node[ QUnit.jsDump.DOMAttrs[a] ]; - if ( val ) { - ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); + if ( attrs ) { + for ( i = 0, len = attrs.length; i < len; i++ ) { + val = attrs[i].nodeValue; + // IE6 includes all attributes in .attributes, even ones not explicitly set. + // Those have values like undefined, null, 0, false, "" or "inherit". + if ( val && val !== "inherit" ) { + ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); + } } } - 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, l = fn.length; @@ -1712,54 +2012,34 @@ QUnit.jsDump = (function() { args = new Array(l); while ( l-- ) { - args[l] = String.fromCharCode(97+l);//97 is 'a' + // 97 is 'a' + args[l] = String.fromCharCode(97+l); } return " " + args.join( ", " ) + " "; }, - key: quote, //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 - attribute: quote, //node calls it internally, it's an html attribute value + // object calls it internally, the key part of an item in a map + key: quote, + // function calls it internally, it's the content of the function + functionCode: "[code]", + // node calls it internally, it's an html attribute value + attribute: quote, string: quote, date: quote, - regexp: literal, //regex + regexp: literal, number: literal, "boolean": literal }, - DOMAttrs: { - //attributes to dump from nodes, name=>realName - id: "id", - name: "name", - "class": "className" - }, - 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. + // if true, entities are escaped ( <, >, \t, space and \n ) + HTML: false, + // indentation unit + indentChar: " ", + // if true, items in a collection, are separated by a \n, else just a space. + multiline: true }; 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 function inArray( elem, array ) { if ( array.indexOf ) { @@ -1790,13 +2070,14 @@ function inArray( elem, array ) { * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" */ QUnit.diff = (function() { + /*jshint eqeqeq:false, eqnull:true */ function diff( o, n ) { var i, ns = {}, os = {}; for ( i = 0; i < n.length; i++ ) { - if ( ns[ n[i] ] == null ) { + if ( !hasOwn.call( ns, n[i] ) ) { ns[ n[i] ] = { rows: [], o: null @@ -1806,7 +2087,7 @@ QUnit.diff = (function() { } for ( i = 0; i < o.length; i++ ) { - if ( os[ o[i] ] == null ) { + if ( !hasOwn.call( os, o[i] ) ) { os[ o[i] ] = { rows: [], n: null @@ -1816,18 +2097,17 @@ QUnit.diff = (function() { } for ( i in ns ) { - if ( !hasOwn.call( ns, i ) ) { - continue; - } - if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { - n[ ns[i].rows[0] ] = { - text: n[ ns[i].rows[0] ], - row: os[i].rows[0] - }; - o[ os[i].rows[0] ] = { - text: o[ os[i].rows[0] ], - row: ns[i].rows[0] - }; + if ( hasOwn.call( ns, i ) ) { + if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { + n[ ns[i].rows[0] ] = { + text: n[ ns[i].rows[0] ], + row: os[i].rows[0] + }; + o[ os[i].rows[0] ] = { + text: o[ os[i].rows[0] ], + row: ns[i].rows[0] + }; + } } } @@ -1923,9 +2203,9 @@ QUnit.diff = (function() { }; }()); -// for CommonJS enviroments, export everything +// for CommonJS environments, export everything if ( typeof exports !== "undefined" ) { - extend(exports, QUnit); + extend( exports, QUnit.constructor.prototype ); } // get at whatever the global object is, like window in browsers diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index af434c4c0f4..880da9f1bc1 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -195,8 +195,7 @@ openerp.testing = {}; 0, module_index + 1 || undefined); // Serialize options for this precise test case - // WARNING: typo is from jquery, do not fix! - var env = QUnit.config.currentModuleTestEnviroment; + var env = QUnit.config.currentModuleTestEnvironment; // section setup // case setup // test