From ecd8ddea33dc40ae2a57e4340be03faf2ba2f99b Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Tue, 1 Aug 2017 09:52:45 -0700 Subject: [PATCH] Tests: Add support for running unit tests via grunt with karma - Update QUnit to 1.23.1 - Remove unused dl#dl from test/index.html - Remove unused map#imgmap from test/index.html - Ensure all urls to data use baseURI - Add the 'grunt karma:main' task - customContextFile & customDebugFile - Add 'npm run jenkins' script Close gh-3744 Fixes gh-1999 --- .travis.yml | 2 + Gruntfile.js | 84 + README.md | 10 +- build/tasks/qunit_fixture.js | 22 + external/qunit/qunit.css | 12 +- external/qunit/qunit.js | 4144 ++++++++++---------- package.json | 16 +- test/data/ajax/content-type.php | 5 - test/data/ajax/evalScript.php | 1 - test/data/ajax/method.php | 1 - test/data/ajax/unreleasedXHR.html | 2 +- test/data/atom+xml.php | 4 - test/data/core/dont_return.php | 3 - test/data/core/dynamic_ready.html | 2 +- test/data/csp.include.html | 14 + test/data/echoData.php | 1 - test/data/echoQuery.php | 1 - test/data/errorWithJSON.php | 6 - test/data/errorWithText.php | 5 - test/data/etag.php | 24 - test/data/event/interactiveReady.html | 2 +- test/data/event/syncReady.html | 2 +- test/data/headers.php | 23 - test/data/if_modified_since.php | 20 - test/data/json.php | 13 - test/data/jsonp.php | 14 - test/data/longLoadScript.php | 4 - test/data/mock.php | 244 ++ test/data/name.php | 24 - test/data/nocontent.php | 5 - test/data/params_html.php | 12 - test/data/qunit-fixture.html | 237 ++ test/data/qunit-fixture.js | 4 + test/data/script.php | 11 - test/data/statusText.php | 5 - test/data/support/csp-clean.php | 3 - test/data/support/csp-log.php | 3 - test/data/support/csp.php | 19 - test/data/{test.html => test.include.html} | 4 +- test/data/test.php | 7 - test/data/testbar.php | 3 - test/data/testinit.js | 37 +- test/data/{text.php => text.txt} | 0 test/data/with_fries_over_jsonp.php | 7 - test/index.html | 247 +- test/karma.context.html | 45 + test/karma.debug.html | 47 + test/middleware-mockserver.js | 284 ++ test/unit/ajax.js | 350 +- test/unit/attributes.js | 4 +- test/unit/basic.js | 6 +- test/unit/core.js | 6 +- test/unit/css.js | 5 +- test/unit/data.js | 2 +- test/unit/effects.js | 2 +- test/unit/event.js | 5 +- test/unit/manipulation.js | 4 +- test/unit/offset.js | 6 +- test/unit/support.js | 6 +- test/unit/traversing.js | 10 +- 60 files changed, 3399 insertions(+), 2692 deletions(-) create mode 100644 build/tasks/qunit_fixture.js delete mode 100644 test/data/ajax/content-type.php delete mode 100644 test/data/ajax/evalScript.php delete mode 100644 test/data/ajax/method.php delete mode 100644 test/data/atom+xml.php delete mode 100644 test/data/core/dont_return.php create mode 100644 test/data/csp.include.html delete mode 100644 test/data/echoData.php delete mode 100644 test/data/echoQuery.php delete mode 100644 test/data/errorWithJSON.php delete mode 100644 test/data/errorWithText.php delete mode 100644 test/data/etag.php delete mode 100644 test/data/headers.php delete mode 100644 test/data/if_modified_since.php delete mode 100644 test/data/json.php delete mode 100644 test/data/jsonp.php delete mode 100644 test/data/longLoadScript.php create mode 100644 test/data/mock.php delete mode 100644 test/data/name.php delete mode 100644 test/data/nocontent.php delete mode 100644 test/data/params_html.php create mode 100644 test/data/qunit-fixture.html create mode 100644 test/data/qunit-fixture.js delete mode 100644 test/data/script.php delete mode 100644 test/data/statusText.php delete mode 100644 test/data/support/csp-clean.php delete mode 100644 test/data/support/csp-log.php delete mode 100644 test/data/support/csp.php rename test/data/{test.html => test.include.html} (56%) delete mode 100644 test/data/test.php delete mode 100644 test/data/testbar.php rename test/data/{text.php => text.txt} (100%) delete mode 100644 test/data/with_fries_over_jsonp.php create mode 100644 test/karma.context.html create mode 100644 test/karma.debug.html create mode 100644 test/middleware-mockserver.js diff --git a/.travis.yml b/.travis.yml index aaae4185b..317c3a854 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,5 @@ node_js: - "6" - "8" - "9" +addons: + chrome: stable diff --git a/Gruntfile.js b/Gruntfile.js index edcdb4f15..19e94fad7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -147,6 +147,88 @@ module.exports = function( grunt ) { "tween" ] }, + karma: { + options: { + customContextFile: "test/karma.context.html", + customDebugFile: "test/karma.debug.html", + frameworks: [ "qunit" ], + middleware: [ "mockserver" ], + plugins: [ + "karma-*", + { + "middleware:mockserver": [ + "factory", + require( "./test/middleware-mockserver.js" ) + ] + } + ], + files: [ + "test/data/jquery-1.9.1.js", + "external/qunit-assert-step/qunit-assert-step.js", + "external/sinon/sinon.js", + "external/npo/npo.js", + "external/requirejs/require.js", + "test/data/testinit.js", + + "dist/jquery.min.js", + + // Replacement for testinit.js#loadTests() + "test/data/testrunner.js", + "test/unit/basic.js", + "test/unit/core.js", + "test/unit/callbacks.js", + "test/unit/deferred.js", + "test/unit/deprecated.js", + "test/unit/support.js", + "test/unit/data.js", + "test/unit/queue.js", + "test/unit/attributes.js", + "test/unit/event.js", + "test/unit/selector.js", + "test/unit/traversing.js", + "test/unit/manipulation.js", + "test/unit/wrap.js", + "test/unit/css.js", + "test/unit/serialize.js", + "test/unit/ajax.js", + "test/unit/effects.js", + "test/unit/offset.js", + "test/unit/dimensions.js", + "test/unit/animation.js", + "test/unit/tween.js", + "test/unit/ready.js", + + { pattern: "dist/jquery.js", included: false, served: true }, + { pattern: "dist/*.map", included: false, served: true }, + { pattern: "external/qunit/qunit.css", included: false, served: true }, + { + pattern: "test/**/*.@(js|css|jpg|html|xml)", + included: false, + served: true + } + ], + reporters: [ "dots" ], + autoWatch: false, + concurrency: 3, + captureTimeout: 20 * 1000, + + // To debug tests with Karma: + // - Run 'grunt karma:chrome' or 'grunt karma:firefox' + // (any karma subtask that has singleRun=false) + // - Press "Debug" in the opened browser window. + singleRun: false + }, + main: { + browsers: [ "ChromeHeadless" ], + singleRun: true + }, + chrome: { + browsers: [ "Chrome" ] + }, + firefox: { + browsers: [ "Firefox" ] + } + }, watch: { files: [ "<%= eslint.dev.src %>" ], tasks: [ "dev" ] @@ -222,6 +304,7 @@ module.exports = function( grunt ) { "newer:uglify", "remove_map_comment", "dist:*", + "qunit_fixture", "compare_size" ] ); @@ -231,6 +314,7 @@ module.exports = function( grunt ) { "uglify", "remove_map_comment", "dist:*", + "qunit_fixture", "eslint:dist", "test:fast", "compare_size" diff --git a/README.md b/README.md index 9e5b130c4..71ed006c1 100644 --- a/README.md +++ b/README.md @@ -323,20 +323,20 @@ fireNative( jQuery("#elem")[0], "click" ); ### Add random number to url to stop caching ### ```js -url( "some/url.php" ); +url( "some/url" ); ``` Example: ```js -url("data/test.html"); +url("index.html"); -=> "data/test.html?10538358428943" +=> "data/index.html?10538358428943" -url("data/test.php?foo=bar"); +url("mock.php?foo=bar"); -=> "data/test.php?foo=bar&10538358345554" +=> "data/mock.php?foo=bar&10538358345554" ``` diff --git a/build/tasks/qunit_fixture.js b/build/tasks/qunit_fixture.js new file mode 100644 index 000000000..ebf0b220c --- /dev/null +++ b/build/tasks/qunit_fixture.js @@ -0,0 +1,22 @@ +var fs = require( "fs" ); + +module.exports = function( grunt ) { + grunt.registerTask( "qunit_fixture", function() { + var dest = "./test/data/qunit-fixture.js"; + fs.writeFileSync( + dest, + "// Generated by build/tasks/qunit_fixture.js\n" + + "QUnit.config.fixture = " + + JSON.stringify( + fs.readFileSync( + "./test/data/qunit-fixture.html", + "utf8" + ).toString() + ) + + ";\n" + + "// Compat with QUnit 1.x:\n" + + "document.getElementById( \"qunit-fixture\" ).innerHTML = QUnit.config.fixture;\n" + ); + grunt.log.ok( "Updated " + dest + "." ); + } ); +}; diff --git a/external/qunit/qunit.css b/external/qunit/qunit.css index a59e2824c..ae68fc412 100644 --- a/external/qunit/qunit.css +++ b/external/qunit/qunit.css @@ -1,12 +1,12 @@ /*! - * QUnit 1.20.0 - * http://qunitjs.com/ + * QUnit 1.23.1 + * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://jquery.org/license * - * Date: 2015-10-27T17:53Z + * Date: 2016-04-12T17:29Z */ /** Font Family and Sizes */ @@ -120,6 +120,10 @@ display: list-item; } +#qunit-tests.hidepass { + position: relative; +} + #qunit-tests.hidepass li.running, #qunit-tests.hidepass li.pass { visibility: hidden; diff --git a/external/qunit/qunit.js b/external/qunit/qunit.js index 904943f08..5df0822ea 100644 --- a/external/qunit/qunit.js +++ b/external/qunit/qunit.js @@ -1,15 +1,15 @@ /*! - * QUnit 1.20.0 - * http://qunitjs.com/ + * QUnit 1.23.1 + * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://jquery.org/license * - * Date: 2015-10-27T17:53Z + * Date: 2016-04-12T17:29Z */ -(function( global ) { +( function( global ) { var QUnit = {}; @@ -27,7 +27,7 @@ var window = global.window; var defined = { document: window && window.document !== undefined, setTimeout: setTimeout !== undefined, - sessionStorage: (function() { + sessionStorage: ( function() { var x = "qunit-test-string"; try { sessionStorage.setItem( x, x ); @@ -46,7 +46,7 @@ var runStarted = false; var toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty; -// returns a new Array with the elements that are in a but not in b +// Returns a new Array with the elements that are in a but not in b function diff( a, b ) { var i, j, result = a.slice(); @@ -63,7 +63,7 @@ function diff( a, b ) { return result; } -// from jquery.js +// From jquery.js function inArray( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); @@ -157,31 +157,6 @@ function is( type, obj ) { return QUnit.objectType( obj ) === type; } -var getUrlParams = function() { - var i, current; - var urlParams = {}; - var location = window.location; - var params = location.search.slice( 1 ).split( "&" ); - var length = params.length; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - if ( urlParams[ current[ 0 ] ] ) { - urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); - } else { - urlParams[ current[ 0 ] ] = current[ 1 ]; - } - } - } - - return urlParams; -}; - // Doesn't support IE6 to IE9, it will return undefined on these browsers // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack function extractStacktrace( e, offset ) { @@ -211,12 +186,12 @@ function extractStacktrace( e, offset ) { // Support: Safari <=6 only } else if ( e.sourceURL ) { - // exclude useless self-reference for generated Error objects + // Exclude useless self-reference for generated Error objects if ( /qunit.js$/.test( e.sourceURL ) ) { return; } - // for actual exceptions, this is useful + // For actual exceptions, this is useful return e.sourceURL + ":" + e.line; } } @@ -243,53 +218,35 @@ function sourceFromStacktrace( offset ) { * `config` initialized at top of scope */ var config = { + // The queue of tests to run queue: [], - // block until document ready + // Block until document ready blocking: true, - // by default, run previously failed tests first + // By default, run previously failed tests first // very useful in combination with "Hide passed tests" checked reorder: true, - // by default, modify document.title when suite is done + // By default, modify document.title when suite is done altertitle: true, // HTML Reporter: collapse every test except the first failing test // If false, all failing tests will be expanded collapse: true, - // by default, scroll to top of the page when suite is done + // By default, scroll to top of the page when suite is done scrolltop: true, - // depth up-to which object will be dumped + // Depth up-to which object will be dumped maxDepth: 5, - // when enabled, all tests must call expect() + // When enabled, all tests must call expect() requireExpects: false, - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "hidepassed", - label: "Hide passed tests", - tooltip: "Only show tests and assertions that fail. Stored as query-strings." - }, - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the " + - "global object (`window` in Browsers). Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + - "exceptions in IE reasonable. Stored as query-strings." - } - ], + // Placeholder for user-configurable form-exposed URL parameters + urlConfig: [], // Set of all modules. modules: [], @@ -306,27 +263,9 @@ var config = { callbacks: {} }; -var urlParams = defined.document ? getUrlParams() : {}; - // Push a loose unnamed module to the modules collection config.modules.push( config.currentModule ); -if ( urlParams.filter === true ) { - delete urlParams.filter; -} - -// String search anywhere in moduleName+testName -config.filter = urlParams.filter; - -config.testId = []; -if ( urlParams.testId ) { - // Ensure that urlParams.testId is an array - urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," ); - for (var i = 0; i < urlParams.testId.length; i++ ) { - config.testId.push( urlParams.testId[ i ] ); - } -} - var loggingCallbacks = {}; // Register logging callbacks @@ -396,7 +335,7 @@ function verifyLoggingCallbacks() { global.console.warn( "QUnit." + loggingCallback + " was replaced with a new value.\n" + "Please, check out the documentation on how to apply logging callbacks.\n" + - "Reference: http://api.qunitjs.com/category/callbacks/" + "Reference: https://api.qunitjs.com/category/callbacks/" ); } } @@ -430,7 +369,7 @@ function verifyLoggingCallbacks() { } QUnit.pushFailure( error, filePath + ":" + linerNr ); } else { - QUnit.test( "global failure", extend(function() { + QUnit.test( "global failure", extend( function() { QUnit.pushFailure( error, filePath + ":" + linerNr ); }, { validTest: true } ) ); } @@ -439,25 +378,23 @@ function verifyLoggingCallbacks() { return ret; }; -} )(); - -QUnit.urlParams = urlParams; +}() ); // Figure out if we're running the tests from a server or not QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" ); // Expose the current QUnit version -QUnit.version = "1.20.0"; +QUnit.version = "1.23.1"; extend( QUnit, { - // call on start of module test to prepend name to all tests + // Call on start of module test to prepend name to all tests module: function( name, testEnvironment, executeNow ) { var module, moduleFns; var currentModule = config.currentModule; if ( arguments.length === 2 ) { - if ( testEnvironment instanceof Function ) { + if ( objectType( testEnvironment ) === "function" ) { executeNow = testEnvironment; testEnvironment = undefined; } @@ -481,7 +418,7 @@ extend( QUnit, { afterEach: setHook( module, "afterEach" ) }; - if ( executeNow instanceof Function ) { + if ( objectType( executeNow ) === "function" ) { config.moduleStack.push( module ); setCurrentModule( module ); executeNow.call( module.testEnvironment, moduleFns ); @@ -499,7 +436,8 @@ extend( QUnit, { var module = { name: moduleName, parentModule: parentModule, - tests: [] + tests: [], + moduleId: generateHash( moduleName ) }; var env = {}; @@ -572,7 +510,7 @@ extend( QUnit, { return; } - // throw an Error if start is called more often than stop + // Throw an Error if start is called more often than stop if ( config.current.semaphore < 0 ) { config.current.semaphore = 0; @@ -633,7 +571,7 @@ extend( QUnit, { offset = ( offset || 0 ) + 2; return sourceFromStacktrace( offset ); } -}); +} ); registerLoggingCallbacks( QUnit ); @@ -656,17 +594,17 @@ function begin() { // Avoid unnecessary information by not logging modules' test environments for ( i = 0, l = config.modules.length; i < l; i++ ) { - modulesLog.push({ + modulesLog.push( { name: config.modules[ i ].name, tests: config.modules[ i ].tests - }); + } ); } // The test run is officially beginning now runLoggingCallbacks( "begin", { totalTests: Test.count, modules: modulesLog - }); + } ); } config.blocking = false; @@ -705,7 +643,7 @@ function pauseProcessing() { if ( config.testTimeout && defined.setTimeout ) { clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { + config.timeout = setTimeout( function() { if ( config.current ) { config.current.semaphore = 0; QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); @@ -722,7 +660,7 @@ function resumeProcessing() { // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) if ( defined.setTimeout ) { - setTimeout(function() { + setTimeout( function() { if ( config.current && config.current.semaphore > 0 ) { return; } @@ -751,7 +689,7 @@ function done() { passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all, runtime: now() - config.moduleStats.started - }); + } ); } delete config.previousModule; @@ -763,7 +701,7 @@ function done() { passed: passed, total: config.stats.all, runtime: runtime - }); + } ); } function setHook( module, hookName ) { @@ -777,6 +715,8 @@ function setHook( module, hookName ) { } var focused = false; +var priorityCount = 0; +var unitSampler; function Test( settings ) { var i, l; @@ -799,10 +739,10 @@ function Test( settings ) { this.testId = generateHash( this.module.name, this.testName ); - this.module.tests.push({ + this.module.tests.push( { name: this.testName, testId: this.testId - }); + } ); if ( settings.skip ) { @@ -838,14 +778,14 @@ Test.prototype = { passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all, runtime: now() - config.moduleStats.started - }); + } ); } config.previousModule = this.module; config.moduleStats = { all: 0, bad: 0, started: now() }; runLoggingCallbacks( "moduleStart", { name: this.module.name, tests: this.module.tests - }); + } ); } config.current = this; @@ -861,7 +801,7 @@ Test.prototype = { name: this.testName, module: this.module.name, testId: this.testId - }); + } ); if ( !config.pollution ) { saveGlobal(); @@ -890,7 +830,7 @@ Test.prototype = { this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - // else next test will carry the responsibility + // Else next test will carry the responsibility saveGlobal(); // Restart the tests if they're blocking @@ -999,7 +939,7 @@ Test.prototype = { // DEPRECATED: this property will be removed in 2.0.0, use runtime instead duration: this.runtime - }); + } ); // QUnit.reset() is deprecated and will be replaced for a new // fixture reset function on QUnit 2.0/2.1. @@ -1019,8 +959,8 @@ Test.prototype = { function run() { - // each of these can by async - synchronize([ + // Each of these can by async + synchronize( [ function() { test.before(); }, @@ -1038,31 +978,33 @@ Test.prototype = { function() { test.finish(); } - ]); + ] ); } // Prioritize previously failed tests, detected from sessionStorage priority = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); - return synchronize( run, priority ); + return synchronize( run, priority, config.seed ); }, - push: function( result, actual, expected, message, negative ) { + pushResult: function( resultInfo ) { + + // Destructure of resultInfo = { result, actual, expected, message, negative } var source, details = { module: this.module.name, name: this.testName, - result: result, - message: message, - actual: actual, - expected: expected, + result: resultInfo.result, + message: resultInfo.message, + actual: resultInfo.actual, + expected: resultInfo.expected, testId: this.testId, - negative: negative || false, + negative: resultInfo.negative || false, runtime: now() - this.started }; - if ( !result ) { + if ( !resultInfo.result ) { source = sourceFromStacktrace(); if ( source ) { @@ -1072,10 +1014,10 @@ Test.prototype = { runLoggingCallbacks( "log", details ); - this.assertions.push({ - result: !!result, - message: message - }); + this.assertions.push( { + result: !!resultInfo.result, + message: resultInfo.message + } ); }, pushFailure: function( message, source, actual ) { @@ -1100,10 +1042,10 @@ Test.prototype = { runLoggingCallbacks( "log", details ); - this.assertions.push({ + this.assertions.push( { result: false, message: message - }); + } ); }, resolvePromise: function( promise, phase ) { @@ -1122,7 +1064,7 @@ Test.prototype = { " " + test.testName + ": " + ( error.message || error ); test.pushFailure( message, extractStacktrace( error, 0 ) ); - // else next test will carry the responsibility + // Else next test will carry the responsibility saveGlobal(); // Unblock @@ -1134,32 +1076,45 @@ Test.prototype = { }, valid: function() { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), - fullName = ( this.module.name + ": " + this.testName ).toLowerCase(); + var filter = config.filter, + regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ), + module = config.module && config.module.toLowerCase(), + fullName = ( this.module.name + ": " + this.testName ); - function testInModuleChain( testModule ) { + function moduleChainNameMatch( testModule ) { var testModuleName = testModule.name ? testModule.name.toLowerCase() : null; if ( testModuleName === module ) { return true; } else if ( testModule.parentModule ) { - return testInModuleChain( testModule.parentModule ); + return moduleChainNameMatch( testModule.parentModule ); } else { return false; } } + function moduleChainIdMatch( testModule ) { + return inArray( testModule.moduleId, config.moduleId ) > -1 || + testModule.parentModule && moduleChainIdMatch( testModule.parentModule ); + } + // Internally-generated tests are always valid if ( this.callback && this.callback.validTest ) { return true; } - if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) { + if ( config.moduleId && config.moduleId.length > 0 && + !moduleChainIdMatch( this.module ) ) { + + return false; + } + + if ( config.testId && config.testId.length > 0 && + inArray( this.testId, config.testId ) < 0 ) { + return false; } - if ( module && !testInModuleChain( this.module ) ) { + if ( module && !moduleChainNameMatch( this.module ) ) { return false; } @@ -1167,7 +1122,23 @@ Test.prototype = { return true; } - include = filter.charAt( 0 ) !== "!"; + return regexFilter ? + this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) : + this.stringFilter( filter, fullName ); + }, + + regexFilter: function( exclude, pattern, flags, fullName ) { + var regex = new RegExp( pattern, flags ); + var match = regex.test( fullName ); + + return match !== exclude; + }, + + stringFilter: function( filter, fullName ) { + filter = filter.toLowerCase(); + fullName = fullName.toLowerCase(); + + var include = filter.charAt( 0 ) !== "!"; if ( !include ) { filter = filter.slice( 1 ); } @@ -1240,8 +1211,9 @@ function generateHash( module, testName ) { return hex.slice( -8 ); } -function synchronize( callback, priority ) { - var last = !priority; +function synchronize( callback, priority, seed ) { + var last = !priority, + index; if ( QUnit.objectType( callback ) === "array" ) { while ( callback.length ) { @@ -1251,7 +1223,15 @@ function synchronize( callback, priority ) { } if ( priority ) { - priorityFill( callback ); + config.queue.splice( priorityCount++, 0, callback ); + } else if ( seed ) { + if ( !unitSampler ) { + unitSampler = unitSamplerGenerator( seed ); + } + + // Insert into a random position after all priority items + index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) ); + config.queue.splice( priorityCount + index, 0, callback ); } else { config.queue.push( callback ); } @@ -1261,21 +1241,24 @@ function synchronize( callback, priority ) { } } -// Place previously failed tests on a queue priority line, respecting the order they get assigned. -function priorityFill( callback ) { - var queue, prioritizedQueue; +function unitSamplerGenerator( seed ) { - queue = config.queue.slice( priorityFill.pos ); - prioritizedQueue = config.queue.slice( 0, -config.queue.length + priorityFill.pos ); + // 32-bit xorshift, requires only a nonzero seed + // http://excamera.com/sphinx/article-xorshift.html + var sample = parseInt( generateHash( seed ), 16 ) || -1; + return function() { + sample ^= sample << 13; + sample ^= sample >>> 17; + sample ^= sample << 5; - queue.unshift( callback ); - queue.unshift.apply( queue, prioritizedQueue ); - - config.queue = queue; + // ECMAScript has no unsigned number type + if ( sample < 0 ) { + sample += 0x100000000; + } - priorityFill.pos += 1; + return sample / 0x100000000; + }; } -priorityFill.pos = 0; function saveGlobal() { config.pollution = []; @@ -1284,7 +1267,7 @@ function saveGlobal() { for ( var key in global ) { if ( hasOwn.call( global, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them + // In Opera sometimes DOM element ids show up here, ignore them if ( /^qunit-test-output/.test( key ) ) { continue; } @@ -1333,12 +1316,12 @@ function test( testName, expected, callback, async ) { expected = null; } - newTest = new Test({ + newTest = new Test( { testName: testName, expected: expected, async: async, callback: callback - }); + } ); newTest.queue(); } @@ -1347,10 +1330,10 @@ function test( testName, expected, callback, async ) { function skip( testName ) { if ( focused ) { return; } - var test = new Test({ + var test = new Test( { testName: testName, skip: true - }); + } ); test.queue(); } @@ -1369,12 +1352,12 @@ function only( testName, expected, callback, async ) { expected = null; } - newTest = new Test({ + newTest = new Test( { testName: testName, expected: expected, async: async, callback: callback - }); + } ); newTest.queue(); } @@ -1430,7 +1413,21 @@ QUnit.assert = Assert.prototype = { }, // Exports test.push() to the user API - push: function( /* result, actual, expected, message, negative */ ) { + // Alias of pushResult. + push: function( result, actual, expected, message, negative ) { + var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert; + return currentAssert.pushResult( { + result: result, + actual: actual, + expected: expected, + message: message, + negative: negative + } ); + }, + + pushResult: function( resultInfo ) { + + // Destructure of resultInfo = { result, actual, expected, message, negative } var assert = this, currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; @@ -1453,57 +1450,112 @@ QUnit.assert = Assert.prototype = { if ( !( assert instanceof Assert ) ) { assert = currentTest.assert; } - return assert.test.push.apply( assert.test, arguments ); + + return assert.test.pushResult( resultInfo ); }, ok: function( result, message ) { message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + QUnit.dump.parse( result ) ); - this.push( !!result, result, true, message ); + this.pushResult( { + result: !!result, + actual: result, + expected: true, + message: message + } ); }, notOk: function( result, message ) { message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " + QUnit.dump.parse( result ) ); - this.push( !result, result, false, message, true ); + this.pushResult( { + result: !result, + actual: result, + expected: false, + message: message + } ); }, equal: function( actual, expected, message ) { /*jshint eqeqeq:false */ - this.push( expected == actual, actual, expected, message ); + this.pushResult( { + result: expected == actual, + actual: actual, + expected: expected, + message: message + } ); }, notEqual: function( actual, expected, message ) { /*jshint eqeqeq:false */ - this.push( expected != actual, actual, expected, message, true ); + this.pushResult( { + result: expected != actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, propEqual: function( actual, expected, message ) { actual = objectValues( actual ); expected = objectValues( expected ); - this.push( QUnit.equiv( actual, expected ), actual, expected, message ); + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); }, notPropEqual: function( actual, expected, message ) { actual = objectValues( actual ); expected = objectValues( expected ); - this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, deepEqual: function( actual, expected, message ) { - this.push( QUnit.equiv( actual, expected ), actual, expected, message ); + this.pushResult( { + result: QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message + } ); }, notDeepEqual: function( actual, expected, message ) { - this.push( !QUnit.equiv( actual, expected ), actual, expected, message, true ); + this.pushResult( { + result: !QUnit.equiv( actual, expected ), + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, strictEqual: function( actual, expected, message ) { - this.push( expected === actual, actual, expected, message ); + this.pushResult( { + result: expected === actual, + actual: actual, + expected: expected, + message: message + } ); }, notStrictEqual: function( actual, expected, message ) { - this.push( expected !== actual, actual, expected, message, true ); + this.pushResult( { + result: expected !== actual, + actual: actual, + expected: expected, + message: message, + negative: true + } ); }, "throws": function( block, expected, message ) { @@ -1521,7 +1573,7 @@ QUnit.assert = Assert.prototype = { currentTest.ignoreGlobalErrors = true; try { block.call( currentTest.testEnvironment ); - } catch (e) { + } catch ( e ) { actual = e; } currentTest.ignoreGlobalErrors = false; @@ -1529,46 +1581,51 @@ QUnit.assert = Assert.prototype = { if ( actual ) { expectedType = QUnit.objectType( expected ); - // we don't want to validate thrown error + // We don't want to validate thrown error if ( !expected ) { ok = true; expectedOutput = null; - // expected is a regexp + // Expected is a regexp } else if ( expectedType === "regexp" ) { ok = expected.test( errorString( actual ) ); - // expected is a string + // Expected is a string } else if ( expectedType === "string" ) { ok = expected === errorString( actual ); - // expected is a constructor, maybe an Error constructor + // Expected is a constructor, maybe an Error constructor } else if ( expectedType === "function" && actual instanceof expected ) { ok = true; - // expected is an Error object + // Expected is an Error object } else if ( expectedType === "object" ) { ok = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message; - // expected is a validation function which returns true if validation passed + // Expected is a validation function which returns true if validation passed } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { expectedOutput = null; ok = true; } } - currentTest.assert.push( ok, actual, expectedOutput, message ); + currentTest.assert.pushResult( { + result: ok, + actual: actual, + expected: expectedOutput, + message: message + } ); } }; // Provide an alternative to assert.throws(), for environments that consider throws a reserved word // Known to us are: Closure Compiler, Narwhal -(function() { +( function() { /*jshint sub:true */ - Assert.prototype.raises = Assert.prototype[ "throws" ]; -}()); + Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation +}() ); function errorString( error ) { var name, message, @@ -1592,7 +1649,7 @@ function errorString( error ) { // Test for equality any JavaScript type. // Author: Philippe Rathé -QUnit.equiv = (function() { +QUnit.equiv = ( function() { // Stack to decide between skip/abort functions var callers = []; @@ -1601,26 +1658,28 @@ QUnit.equiv = (function() { var parents = []; var parentsB = []; - function useStrictEquality( b, a ) { + var getProto = Object.getPrototypeOf || function( obj ) { - /*jshint eqeqeq:false */ - if ( b instanceof a.constructor || a instanceof b.constructor ) { + /*jshint proto: true */ + return obj.__proto__; + }; - // 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; + function useStrictEquality( b, a ) { + + // To catch short annotation VS 'new' annotation of a declaration. e.g.: + // `var i = 1;` + // `var j = new Number(1);` + if ( typeof a === "object" ) { + a = a.valueOf(); } + if ( typeof b === "object" ) { + b = b.valueOf(); + } + + return a === b; } function compareConstructors( a, b ) { - var getProto = Object.getPrototypeOf || function( obj ) { - - /*jshint proto: true */ - return obj.__proto__; - }; var protoA = getProto( a ); var protoB = getProto( b ); @@ -1649,6 +1708,10 @@ QUnit.equiv = (function() { return false; } + function getRegExpFlags( regexp ) { + return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ]; + } + var callbacks = { "string": useStrictEquality, "boolean": useStrictEquality, @@ -1656,28 +1719,17 @@ QUnit.equiv = (function() { "null": useStrictEquality, "undefined": useStrictEquality, "symbol": useStrictEquality, + "date": useStrictEquality, - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); + "nan": function() { + return true; }, "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - - // The regex itself - a.source === b.source && + return a.source === b.source && - // And its modifiers - a.global === b.global && - - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; + // Include flags in the comparison + getRegExpFlags( a ) === getRegExpFlags( b ); }, // - skip when the property is a method of an instance (OOP) @@ -1691,14 +1743,10 @@ QUnit.equiv = (function() { "array": function( b, a ) { var i, j, len, loop, aCircular, bCircular; - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - len = a.length; if ( len !== b.length ) { - // safe and faster + + // Safe and faster return false; } @@ -1732,43 +1780,53 @@ QUnit.equiv = (function() { }, "set": function( b, a ) { - var aArray, bArray; + var innerEq, + outerEq = true; - // `b` could be any object here - if ( QUnit.objectType( b ) !== "set" ) { + if ( a.size !== b.size ) { return false; } - aArray = []; - a.forEach( function( v ) { - aArray.push( v ); - }); - bArray = []; - b.forEach( function( v ) { - bArray.push( v ); - }); + a.forEach( function( aVal ) { + innerEq = false; + + b.forEach( function( bVal ) { + if ( innerEquiv( bVal, aVal ) ) { + innerEq = true; + } + } ); + + if ( !innerEq ) { + outerEq = false; + } + } ); - return innerEquiv( bArray, aArray ); + return outerEq; }, "map": function( b, a ) { - var aArray, bArray; + var innerEq, + outerEq = true; - // `b` could be any object here - if ( QUnit.objectType( b ) !== "map" ) { + if ( a.size !== b.size ) { return false; } - aArray = []; - a.forEach( function( v, k ) { - aArray.push( [ k, v ] ); - }); - bArray = []; - b.forEach( function( v, k ) { - bArray.push( [ k, v ] ); - }); + a.forEach( function( aVal, aKey ) { + innerEq = false; + + b.forEach( function( bVal, bKey ) { + if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) { + innerEq = true; + } + } ); + + if ( !innerEq ) { + outerEq = false; + } + } ); - return innerEquiv( bArray, aArray ); + return outerEq; }, "object": function( b, a ) { @@ -1830,45 +1888,31 @@ QUnit.equiv = (function() { }; function typeEquiv( a, b ) { - var prop = QUnit.objectType( a ); - return callbacks[ prop ]( b, a ); + var type = QUnit.objectType( a ); + return QUnit.objectType( b ) === type && callbacks[ type ]( b, a ); } // The real equiv function - function innerEquiv() { - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { + function innerEquiv( a, b ) { - // End transition + // We're done when there's nothing more to compare + if ( arguments.length < 2 ) { return true; } - return ( (function( a, b ) { - if ( a === b ) { - - // Catch the most you can - return true; - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType( a ) !== QUnit.objectType( b ) ) { - - // Don't lose time with error prone cases - return false; - } else { - return typeEquiv( a, b ); - } + // Require type-specific equality + return ( a === b || typeEquiv( a, b ) ) && - // Apply transition with (1..n) arguments - }( args[ 0 ], args[ 1 ] ) ) && - innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) ); + // ...across all consecutive argument pairs + ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) ); } return innerEquiv; -}()); +}() ); // Based on jsDump by Ariel Flesler // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html -QUnit.dump = (function() { +QUnit.dump = ( function() { function quote( str ) { return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\""; } @@ -1906,7 +1950,7 @@ QUnit.dump = (function() { var reName = /^function (\w+)/, dump = { - // objType is used mostly internally, you can fix a (custom) type in advance + // The objType is used mostly internally, you can fix a (custom) type in advance parse: function( obj, objType, stack ) { stack = stack || []; var res, parser, parserType, @@ -1950,7 +1994,7 @@ QUnit.dump = (function() { type = "node"; } else if ( - // native arrays + // Native arrays toString.call( obj ) === "[object Array]" || // NodeList objects @@ -1966,10 +2010,12 @@ QUnit.dump = (function() { } return type; }, + separator: function() { return this.multiline ? this.HTML ? "
" : "\n" : this.HTML ? " " : " "; }, - // extra can be a number, shortcut for increasing-calling-decreasing + + // Extra can be a number, shortcut for increasing-calling-decreasing indent: function( extra ) { if ( !this.multiline ) { return ""; @@ -1989,11 +2035,11 @@ QUnit.dump = (function() { setParser: function( name, parser ) { this.parsers[ name ] = parser; }, + // The next 3 are exposed so you can use them quote: quote, literal: literal, join: join, - // depth: 1, maxDepth: QUnit.config.maxDepth, @@ -2010,13 +2056,13 @@ QUnit.dump = (function() { "function": function( fn ) { var ret = "function", - // functions never have name in IE + // Functions never have name in IE name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; if ( name ) { ret += " " + name; } - ret += "( "; + ret += "("; ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); return join( ret, dump.parse( fn, "functionCode" ), "}" ); @@ -2087,7 +2133,7 @@ QUnit.dump = (function() { return ret + open + "/" + tag + close; }, - // function calls it internally, it's the arguments part of the function + // Function calls it internally, it's the arguments part of the function functionArgs: function( fn ) { var args, l = fn.length; @@ -2104,11 +2150,14 @@ QUnit.dump = (function() { } return " " + args.join( ", " ) + " "; }, - // object calls it internally, the key part of an item in a map + + // Object calls it internally, the key part of an item in a map key: quote, - // function calls it internally, it's the content of the function + + // Function calls it internally, it's the content of the function functionCode: "[code]", - // node calls it internally, it's an html attribute value + + // Node calls it internally, it's a html attribute value attribute: quote, string: quote, date: quote, @@ -2116,42 +2165,45 @@ QUnit.dump = (function() { number: literal, "boolean": literal }, - // if true, entities are escaped ( <, >, \t, space and \n ) + + // If true, entities are escaped ( <, >, \t, space and \n ) HTML: false, - // indentation unit + + // Indentation unit indentChar: " ", - // if true, items in a collection, are separated by a \n, else just a space. + + // If true, items in a collection, are separated by a \n, else just a space. multiline: true }; return dump; -}()); +}() ); -// back compat +// Back compat QUnit.jsDump = QUnit.dump; -// For browser, export only select globals -if ( defined.document ) { +// Deprecated +// Extend assert methods to QUnit for Backwards compatibility +( function() { + var i, + assertions = Assert.prototype; - // Deprecated - // Extend assert methods to QUnit and Global scope through Backwards compatibility - (function() { - var i, - assertions = Assert.prototype; + function applyCurrent( current ) { + return function() { + var assert = new Assert( QUnit.config.current ); + current.apply( assert, arguments ); + }; + } - function applyCurrent( current ) { - return function() { - var assert = new Assert( QUnit.config.current ); - current.apply( assert, arguments ); - }; - } + for ( i in assertions ) { + QUnit[ i ] = applyCurrent( assertions[ i ] ); + } +}() ); - for ( i in assertions ) { - QUnit[ i ] = applyCurrent( assertions[ i ] ); - } - })(); +// For browser, export only select globals +if ( defined.document ) { - (function() { + ( function() { var i, l, keys = [ "test", @@ -2177,7 +2229,7 @@ if ( defined.document ) { for ( i = 0, l = keys.length; i < l; i++ ) { window[ keys[ i ] ] = QUnit[ keys[ i ] ]; } - })(); + }() ); window.QUnit = QUnit; } @@ -2202,1957 +2254,2081 @@ if ( typeof define === "function" && define.amd ) { QUnit.config.autostart = false; } -/* - * 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), - * modifications are licensed as more fully set forth in LICENSE.txt. - * - * The original source of google-diff-match-patch is attributable and licensed as follows: - * - * Copyright 2006 Google Inc. - * http://code.google.com/p/google-diff-match-patch/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * More Info: - * https://code.google.com/p/google-diff-match-patch/ - * - * Usage: QUnit.diff(expected, actual) - * - */ -QUnit.diff = ( function() { - function DiffMatchPatch() { - } +// Get a reference to the global object, like window in browsers +}( ( function() { + return this; +}() ) ) ); - // DIFF FUNCTIONS +( function() { - /** - * The data structure representing a diff is an array of tuples: - * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] - * which means: delete 'Hello', add 'Goodbye' and keep ' world.' - */ - var DIFF_DELETE = -1, - DIFF_INSERT = 1, - DIFF_EQUAL = 0; +// Only interact with URLs via window.location +var location = typeof window !== "undefined" && window.location; +if ( !location ) { + return; +} - /** - * Find the differences between two texts. Simplifies the problem by stripping - * any common prefix or suffix off the texts before diffing. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @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. - * @return {!Array.} Array of diff tuples. - */ - DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) { - var deadline, checklines, commonlength, - commonprefix, commonsuffix, diffs; +var urlParams = getUrlParams(); - // The diff must be complete in up to 1 second. - deadline = ( new Date() ).getTime() + 1000; +QUnit.urlParams = urlParams; - // Check for null inputs. - if ( text1 === null || text2 === null ) { - throw new Error( "Null input. (DiffMain)" ); - } +// Match module/test by inclusion in an array +QUnit.config.moduleId = [].concat( urlParams.moduleId || [] ); +QUnit.config.testId = [].concat( urlParams.testId || [] ); - // Check for equality (speedup). - if ( text1 === text2 ) { - if ( text1 ) { - return [ - [ DIFF_EQUAL, text1 ] - ]; - } - return []; - } +// Exact case-insensitive match of the module name +QUnit.config.module = urlParams.module; - if ( typeof optChecklines === "undefined" ) { - optChecklines = true; - } +// Regular expression or case-insenstive substring match against "moduleName: testName" +QUnit.config.filter = urlParams.filter; - checklines = optChecklines; +// Test order randomization +if ( urlParams.seed === true ) { - // Trim off common prefix (speedup). - commonlength = this.diffCommonPrefix( text1, text2 ); - commonprefix = text1.substring( 0, commonlength ); - text1 = text1.substring( commonlength ); - text2 = text2.substring( commonlength ); + // Generate a random seed if the option is specified without a value + QUnit.config.seed = Math.random().toString( 36 ).slice( 2 ); +} else if ( urlParams.seed ) { + QUnit.config.seed = urlParams.seed; +} - // Trim off common suffix (speedup). - commonlength = this.diffCommonSuffix( text1, text2 ); - commonsuffix = text1.substring( text1.length - commonlength ); - text1 = text1.substring( 0, text1.length - commonlength ); - text2 = text2.substring( 0, text2.length - commonlength ); +// Add URL-parameter-mapped config values with UI form rendering data +QUnit.config.urlConfig.push( + { + id: "hidepassed", + label: "Hide passed tests", + tooltip: "Only show tests and assertions that fail. Stored as query-strings." + }, + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the " + + "global object (`window` in Browsers). Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + + "exceptions in IE reasonable. Stored as query-strings." + } +); - // Compute the diff on the middle block. - diffs = this.diffCompute( text1, text2, checklines, deadline ); +QUnit.begin( function() { + var i, option, + urlConfig = QUnit.config.urlConfig; - // Restore the prefix and suffix. - if ( commonprefix ) { - diffs.unshift( [ DIFF_EQUAL, commonprefix ] ); + for ( i = 0; i < urlConfig.length; i++ ) { + + // Options can be either strings or objects with nonempty "id" properties + option = QUnit.config.urlConfig[ i ]; + if ( typeof option !== "string" ) { + option = option.id; } - if ( commonsuffix ) { - diffs.push( [ DIFF_EQUAL, commonsuffix ] ); + + if ( QUnit.config[ option ] === undefined ) { + QUnit.config[ option ] = urlParams[ option ]; } - this.diffCleanupMerge( diffs ); - return diffs; - }; + } +} ); - /** - * Reduce the number of edits by eliminating operationally trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) { - var changes, equalities, equalitiesLength, lastequality, - pointer, preIns, preDel, postIns, postDel; - changes = false; - equalities = []; // Stack of indices where equalities are found. - equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - lastequality = null; - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - pointer = 0; // Index of current position. - // Is there an insertion operation before the last equality. - preIns = false; - // Is there a deletion operation before the last equality. - preDel = false; - // Is there an insertion operation after the last equality. - postIns = false; - // Is there a deletion operation after the last equality. - postDel = false; - while ( pointer < diffs.length ) { +function getUrlParams() { + var i, param, name, value; + var urlParams = {}; + var params = location.search.slice( 1 ).split( "&" ); + var length = params.length; - // Equality found. - if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { - if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) { + for ( i = 0; i < length; i++ ) { + if ( params[ i ] ) { + param = params[ i ].split( "=" ); + name = decodeURIComponent( param[ 0 ] ); - // Candidate found. - equalities[ equalitiesLength++ ] = pointer; - preIns = postIns; - preDel = postDel; - lastequality = diffs[ pointer ][ 1 ]; - } else { + // Allow just a key to turn on a flag, e.g., test.html?noglobals + value = param.length === 1 || + decodeURIComponent( param.slice( 1 ).join( "=" ) ) ; + if ( urlParams[ name ] ) { + urlParams[ name ] = [].concat( urlParams[ name ], value ); + } else { + urlParams[ name ] = value; + } + } + } - // Not a candidate, and can never become one. - equalitiesLength = 0; - lastequality = null; - } - postIns = postDel = false; + return urlParams; +} - // An insertion or deletion. - } else { +// Don't load the HTML Reporter on non-browser environments +if ( typeof window === "undefined" || !window.document ) { + return; +} - if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) { - postDel = true; - } else { - postIns = true; - } +// Deprecated QUnit.init - Ref #530 +// Re-initialize the configuration options +QUnit.init = function() { + var config = QUnit.config; - /* - * Five types to be split: - * ABXYCD - * AXCD - * ABXC - * AXCD - * ABXC - */ - if ( lastequality && ( ( preIns && preDel && postIns && postDel ) || - ( ( lastequality.length < 2 ) && - ( preIns + preDel + postIns + postDel ) === 3 ) ) ) { + config.stats = { all: 0, bad: 0 }; + config.moduleStats = { all: 0, bad: 0 }; + config.started = 0; + config.updateRate = 1000; + config.blocking = false; + config.autostart = true; + config.autorun = false; + config.filter = ""; + config.queue = []; - // Duplicate record. - diffs.splice( - equalities[ equalitiesLength - 1 ], - 0, - [ DIFF_DELETE, lastequality ] - ); + appendInterface(); +}; - // Change second copy to insert. - diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; - equalitiesLength--; // Throw away the equality we just deleted; - lastequality = null; - if ( preIns && preDel ) { - // No changes made which could affect previous entry, keep going. - postIns = postDel = true; - equalitiesLength = 0; - } else { - equalitiesLength--; // Throw away the previous equality. - pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; - postIns = postDel = false; - } - changes = true; - } +var config = QUnit.config, + document = window.document, + collapseNext = false, + hasOwn = Object.prototype.hasOwnProperty, + unfilteredUrl = setUrl( { filter: undefined, module: undefined, + moduleId: undefined, testId: undefined } ), + defined = { + sessionStorage: ( function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch ( e ) { + return false; } - pointer++; - } + }() ) + }, + modulesList = []; - if ( changes ) { - this.diffCleanupMerge( diffs ); - } - }; +/** +* Escape text for attribute or text content. +*/ +function escapeText( s ) { + if ( !s ) { + return ""; + } + s = s + ""; - /** - * Convert a diff array into a pretty HTML report. - * @param {!Array.} diffs Array of diff tuples. - * @param {integer} string to be beautified. - * @return {string} HTML representation. - */ - DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) { - 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. - switch ( op ) { - case DIFF_INSERT: - html[ x ] = "" + data + ""; - break; - case DIFF_DELETE: - html[ x ] = "" + data + ""; - break; - case DIFF_EQUAL: - html[ x ] = "" + data + ""; - break; - } + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { + switch ( s ) { + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; } - return html.join( "" ); - }; + } ); +} - /** - * Determine the common prefix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the start of each - * string. - */ - DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { - var pointermid, pointermax, pointermin, pointerstart; - // Quick check for common null cases. - if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) { - return 0; - } - // Binary search. - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ - pointermin = 0; - pointermax = Math.min( text1.length, text2.length ); - pointermid = pointermax; - pointerstart = 0; - while ( pointermin < pointermid ) { - if ( text1.substring( pointerstart, pointermid ) === - text2.substring( pointerstart, pointermid ) ) { - pointermin = pointermid; - pointerstart = pointermin; - } else { - pointermax = pointermid; - } - pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); - } - return pointermid; - }; +/** + * @param {HTMLElement} elem + * @param {string} type + * @param {Function} fn + */ +function addEvent( elem, type, fn ) { + if ( elem.addEventListener ) { - /** - * Determine the common suffix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of each string. - */ - DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { - var pointermid, pointermax, pointermin, pointerend; - // Quick check for common null cases. - if ( !text1 || - !text2 || - text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) { - return 0; - } - // Binary search. - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ - pointermin = 0; - pointermax = Math.min( text1.length, text2.length ); - pointermid = pointermax; - pointerend = 0; - while ( pointermin < pointermid ) { - if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) === - text2.substring( text2.length - pointermid, text2.length - pointerend ) ) { - pointermin = pointermid; - pointerend = pointermin; - } else { - pointermax = pointermid; + // Standards-based browsers + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + + // Support: IE <9 + elem.attachEvent( "on" + type, function() { + var event = window.event; + if ( !event.target ) { + event.target = event.srcElement || document; } - pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); - } - return pointermid; - }; - /** - * Find the differences between two texts. Assumes that the texts do not - * have any common prefix or suffix. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean} checklines Speedup flag. If false, then don't run a - * line-level diff first to identify the changed areas. - * If true, then run a faster, slightly less optimal diff. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) { - var diffs, longtext, shorttext, i, hm, - text1A, text2A, text1B, text2B, - midCommon, diffsA, diffsB; + fn.call( elem, event ); + } ); + } +} - if ( !text1 ) { - // Just add some text (speedup). - return [ - [ DIFF_INSERT, text2 ] - ]; - } +/** + * @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 ); + } +} - if ( !text2 ) { - // Just delete some text (speedup). - return [ - [ DIFF_DELETE, text1 ] - ]; - } +function hasClass( elem, name ) { + return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; +} - longtext = text1.length > text2.length ? text1 : text2; - shorttext = text1.length > text2.length ? text2 : text1; - i = longtext.indexOf( shorttext ); - if ( i !== -1 ) { - // Shorter text is inside the longer text (speedup). - diffs = [ - [ DIFF_INSERT, longtext.substring( 0, i ) ], - [ DIFF_EQUAL, shorttext ], - [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] - ]; - // Swap insertions for deletions if diff is reversed. - if ( text1.length > text2.length ) { - diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE; - } - return diffs; - } +function addClass( elem, name ) { + if ( !hasClass( elem, name ) ) { + elem.className += ( elem.className ? " " : "" ) + name; + } +} - if ( shorttext.length === 1 ) { - // Single character string. - // After the previous speedup, the character can't be an equality. - return [ - [ DIFF_DELETE, text1 ], - [ DIFF_INSERT, text2 ] - ]; - } +function toggleClass( elem, name, force ) { + if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) { + addClass( elem, name ); + } else { + removeClass( elem, name ); + } +} - // Check to see if the problem can be split in two. - hm = this.diffHalfMatch( text1, text2 ); - if ( hm ) { - // A half-match was found, sort out the return data. - text1A = hm[ 0 ]; - text1B = hm[ 1 ]; - text2A = hm[ 2 ]; - text2B = hm[ 3 ]; - midCommon = hm[ 4 ]; - // Send both pairs off for separate processing. - diffsA = this.DiffMain( text1A, text2A, checklines, deadline ); - diffsB = this.DiffMain( text1B, text2B, checklines, deadline ); - // Merge the results. - return diffsA.concat( [ - [ DIFF_EQUAL, midCommon ] - ], diffsB ); - } +function removeClass( elem, name ) { + var set = " " + elem.className + " "; - if ( checklines && text1.length > 100 && text2.length > 100 ) { - return this.diffLineMode( text1, text2, deadline ); - } + // Class name may appear multiple times + while ( set.indexOf( " " + name + " " ) >= 0 ) { + set = set.replace( " " + name + " ", " " ); + } - return this.diffBisect( text1, text2, deadline ); - }; + // Trim for prettiness + elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); +} - /** - * Do the two texts share a substring which is at least half the length of the - * longer text? - * This speedup can produce non-minimal diffs. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {Array.} Five element Array, containing the prefix of - * text1, the suffix of text1, the prefix of text2, the suffix of - * text2 and the common middle. Or null if there was no match. - * @private - */ - DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) { - var longtext, shorttext, dmp, - text1A, text2B, text2A, text1B, midCommon, - hm1, hm2, hm; +function id( name ) { + return document.getElementById && document.getElementById( name ); +} - longtext = text1.length > text2.length ? text1 : text2; - shorttext = text1.length > text2.length ? text2 : text1; - if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) { - return null; // Pointless. +function getUrlConfigHtml() { + var i, j, val, + escaped, escapedTooltip, + selection = false, + urlConfig = config.urlConfig, + urlConfigHtml = ""; + + for ( i = 0; i < urlConfig.length; i++ ) { + + // Options can be either strings or objects with nonempty "id" properties + val = config.urlConfig[ i ]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val + }; } - dmp = this; // 'this' becomes 'window' in a closure. - /** - * Does a substring of shorttext exist within longtext such that the substring - * is at least half the length of longtext? - * Closure, but does not reference any external variables. - * @param {string} longtext Longer string. - * @param {string} shorttext Shorter string. - * @param {number} i Start index of quarter length substring within longtext. - * @return {Array.} Five element Array, containing the prefix of - * longtext, the suffix of longtext, the prefix of shorttext, the suffix - * of shorttext and the common middle. Or null if there was no match. - * @private - */ - function diffHalfMatchI( longtext, shorttext, i ) { - var seed, j, bestCommon, prefixLength, suffixLength, - bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; - // Start with a 1/4 length substring at position i as a seed. - seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) ); - j = -1; - bestCommon = ""; - while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) { - prefixLength = dmp.diffCommonPrefix( longtext.substring( i ), - shorttext.substring( j ) ); - suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ), - shorttext.substring( 0, j ) ); - if ( bestCommon.length < suffixLength + prefixLength ) { - bestCommon = shorttext.substring( j - suffixLength, j ) + - shorttext.substring( j, j + prefixLength ); - bestLongtextA = longtext.substring( 0, i - suffixLength ); - bestLongtextB = longtext.substring( i + prefixLength ); - bestShorttextA = shorttext.substring( 0, j - suffixLength ); - bestShorttextB = shorttext.substring( j + prefixLength ); + escaped = escapeText( val.id ); + escapedTooltip = escapeText( val.tooltip ); + + if ( !val.value || typeof val.value === "string" ) { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; } + } - // First check if the second quarter is the seed for a half-match. - hm1 = diffHalfMatchI( longtext, shorttext, - Math.ceil( longtext.length / 4 ) ); - // Check again based on the third quarter. - hm2 = diffHalfMatchI( longtext, shorttext, - Math.ceil( longtext.length / 2 ) ); - if ( !hm1 && !hm2 ) { - return null; - } else if ( !hm2 ) { - hm = hm1; - } else if ( !hm1 ) { - hm = hm2; - } else { - // Both matched. Select the longest. - hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2; - } - - // A half-match was found, sort out the return data. - text1A, text1B, text2A, text2B; - if ( text1.length > text2.length ) { - text1A = hm[ 0 ]; - text1B = hm[ 1 ]; - text2A = hm[ 2 ]; - text2B = hm[ 3 ]; - } else { - text2A = hm[ 0 ]; - text2B = hm[ 1 ]; - text1A = hm[ 2 ]; - text1B = hm[ 3 ]; - } - midCommon = hm[ 4 ]; - return [ text1A, text1B, text2A, text2B, midCommon ]; - }; + return urlConfigHtml; +} - /** - * Do a quick line-level diff on both strings, then rediff the parts for - * greater accuracy. - * This speedup can produce non-minimal diffs. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) { - var a, diffs, linearray, pointer, countInsert, - countDelete, textInsert, textDelete, j; - // Scan the text on a line-by-line basis first. - a = this.diffLinesToChars( text1, text2 ); - text1 = a.chars1; - text2 = a.chars2; - linearray = a.lineArray; +// Handle "click" events on toolbar checkboxes and "change" for select menus. +// Updates the URL with the new state of `config.urlConfig` values. +function toolbarChanged() { + var updatedUrl, value, tests, + field = this, + params = {}; - diffs = this.DiffMain( text1, text2, false, deadline ); + // Detect if field is a select menu or a checkbox + if ( "selectedIndex" in field ) { + value = field.options[ field.selectedIndex ].value || undefined; + } else { + value = field.checked ? ( field.defaultValue || true ) : undefined; + } - // Convert the diff back to original text. - this.diffCharsToLines( diffs, linearray ); - // Eliminate freak matches (e.g. blank lines) - this.diffCleanupSemantic( diffs ); + params[ field.name ] = value; + updatedUrl = setUrl( params ); - // Rediff any replacement blocks, this time character-by-character. - // Add a dummy entry at the end. - diffs.push( [ DIFF_EQUAL, "" ] ); - pointer = 0; - countDelete = 0; - countInsert = 0; - textDelete = ""; - textInsert = ""; - while ( pointer < diffs.length ) { - switch ( diffs[ pointer ][ 0 ] ) { - case DIFF_INSERT: - countInsert++; - textInsert += diffs[ pointer ][ 1 ]; - break; - case DIFF_DELETE: - countDelete++; - textDelete += diffs[ pointer ][ 1 ]; - break; - case DIFF_EQUAL: - // Upon reaching an equality, check for prior redundancies. - if ( countDelete >= 1 && countInsert >= 1 ) { - // Delete the offending records and add the merged ones. - diffs.splice( pointer - countDelete - countInsert, - countDelete + countInsert ); - pointer = pointer - countDelete - countInsert; - a = this.DiffMain( textDelete, textInsert, false, deadline ); - for ( j = a.length - 1; j >= 0; j-- ) { - diffs.splice( pointer, 0, a[ j ] ); - } - pointer = pointer + a.length; - } - countInsert = 0; - countDelete = 0; - textDelete = ""; - textInsert = ""; - break; - } - pointer++; + // Check if we can apply the change without a page refresh + if ( "hidepassed" === field.name && "replaceState" in window.history ) { + QUnit.urlParams[ field.name ] = value; + config[ field.name ] = value || false; + tests = id( "qunit-tests" ); + if ( tests ) { + toggleClass( tests, "hidepass", value || false ); } - diffs.pop(); // Remove the dummy entry at the end. + window.history.replaceState( null, "", updatedUrl ); + } else { + window.location = updatedUrl; + } +} - return diffs; - }; +function setUrl( params ) { + var key, arrValue, i, + querystring = "?", + location = window.location; - /** - * Find the 'middle snake' of a diff, split the problem in two - * and return the recursively constructed diff. - * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) { - var text1Length, text2Length, maxD, vOffset, vLength, - v1, v2, x, delta, front, k1start, k1end, k2start, - k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; - // Cache the text lengths to prevent multiple calls. - text1Length = text1.length; - text2Length = text2.length; - maxD = Math.ceil( ( text1Length + text2Length ) / 2 ); - vOffset = maxD; - vLength = 2 * maxD; - v1 = new Array( vLength ); - v2 = new Array( vLength ); - // Setting all elements to -1 is faster in Chrome & Firefox than mixing - // integers and undefined. - for ( x = 0; x < vLength; x++ ) { - v1[ x ] = -1; - v2[ x ] = -1; - } - v1[ vOffset + 1 ] = 0; - v2[ vOffset + 1 ] = 0; - delta = text1Length - text2Length; - // If the total number of characters is odd, then the front path will collide - // with the reverse path. - front = ( delta % 2 !== 0 ); - // Offsets for start and end of k loop. - // Prevents mapping of space beyond the grid. - k1start = 0; - k1end = 0; - k2start = 0; - k2end = 0; - for ( d = 0; d < maxD; d++ ) { - // Bail out if deadline is reached. - if ( ( new Date() ).getTime() > deadline ) { - break; - } + params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); - // Walk the front path one step. - for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) { - k1Offset = vOffset + k1; - if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) { - x1 = v1[ k1Offset + 1 ]; - } else { - x1 = v1[ k1Offset - 1 ] + 1; - } - y1 = x1 - k1; - while ( x1 < text1Length && y1 < text2Length && - text1.charAt( x1 ) === text2.charAt( y1 ) ) { - x1++; - y1++; - } - v1[ k1Offset ] = x1; - if ( x1 > text1Length ) { - // Ran off the right of the graph. - k1end += 2; - } else if ( y1 > text2Length ) { - // Ran off the bottom of the graph. - k1start += 2; - } else if ( front ) { - k2Offset = vOffset + delta - k1; - if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) { - // Mirror x2 onto top-left coordinate system. - x2 = text1Length - v2[ k2Offset ]; - if ( x1 >= x2 ) { - // Overlap detected. - return this.diffBisectSplit( text1, text2, x1, y1, deadline ); - } - } - } - } + for ( key in params ) { - // Walk the reverse path one step. - for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) { - k2Offset = vOffset + k2; - if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) { - x2 = v2[ k2Offset + 1 ]; - } else { - x2 = v2[ k2Offset - 1 ] + 1; - } - y2 = x2 - k2; - while ( x2 < text1Length && y2 < text2Length && - text1.charAt( text1Length - x2 - 1 ) === - text2.charAt( text2Length - y2 - 1 ) ) { - x2++; - y2++; - } - v2[ k2Offset ] = x2; - if ( x2 > text1Length ) { - // Ran off the left of the graph. - k2end += 2; - } else if ( y2 > text2Length ) { - // Ran off the top of the graph. - k2start += 2; - } else if ( !front ) { - k1Offset = vOffset + delta - k2; - if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) { - x1 = v1[ k1Offset ]; - y1 = vOffset + x1 - k1Offset; - // Mirror x2 onto top-left coordinate system. - x2 = text1Length - x2; - if ( x1 >= x2 ) { - // Overlap detected. - return this.diffBisectSplit( text1, text2, x1, y1, deadline ); - } - } + // Skip inherited or undefined properties + if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) { + + // Output a parameter for each value of this key (but usually just one) + arrValue = [].concat( params[ key ] ); + for ( i = 0; i < arrValue.length; i++ ) { + querystring += encodeURIComponent( key ); + if ( arrValue[ i ] !== true ) { + querystring += "=" + encodeURIComponent( arrValue[ i ] ); } + querystring += "&"; } } - // Diff took too long and hit the deadline or - // number of diffs equals number of characters, no commonality at all. - return [ - [ DIFF_DELETE, text1 ], - [ DIFF_INSERT, text2 ] - ]; - }; + } + return location.protocol + "//" + location.host + + location.pathname + querystring.slice( 0, -1 ); +} - /** - * Given the location of the 'middle snake', split the diff in two parts - * and recurse. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} x Index of split point in text1. - * @param {number} y Index of split point in text2. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ - DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) { - var text1a, text1b, text2a, text2b, diffs, diffsb; - text1a = text1.substring( 0, x ); - text2a = text2.substring( 0, y ); - text1b = text1.substring( x ); - text2b = text2.substring( y ); +function applyUrlParams() { + var selectedModule, + modulesList = id( "qunit-modulefilter" ), + filter = id( "qunit-filter-input" ).value; - // Compute both diffs serially. - diffs = this.DiffMain( text1a, text2a, false, deadline ); - diffsb = this.DiffMain( text1b, text2b, false, deadline ); + selectedModule = modulesList ? + decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) : + undefined; - return diffs.concat( diffsb ); - }; + window.location = setUrl( { + module: ( selectedModule === "" ) ? undefined : selectedModule, + filter: ( filter === "" ) ? undefined : filter, - /** - * Reduce the number of edits by eliminating semantically trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) { - var changes, equalities, equalitiesLength, lastequality, - pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, - lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; - changes = false; - equalities = []; // Stack of indices where equalities are found. - equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - lastequality = null; - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - pointer = 0; // Index of current position. - // Number of characters that changed prior to the equality. - lengthInsertions1 = 0; - lengthDeletions1 = 0; - // Number of characters that changed after the equality. - lengthInsertions2 = 0; - lengthDeletions2 = 0; - while ( pointer < diffs.length ) { - if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found. - equalities[ equalitiesLength++ ] = pointer; - lengthInsertions1 = lengthInsertions2; - lengthDeletions1 = lengthDeletions2; - lengthInsertions2 = 0; - lengthDeletions2 = 0; - lastequality = diffs[ pointer ][ 1 ]; - } else { // An insertion or deletion. - if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) { - lengthInsertions2 += diffs[ pointer ][ 1 ].length; - } else { - lengthDeletions2 += diffs[ pointer ][ 1 ].length; - } - // Eliminate an equality that is smaller or equal to the edits on both - // sides of it. - if ( lastequality && ( lastequality.length <= - Math.max( lengthInsertions1, lengthDeletions1 ) ) && - ( lastequality.length <= Math.max( lengthInsertions2, - lengthDeletions2 ) ) ) { + // Remove moduleId and testId filters + moduleId: undefined, + testId: undefined + } ); +} - // Duplicate record. - diffs.splice( - equalities[ equalitiesLength - 1 ], - 0, - [ DIFF_DELETE, lastequality ] - ); +function toolbarUrlConfigContainer() { + var urlConfigContainer = document.createElement( "span" ); - // Change second copy to insert. - diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; + urlConfigContainer.innerHTML = getUrlConfigHtml(); + addClass( urlConfigContainer, "qunit-url-config" ); - // Throw away the equality we just deleted. - equalitiesLength--; + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" for checkboxes + addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); + addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); - // Throw away the previous equality (it needs to be reevaluated). - equalitiesLength--; - pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; + return urlConfigContainer; +} - // Reset the counters. - lengthInsertions1 = 0; - lengthDeletions1 = 0; - lengthInsertions2 = 0; - lengthDeletions2 = 0; - lastequality = null; - changes = true; - } - } - pointer++; - } +function toolbarLooseFilter() { + var filter = document.createElement( "form" ), + label = document.createElement( "label" ), + input = document.createElement( "input" ), + button = document.createElement( "button" ); - // Normalize the diff. - if ( changes ) { - this.diffCleanupMerge( diffs ); - } + addClass( filter, "qunit-filter" ); - // Find any overlaps between deletions and insertions. - // e.g: abcxxxxxxdef - // -> abcxxxdef - // e.g: xxxabcdefxxx - // -> defxxxabc - // Only extract an overlap if it is as big as the edit ahead or behind it. - pointer = 1; - while ( pointer < diffs.length ) { - if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE && - diffs[ pointer ][ 0 ] === DIFF_INSERT ) { - deletion = diffs[ pointer - 1 ][ 1 ]; - insertion = diffs[ pointer ][ 1 ]; - overlapLength1 = this.diffCommonOverlap( deletion, insertion ); - overlapLength2 = this.diffCommonOverlap( insertion, deletion ); - if ( overlapLength1 >= overlapLength2 ) { - if ( overlapLength1 >= deletion.length / 2 || - overlapLength1 >= insertion.length / 2 ) { - // Overlap found. Insert an equality and trim the surrounding edits. - diffs.splice( - pointer, - 0, - [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] - ); - diffs[ pointer - 1 ][ 1 ] = - deletion.substring( 0, deletion.length - overlapLength1 ); - diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 ); - pointer++; - } - } else { - if ( overlapLength2 >= deletion.length / 2 || - overlapLength2 >= insertion.length / 2 ) { + label.innerHTML = "Filter: "; - // Reverse overlap found. - // Insert an equality and swap and trim the surrounding edits. - diffs.splice( - pointer, - 0, - [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] - ); + input.type = "text"; + input.value = config.filter || ""; + input.name = "filter"; + input.id = "qunit-filter-input"; - diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT; - diffs[ pointer - 1 ][ 1 ] = - insertion.substring( 0, insertion.length - overlapLength2 ); - diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE; - diffs[ pointer + 1 ][ 1 ] = - deletion.substring( overlapLength2 ); - pointer++; - } - } - pointer++; - } - pointer++; - } - }; + button.innerHTML = "Go"; - /** - * Determine if the suffix of one string is the prefix of another. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of the first - * string and the start of the second string. - * @private - */ - DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) { - var text1Length, text2Length, textLength, - best, length, pattern, found; - // Cache the text lengths to prevent multiple calls. - text1Length = text1.length; - text2Length = text2.length; - // Eliminate the null case. - if ( text1Length === 0 || text2Length === 0 ) { - return 0; - } - // Truncate the longer string. - if ( text1Length > text2Length ) { - text1 = text1.substring( text1Length - text2Length ); - } else if ( text1Length < text2Length ) { - text2 = text2.substring( 0, text1Length ); - } - textLength = Math.min( text1Length, text2Length ); - // Quick check for the worst case. - if ( text1 === text2 ) { - return textLength; - } + label.appendChild( input ); - // Start by looking for a single character match - // and increase length until no match is found. - // Performance analysis: http://neil.fraser.name/news/2010/11/04/ - best = 0; - length = 1; - while ( true ) { - pattern = text1.substring( textLength - length ); - found = text2.indexOf( pattern ); - if ( found === -1 ) { - return best; - } - length += found; - if ( found === 0 || text1.substring( textLength - length ) === - text2.substring( 0, length ) ) { - best = length; - length++; - } + filter.appendChild( label ); + filter.appendChild( button ); + addEvent( filter, "submit", function( ev ) { + applyUrlParams(); + + if ( ev && ev.preventDefault ) { + ev.preventDefault(); } - }; - /** - * Split two texts into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {{chars1: string, chars2: string, lineArray: !Array.}} - * An object containing the encoded text1, the encoded text2 and - * the array of unique strings. - * The zeroth element of the array of unique strings is intentionally blank. - * @private - */ - DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) { - var lineArray, lineHash, chars1, chars2; - lineArray = []; // e.g. lineArray[4] === 'Hello\n' - lineHash = {}; // e.g. lineHash['Hello\n'] === 4 + return false; + } ); - // '\x00' is a valid character, but various debuggers don't like it. - // So we'll insert a junk entry to avoid generating a null character. - lineArray[ 0 ] = ""; + return filter; +} - /** - * Split a text into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * Modifies linearray and linehash through being a closure. - * @param {string} text String to encode. - * @return {string} Encoded string. - * @private - */ - function diffLinesToCharsMunge( text ) { - var chars, lineStart, lineEnd, lineArrayLength, line; - chars = ""; - // Walk the text, pulling out a substring for each line. - // text.split('\n') would would temporarily double our memory footprint. - // Modifying text would create many large strings to garbage collect. - lineStart = 0; - lineEnd = -1; - // Keeping our own length variable is faster than looking it up. - lineArrayLength = lineArray.length; - while ( lineEnd < text.length - 1 ) { - lineEnd = text.indexOf( "\n", lineStart ); - if ( lineEnd === -1 ) { - lineEnd = text.length - 1; - } - line = text.substring( lineStart, lineEnd + 1 ); - lineStart = lineEnd + 1; +function toolbarModuleFilterHtml() { + var i, + moduleFilterHtml = ""; - if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) : - ( lineHash[ line ] !== undefined ) ) { - chars += String.fromCharCode( lineHash[ line ] ); - } else { - chars += String.fromCharCode( lineArrayLength ); - lineHash[ line ] = lineArrayLength; - lineArray[ lineArrayLength++ ] = line; - } - } - return chars; - } + if ( !modulesList.length ) { + return false; + } - chars1 = diffLinesToCharsMunge( text1 ); - chars2 = diffLinesToCharsMunge( text2 ); - return { - chars1: chars1, - chars2: chars2, - lineArray: lineArray - }; - }; + moduleFilterHtml += "" + + ""; - /** - * Reorder and merge like edit sections. Merge equalities. - * Any edit section can move as long as it doesn't cross an equality. - * @param {!Array.} diffs Array of diff tuples. - */ - DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) { - var pointer, countDelete, countInsert, textInsert, textDelete, - commonlength, changes, diffPointer, position; - diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end. - pointer = 0; - countDelete = 0; - countInsert = 0; - textDelete = ""; - textInsert = ""; - commonlength; - while ( pointer < diffs.length ) { - switch ( diffs[ pointer ][ 0 ] ) { - case DIFF_INSERT: - countInsert++; - textInsert += diffs[ pointer ][ 1 ]; - pointer++; - break; - case DIFF_DELETE: - countDelete++; - textDelete += diffs[ pointer ][ 1 ]; - pointer++; - break; - case DIFF_EQUAL: - // Upon reaching an equality, check for prior redundancies. - if ( countDelete + countInsert > 1 ) { - if ( countDelete !== 0 && countInsert !== 0 ) { - // Factor out any common prefixies. - commonlength = this.diffCommonPrefix( textInsert, textDelete ); - if ( commonlength !== 0 ) { - if ( ( pointer - countDelete - countInsert ) > 0 && - diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] === - DIFF_EQUAL ) { - diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] += - textInsert.substring( 0, commonlength ); - } else { - diffs.splice( 0, 0, [ DIFF_EQUAL, - textInsert.substring( 0, commonlength ) - ] ); - pointer++; - } - textInsert = textInsert.substring( commonlength ); - textDelete = textDelete.substring( commonlength ); - } - // Factor out any common suffixies. - commonlength = this.diffCommonSuffix( textInsert, textDelete ); - if ( commonlength !== 0 ) { - diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length - - commonlength ) + diffs[ pointer ][ 1 ]; - textInsert = textInsert.substring( 0, textInsert.length - - commonlength ); - textDelete = textDelete.substring( 0, textDelete.length - - commonlength ); - } - } - // Delete the offending records and add the merged ones. - if ( countDelete === 0 ) { - diffs.splice( pointer - countInsert, - countDelete + countInsert, [ DIFF_INSERT, textInsert ] ); - } else if ( countInsert === 0 ) { - diffs.splice( pointer - countDelete, - countDelete + countInsert, [ DIFF_DELETE, textDelete ] ); - } else { - 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 ) { + return moduleFilterHtml; +} - // Merge this equality with the previous one. - diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ]; - diffs.splice( pointer, 1 ); - } else { - pointer++; - } - countInsert = 0; - countDelete = 0; - textDelete = ""; - textInsert = ""; - break; - } +function toolbarModuleFilter() { + var toolbar = id( "qunit-testrunner-toolbar" ), + moduleFilter = document.createElement( "span" ), + moduleFilterHtml = toolbarModuleFilterHtml(); + + if ( !toolbar || !moduleFilterHtml ) { + return false; + } + + moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); + moduleFilter.innerHTML = moduleFilterHtml; + + addEvent( moduleFilter.lastChild, "change", applyUrlParams ); + + toolbar.appendChild( moduleFilter ); +} + +function appendToolbar() { + var toolbar = id( "qunit-testrunner-toolbar" ); + + if ( toolbar ) { + toolbar.appendChild( toolbarUrlConfigContainer() ); + toolbar.appendChild( toolbarLooseFilter() ); + toolbarModuleFilter(); + } +} + +function appendHeader() { + var header = id( "qunit-header" ); + + if ( header ) { + header.innerHTML = "" + header.innerHTML + + " "; + } +} + +function appendBanner() { + var banner = id( "qunit-banner" ); + + if ( banner ) { + banner.className = ""; + } +} + +function appendTestResults() { + var tests = id( "qunit-tests" ), + result = id( "qunit-testresult" ); + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + tests.innerHTML = ""; + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
 "; + } +} + +function storeFixture() { + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + config.fixture = fixture.innerHTML; + } +} + +function appendFilteredTest() { + var testId = QUnit.config.testId; + if ( !testId || testId.length <= 0 ) { + return ""; + } + return "
Rerunning selected tests: " + + escapeText( testId.join( ", " ) ) + + " Run all tests
"; +} + +function appendUserAgent() { + var userAgent = id( "qunit-userAgent" ); + + if ( userAgent ) { + userAgent.innerHTML = ""; + userAgent.appendChild( + document.createTextNode( + "QUnit " + QUnit.version + "; " + navigator.userAgent + ) + ); + } +} + +function appendInterface() { + var qunit = id( "qunit" ); + + if ( qunit ) { + qunit.innerHTML = + "

" + escapeText( document.title ) + "

" + + "

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

" + + "
    "; + } + + appendHeader(); + appendBanner(); + appendTestResults(); + appendUserAgent(); + appendToolbar(); +} + +function appendTestsList( modules ) { + var i, l, x, z, test, moduleObj; + + for ( i = 0, l = modules.length; i < l; i++ ) { + moduleObj = modules[ i ]; + + for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { + test = moduleObj.tests[ x ]; + + appendTest( test.name, test.testId, moduleObj.name ); } - if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) { - diffs.pop(); // Remove the dummy entry at the end. + } +} + +function appendTest( name, testId, moduleName ) { + var title, rerunTrigger, testBlock, assertList, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + title = document.createElement( "strong" ); + title.innerHTML = getNameHtml( name, moduleName ); + + rerunTrigger = document.createElement( "a" ); + rerunTrigger.innerHTML = "Rerun"; + rerunTrigger.href = setUrl( { testId: testId } ); + + testBlock = document.createElement( "li" ); + testBlock.appendChild( title ); + testBlock.appendChild( rerunTrigger ); + testBlock.id = "qunit-test-output-" + testId; + + assertList = document.createElement( "ol" ); + assertList.className = "qunit-assert-list"; + + testBlock.appendChild( assertList ); + + tests.appendChild( testBlock ); +} + +// HTML Reporter initialization and load +QUnit.begin( function( details ) { + var i, moduleObj, tests; + + // Sort modules by name for the picker + for ( i = 0; i < details.modules.length; i++ ) { + moduleObj = details.modules[ i ]; + if ( moduleObj.name ) { + modulesList.push( moduleObj.name ); } + } + modulesList.sort( function( a, b ) { + return a.localeCompare( b ); + } ); + + // Capture fixture HTML from the page + storeFixture(); + + // Initialize QUnit elements + appendInterface(); + appendTestsList( details.modules ); + tests = id( "qunit-tests" ); + if ( tests && config.hidepassed ) { + addClass( tests, "hidepass" ); + } +} ); + +QUnit.done( function( details ) { + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + html = [ + "Tests completed in ", + details.runtime, + " milliseconds.
    ", + "", + details.passed, + " assertions of ", + details.total, + " passed, ", + details.failed, + " failed." + ].join( "" ); + + if ( banner ) { + banner.className = details.failed ? "qunit-fail" : "qunit-pass"; + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + if ( config.altertitle && document.title ) { + + // Show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document.title = [ + ( details.failed ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); + } + + // Clear own sessionStorage items if all tests passed + if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); + } + } + } + + // Scroll back to top to show results + if ( config.scrolltop && window.scrollTo ) { + window.scrollTo( 0, 0 ); + } +} ); - // Second pass: look for single edits surrounded on both sides by equalities - // which can be shifted sideways to eliminate an equality. - // e.g: ABAC -> ABAC - changes = false; - pointer = 1; +function getNameHtml( name, module ) { + var nameHtml = ""; - // 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 ) { + if ( module ) { + nameHtml = "" + escapeText( module ) + ": "; + } - diffPointer = diffs[ pointer ][ 1 ]; - position = diffPointer.substring( - diffPointer.length - diffs[ pointer - 1 ][ 1 ].length - ); + nameHtml += "" + escapeText( name ) + ""; - // This is a single edit surrounded by equalities. - if ( position === diffs[ pointer - 1 ][ 1 ] ) { + return nameHtml; +} - // 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.splice( pointer - 1, 1 ); - changes = true; - } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) === - diffs[ pointer + 1 ][ 1 ] ) { +QUnit.testStart( function( details ) { + var running, testBlock, bad; - // Shift the edit over the next equality. - diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ]; - diffs[ pointer ][ 1 ] = - diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) + - diffs[ pointer + 1 ][ 1 ]; - diffs.splice( pointer + 1, 1 ); - changes = true; - } - } - pointer++; - } - // If shifts were made, the diff needs reordering and another shift sweep. - if ( changes ) { - this.diffCleanupMerge( diffs ); - } - }; + testBlock = id( "qunit-test-output-" + details.testId ); + if ( testBlock ) { + testBlock.className = "running"; + } else { - return function( o, n ) { - var diff, output, text; - diff = new DiffMatchPatch(); - output = diff.DiffMain( o, n ); - diff.diffCleanupEfficiency( output ); - text = diff.diffPrettyHtml( output ); + // Report later registered tests + appendTest( details.name, details.testId, details.module ); + } - return text; - }; -}() ); + running = id( "qunit-testresult" ); + if ( running ) { + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); -// Get a reference to the global object, like window in browsers -}( (function() { - return this; -})() )); + running.innerHTML = ( bad ? + "Rerunning previously failed test:
    " : + "Running:
    " ) + + getNameHtml( details.name, details.module ); + } -(function() { +} ); -// Don't load the HTML Reporter on non-Browser environments -if ( typeof window === "undefined" || !window.document ) { - return; -} +function stripHtml( string ) { -// Deprecated QUnit.init - Ref #530 -// Re-initialize the configuration options -QUnit.init = function() { - var tests, banner, result, qunit, - config = QUnit.config; + // Strip tags, html entity and whitespaces + return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\"/g, "" ).replace( /\s+/g, "" ); +} - config.stats = { all: 0, bad: 0 }; - config.moduleStats = { all: 0, bad: 0 }; - config.started = 0; - config.updateRate = 1000; - config.blocking = false; - config.autostart = true; - config.autorun = false; - config.filter = ""; - config.queue = []; +QUnit.log( function( details ) { + var assertList, assertLi, + message, expected, actual, diff, + showDiff = false, + testItem = id( "qunit-test-output-" + details.testId ); - // Return on non-browser environments - // This is necessary to not break on node tests - if ( typeof window === "undefined" ) { + if ( !testItem ) { return; } - qunit = id( "qunit" ); - if ( qunit ) { - qunit.innerHTML = - "

    " + escapeText( document.title ) + "

    " + - "

    " + - "
    " + - "

    " + - "
      "; - } + message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); + message = "" + message + ""; + message += "@ " + details.runtime + " ms"; - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); + // The pushFailure doesn't provide details.expected + // when it calls, it's implicit to also not show expected and diff stuff + // Also, we need to check details.expected existence, as it can exist and be undefined + if ( !details.result && hasOwn.call( details, "expected" ) ) { + if ( details.negative ) { + expected = "NOT " + QUnit.dump.parse( details.expected ); + } else { + expected = QUnit.dump.parse( details.expected ); + } - if ( tests ) { - tests.innerHTML = ""; - } + actual = QUnit.dump.parse( details.actual ); + message += ""; - if ( banner ) { - banner.className = ""; - } + if ( actual !== expected ) { - if ( result ) { - result.parentNode.removeChild( result ); - } + message += ""; - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
       "; - } -}; + // 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; + } -var config = QUnit.config, - collapseNext = false, - hasOwn = Object.prototype.hasOwnProperty, - defined = { - document: window.document !== undefined, - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch ( e ) { - return false; + // Don't show diff if expected and actual are totally different + if ( showDiff ) { + message += ""; } - }()) - }, - modulesList = []; + } else if ( expected.indexOf( "[object Array]" ) !== -1 || + expected.indexOf( "[object Object]" ) !== -1 ) { + message += ""; + } else { + message += ""; + } -/** -* Escape text for attribute or text content. -*/ -function escapeText( s ) { - if ( !s ) { - return ""; + if ( details.source ) { + message += ""; + } + + message += "
      Expected:
      " +
      +			escapeText( expected ) +
      +			"
      Result:
      " +
      +				escapeText( actual ) + "
      Diff:
      " +
      +					diff + "
      Message: " + + "Diff suppressed as the depth of object is more than current max depth (" + + QUnit.config.maxDepth + ").

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

      Message: " + + "Diff suppressed as the expected and actual results have an equivalent" + + " serialization
      Source:
      " +
      +				escapeText( details.source ) + "
      "; + + // This occurs when pushFailure is set and we have an extracted stack trace + } else if ( !details.result && details.source ) { + message += "" + + "" + + "
      Source:
      " +
      +			escapeText( details.source ) + "
      "; } - s = s + ""; - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch ( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + assertLi = document.createElement( "li" ); + assertLi.className = details.result ? "pass" : "fail"; + assertLi.innerHTML = message; + assertList.appendChild( assertLi ); +} ); + +QUnit.testDone( function( details ) { + var testTitle, time, testItem, assertList, + good, bad, testCounts, skipped, sourceName, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; + } + + testItem = id( "qunit-test-output-" + details.testId ); + + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + + good = details.passed; + bad = details.failed; + + // Store result when possible + if ( config.reorder && defined.sessionStorage ) { + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); + } else { + sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); } - }); -} + } -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ -function addEvent( elem, type, fn ) { - if ( elem.addEventListener ) { + if ( bad === 0 ) { - // Standards-based browsers - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { + // Collapse the passing tests + addClass( assertList, "qunit-collapsed" ); + } else if ( bad && config.collapse && !collapseNext ) { - // support: IE <9 - elem.attachEvent( "on" + type, function() { - var event = window.event; - if ( !event.target ) { - event.target = event.srcElement || document; - } + // Skip collapsing the first failing test + collapseNext = true; + } else { - fn.call( elem, event ); - }); + // Collapse remaining tests + addClass( assertList, "qunit-collapsed" ); } -} -/** - * @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 ); - } -} + // The testItem.firstChild is the test name + testTitle = testItem.firstChild; -function hasClass( elem, name ) { - return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; -} + testCounts = bad ? + "" + bad + ", " + "" + good + ", " : + ""; -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += ( elem.className ? " " : "" ) + name; - } -} + testTitle.innerHTML += " (" + testCounts + + details.assertions.length + ")"; -function toggleClass( elem, name ) { - if ( hasClass( elem, name ) ) { - removeClass( elem, name ); + if ( details.skipped ) { + testItem.className = "skipped"; + skipped = document.createElement( "em" ); + skipped.className = "qunit-skipped-label"; + skipped.innerHTML = "skipped"; + testItem.insertBefore( skipped, testTitle ); } else { - addClass( elem, name ); - } -} + addEvent( testTitle, "click", function() { + toggleClass( assertList, "qunit-collapsed" ); + } ); -function removeClass( elem, name ) { - var set = " " + elem.className + " "; + testItem.className = bad ? "fail" : "pass"; - // Class name may appear multiple times - while ( set.indexOf( " " + name + " " ) >= 0 ) { - set = set.replace( " " + name + " ", " " ); + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = details.runtime + " ms"; + testItem.insertBefore( time, assertList ); } - // trim for prettiness - elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); -} + // Show the source of the test when showing assertions + if ( details.source ) { + sourceName = document.createElement( "p" ); + sourceName.innerHTML = "Source: " + details.source; + addClass( sourceName, "qunit-source" ); + if ( bad === 0 ) { + addClass( sourceName, "qunit-collapsed" ); + } + addEvent( testTitle, "click", function() { + toggleClass( sourceName, "qunit-collapsed" ); + } ); + testItem.appendChild( sourceName ); + } +} ); -function id( name ) { - return defined.document && document.getElementById && document.getElementById( name ); +// 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 ); } -function getUrlConfigHtml() { - var i, j, val, - escaped, escapedTooltip, - selection = false, - len = config.urlConfig.length, - urlConfigHtml = ""; +/* + * 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), + * modifications are licensed as more fully set forth in LICENSE.txt. + * + * The original source of google-diff-match-patch is attributable and licensed as follows: + * + * Copyright 2006 Google Inc. + * https://code.google.com/p/google-diff-match-patch/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More Info: + * https://code.google.com/p/google-diff-match-patch/ + * + * Usage: QUnit.diff(expected, actual) + * + */ +QUnit.diff = ( function() { + function DiffMatchPatch() { + } - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[ i ]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val - }; - } + // DIFF FUNCTIONS - escaped = escapeText( val.id ); - escapedTooltip = escapeText( val.tooltip ); + /** + * The data structure representing a diff is an array of tuples: + * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] + * which means: delete 'Hello', add 'Goodbye' and keep ' world.' + */ + var DIFF_DELETE = -1, + DIFF_INSERT = 1, + DIFF_EQUAL = 0; - if ( config[ val.id ] === undefined ) { - config[ val.id ] = QUnit.urlParams[ val.id ]; - } + /** + * Find the differences between two texts. Simplifies the problem by stripping + * any common prefix or suffix off the texts before diffing. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @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. + * @return {!Array.} Array of diff tuples. + */ + DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) { + var deadline, checklines, commonlength, + commonprefix, commonsuffix, diffs; - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; + return []; } - } - return urlConfigHtml; -} + if ( typeof optChecklines === "undefined" ) { + optChecklines = true; + } -// Handle "click" events on toolbar checkboxes and "change" for select menus. -// Updates the URL with the new state of `config.urlConfig` values. -function toolbarChanged() { - var updatedUrl, value, - field = this, - params = {}; + checklines = optChecklines; - // Detect if field is a select menu or a checkbox - if ( "selectedIndex" in field ) { - value = field.options[ field.selectedIndex ].value || undefined; - } else { - value = field.checked ? ( field.defaultValue || true ) : undefined; - } + // Trim off common prefix (speedup). + commonlength = this.diffCommonPrefix( text1, text2 ); + commonprefix = text1.substring( 0, commonlength ); + text1 = text1.substring( commonlength ); + text2 = text2.substring( commonlength ); - params[ field.name ] = value; - updatedUrl = setUrl( params ); + // Trim off common suffix (speedup). + commonlength = this.diffCommonSuffix( text1, text2 ); + commonsuffix = text1.substring( text1.length - commonlength ); + text1 = text1.substring( 0, text1.length - commonlength ); + text2 = text2.substring( 0, text2.length - commonlength ); - if ( "hidepassed" === field.name && "replaceState" in window.history ) { - config[ field.name ] = value || false; - if ( value ) { - addClass( id( "qunit-tests" ), "hidepass" ); - } else { - removeClass( id( "qunit-tests" ), "hidepass" ); + // Compute the diff on the middle block. + diffs = this.diffCompute( text1, text2, checklines, deadline ); + + // Restore the prefix and suffix. + if ( commonprefix ) { + diffs.unshift( [ DIFF_EQUAL, commonprefix ] ); + } + if ( commonsuffix ) { + diffs.push( [ DIFF_EQUAL, commonsuffix ] ); } + this.diffCleanupMerge( diffs ); + return diffs; + }; - // It is not necessary to refresh the whole page - window.history.replaceState( null, "", updatedUrl ); - } else { - window.location = updatedUrl; - } -} + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) { + var changes, equalities, equalitiesLength, lastequality, + pointer, preIns, preDel, postIns, postDel; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; -function setUrl( params ) { - var key, - querystring = "?"; + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. - params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); + // Is there an insertion operation before the last equality. + preIns = false; + + // Is there a deletion operation before the last equality. + preDel = false; + + // Is there an insertion operation after the last equality. + postIns = false; - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - if ( params[ key ] === undefined ) { - continue; - } - querystring += encodeURIComponent( key ); - if ( params[ key ] !== true ) { - querystring += "=" + encodeURIComponent( params[ key ] ); - } - querystring += "&"; - } - } - return location.protocol + "//" + location.host + - location.pathname + querystring.slice( 0, -1 ); -} + // Is there a deletion operation after the last equality. + postDel = false; + while ( pointer < diffs.length ) { -function applyUrlParams() { - var selectedModule, - modulesList = id( "qunit-modulefilter" ), - filter = id( "qunit-filter-input" ).value; + // Equality found. + if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { + if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) { - selectedModule = modulesList ? - decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) : - undefined; + // Candidate found. + equalities[ equalitiesLength++ ] = pointer; + preIns = postIns; + preDel = postDel; + lastequality = diffs[ pointer ][ 1 ]; + } else { - window.location = setUrl({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - filter: ( filter === "" ) ? undefined : filter, + // Not a candidate, and can never become one. + equalitiesLength = 0; + lastequality = null; + } + postIns = postDel = false; - // Remove testId filter - testId: undefined - }); -} + // An insertion or deletion. + } else { -function toolbarUrlConfigContainer() { - var urlConfigContainer = document.createElement( "span" ); + if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) { + postDel = true; + } else { + postIns = true; + } - urlConfigContainer.innerHTML = getUrlConfigHtml(); - addClass( urlConfigContainer, "qunit-url-config" ); + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if ( lastequality && ( ( preIns && preDel && postIns && postDel ) || + ( ( lastequality.length < 2 ) && + ( preIns + preDel + postIns + postDel ) === 3 ) ) ) { - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" for checkboxes - addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); - addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); + // Duplicate record. + diffs.splice( + equalities[ equalitiesLength - 1 ], + 0, + [ DIFF_DELETE, lastequality ] + ); - return urlConfigContainer; -} + // Change second copy to insert. + diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; + equalitiesLength--; // Throw away the equality we just deleted; + lastequality = null; + if ( preIns && preDel ) { -function toolbarLooseFilter() { - var filter = document.createElement( "form" ), - label = document.createElement( "label" ), - input = document.createElement( "input" ), - button = document.createElement( "button" ); + // No changes made which could affect previous entry, keep going. + postIns = postDel = true; + equalitiesLength = 0; + } else { + equalitiesLength--; // Throw away the previous equality. + pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; + postIns = postDel = false; + } + changes = true; + } + } + pointer++; + } - addClass( filter, "qunit-filter" ); + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + }; - label.innerHTML = "Filter: "; + /** + * Convert a diff array into a pretty HTML report. + * @param {!Array.} diffs Array of diff tuples. + * @param {integer} string to be beautified. + * @return {string} HTML representation. + */ + DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) { + 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. + switch ( op ) { + case DIFF_INSERT: + html[ x ] = "" + escapeText( data ) + ""; + break; + case DIFF_DELETE: + html[ x ] = "" + escapeText( data ) + ""; + break; + case DIFF_EQUAL: + html[ x ] = "" + escapeText( data ) + ""; + break; + } + } + return html.join( "" ); + }; - input.type = "text"; - input.value = config.filter || ""; - input.name = "filter"; - input.id = "qunit-filter-input"; + /** + * Determine the common prefix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerstart; - button.innerHTML = "Go"; + // Quick check for common null cases. + if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) { + return 0; + } - label.appendChild( input ); + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min( text1.length, text2.length ); + pointermid = pointermax; + pointerstart = 0; + while ( pointermin < pointermid ) { + if ( text1.substring( pointerstart, pointermid ) === + text2.substring( pointerstart, pointermid ) ) { + pointermin = pointermid; + pointerstart = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); + } + return pointermid; + }; - filter.appendChild( label ); - filter.appendChild( button ); - addEvent( filter, "submit", function( ev ) { - applyUrlParams(); + /** + * Determine the common suffix of two strings. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) { + var pointermid, pointermax, pointermin, pointerend; - if ( ev && ev.preventDefault ) { - ev.preventDefault(); + // Quick check for common null cases. + if ( !text1 || + !text2 || + text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) { + return 0; } - return false; - }); + // Binary search. + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + pointermin = 0; + pointermax = Math.min( text1.length, text2.length ); + pointermid = pointermax; + pointerend = 0; + while ( pointermin < pointermid ) { + if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) === + text2.substring( text2.length - pointermid, text2.length - pointerend ) ) { + pointermin = pointermid; + pointerend = pointermin; + } else { + pointermax = pointermid; + } + pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin ); + } + return pointermid; + }; - return filter; -} + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean} checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster, slightly less optimal diff. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) { + var diffs, longtext, shorttext, i, hm, + text1A, text2A, text1B, text2B, + midCommon, diffsA, diffsB; -function toolbarModuleFilterHtml() { - var i, - moduleFilterHtml = ""; + if ( !text1 ) { - if ( !modulesList.length ) { - return false; - } + // Just add some text (speedup). + return [ + [ DIFF_INSERT, text2 ] + ]; + } - modulesList.sort(function( a, b ) { - return a.localeCompare( b ); - }); + if ( !text2 ) { - moduleFilterHtml += "" + - ""; + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + i = longtext.indexOf( shorttext ); + if ( i !== -1 ) { - return moduleFilterHtml; -} + // Shorter text is inside the longer text (speedup). + diffs = [ + [ DIFF_INSERT, longtext.substring( 0, i ) ], + [ DIFF_EQUAL, shorttext ], + [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ] + ]; -function toolbarModuleFilter() { - var toolbar = id( "qunit-testrunner-toolbar" ), - moduleFilter = document.createElement( "span" ), - moduleFilterHtml = toolbarModuleFilterHtml(); + // Swap insertions for deletions if diff is reversed. + if ( text1.length > text2.length ) { + diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE; + } + return diffs; + } - if ( !toolbar || !moduleFilterHtml ) { - return false; - } + if ( shorttext.length === 1 ) { - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; + // Single character string. + // After the previous speedup, the character can't be an equality. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + } - addEvent( moduleFilter.lastChild, "change", applyUrlParams ); + // Check to see if the problem can be split in two. + hm = this.diffHalfMatch( text1, text2 ); + if ( hm ) { - toolbar.appendChild( moduleFilter ); -} + // A half-match was found, sort out the return data. + text1A = hm[ 0 ]; + text1B = hm[ 1 ]; + text2A = hm[ 2 ]; + text2B = hm[ 3 ]; + midCommon = hm[ 4 ]; -function appendToolbar() { - var toolbar = id( "qunit-testrunner-toolbar" ); + // Send both pairs off for separate processing. + diffsA = this.DiffMain( text1A, text2A, checklines, deadline ); + diffsB = this.DiffMain( text1B, text2B, checklines, deadline ); - if ( toolbar ) { - toolbar.appendChild( toolbarUrlConfigContainer() ); - toolbar.appendChild( toolbarLooseFilter() ); - } -} + // Merge the results. + return diffsA.concat( [ + [ DIFF_EQUAL, midCommon ] + ], diffsB ); + } -function appendHeader() { - var header = id( "qunit-header" ); + if ( checklines && text1.length > 100 && text2.length > 100 ) { + return this.diffLineMode( text1, text2, deadline ); + } - if ( header ) { - header.innerHTML = "" + header.innerHTML + " "; - } -} + return this.diffBisect( text1, text2, deadline ); + }; -function appendBanner() { - var banner = id( "qunit-banner" ); + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * This speedup can produce non-minimal diffs. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + * @private + */ + DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) { + var longtext, shorttext, dmp, + text1A, text2B, text2A, text1B, midCommon, + hm1, hm2, hm; - if ( banner ) { - banner.className = ""; - } -} + longtext = text1.length > text2.length ? text1 : text2; + shorttext = text1.length > text2.length ? text2 : text1; + if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) { + return null; // Pointless. + } + dmp = this; // 'this' becomes 'window' in a closure. -function appendTestResults() { - var tests = id( "qunit-tests" ), - result = id( "qunit-testresult" ); + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext. + * @return {Array.} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diffHalfMatchI( longtext, shorttext, i ) { + var seed, j, bestCommon, prefixLength, suffixLength, + bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB; - if ( result ) { - result.parentNode.removeChild( result ); - } + // Start with a 1/4 length substring at position i as a seed. + seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) ); + j = -1; + bestCommon = ""; + while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) { + prefixLength = dmp.diffCommonPrefix( longtext.substring( i ), + shorttext.substring( j ) ); + suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ), + shorttext.substring( 0, j ) ); + if ( bestCommon.length < suffixLength + prefixLength ) { + bestCommon = shorttext.substring( j - suffixLength, j ) + + shorttext.substring( j, j + prefixLength ); + bestLongtextA = longtext.substring( 0, i - suffixLength ); + bestLongtextB = longtext.substring( i + prefixLength ); + bestShorttextA = shorttext.substring( 0, j - suffixLength ); + bestShorttextB = shorttext.substring( j + prefixLength ); + } + } + if ( bestCommon.length * 2 >= longtext.length ) { + return [ bestLongtextA, bestLongtextB, + bestShorttextA, bestShorttextB, bestCommon + ]; + } else { + return null; + } + } - if ( tests ) { - tests.innerHTML = ""; - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
       "; - } -} + // First check if the second quarter is the seed for a half-match. + hm1 = diffHalfMatchI( longtext, shorttext, + Math.ceil( longtext.length / 4 ) ); -function storeFixture() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - config.fixture = fixture.innerHTML; - } -} + // Check again based on the third quarter. + hm2 = diffHalfMatchI( longtext, shorttext, + Math.ceil( longtext.length / 2 ) ); + if ( !hm1 && !hm2 ) { + return null; + } else if ( !hm2 ) { + hm = hm1; + } else if ( !hm1 ) { + hm = hm2; + } else { -function appendFilteredTest() { - var testId = QUnit.config.testId; - if ( !testId || testId.length <= 0 ) { - return ""; - } - return "
      Rerunning selected tests: " + testId.join(", ") + - " " + "Run all tests" + "
      "; -} + // Both matched. Select the longest. + hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2; + } -function appendUserAgent() { - var userAgent = id( "qunit-userAgent" ); + // A half-match was found, sort out the return data. + text1A, text1B, text2A, text2B; + if ( text1.length > text2.length ) { + text1A = hm[ 0 ]; + text1B = hm[ 1 ]; + text2A = hm[ 2 ]; + text2B = hm[ 3 ]; + } else { + text2A = hm[ 0 ]; + text2B = hm[ 1 ]; + text1A = hm[ 2 ]; + text1B = hm[ 3 ]; + } + midCommon = hm[ 4 ]; + return [ text1A, text1B, text2A, text2B, midCommon ]; + }; - if ( userAgent ) { - userAgent.innerHTML = ""; - userAgent.appendChild( - document.createTextNode( - "QUnit " + QUnit.version + "; " + navigator.userAgent - ) - ); - } -} + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time when the diff should be complete by. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) { + var a, diffs, linearray, pointer, countInsert, + countDelete, textInsert, textDelete, j; -function appendTestsList( modules ) { - var i, l, x, z, test, moduleObj; + // Scan the text on a line-by-line basis first. + a = this.diffLinesToChars( text1, text2 ); + text1 = a.chars1; + text2 = a.chars2; + linearray = a.lineArray; - for ( i = 0, l = modules.length; i < l; i++ ) { - moduleObj = modules[ i ]; + diffs = this.DiffMain( text1, text2, false, deadline ); - if ( moduleObj.name ) { - modulesList.push( moduleObj.name ); - } + // Convert the diff back to original text. + this.diffCharsToLines( diffs, linearray ); - for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { - test = moduleObj.tests[ x ]; + // Eliminate freak matches (e.g. blank lines) + this.diffCleanupSemantic( diffs ); - appendTest( test.name, test.testId, moduleObj.name ); - } - } -} + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.push( [ DIFF_EQUAL, "" ] ); + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + while ( pointer < diffs.length ) { + switch ( diffs[ pointer ][ 0 ] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[ pointer ][ 1 ]; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[ pointer ][ 1 ]; + break; + case DIFF_EQUAL: -function appendTest( name, testId, moduleName ) { - var title, rerunTrigger, testBlock, assertList, - tests = id( "qunit-tests" ); + // Upon reaching an equality, check for prior redundancies. + if ( countDelete >= 1 && countInsert >= 1 ) { - if ( !tests ) { - return; - } + // Delete the offending records and add the merged ones. + diffs.splice( pointer - countDelete - countInsert, + countDelete + countInsert ); + pointer = pointer - countDelete - countInsert; + a = this.DiffMain( textDelete, textInsert, false, deadline ); + for ( j = a.length - 1; j >= 0; j-- ) { + diffs.splice( pointer, 0, a[ j ] ); + } + pointer = pointer + a.length; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + pointer++; + } + diffs.pop(); // Remove the dummy entry at the end. - title = document.createElement( "strong" ); - title.innerHTML = getNameHtml( name, moduleName ); + return diffs; + }; - rerunTrigger = document.createElement( "a" ); - rerunTrigger.innerHTML = "Rerun"; - rerunTrigger.href = setUrl({ testId: testId }); + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) { + var text1Length, text2Length, maxD, vOffset, vLength, + v1, v2, x, delta, front, k1start, k1end, k2start, + k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2; - testBlock = document.createElement( "li" ); - testBlock.appendChild( title ); - testBlock.appendChild( rerunTrigger ); - testBlock.id = "qunit-test-output-" + testId; + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + maxD = Math.ceil( ( text1Length + text2Length ) / 2 ); + vOffset = maxD; + vLength = 2 * maxD; + v1 = new Array( vLength ); + v2 = new Array( vLength ); - assertList = document.createElement( "ol" ); - assertList.className = "qunit-assert-list"; + // Setting all elements to -1 is faster in Chrome & Firefox than mixing + // integers and undefined. + for ( x = 0; x < vLength; x++ ) { + v1[ x ] = -1; + v2[ x ] = -1; + } + v1[ vOffset + 1 ] = 0; + v2[ vOffset + 1 ] = 0; + delta = text1Length - text2Length; - testBlock.appendChild( assertList ); + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + front = ( delta % 2 !== 0 ); - tests.appendChild( testBlock ); -} + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + k1start = 0; + k1end = 0; + k2start = 0; + k2end = 0; + for ( d = 0; d < maxD; d++ ) { -// HTML Reporter initialization and load -QUnit.begin(function( details ) { - var qunit = id( "qunit" ); + // Bail out if deadline is reached. + if ( ( new Date() ).getTime() > deadline ) { + break; + } - // Fixture is the only one necessary to run without the #qunit element - storeFixture(); + // Walk the front path one step. + for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) { + k1Offset = vOffset + k1; + if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) { + x1 = v1[ k1Offset + 1 ]; + } else { + x1 = v1[ k1Offset - 1 ] + 1; + } + y1 = x1 - k1; + while ( x1 < text1Length && y1 < text2Length && + text1.charAt( x1 ) === text2.charAt( y1 ) ) { + x1++; + y1++; + } + v1[ k1Offset ] = x1; + if ( x1 > text1Length ) { - if ( qunit ) { - qunit.innerHTML = - "

      " + escapeText( document.title ) + "

      " + - "

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

      " + - "
        "; - } + // Ran off the right of the graph. + k1end += 2; + } else if ( y1 > text2Length ) { - appendHeader(); - appendBanner(); - appendTestResults(); - appendUserAgent(); - appendToolbar(); - appendTestsList( details.modules ); - toolbarModuleFilter(); + // Ran off the bottom of the graph. + k1start += 2; + } else if ( front ) { + k2Offset = vOffset + delta - k1; + if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) { - if ( qunit && config.hidepassed ) { - addClass( qunit.lastChild, "hidepass" ); - } -}); + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - v2[ k2Offset ]; + if ( x1 >= x2 ) { -QUnit.done(function( details ) { - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - html = [ - "Tests completed in ", - details.runtime, - " milliseconds.
        ", - "", - details.passed, - " assertions of ", - details.total, - " passed, ", - details.failed, - " failed." - ].join( "" ); + // Overlap detected. + return this.diffBisectSplit( text1, text2, x1, y1, deadline ); + } + } + } + } - if ( banner ) { - banner.className = details.failed ? "qunit-fail" : "qunit-pass"; - } + // Walk the reverse path one step. + for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) { + k2Offset = vOffset + k2; + if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) { + x2 = v2[ k2Offset + 1 ]; + } else { + x2 = v2[ k2Offset - 1 ] + 1; + } + y2 = x2 - k2; + while ( x2 < text1Length && y2 < text2Length && + text1.charAt( text1Length - x2 - 1 ) === + text2.charAt( text2Length - y2 - 1 ) ) { + x2++; + y2++; + } + v2[ k2Offset ] = x2; + if ( x2 > text1Length ) { - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } + // Ran off the left of the graph. + k2end += 2; + } else if ( y2 > text2Length ) { - if ( config.altertitle && defined.document && document.title ) { + // Ran off the top of the graph. + k2start += 2; + } else if ( !front ) { + k1Offset = vOffset + delta - k2; + if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) { + x1 = v1[ k1Offset ]; + y1 = vOffset + x1 - k1Offset; - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( details.failed ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } + // Mirror x2 onto top-left coordinate system. + x2 = text1Length - x2; + if ( x1 >= x2 ) { - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); + // Overlap detected. + return this.diffBisectSplit( text1, text2, x1, y1, deadline ); + } + } + } } } - } - - // scroll back to top to show results - if ( config.scrolltop && window.scrollTo ) { - window.scrollTo( 0, 0 ); - } -}); -function getNameHtml( name, module ) { - var nameHtml = ""; + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + return [ + [ DIFF_DELETE, text1 ], + [ DIFF_INSERT, text2 ] + ]; + }; - if ( module ) { - nameHtml = "" + escapeText( module ) + ": "; - } + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {number} x Index of split point in text1. + * @param {number} y Index of split point in text2. + * @param {number} deadline Time at which to bail if not yet complete. + * @return {!Array.} Array of diff tuples. + * @private + */ + DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) { + var text1a, text1b, text2a, text2b, diffs, diffsb; + text1a = text1.substring( 0, x ); + text2a = text2.substring( 0, y ); + text1b = text1.substring( x ); + text2b = text2.substring( y ); - nameHtml += "" + escapeText( name ) + ""; + // Compute both diffs serially. + diffs = this.DiffMain( text1a, text2a, false, deadline ); + diffsb = this.DiffMain( text1b, text2b, false, deadline ); - return nameHtml; -} + return diffs.concat( diffsb ); + }; -QUnit.testStart(function( details ) { - var running, testBlock, bad; + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) { + var changes, equalities, equalitiesLength, lastequality, + pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, + lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2; + changes = false; + equalities = []; // Stack of indices where equalities are found. + equalitiesLength = 0; // Keeping our own length var is faster in JS. + /** @type {?string} */ + lastequality = null; - testBlock = id( "qunit-test-output-" + details.testId ); - if ( testBlock ) { - testBlock.className = "running"; - } else { + // Always equal to diffs[equalities[equalitiesLength - 1]][1] + pointer = 0; // Index of current position. - // Report later registered tests - appendTest( details.name, details.testId, details.module ); - } + // Number of characters that changed prior to the equality. + lengthInsertions1 = 0; + lengthDeletions1 = 0; - running = id( "qunit-testresult" ); - if ( running ) { - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name ); + // Number of characters that changed after the equality. + lengthInsertions2 = 0; + lengthDeletions2 = 0; + while ( pointer < diffs.length ) { + if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found. + equalities[ equalitiesLength++ ] = pointer; + lengthInsertions1 = lengthInsertions2; + lengthDeletions1 = lengthDeletions2; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = diffs[ pointer ][ 1 ]; + } else { // An insertion or deletion. + if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) { + lengthInsertions2 += diffs[ pointer ][ 1 ].length; + } else { + lengthDeletions2 += diffs[ pointer ][ 1 ].length; + } - running.innerHTML = ( bad ? - "Rerunning previously failed test:
        " : - "Running:
        " ) + - getNameHtml( details.name, details.module ); - } + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if ( lastequality && ( lastequality.length <= + Math.max( lengthInsertions1, lengthDeletions1 ) ) && + ( lastequality.length <= Math.max( lengthInsertions2, + lengthDeletions2 ) ) ) { -}); + // Duplicate record. + diffs.splice( + equalities[ equalitiesLength - 1 ], + 0, + [ DIFF_DELETE, lastequality ] + ); -function stripHtml( string ) { - // strip tags, html entity and whitespaces - return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/\"/g, "").replace(/\s+/g, ""); -} + // Change second copy to insert. + diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT; -QUnit.log(function( details ) { - var assertList, assertLi, - message, expected, actual, diff, - showDiff = false, - testItem = id( "qunit-test-output-" + details.testId ); + // Throw away the equality we just deleted. + equalitiesLength--; - if ( !testItem ) { - return; - } + // Throw away the previous equality (it needs to be reevaluated). + equalitiesLength--; + pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1; - message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); - message = "" + message + ""; - message += "@ " + details.runtime + " ms"; + // Reset the counters. + lengthInsertions1 = 0; + lengthDeletions1 = 0; + lengthInsertions2 = 0; + lengthDeletions2 = 0; + lastequality = null; + changes = true; + } + } + pointer++; + } - // pushFailure doesn't provide details.expected - // when it calls, it's implicit to also not show expected and diff stuff - // Also, we need to check details.expected existence, as it can exist and be undefined - if ( !details.result && hasOwn.call( details, "expected" ) ) { - if ( details.negative ) { - expected = escapeText( "NOT " + QUnit.dump.parse( details.expected ) ); - } else { - expected = escapeText( QUnit.dump.parse( details.expected ) ); + // Normalize the diff. + if ( changes ) { + this.diffCleanupMerge( diffs ); } - actual = escapeText( QUnit.dump.parse( details.actual ) ); - message += ""; + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = 1; + while ( pointer < diffs.length ) { + if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE && + diffs[ pointer ][ 0 ] === DIFF_INSERT ) { + deletion = diffs[ pointer - 1 ][ 1 ]; + insertion = diffs[ pointer ][ 1 ]; + overlapLength1 = this.diffCommonOverlap( deletion, insertion ); + overlapLength2 = this.diffCommonOverlap( insertion, deletion ); + if ( overlapLength1 >= overlapLength2 ) { + if ( overlapLength1 >= deletion.length / 2 || + overlapLength1 >= insertion.length / 2 ) { - if ( actual !== expected ) { + // Overlap found. Insert an equality and trim the surrounding edits. + 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 ); + pointer++; + } + } else { + if ( overlapLength2 >= deletion.length / 2 || + overlapLength2 >= insertion.length / 2 ) { - message += ""; + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + diffs.splice( + pointer, + 0, + [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] + ); - // 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; + diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT; + diffs[ pointer - 1 ][ 1 ] = + insertion.substring( 0, insertion.length - overlapLength2 ); + diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE; + diffs[ pointer + 1 ][ 1 ] = + deletion.substring( overlapLength2 ); + pointer++; + } + } + pointer++; } + pointer++; + } + }; - // Don't show diff if expected and actual are totally different - if ( showDiff ) { - message += ""; - } - } else if ( expected.indexOf( "[object Array]" ) !== -1 || - expected.indexOf( "[object Object]" ) !== -1 ) { - message += ""; + /** + * Determine if the suffix of one string is the prefix of another. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of the first + * string and the start of the second string. + * @private + */ + DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) { + var text1Length, text2Length, textLength, + best, length, pattern, found; + + // Cache the text lengths to prevent multiple calls. + text1Length = text1.length; + text2Length = text2.length; + + // Eliminate the null case. + if ( text1Length === 0 || text2Length === 0 ) { + return 0; } - if ( details.source ) { - message += ""; + // Truncate the longer string. + if ( text1Length > text2Length ) { + text1 = text1.substring( text1Length - text2Length ); + } else if ( text1Length < text2Length ) { + text2 = text2.substring( 0, text1Length ); } + textLength = Math.min( text1Length, text2Length ); - message += "
        Expected:
        " +
        -			expected +
        -			"
        Result:
        " +
        -				actual + "
        Diff:
        " +
        -					diff + "
        Message: " + - "Diff suppressed as the depth of object is more than current max depth (" + - QUnit.config.maxDepth + ").

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

        Source:
        " +
        -				escapeText( details.source ) + "
        "; + // Quick check for the worst case. + if ( text1 === text2 ) { + return textLength; + } - // this occours when pushFailure is set and we have an extracted stack trace - } else if ( !details.result && details.source ) { - message += "" + - "" + - "
        Source:
        " +
        -			escapeText( details.source ) + "
        "; - } + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ + best = 0; + length = 1; + while ( true ) { + pattern = text1.substring( textLength - length ); + found = text2.indexOf( pattern ); + if ( found === -1 ) { + return best; + } + length += found; + if ( found === 0 || text1.substring( textLength - length ) === + text2.substring( 0, length ) ) { + best = length; + length++; + } + } + }; - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + /** + * Split two texts into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {{chars1: string, chars2: string, lineArray: !Array.}} + * An object containing the encoded text1, the encoded text2 and + * the array of unique strings. + * The zeroth element of the array of unique strings is intentionally blank. + * @private + */ + DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) { + var lineArray, lineHash, chars1, chars2; + lineArray = []; // E.g. lineArray[4] === 'Hello\n' + lineHash = {}; // E.g. lineHash['Hello\n'] === 4 - assertLi = document.createElement( "li" ); - assertLi.className = details.result ? "pass" : "fail"; - assertLi.innerHTML = message; - assertList.appendChild( assertLi ); -}); + // '\x00' is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray[ 0 ] = ""; -QUnit.testDone(function( details ) { - var testTitle, time, testItem, assertList, - good, bad, testCounts, skipped, sourceName, - tests = id( "qunit-tests" ); + /** + * Split a text into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * Modifies linearray and linehash through being a closure. + * @param {string} text String to encode. + * @return {string} Encoded string. + * @private + */ + function diffLinesToCharsMunge( text ) { + var chars, lineStart, lineEnd, lineArrayLength, line; + chars = ""; - if ( !tests ) { - return; - } + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + lineStart = 0; + lineEnd = -1; - testItem = id( "qunit-test-output-" + details.testId ); + // Keeping our own length variable is faster than looking it up. + lineArrayLength = lineArray.length; + while ( lineEnd < text.length - 1 ) { + lineEnd = text.indexOf( "\n", lineStart ); + if ( lineEnd === -1 ) { + lineEnd = text.length - 1; + } + line = text.substring( lineStart, lineEnd + 1 ); + lineStart = lineEnd + 1; - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; + if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) : + ( lineHash[ line ] !== undefined ) ) { + chars += String.fromCharCode( lineHash[ line ] ); + } else { + chars += String.fromCharCode( lineArrayLength ); + lineHash[ line ] = lineArrayLength; + lineArray[ lineArrayLength++ ] = line; + } + } + return chars; + } - good = details.passed; - bad = details.failed; + chars1 = diffLinesToCharsMunge( text1 ); + chars2 = diffLinesToCharsMunge( text2 ); + return { + chars1: chars1, + chars2: chars2, + lineArray: lineArray + }; + }; - // store result when possible - if ( config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param {!Array.} diffs Array of diff tuples. + * @param {!Array.} lineArray Array of unique strings. + * @private + */ + DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) { + var x, chars, text, y; + for ( x = 0; x < diffs.length; x++ ) { + chars = diffs[ x ][ 1 ]; + text = []; + for ( y = 0; y < chars.length; y++ ) { + text[ y ] = lineArray[ chars.charCodeAt( y ) ]; + } + diffs[ x ][ 1 ] = text.join( "" ); } - } + }; - if ( bad === 0 ) { + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {!Array.} diffs Array of diff tuples. + */ + DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) { + var pointer, countDelete, countInsert, textInsert, textDelete, + commonlength, changes, diffPointer, position; + diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end. + pointer = 0; + countDelete = 0; + countInsert = 0; + textDelete = ""; + textInsert = ""; + commonlength; + while ( pointer < diffs.length ) { + switch ( diffs[ pointer ][ 0 ] ) { + case DIFF_INSERT: + countInsert++; + textInsert += diffs[ pointer ][ 1 ]; + pointer++; + break; + case DIFF_DELETE: + countDelete++; + textDelete += diffs[ pointer ][ 1 ]; + pointer++; + break; + case DIFF_EQUAL: - // Collapse the passing tests - addClass( assertList, "qunit-collapsed" ); - } else if ( bad && config.collapse && !collapseNext ) { + // Upon reaching an equality, check for prior redundancies. + if ( countDelete + countInsert > 1 ) { + if ( countDelete !== 0 && countInsert !== 0 ) { - // Skip collapsing the first failing test - collapseNext = true; - } else { + // Factor out any common prefixes. + commonlength = this.diffCommonPrefix( textInsert, textDelete ); + if ( commonlength !== 0 ) { + if ( ( pointer - countDelete - countInsert ) > 0 && + diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] === + DIFF_EQUAL ) { + diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] += + textInsert.substring( 0, commonlength ); + } else { + diffs.splice( 0, 0, [ DIFF_EQUAL, + textInsert.substring( 0, commonlength ) + ] ); + pointer++; + } + textInsert = textInsert.substring( commonlength ); + textDelete = textDelete.substring( commonlength ); + } - // Collapse remaining tests - addClass( assertList, "qunit-collapsed" ); - } + // Factor out any common suffixies. + commonlength = this.diffCommonSuffix( textInsert, textDelete ); + if ( commonlength !== 0 ) { + diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length - + commonlength ) + diffs[ pointer ][ 1 ]; + textInsert = textInsert.substring( 0, textInsert.length - + commonlength ); + textDelete = textDelete.substring( 0, textDelete.length - + commonlength ); + } + } - // testItem.firstChild is the test name - testTitle = testItem.firstChild; + // Delete the offending records and add the merged ones. + if ( countDelete === 0 ) { + diffs.splice( pointer - countInsert, + countDelete + countInsert, [ DIFF_INSERT, textInsert ] ); + } else if ( countInsert === 0 ) { + diffs.splice( pointer - countDelete, + countDelete + countInsert, [ DIFF_DELETE, textDelete ] ); + } else { + 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 ) { - testCounts = bad ? - "" + bad + ", " + "" + good + ", " : - ""; + // Merge this equality with the previous one. + diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ]; + diffs.splice( pointer, 1 ); + } else { + pointer++; + } + countInsert = 0; + countDelete = 0; + textDelete = ""; + textInsert = ""; + break; + } + } + if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) { + diffs.pop(); // Remove the dummy entry at the end. + } - testTitle.innerHTML += " (" + testCounts + - details.assertions.length + ")"; + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + changes = false; + pointer = 1; - if ( details.skipped ) { - testItem.className = "skipped"; - skipped = document.createElement( "em" ); - skipped.className = "qunit-skipped-label"; - skipped.innerHTML = "skipped"; - testItem.insertBefore( skipped, testTitle ); - } else { - addEvent( testTitle, "click", function() { - toggleClass( assertList, "qunit-collapsed" ); - }); + // 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 ) { - testItem.className = bad ? "fail" : "pass"; + diffPointer = diffs[ pointer ][ 1 ]; + position = diffPointer.substring( + diffPointer.length - diffs[ pointer - 1 ][ 1 ].length + ); - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = details.runtime + " ms"; - testItem.insertBefore( time, assertList ); - } + // This is a single edit surrounded by equalities. + if ( position === diffs[ pointer - 1 ][ 1 ] ) { - // Show the source of the test when showing assertions - if ( details.source ) { - sourceName = document.createElement( "p" ); - sourceName.innerHTML = "Source: " + details.source; - addClass( sourceName, "qunit-source" ); - if ( bad === 0 ) { - addClass( sourceName, "qunit-collapsed" ); + // 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.splice( pointer - 1, 1 ); + changes = true; + } 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 ] = + diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) + + diffs[ pointer + 1 ][ 1 ]; + diffs.splice( pointer + 1, 1 ); + changes = true; + } + } + pointer++; } - addEvent( testTitle, "click", function() { - toggleClass( sourceName, "qunit-collapsed" ); - }); - testItem.appendChild( sourceName ); - } -}); -if ( defined.document ) { + // If shifts were made, the diff needs reordering and another shift sweep. + if ( changes ) { + this.diffCleanupMerge( diffs ); + } + }; - // Avoid readyState issue with phantomjs - // Ref: #818 - var notPhantom = ( function( p ) { - return !( p && p.version && p.version.major > 0 ); - } )( window.phantom ); + return function( o, n ) { + var diff, output, text; + diff = new DiffMatchPatch(); + output = diff.DiffMain( o, n ); + diff.diffCleanupEfficiency( output ); + text = diff.diffPrettyHtml( output ); - if ( notPhantom && document.readyState === "complete" ) { - QUnit.load(); - } else { - addEvent( window, "load", QUnit.load ); - } -} else { - config.pageLoaded = true; - config.autorun = true; -} + return text; + }; +}() ); -})(); +}() ); diff --git a/package.json b/package.json index f273608b8..ba9883826 100644 --- a/package.json +++ b/package.json @@ -39,18 +39,25 @@ "grunt-eslint": "20.0.0", "grunt-git-authors": "3.2.0", "grunt-jsonlint": "1.1.0", + "grunt-karma": "2.0.0", "grunt-newer": "1.3.0", "grunt-npmcopy": "0.1.0", "gzip-js": "0.3.2", "husky": "0.14.3", "insight": "0.8.4", "jsdom": "5.6.1", + "karma": "1.7.0", + "karma-browserstack-launcher": "1.3.0", + "karma-chrome-launcher": "2.2.0", + "karma-firefox-launcher": "1.0.1", + "karma-qunit": "1.2.1", "load-grunt-tasks": "3.5.2", "native-promise-only": "0.8.1", "promises-aplus-tests": "2.1.2", "q": "1.5.0", - "qunit-assert-step": "1.1.1", - "qunitjs": "2.4.0", + "qunit-assert-step": "1.0.3", + "qunitjs": "1.23.1", + "raw-body": "2.2.0", "requirejs": "2.3.3", "sinon": "2.3.7", "sizzle": "2.3.3", @@ -61,8 +68,9 @@ "scripts": { "build": "npm install && grunt", "start": "grunt watch", - "test": "grunt && grunt test:slow", - "precommit": "grunt lint:newer", + "test": "grunt && grunt test:slow karma:main", + "jenkins": "grunt && grunt test:slow", + "precommit": "grunt lint:newer qunit_fixture", "commitmsg": "node node_modules/commitplease" }, "commitplease": { diff --git a/test/data/ajax/content-type.php b/test/data/ajax/content-type.php deleted file mode 100644 index 162e3636d..000000000 --- a/test/data/ajax/content-type.php +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/test/data/ajax/evalScript.php b/test/data/ajax/evalScript.php deleted file mode 100644 index ea9b8c55f..000000000 --- a/test/data/ajax/evalScript.php +++ /dev/null @@ -1 +0,0 @@ -ok( "" === "GET", "request method is " ); \ No newline at end of file diff --git a/test/data/ajax/method.php b/test/data/ajax/method.php deleted file mode 100644 index d76ff964b..000000000 --- a/test/data/ajax/method.php +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/data/ajax/unreleasedXHR.html b/test/data/ajax/unreleasedXHR.html index 3eedaabf8..51c5b9486 100644 --- a/test/data/ajax/unreleasedXHR.html +++ b/test/data/ajax/unreleasedXHR.html @@ -15,7 +15,7 @@ jQuery(function() { }, 200 ); var number = 50; while( number-- ) { - jQuery.ajax("../name.php?wait=10"); + jQuery.ajax("../mock.php?action=wait&wait=10"); } }); diff --git a/test/data/atom+xml.php b/test/data/atom+xml.php deleted file mode 100644 index 944591abf..000000000 --- a/test/data/atom+xml.php +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/test/data/core/dont_return.php b/test/data/core/dont_return.php deleted file mode 100644 index 1eef33692..000000000 --- a/test/data/core/dont_return.php +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/test/data/core/dynamic_ready.html b/test/data/core/dynamic_ready.html index 1db068b95..e8180cd2b 100644 --- a/test/data/core/dynamic_ready.html +++ b/test/data/core/dynamic_ready.html @@ -7,7 +7,7 @@ - + + + + + + +

        CSP Test Page

        + + diff --git a/test/data/echoData.php b/test/data/echoData.php deleted file mode 100644 index a37ba515a..000000000 --- a/test/data/echoData.php +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/data/echoQuery.php b/test/data/echoQuery.php deleted file mode 100644 index b72f329c9..000000000 --- a/test/data/echoQuery.php +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/data/errorWithJSON.php b/test/data/errorWithJSON.php deleted file mode 100644 index 62b187ecc..000000000 --- a/test/data/errorWithJSON.php +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/test/data/event/interactiveReady.html b/test/data/event/interactiveReady.html index 3fb25f907..a80b79467 100644 --- a/test/data/event/interactiveReady.html +++ b/test/data/event/interactiveReady.html @@ -18,7 +18,7 @@ jQuery( function () { oldIE into thinking the dom is ready, but it's not... leaving this check here for future trailblazers to attempt fixing this...--> - +
        diff --git a/test/data/event/syncReady.html b/test/data/event/syncReady.html index 61a50e423..5aa510459 100644 --- a/test/data/event/syncReady.html +++ b/test/data/event/syncReady.html @@ -18,7 +18,7 @@ jQuery( document ).ready(function () { oldIE into thinking the dom is ready, but it's not... leaving this check here for future trailblazers to attempt fixing this...--> - +
        diff --git a/test/data/headers.php b/test/data/headers.php deleted file mode 100644 index 79c183055..000000000 --- a/test/data/headers.php +++ /dev/null @@ -1,23 +0,0 @@ - $value ) { - - $key = str_replace( "_" , "-" , substr( $key , 0 , 5 ) == "HTTP_" ? substr( $key , 5 ) : $key ); - $headers[ $key ] = $value; - -} - -foreach( explode( "_" , $_GET[ "keys" ] ) as $key ) { - - // Only echo if key exists in the header - if ( isset( $headers[ strtoupper( $key ) ] ) ) { - echo "$key: " . @$headers[ strtoupper( $key ) ] . "\n"; - } - -} diff --git a/test/data/if_modified_since.php b/test/data/if_modified_since.php deleted file mode 100644 index 098b7da85..000000000 --- a/test/data/if_modified_since.php +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/test/data/json.php b/test/data/json.php deleted file mode 100644 index d6e0f2fc7..000000000 --- a/test/data/json.php +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/test/data/jsonp.php b/test/data/jsonp.php deleted file mode 100644 index 6c13d72e9..000000000 --- a/test/data/jsonp.php +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/test/data/longLoadScript.php b/test/data/longLoadScript.php deleted file mode 100644 index ba47168b4..000000000 --- a/test/data/longLoadScript.php +++ /dev/null @@ -1,4 +0,0 @@ - \ No newline at end of file diff --git a/test/data/mock.php b/test/data/mock.php new file mode 100644 index 000000000..692c417e8 --- /dev/null +++ b/test/data/mock.php @@ -0,0 +1,244 @@ +query['contentType']; + header("Content-type: $type"); + echo $req->query['response']; + } + + protected function wait( $req ) { + $wait = (int) $req->query['wait']; + sleep( $wait ); + if ( isset( $req->query['script'] ) ) { + header( 'Content-type: text/javascript' ); + } else { + header( 'Content-type: text/html' ); + echo 'ERROR '; + } + } + + protected function name( $req ) { + if ( $req->query['name'] === 'foo' ) { + echo 'bar'; + } elseif ( $_POST['name'] === 'peter' ) { + echo 'pan'; + } else { + echo 'ERROR'; + } + } + + protected function xml( $req ) { + header( 'Content-type: text/xml' ); + if ( $req->query['cal'] !== '5-2' && $_POST['cal'] !== '5-2' ) { + echo 'ERROR'; + return; + } + echo "5-23\n"; + } + + protected function atom( $req ) { + header( 'Content-type: atom+xml' ); + echo ''; + } + + protected function script( $req ) { + if ( isset( $req->query['header'] ) ) { + if ( $req->query['header'] === 'ecma' ) { + header( 'Content-type: application/ecmascript' ); + } else { + header( 'Content-type: text/javascript' ); + } + } else { + header( 'Content-type: text/html' ); + } + echo 'ok( true, "mock executed" );'; + } + + // Used to be in test.js, but was renamed to testbar.php + // https://github.com/jquery/jquery/commit/d89c278a33#commitcomment-23423165 + protected function testbar( $req ) { + echo 'this.testBar = "bar"; +jQuery("#ap").html("bar"); +ok( true, "mock executed");'; + } + + protected function json( $req ) { + if ( isset( $req->query['header'] ) ) { + header( 'Content-type: application/json' ); + } + + if ( isset( $req->query['array'] ) ) { + echo '[ {"name": "John", "age": 21}, {"name": "Peter", "age": 25 } ]'; + } else { + echo '{ "data": {"lang": "en", "length": 25} }'; + } + } + + protected function jsonp( $req ) { + if ( isset( $req->query['callback'] ) ) { + $callback = $req->query['callback']; + } elseif ( $req->method === 'GET' ) { + // Try REST-like path + preg_match( '/\/([^\/?]+)\?.+$/', $req->url, $m ); + $callback = $m[1]; + } else { + $callback = $_POST['callback']; + } + if ( isset( $req->query['array'] ) ) { + echo $callback . '([ {"name": "John", "age": 21}, {"name": "Peter", "age": 25 } ])'; + } else { + echo $callback . '({ "data": {"lang": "en", "length": 25} })'; + } + } + + protected function xmlOverJsonp( $req ) { + $callback = $_REQUEST['callback']; + $text = json_encode( file_get_contents( __DIR__ . '/with_fries.xml' ) ); + echo "$callback($text)\n"; + } + + protected function error( $req ) { + header( 'HTTP/1.0 400 Bad Request' ); + if ( isset( $req->query['json'] ) ) { + header( 'Content-Type: application/json' ); + echo '{ "code": 40, "message": "Bad Request" }'; + } else { + echo 'plain text message'; + } + } + + protected function headers( $req ) { + header( 'Sample-Header: Hello World' ); + header( 'Empty-Header: ' ); + header( 'Sample-Header2: Hello World 2' ); + + foreach ( explode( '|' , $req->query[ 'keys' ] ) as $key ) { + // Only echo if key exists in the header + if ( isset( $req->headers[ strtoupper( $key ) ] ) ) { + echo "$key: " . $req->headers[ strtoupper( $key ) ] . "\n"; + } + } + + } + + protected function echoData( $req ) { + echo file_get_contents('php://input'); + } + + protected function echoQuery( $req ) { + echo $_SERVER['QUERY_STRING']; + } + + protected function echoMethod( $req ) { + echo $req->method; + } + + protected function echoHtml( $req ) { + header( 'Content-type: text/html' ); + echo '
        ' . $req->method . '
        '; + echo '
        ' . $_SERVER['QUERY_STRING'] . '
        '; + echo '
        ' . file_get_contents('php://input') . '
        '; + } + + protected function etag( $req ) { + $hash = md5( $req->query['ts'] ); + $etag = 'W/"' . $hash . '"'; + + $ifNoneMatch = isset( $req->headers['IF-NONE-MATCH'] ) ? $req->headers['IF-NONE-MATCH'] : ''; + if ($ifNoneMatch === $etag) { + header('HTTP/1.0 304 Not Modified'); + return; + } + + header("Etag: $etag"); + echo "ETag: $etag\n"; + if ( $ifNoneMatch ) { + echo "If-None-Match: $ifNoneMatch\n"; + } + } + + protected function ims( $req ) { + $ts = $req->query['ts']; + + $ims = isset( $req->headers['IF-MODIFIED-SINCE'] ) ? $req->headers['IF-MODIFIED-SINCE'] : ''; + if ($ims === $ts) { + header('HTTP/1.0 304 Not Modified'); + return; + } + + header("Last-Modified: $ts"); + echo "Last-Modified: $ts\n"; + if ( $ims ) { + echo "If-Modified-Since: $ims\n"; + } + } + + protected function status( $req ) { + header( "HTTP/1.0 {$req->query['code']} {$req->query['text']}" ); + } + + protected function testHTML( $req ) { + header( 'Content-type: text/html' ); + $html = file_get_contents( __DIR__ . '/test.include.html' ); + $html = str_replace( '{{baseURL}}', $req->query['baseURL'], $html ); + echo $html; + } + + protected function cspFrame( $req ) { + // This is CSP only for browsers with "Content-Security-Policy" header support + // i.e. no old WebKit or old Firefox + header( "Content-Security-Policy: default-src 'self'; report-uri ./mock.php?action=cspLog" ); + header( 'Content-type: text/html' ); + echo file_get_contents( __DIR__ . '/csp.include.html' ); + } + + protected function cspLog( $req ) { + file_put_contents( $this->cspFile, 'error' ); + } + + protected function cspClean( $req ) { + file_put_contents( $this->cspFile, '' ); + unlink( $this->cspFile ); + } + + public function __construct() { + $this->cspFile = __DIR__ . '/support/csp.log'; + } + + public function respond( stdClass $req ) { + if ( !isset( $req->query['action'] ) || !method_exists( $this, $req->query['action'] ) ) { + header( "HTTP/1.0 400 Bad Request" ); + echo "Invalid action query.\n"; + return; + } + $this->{$req->query['action']}( $req ); + } +} + +// Don't include PHP errors in http response +error_reporting( 0 ); + +// Collect headers +$headers = array(); +foreach ( $_SERVER as $name => $value ) { + if ( substr( $name, 0, 5 ) === 'HTTP_' ) { + $name = str_replace( '_', '-', substr( $name, 5 ) ); + $headers[$name] = $value; + } elseif ( $name === 'CONTENT_LENGTH' ) { + $headers['CONTENT-LENGTH'] = $value; + } elseif ( $name === 'CONTENT_TYPE' ) { + $headers['CONTENT-TYPE'] = $value; + } +} + +$mock = new MockServer(); +$req = (object) array( + 'query' => $_GET, + 'headers' => $headers, + 'method' => $_SERVER['REQUEST_METHOD'], + 'url' => $_SERVER['REQUEST_URI'], +); +$mock->respond( $req ); diff --git a/test/data/name.php b/test/data/name.php deleted file mode 100644 index 64028585d..000000000 --- a/test/data/name.php +++ /dev/null @@ -1,24 +0,0 @@ -$xml$result"; - die(); -} -$name = $_REQUEST['name']; -if($name == 'foo') { - echo "bar"; - die(); -} else if($name == 'peter') { - echo "pan"; - die(); -} - -echo 'ERROR '; -?> \ No newline at end of file diff --git a/test/data/nocontent.php b/test/data/nocontent.php deleted file mode 100644 index 9c8431bd7..000000000 --- a/test/data/nocontent.php +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/test/data/params_html.php b/test/data/params_html.php deleted file mode 100644 index e88ef1521..000000000 --- a/test/data/params_html.php +++ /dev/null @@ -1,12 +0,0 @@ -
        -$value ) - echo "$value"; -?> -
        -
        -$value ) - echo "$value"; -?> -
        \ No newline at end of file diff --git a/test/data/qunit-fixture.html b/test/data/qunit-fixture.html new file mode 100644 index 000000000..e0fd3e60e --- /dev/null +++ b/test/data/qunit-fixture.html @@ -0,0 +1,237 @@ +

        See this blog entry for more information.

        +

        + Here are some links in a normal paragraph: Google, + Google Groups (Link). + This link has class="blog": + diveintomark + +

        +
        +

        Everything inside the red border is inside a div with id="foo".

        +

        This is a normal link: Yahoo

        +

        This link has class="blog": Simon Willison's Weblog

        + +
        +
        +
        +
        + +

        Try them out:

        +
          +
            +
            + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + test element +
            +Float test. + +
            + + +
            +
            + +
            + + + + +
            + +
            + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            +
            +
            + +
            +
            hi there
            +
            +
            +
            +
            +
            +
            +
            +
            + +
            +
              +
            1. Rice
            2. +
            3. Beans
            4. +
            5. Blinis
            6. +
            7. Tofu
            8. +
            + +
            I'm hungry. I should...
            + ...Eat lots of food... | + ...Eat a little food... | + ...Eat no food... + ...Eat a burger... + ...Eat some funyuns... + ...Eat some funyuns... + + + + + + +
            + +
            + + +
            + +
            + 1 + 2 + + + + + + + + +
            +
            +
            +
            fadeIn
            fadeIn
            +
            fadeOut
            fadeOut
            + +
            show
            show
            +
            hide
            hide
            +
            hide
            hide
            + +
            togglein
            togglein
            +
            toggleout
            toggleout
            +
            toggleout
            toggleout
            + +
            slideUp
            slideUp
            +
            slideDown
            slideDown
            +
            slideUp
            slideUp
            + +
            slideToggleIn
            slideToggleIn
            +
            slideToggleOut
            slideToggleOut
            + +
            fadeToggleIn
            fadeToggleIn
            +
            fadeToggleOut
            fadeToggleOut
            + +
            fadeTo
            fadeTo
            +
            + +
            + +
            diff --git a/test/data/qunit-fixture.js b/test/data/qunit-fixture.js new file mode 100644 index 000000000..6ea2f3b3d --- /dev/null +++ b/test/data/qunit-fixture.js @@ -0,0 +1,4 @@ +// Generated by build/tasks/qunit_fixture.js +QUnit.config.fixture = "

            See this blog entry for more information.

            \n

            \n\tHere are some links in a normal paragraph: Google,\n\tGoogle Groups (Link).\n\tThis link has class=\"blog\":\n\tdiveintomark\n\n

            \n
            \n\t

            Everything inside the red border is inside a div with id=\"foo\".

            \n\t

            This is a normal link: Yahoo

            \n\t

            This link has class=\"blog\": Simon Willison's Weblog

            \n\n
            \n
            \n\t
            \n
            \n\n

            Try them out:

            \n
              \n
                \n
                \n\t\n\t\n\t\n\t\n\n\t\n\t\n\t\n\n\t\n\t\n\n\t\n\t\n\n\t\n\n\t\n\n\t\n\t\n\t\n\t\n\t\n\n\t\n\t\t\n\t\t\n\t\n\n\t\n\t\n\t\n\t\n\t\n\t\n\n\ttest element\n
                \nFloat test.\n\n
                \n\t\n\t\n
                \n
                \n\n
                \n\t\n\t\n\t\n\t\n
                \n\n
                \n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\t\n\t\n
                \n
                \n\t
                \n\t\t
                \n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t
                \n\t
                \n\t
                hi there
                \n\t
                \n\t\t
                hidden
                \n\t
                \n\t
                \n\t\t
                \n\t
                \n\t
                \n
                \n\n
                \n\t
                  \n\t\t
                1. Rice
                2. \n\t\t
                3. Beans
                4. \n\t\t
                5. Blinis
                6. \n\t\t
                7. Tofu
                8. \n\t
                \n\n\t
                I'm hungry. I should...
                \n\t...Eat lots of food... |\n\t...Eat a little food... |\n\t...Eat no food...\n\t...Eat a burger...\n\t...Eat some funyuns...\n\t...Eat some funyuns...\n\t\n\t\n\t\n\t\n\t\t\n\t\n
                \n\n
                \n\t\n\t\n
                \n\n
                \n\t1\n\t2\n\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n
                \n
                \n\t
                \n\t\t
                fadeIn
                fadeIn
                \n\t\t
                fadeOut
                fadeOut
                \n\n\t\t
                show
                show
                \n\t\t
                hide
                hide
                \n\t\t
                hide
                hide
                \n\n\t\t
                togglein
                togglein
                \n\t\t
                toggleout
                toggleout
                \n\t\t
                toggleout
                toggleout
                \n\n\t\t
                slideUp
                slideUp
                \n\t\t
                slideDown
                slideDown
                \n\t\t
                slideUp
                slideUp
                \n\n\t\t
                slideToggleIn
                slideToggleIn
                \n\t\t
                slideToggleOut
                slideToggleOut
                \n\n\t\t
                fadeToggleIn
                fadeToggleIn
                \n\t\t
                fadeToggleOut
                fadeToggleOut
                \n\n\t\t
                fadeTo
                fadeTo
                \n\t
                \n\n\t
                \n\t\n
                \n"; +// Compat with QUnit 1.x: +document.getElementById( "qunit-fixture" ).innerHTML = QUnit.config.fixture; diff --git a/test/data/script.php b/test/data/script.php deleted file mode 100644 index fb7110491..000000000 --- a/test/data/script.php +++ /dev/null @@ -1,11 +0,0 @@ - -ok( true, "Script executed correctly." ); diff --git a/test/data/statusText.php b/test/data/statusText.php deleted file mode 100644 index daf58ce3f..000000000 --- a/test/data/statusText.php +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/test/data/support/csp-clean.php b/test/data/support/csp-clean.php deleted file mode 100644 index e16d047a3..000000000 --- a/test/data/support/csp-clean.php +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/test/data/support/csp-log.php b/test/data/support/csp-log.php deleted file mode 100644 index efbb9d7bc..000000000 --- a/test/data/support/csp-log.php +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/test/data/support/csp.php b/test/data/support/csp.php deleted file mode 100644 index 446000239..000000000 --- a/test/data/support/csp.php +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - CSP Test Page - - - - - - -

                CSP Test Page

                - - diff --git a/test/data/test.html b/test/data/test.include.html similarity index 56% rename from test/data/test.html rename to test/data/test.include.html index f5bc2199f..73299db07 100644 --- a/test/data/test.html +++ b/test/data/test.include.html @@ -1,7 +1,7 @@ html text
                - + blabla diff --git a/test/data/test.php b/test/data/test.php deleted file mode 100644 index d93dafad8..000000000 --- a/test/data/test.php +++ /dev/null @@ -1,7 +0,0 @@ -html text
                - - -blabla diff --git a/test/data/testbar.php b/test/data/testbar.php deleted file mode 100644 index 21aa6882b..000000000 --- a/test/data/testbar.php +++ /dev/null @@ -1,3 +0,0 @@ -this.testBar = "bar"; -jQuery("#ap").html("bar"); -ok( true, "testbar.php executed"); diff --git a/test/data/testinit.js b/test/data/testinit.js index 1aa9a65da..d6c0236e6 100644 --- a/test/data/testinit.js +++ b/test/data/testinit.js @@ -1,6 +1,11 @@ /* eslint no-multi-str: "off" */ -var baseURL = "", +// baseURL is intentionally set to "data/" instead of "". +// This is not just for convenience (since most files are in data/) +// but also to ensure that urls without prefix fail. +// Otherwise it's easy to write tests that pass on test/index.html +// but fail in Karma runner (where the baseURL is different). +var baseURL = "data/", supportjQuery = this.jQuery, // see RFC 2606 @@ -148,11 +153,13 @@ window.fireNative = document.createEvent ? /** * Add random number to url to stop caching * - * @example url("data/test.html") - * @result "data/test.html?10538358428943" + * Also prefixes with baseURL automatically. * - * @example url("data/test.php?foo=bar") - * @result "data/test.php?foo=bar&10538358345554" + * @example url("index.html") + * @result "data/index.html?10538358428943" + * + * @example url("mock.php?foo=bar") + * @result "data/mock.php?foo=bar&10538358345554" */ function url( value ) { return baseURL + value + ( /\?/.test( value ) ? "&" : "?" ) + @@ -239,7 +246,7 @@ this.testIframe = function( title, fileName, func, wrapper ) { var done = assert.async(), $iframe = supportjQuery( " -
                -
                -

                See this blog entry for more information.

                -

                - Here are some links in a normal paragraph: Google, - Google Groups (Link). - This link has class="blog": - diveintomark - -

                -
                -

                Everything inside the red border is inside a div with id="foo".

                -

                This is a normal link: Yahoo

                -

                This link has class="blog": Simon Willison's Weblog

                - -
                -
                -
                -
                - -

                Try them out:

                -
                  -
                    -
                    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - test element -
                    - Float test. - -
                    - - -
                    -
                    - -
                    - - - - -
                    - -
                    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    -
                    -
                    - -
                    -
                    hi there
                    -
                    -
                    -
                    -
                    -
                    -
                    -
                    -
                    - -
                    -
                      -
                    1. Rice
                    2. -
                    3. Beans
                    4. -
                    5. Blinis
                    6. -
                    7. Tofu
                    8. -
                    - -
                    I'm hungry. I should...
                    - ...Eat lots of food... | - ...Eat a little food... | - ...Eat no food... - ...Eat a burger... - ...Eat some funyuns... - ...Eat some funyuns... - - - - - - -
                    - -
                    - - -
                    - -
                    - 1 - 2 - - - - - - - - -
                    -
                    -
                    -
                    fadeIn
                    fadeIn
                    -
                    fadeOut
                    fadeOut
                    - -
                    show
                    show
                    -
                    hide
                    hide
                    -
                    hide
                    hide
                    - -
                    togglein
                    togglein
                    -
                    toggleout
                    toggleout
                    -
                    toggleout
                    toggleout
                    - -
                    slideUp
                    slideUp
                    -
                    slideDown
                    slideDown
                    -
                    slideUp
                    slideUp
                    - -
                    slideToggleIn
                    slideToggleIn
                    -
                    slideToggleOut
                    slideToggleOut
                    - -
                    fadeToggleIn
                    fadeToggleIn
                    -
                    fadeToggleOut
                    fadeToggleOut
                    - -
                    fadeTo
                    fadeTo
                    -
                    - -
                    - -
                    -
                    -
                    - - - - +
                    + diff --git a/test/karma.context.html b/test/karma.context.html new file mode 100644 index 000000000..e4f8a4f88 --- /dev/null +++ b/test/karma.context.html @@ -0,0 +1,45 @@ + + + + + CONTEXT + + + + +
                    + + + + +
                    + + + + + + %SCRIPTS% + + + + + + diff --git a/test/karma.debug.html b/test/karma.debug.html new file mode 100644 index 000000000..950064db7 --- /dev/null +++ b/test/karma.debug.html @@ -0,0 +1,47 @@ + + + +%X_UA_COMPATIBLE% + DEBUG + + + + + + +
                    + + + + +
                    + + + + + + + %SCRIPTS% + + + + + + diff --git a/test/middleware-mockserver.js b/test/middleware-mockserver.js new file mode 100644 index 000000000..35623761d --- /dev/null +++ b/test/middleware-mockserver.js @@ -0,0 +1,284 @@ +/* eslint-env node */ +var url = require( "url" ); +var fs = require( "fs" ); +var getRawBody = require( "raw-body" ); + +var cspLog = ""; +/** + * Keep in sync with /test/mock.php + */ +var mocks = { + contentType: function( req, resp ) { + resp.writeHead( 200, { + "content-type": req.query.contentType + } ); + resp.end( req.query.response ); + }, + wait: function( req, resp ) { + var wait = Number( req.query.wait ) * 1000; + setTimeout( function() { + if ( req.query.script ) { + resp.writeHead( 200, { "content-type": "text/javascript" } ); + } else { + resp.writeHead( 200, { "content-type": "text/html" } ); + resp.end( "ERROR " ); + } + }, wait ); + }, + name: function( req, resp, next ) { + resp.writeHead( 200 ); + if ( req.query.name === "foo" ) { + resp.end( "bar" ); + return; + } + getBody( req ).then( function( body ) { + if ( body === "name=peter" ) { + resp.end( "pan" ); + } else { + resp.end( "ERROR" ); + } + }, next ); + }, + xml: function( req, resp, next ) { + var content = "5-23"; + resp.writeHead( 200, { "content-type": "text/xml" } ); + + if ( req.query.cal === "5-2" ) { + resp.end( content ); + return; + } + getBody( req ).then( function( body ) { + if ( body === "cal=5-2" ) { + resp.end( content ); + } else { + resp.end( "ERROR" ); + } + }, next ); + }, + atom: function( req, resp, next ) { + resp.writeHead( 200, { "content-type": "atom+xml" } ); + resp.end( "" ); + }, + script: function( req, resp ) { + if ( req.query.header === "ecma" ) { + resp.writeHead( 200, { "content-type": "application/ecmascript" } ); + } else if ( req.query.header ) { + resp.writeHead( 200, { "content-type": "text/javascript" } ); + } else { + resp.writeHead( 200, { "content-type": "text/html" } ); + } + resp.end( "ok( true, \"mock executed\" );" ); + }, + testbar: function( req, resp ) { + resp.writeHead( 200 ); + resp.end( + "this.testBar = 'bar'; " + + "jQuery('#ap').html('bar'); " + + "ok( true, 'mock executed');" + ); + }, + json: function( req, resp ) { + if ( req.query.header ) { + resp.writeHead( 200, { "content-type": "application/json" } ); + } + if ( req.query.array ) { + resp.end( JSON.stringify( + [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ] + ) ); + } else { + resp.end( JSON.stringify( + { data: { lang: "en", length: 25 } } + ) ); + } + }, + jsonp: function( req, resp, next ) { + var callback; + if ( req.query.callback ) { + callback = Promise.resolve( req.query.callback ); + } else if ( req.method === "GET" ) { + callback = Promise.resolve( req.url.match( /^.+\/([^\/?.]+)\?.+$/ )[ 1 ] ); + } else { + callback = getBody( req ).then( function( body ) { + return body.trim().replace( "callback=", "" ); + } ); + } + var json = req.query.array ? + JSON.stringify( + [ { name: "John", age: 21 }, { name: "Peter", age: 25 } ] + ) : + JSON.stringify( + { data: { lang: "en", length: 25 } } + ); + callback.then( function( cb ) { + resp.end( cb + "(" + json + ")" ); + }, next ); + }, + xmlOverJsonp: function( req, resp ) { + var callback = req.query.callback; + var body = fs.readFileSync( __dirname + "/data/with_fries.xml" ).toString(); + resp.writeHead( 200 ); + resp.end( callback + "(" + JSON.stringify( body ) + ")\n" ); + }, + error: function( req, resp ) { + if ( req.query.json ) { + resp.writeHead( 400, { "content-type": "application/json" } ); + resp.end( "{ \"code\": 40, \"message\": \"Bad Request\" }" ); + } else { + resp.writeHead( 400 ); + resp.end( "plain text message" ); + } + }, + headers: function( req, resp ) { + resp.writeHead( 200, { + "Sample-Header": "Hello World", + "Empty-Header": "", + "Sample-Header2": "Hello World 2" + } ); + req.query.keys.split( "|" ).forEach( function( key ) { + if ( req.headers[ key.toLowerCase() ] ) { + resp.write( key + ": " + req.headers[ key.toLowerCase() ] + "\n" ); + } + } ); + resp.end(); + }, + echoData: function( req, resp, next ) { + getBody( req ).then( function( body ) { + resp.end( body ); + }, next ); + }, + echoQuery: function( req, resp ) { + resp.end( req.parsed.search.slice( 1 ) ); + }, + echoMethod: function( req, resp ) { + resp.end( req.method ); + }, + echoHtml: function( req, resp, next ) { + resp.writeHead( 200, { "Content-Type": "text/html" } ); + resp.write( "
                    " + req.method + "
                    " ); + resp.write( "
                    " + req.parsed.search.slice( 1 ) + "
                    " ); + getBody( req ).then( function( body ) { + resp.write( "
                    " + body + "
                    " ); + resp.end( body ); + }, next ); + }, + etag: function( req, resp ) { + var hash = Number( req.query.ts ).toString( 36 ); + var etag = "W/\"" + hash + "\""; + if ( req.headers[ "if-none-match" ] === etag ) { + resp.writeHead( 304 ); + resp.end(); + return; + } + resp.writeHead( 200, { + "Etag": etag + } ); + resp.end(); + }, + ims: function( req, resp, next ) { + var ts = req.query.ts; + if ( req.headers[ "if-modified-since" ] === ts ) { + resp.writeHead( 304 ); + resp.end(); + return; + } + resp.writeHead( 200, { + "Last-Modified": ts + } ); + resp.end(); + }, + status: function( req, resp, next ) { + resp.writeHead( Number( req.query.code ) ); + resp.end(); + }, + testHTML: function( req, resp ) { + resp.writeHead( 200, { "Content-Type": "text/html" } ); + var body = fs.readFileSync( __dirname + "/data/test.include.html" ).toString(); + body = body.replace( /{{baseURL}}/g, req.query.baseURL ); + resp.end( body ); + }, + cspFrame: function( req, resp ) { + resp.writeHead( 200, { + "Content-Type": "text/html", + "Content-Security-Policy": "default-src 'self'; report-uri /base/test/data/mock.php?action=cspLog" + } ); + var body = fs.readFileSync( __dirname + "/data/csp.include.html" ).toString(); + resp.end( body ); + }, + cspLog: function( req, resp ) { + cspLog = "error"; + resp.writeHead( 200 ); + resp.end(); + }, + cspClean: function( req, resp ) { + cspLog = ""; + resp.writeHead( 200 ); + resp.end(); + } +}; +var handlers = { + "test/data/mock.php": function( req, resp, next ) { + if ( !mocks[ req.query.action ] ) { + resp.writeHead( 400 ); + resp.end( "Invalid action query.\n" ); + console.log( "Invalid action query:", req.method, req.url ); + return; + } + mocks[ req.query.action ]( req, resp, next ); + }, + "test/data/support/csp.log": function( req, resp ) { + resp.writeHead( 200 ); + resp.end( cspLog ); + }, + "test/data/404.txt": function( req, resp ) { + resp.writeHead( 404 ); + resp.end( "" ); + } +}; + +/** + * Connect-compatible middleware factory for mocking server responses. + * Used by Ajax unit tests when run via Karma. + * + * Despite Karma using Express, it uses Connect to deal with custom middleware, + * which passes the raw Node Request and Response objects instead of the + * Express versions of these (e.g. no req.path, req.query, resp.set). + */ +function MockserverMiddlewareFactory() { + /** + * @param {http.IncomingMessage} req + * @param {http.ServerResponse} resp + * @param {Function} next Continue request handling + */ + return function( req, resp, next ) { + var method = req.method, + parsed = url.parse( req.url, /* parseQuery */ true ), + path = parsed.pathname.replace( /^\/base\//, "" ), + query = parsed.query, + subReq = Object.assign( Object.create( req ), { + query: query, + parsed: parsed + } ); + + if ( /^test\/data\/mock.php\//.test( path ) ) { + // Support REST-like Apache PathInfo + path = "test\/data\/mock.php"; + } + + if ( !handlers[ path ] ) { + next(); + return; + } + + handlers[ path ]( subReq, resp, next ); + }; +} + +function getBody( req ) { + return req.method !== "POST" ? + Promise.resolve( "" ) : + getRawBody( req, { + encoding: true + } ); +} + +module.exports = MockserverMiddlewareFactory; diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 4dc7b9d3a..a8fd3a075 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -40,7 +40,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - success callbacks", 8, function( assert ) { return { setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxSuccess", assert ), - url: url( "data/name.html" ), + url: url( "name.html" ), beforeSend: function() { assert.ok( true, "beforeSend" ); }, @@ -57,7 +57,7 @@ QUnit.module( "ajax", { return { setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxSuccess", assert ), create: function( options ) { - return jQuery.ajax( url( "data/name.html" ), options ); + return jQuery.ajax( url( "name.html" ), options ); }, beforeSend: function() { assert.ok( true, "beforeSend" ); @@ -77,7 +77,7 @@ QUnit.module( "ajax", { create: function( options ) { options.crossDomain = true; options.dataType = "script"; - return jQuery.ajax( url( "data/script.php?header=ecma" ), options ); + return jQuery.ajax( url( "mock.php?action=script&header=ecma" ), options ); }, success: function() { assert.ok( true, "success" ); @@ -93,7 +93,7 @@ QUnit.module( "ajax", { return { create: function( options ) { options.crossDomain = true; - return jQuery.ajax( url( "data/script.php" ), options ); + return jQuery.ajax( url( "mock.php?action=script" ), options ); }, success: function() { assert.ok( true, "success" ); @@ -112,7 +112,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - success callbacks (late binding)", 8, function( assert ) { return { setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxSuccess", assert ), - url: url( "data/name.html" ), + url: url( "name.html" ), beforeSend: function() { assert.ok( true, "beforeSend" ); }, @@ -132,7 +132,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - success callbacks (oncomplete binding)", 8, function( assert ) { return { setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxSuccess", assert ), - url: url( "data/name.html" ), + url: url( "name.html" ), beforeSend: function() { assert.ok( true, "beforeSend" ); }, @@ -152,7 +152,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - error callbacks", 8, function( assert ) { return { setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxError", assert ), - url: url( "data/name.php?wait=5" ), + url: url( "mock.php?action=wait&wait=5" ), beforeSend: function() { assert.ok( true, "beforeSend" ); }, @@ -170,7 +170,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - textStatus and errorThrown values", 4, function( assert ) { return [ { - url: url( "data/name.php?wait=5" ), + url: url( "mock.php?action=wait&wait=5" ), error: function( _, textStatus, errorThrown ) { assert.strictEqual( textStatus, "abort", "textStatus is 'abort' for abort" ); assert.strictEqual( errorThrown, "abort", "errorThrown is 'abort' for abort" ); @@ -180,7 +180,7 @@ QUnit.module( "ajax", { } }, { - url: url( "data/name.php?wait=5" ), + url: url( "mock.php?action=wait&wait=5" ), error: function( _, textStatus, errorThrown ) { assert.strictEqual( textStatus, "mystatus", "textStatus is 'mystatus' for abort('mystatus')" ); assert.strictEqual( errorThrown, "mystatus", "errorThrown is 'mystatus' for abort('mystatus')" ); @@ -193,7 +193,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - responseText on error", 1, function( assert ) { return { - url: url( "data/errorWithText.php" ), + url: url( "mock.php?action=error" ), error: function( xhr ) { assert.strictEqual( xhr.responseText, "plain text message", "Test jqXHR.responseText is filled for HTTP errors" ); } @@ -204,7 +204,7 @@ QUnit.module( "ajax", { var previousUrl, firstTime = true; jQuery.ajax( { - url: url( "data/errorWithText.php" ), + url: url( "mock.php?action=error" ), error: function() { if ( firstTime ) { firstTime = false; @@ -212,10 +212,7 @@ QUnit.module( "ajax", { } else { assert.ok( true, "Test retrying with jQuery.ajax(this) works" ); jQuery.ajax( { - url: url( "data/errorWithText.php" ), - data: { - "x": 1 - }, + url: url( "mock.php?action=error&x=2" ), beforeSend: function() { if ( !previousUrl ) { previousUrl = this.url; @@ -241,7 +238,7 @@ QUnit.module( "ajax", { xhr.setRequestHeader( "ajax-send", "test" ); } ); }, - url: url( "data/headers.php?keys=siMPle_SometHing-elsE_OthEr_Nullable_undefined_Empty_ajax-send" ), + url: url( "mock.php?action=headers&keys=siMPle|SometHing-elsE|OthEr|Nullable|undefined|Empty|ajax-send" ), headers: { "siMPle": "value", "SometHing-elsE": "other value", @@ -281,7 +278,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - Accept header", 1, function( assert ) { return { - url: url( "data/headers.php?keys=accept" ), + url: url( "mock.php?action=headers&keys=accept" ), headers: { Accept: "very wrong accept value" }, @@ -297,14 +294,14 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - contentType", 2, function( assert ) { return [ { - url: url( "data/headers.php?keys=content-type" ), + url: url( "mock.php?action=headers&keys=content-type" ), contentType: "test", success: function( data ) { assert.strictEqual( data, "content-type: test\n", "Test content-type is sent when options.contentType is set" ); } }, { - url: url( "data/headers.php?keys=content-type" ), + url: url( "mock.php?action=headers&keys=content-type" ), contentType: false, success: function( data ) { @@ -330,34 +327,34 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - hash", 4, function( assert ) { return [ { - url: "data/name.html#foo", + url: baseURL + "name.html#foo", beforeSend: function( xhr, settings ) { - assert.equal( settings.url, "data/name.html#foo", "Make sure that the URL has its hash." ); + assert.equal( settings.url, baseURL + "name.html#foo", "Make sure that the URL has its hash." ); return false; }, error: true }, { - url: "data/name.html?abc#foo", + url: baseURL + "name.html?abc#foo", beforeSend: function( xhr, settings ) { - assert.equal( settings.url, "data/name.html?abc#foo", "Make sure that the URL has its hash." ); + assert.equal( settings.url, baseURL + "name.html?abc#foo", "Make sure that the URL has its hash." ); return false; }, error: true }, { - url: "data/name.html?abc#foo", + url: baseURL + "name.html?abc#foo", data: { "test": 123 }, beforeSend: function( xhr, settings ) { - assert.equal( settings.url, "data/name.html?abc&test=123#foo", "Make sure that the URL has its hash." ); + assert.equal( settings.url, baseURL + "name.html?abc&test=123#foo", "Make sure that the URL has its hash." ); return false; }, error: true }, { - url: "data/name.html?abc#brownies", + url: baseURL + "name.html?abc#brownies", data: { "devo": "hat" }, @@ -365,7 +362,7 @@ QUnit.module( "ajax", { beforeSend: function( xhr, settings ) { // Remove the random number, but ensure the cache-buster param is there var url = settings.url.replace( /\d+/, "" ); - assert.equal( url, "data/name.html?abc&devo=hat&_=#brownies", "Make sure that the URL has its hash." ); + assert.equal( url, baseURL + "name.html?abc&devo=hat&_=#brownies", "Make sure that the URL has its hash." ); return false; }, error: true @@ -492,7 +489,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - abort", 9, function( assert ) { return { setup: addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxError ajaxComplete", assert ), - url: url( "data/name.php?wait=5" ), + url: url( "mock.php?action=wait&wait=5" ), beforeSend: function() { assert.ok( true, "beforeSend" ); }, @@ -510,7 +507,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - native abort", 2, function( assert ) { return { - url: url( "data/name.php?wait=1" ), + url: url( "mock.php?action=wait&wait=1" ), xhr: function() { var xhr = new window.XMLHttpRequest(); setTimeout( function() { @@ -529,7 +526,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - native timeout", 2, function( assert ) { return { - url: url( "data/name.php?wait=1" ), + url: url( "mock.php?action=wait&wait=1" ), xhr: function() { var xhr = new window.XMLHttpRequest(); xhr.timeout = 1; @@ -566,13 +563,13 @@ QUnit.module( "ajax", { .ajaxSuccess( event ); }, requests: [ { - url: url( "data/name.html" ), + url: url( "name.html" ), context: context, beforeSend: callback( "beforeSend" ), success: callback( "success" ), complete: callback( "complete" ) }, { - url: url( "data/404.html" ), + url: url( "404.txt" ), context: context, beforeSend: callback( "beforeSend" ), error: callback( "error" ), @@ -588,7 +585,7 @@ QUnit.module( "ajax", { }; } return { - url: url( "data/404.html" ), + url: url( "404.txt" ), beforeSend: nocallback( "beforeSend" ), error: nocallback( "error" ), complete: nocallback( "complete" ) @@ -598,7 +595,7 @@ QUnit.module( "ajax", { ajaxTest( "#15118 - jQuery.ajax() - function without jQuery.event", 1, function( assert ) { var holder; return { - url: url( "data/json.php" ), + url: url( "mock.php?action=json" ), setup: function() { holder = jQuery.event; delete jQuery.event; @@ -622,7 +619,7 @@ QUnit.module( "ajax", { assert.equal( jqXHR.statusText, "abort", "jqXHR.statusText equals abort on global ajaxComplete and ajaxError events" ); } ); }, - url: url( "data/name.html" ), + url: url( "name.html" ), error: true, complete: function() { assert.ok( true, "complete" ); @@ -632,7 +629,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - context modification", 1, function( assert ) { return { - url: url( "data/name.html" ), + url: url( "name.html" ), context: {}, beforeSend: function() { this.test = "foo"; @@ -654,12 +651,12 @@ QUnit.module( "ajax", { assert.strictEqual( jQuery.ajaxSettings.context, obj, "Make sure the context is properly set in ajaxSettings." ); }, requests: [ { - url: url( "data/name.html" ), + url: url( "name.html" ), success: function() { assert.strictEqual( this, obj, "Make sure the original object is maintained." ); } }, { - url: url( "data/name.html" ), + url: url( "name.html" ), context: {}, success: function() { assert.ok( this !== obj, "Make sure overriding context is possible." ); @@ -672,7 +669,7 @@ QUnit.module( "ajax", { return { setup: addGlobalEvents( "", assert ), global: false, - url: url( "data/name.html" ), + url: url( "name.html" ), beforeSend: function() { assert.ok( true, "beforeSend" ); }, @@ -687,7 +684,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - xml: non-namespace elements inside namespaced elements", 3, function( assert ) { return { - url: url( "data/with_fries.xml" ), + url: url( "with_fries.xml" ), dataType: "xml", success: function( resp ) { assert.equal( jQuery( "properties", resp ).length, 1, "properties in responseXML" ); @@ -699,7 +696,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - xml: non-namespace elements inside namespaced elements (over JSONP)", 3, function( assert ) { return { - url: url( "data/with_fries_over_jsonp.php" ), + url: url( "mock.php?action=xmlOverJsonp" ), dataType: "jsonp xml", success: function( resp ) { assert.equal( jQuery( "properties", resp ).length, 1, "properties in responseXML" ); @@ -712,14 +709,14 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - HEAD requests", 2, function( assert ) { return [ { - url: url( "data/name.html" ), + url: url( "name.html" ), type: "HEAD", success: function( data, status, xhr ) { assert.ok( /Date/i.test( xhr.getAllResponseHeaders() ), "No Date in HEAD response" ); } }, { - url: url( "data/name.html" ), + url: url( "name.html" ), data: { "whip_it": "good" }, @@ -733,7 +730,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - beforeSend", 1, function( assert ) { return { - url: url( "data/name.html" ), + url: url( "name.html" ), beforeSend: function() { this.check = true; }, @@ -747,7 +744,7 @@ QUnit.module( "ajax", { return { create: function() { return jQuery.ajax( { - url: url( "data/name.html" ), + url: url( "name.html" ), beforeSend: function( xhr ) { assert.ok( true, "beforeSend got called, canceling" ); xhr.abort(); @@ -776,7 +773,7 @@ QUnit.module( "ajax", { Globals.register( "testBar" ); }, dataType: "html", - url: url( "data/test.html" ), + url: url( "mock.php?action=testHTML&baseURL=" + baseURL ), success: function( data ) { assert.ok( data.match( /^html text/ ), "Check content for datatype html" ); jQuery( "#ap" ).html( data ); @@ -788,7 +785,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - synchronous request", 1, function( assert ) { return { - url: url( "data/json_obj.js" ), + url: url( "json_obj.js" ), dataType: "text", async: false, success: true, @@ -800,7 +797,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - synchronous request with callbacks", 2, function( assert ) { return { - url: url( "data/json_obj.js" ), + url: url( "json_obj.js" ), async: false, dataType: "text", success: true, @@ -816,7 +813,7 @@ QUnit.module( "ajax", { } ); QUnit.asyncTest( "jQuery.ajax(), jQuery.get[Script|JSON](), jQuery.post(), pass-through request object", 8, function( assert ) { - var target = "data/name.html", + var target = "name.html", successCount = 0, errorCount = 0, errorEx = "", @@ -837,8 +834,8 @@ QUnit.module( "ajax", { assert.ok( jQuery.get( url( target ), success ), "get" ); assert.ok( jQuery.post( url( target ), success ), "post" ); - assert.ok( jQuery.getScript( url( "data/testbar.php" ), success ), "script" ); - assert.ok( jQuery.getJSON( url( "data/json_obj.js" ), success ), "json" ); + assert.ok( jQuery.getScript( url( "mock.php?action=testbar" ), success ), "script" ); + assert.ok( jQuery.getJSON( url( "json_obj.js" ), success ), "json" ); assert.ok( jQuery.ajax( { url: url( target ), success: success @@ -847,7 +844,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - cache", 28, function( assert ) { var re = /_=(.*?)(&|$)/g, - rootUrl = "data/text.php"; + rootUrl = baseURL + "text.txt"; function request( url, title ) { return { @@ -908,7 +905,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - JSONP - Query String (?n)" + label, 4, function( assert ) { return [ { - url: "data/jsonp.php?callback=?", + url: baseURL + "mock.php?action=jsonp&callback=?", dataType: "jsonp", crossDomain: crossDomain, success: function( data ) { @@ -916,7 +913,7 @@ QUnit.module( "ajax", { } }, { - url: "data/jsonp.php?callback=??", + url: baseURL + "mock.php?action=jsonp&callback=??", dataType: "jsonp", crossDomain: crossDomain, success: function( data ) { @@ -924,7 +921,7 @@ QUnit.module( "ajax", { } }, { - url: "data/jsonp.php/??", + url: baseURL + "mock.php/???action=jsonp", dataType: "jsonp", crossDomain: crossDomain, success: function( data ) { @@ -932,7 +929,7 @@ QUnit.module( "ajax", { } }, { - url: "data/jsonp.php/???json=1", + url: baseURL + "mock.php/???action=jsonp&array=1", dataType: "jsonp", crossDomain: crossDomain, success: function( data ) { @@ -953,7 +950,7 @@ QUnit.module( "ajax", { }; }, requests: [ { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, jsonp: "callback", @@ -961,7 +958,7 @@ QUnit.module( "ajax", { assert.ok( data[ "data" ], "JSON results returned (GET, data obj callback)" ); } }, { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, jsonpCallback: "jsonpResults", @@ -974,7 +971,7 @@ QUnit.module( "ajax", { assert.ok( data.data, "JSON results returned (GET, custom callback name)" ); } }, { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, jsonpCallback: "functionToCleanUp", @@ -983,7 +980,7 @@ QUnit.module( "ajax", { assert.strictEqual( window[ "functionToCleanUp" ], true, "Callback was removed (GET, custom callback name to be cleaned up)" ); var xhr; jQuery.ajax( { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, jsonpCallback: "functionToCleanUp", @@ -998,13 +995,13 @@ QUnit.module( "ajax", { } ); } }, { - url: "data/jsonp.php?callback=XXX", + url: baseURL + "mock.php?action=jsonp&callback=XXX", dataType: "jsonp", jsonp: false, jsonpCallback: "XXX", crossDomain: crossDomain, beforeSend: function() { - assert.ok( /^data\/jsonp.php\?callback=XXX&_=\d+$/.test( this.url ), "The URL wasn't messed with (GET, custom callback name with no url manipulation)" ); + assert.ok( /action=jsonp&callback=XXX&_=\d+$/.test( this.url ), "The URL wasn't messed with (GET, custom callback name with no url manipulation)" ); }, success: function( data ) { assert.ok( data[ "data" ], "JSON results returned (GET, custom callback name with no url manipulation)" ); @@ -1016,7 +1013,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - JSONP - Callback in data" + label, 2, function( assert ) { return [ { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, data: "callback=?", @@ -1025,7 +1022,7 @@ QUnit.module( "ajax", { } }, { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, data: "callback=??", @@ -1040,7 +1037,7 @@ QUnit.module( "ajax", { return [ { type: "POST", - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, success: function( data ) { @@ -1049,7 +1046,7 @@ QUnit.module( "ajax", { }, { type: "POST", - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", data: "callback=?", dataType: "jsonp", crossDomain: crossDomain, @@ -1059,7 +1056,7 @@ QUnit.module( "ajax", { }, { type: "POST", - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", jsonp: "callback", dataType: "jsonp", crossDomain: crossDomain, @@ -1073,7 +1070,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - JSONP" + label, 3, function( assert ) { return [ { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, success: function( data ) { @@ -1094,7 +1091,7 @@ QUnit.module( "ajax", { promise.abort = request.abort; return promise; }, - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, success: true @@ -1109,7 +1106,7 @@ QUnit.module( "ajax", { setup: function() { Globals.register( "testBar" ); }, - url: window.location.href.replace( /[^\/]*$/, "" ) + "data/testbar.php", + url: window.location.href.replace( /[^\/]*$/, "" ) + baseURL + "mock.php?action=testbar", dataType: "script", success: function() { assert.strictEqual( window[ "testBar" ], "bar", "Script results returned (GET, no callback)" ); @@ -1122,7 +1119,7 @@ QUnit.module( "ajax", { setup: function() { Globals.register( "testBar" ); }, - url: window.location.href.replace( /[^\/]*$/, "" ) + "data/testbar.php", + url: window.location.href.replace( /[^\/]*$/, "" ) + baseURL + "mock.php?action=testbar", type: "POST", dataType: "script", success: function( data, status ) { @@ -1137,7 +1134,7 @@ QUnit.module( "ajax", { setup: function() { Globals.register( "testBar" ); }, - url: window.location.href.replace( /[^\/]*$/, "" ).replace( /^.*?\/\//, "//" ) + "data/testbar.php", + url: window.location.href.replace( /[^\/]*$/, "" ).replace( /^.*?\/\//, "//" ) + baseURL + "mock.php?action=testbar", dataType: "script", success: function() { assert.strictEqual( window[ "testBar" ], "bar", "Script results returned (GET, no callback)" ); @@ -1147,7 +1144,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - malformed JSON", 2, function( assert ) { return { - url: "data/badjson.js", + url: baseURL + "badjson.js", dataType: "json", error: function( xhr, msg, detailedMsg ) { assert.strictEqual( msg, "parsererror", "A parse error occurred." ); @@ -1159,14 +1156,14 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - script by content-type", 2, function() { return [ { - url: "data/script.php", + url: baseURL + "mock.php?action=script", data: { "header": "script" }, success: true }, { - url: "data/script.php", + url: baseURL + "mock.php?action=script", data: { "header": "ecma" }, @@ -1177,10 +1174,10 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - JSON by content-type", 5, function( assert ) { return { - url: "data/json.php", + url: baseURL + "mock.php?action=json", data: { "header": "json", - "json": "array" + "array": "1" }, success: function( json ) { assert.ok( json.length >= 2, "Check length" ); @@ -1194,10 +1191,10 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - JSON by content-type disabled with options", 6, function( assert ) { return { - url: url( "data/json.php" ), + url: url( "mock.php?action=json" ), data: { "header": "json", - "json": "array" + "array": "1" }, contents: { "json": false @@ -1217,7 +1214,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - simple get", 1, function( assert ) { return { type: "GET", - url: url( "data/name.php?name=foo" ), + url: url( "mock.php?action=name&name=foo" ), success: function( msg ) { assert.strictEqual( msg, "bar", "Check for GET" ); } @@ -1227,7 +1224,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - simple post", 1, function( assert ) { return { type: "POST", - url: url( "data/name.php" ), + url: url( "mock.php?action=name" ), data: "name=peter", success: function( msg ) { assert.strictEqual( msg, "pan", "Check for POST" ); @@ -1237,7 +1234,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - data option - empty bodies for non-GET requests", 1, function( assert ) { return { - url: "data/echoData.php", + url: baseURL + "mock.php?action=echoData", data: undefined, type: "post", success: function( result ) { @@ -1299,11 +1296,11 @@ QUnit.module( "ajax", { function( label, cache ) { jQuery.each( { - "If-Modified-Since": "if_modified_since.php", - "Etag": "etag.php" + "If-Modified-Since": "mock.php?action=ims", + "Etag": "mock.php?action=etag" }, function( type, url ) { - url = "data/" + url + "?ts=" + ifModifiedNow++; + url = baseURL + url + "&ts=" + ifModifiedNow++; QUnit.asyncTest( "jQuery.ajax() - " + type + " support" + label, 4, function( assert ) { jQuery.ajax( { url: url, @@ -1355,7 +1352,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - atom+xml", 1, function( assert ) { return { - url: url( "data/atom+xml.php" ), + url: url( "mock.php?action=atom" ), success: function() { assert.ok( true, "success" ); } @@ -1363,10 +1360,10 @@ QUnit.module( "ajax", { } ); QUnit.asyncTest( "jQuery.ajax() - statusText", 3, function( assert ) { - jQuery.ajax( url( "data/statusText.php?status=200&text=Hello" ) ).done( function( _, statusText, jqXHR ) { + jQuery.ajax( url( "mock.php?action=status&code=200&text=Hello" ) ).done( function( _, statusText, jqXHR ) { assert.strictEqual( statusText, "success", "callback status text ok for success" ); assert.ok( jqXHR.statusText === "Hello" || jqXHR.statusText === "OK", "jqXHR status text ok for success (" + jqXHR.statusText + ")" ); - jQuery.ajax( url( "data/statusText.php?status=404&text=World" ) ).fail( function( jqXHR, statusText ) { + jQuery.ajax( url( "mock.php?action=status&code=404&text=World" ) ).fail( function( jqXHR, statusText ) { assert.strictEqual( statusText, "error", "callback status text ok for error" ); QUnit.start(); } ); @@ -1398,11 +1395,10 @@ QUnit.module( "ajax", { jQuery.each( /* jQuery.each arguments start */ { - "data/name.html": true, - "data/someFileThatDoesNotExist.html": false + "name.html": true, + "404.txt": false }, function( uri, isSuccess ) { - jQuery.ajax( url( uri ), { statusCode: createStatusCodes( "in options", isSuccess ), complete: countComplete @@ -1478,7 +1474,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - transitive conversions", 8, function( assert ) { return [ { - url: url( "data/json.php" ), + url: url( "mock.php?action=json" ), converters: { "json myJson": function( data ) { assert.ok( true, "converter called" ); @@ -1493,7 +1489,7 @@ QUnit.module( "ajax", { } }, { - url: url( "data/json.php" ), + url: url( "mock.php?action=json" ), converters: { "json myJson": function( data ) { assert.ok( true, "converter called (*)" ); @@ -1514,7 +1510,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - overrideMimeType", 2, function( assert ) { return [ { - url: url( "data/json.php" ), + url: url( "mock.php?action=json" ), beforeSend: function( xhr ) { xhr.overrideMimeType( "application/json" ); }, @@ -1523,7 +1519,7 @@ QUnit.module( "ajax", { } }, { - url: url( "data/json.php" ), + url: url( "mock.php?action=json" ), mimeType: "application/json", success: function( json ) { assert.ok( json.data, "Mimetype overridden using mimeType option" ); @@ -1534,7 +1530,7 @@ QUnit.module( "ajax", { ajaxTest( "jQuery.ajax() - empty json gets to error callback instead of success callback.", 1, function( assert ) { return { - url: url( "data/echoData.php" ), + url: url( "mock.php?action=echoData" ), error: function( _, __, error ) { assert.equal( typeof error === "object", true, "Didn't get back error object for empty json response" ); }, @@ -1546,7 +1542,7 @@ QUnit.module( "ajax", { return { create: function() { return jQuery.ajax( { - url: url( "data/name.html" ), + url: url( "name.html" ), beforeSend: function() { assert.ok( true, "beforeSend got called, canceling" ); return false; @@ -1570,14 +1566,14 @@ QUnit.module( "ajax", { ajaxTest( "#2806 - jQuery.ajax() - data option - evaluate function values", 1, function( assert ) { return { - url: "data/echoQuery.php", + url: baseURL + "mock.php?action=echoQuery", data: { key: function() { return "value"; } }, success: function( result ) { - assert.strictEqual( result, "key=value" ); + assert.strictEqual( result, "action=echoQuery&key=value" ); } }; } ); @@ -1602,7 +1598,7 @@ QUnit.module( "ajax", { jQuery.each( [ " - Same Domain", " - Cross Domain" ], function( crossDomain, label ) { ajaxTest( "#7578 - jQuery.ajax() - JSONP - default for cache option" + label, 1, function( assert ) { return { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, beforeSend: function() { @@ -1626,7 +1622,7 @@ QUnit.module( "ajax", { }, { create: function() { - return jQuery.ajax( "data/name.html" ); + return jQuery.ajax( baseURL + "name.html" ); }, done: function() { assert.ok( true, "With only string URL argument" ); @@ -1634,7 +1630,7 @@ QUnit.module( "ajax", { }, { create: function() { - return jQuery.ajax( "data/name.html", {} ); + return jQuery.ajax( baseURL + "name.html", {} ); }, done: function() { assert.ok( true, "With string URL param and map" ); @@ -1644,7 +1640,7 @@ QUnit.module( "ajax", { create: function( options ) { return jQuery.ajax( options ); }, - url: "data/name.html", + url: baseURL + "name.html", success: function() { assert.ok( true, "With only map" ); } @@ -1655,7 +1651,7 @@ QUnit.module( "ajax", { jQuery.each( [ " - Same Domain", " - Cross Domain" ], function( crossDomain, label ) { ajaxTest( "#8205 - jQuery.ajax() - JSONP - re-use callbacks name" + label, 4, function( assert ) { return { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, beforeSend: function( jqXHR, s ) { @@ -1678,7 +1674,7 @@ QUnit.module( "ajax", { ); jQuery.ajax( { - url: "data/jsonp.php", + url: baseURL + "mock.php?action=jsonp", dataType: "jsonp", crossDomain: crossDomain, beforeSend: function() { @@ -1737,7 +1733,7 @@ QUnit.module( "ajax", { ajaxTest( "#11151 - jQuery.ajax() - parse error body", 2, function( assert ) { return { - url: url( "data/errorWithJSON.php" ), + url: url( "mock.php?action=error&json=1" ), dataFilter: function( string ) { assert.ok( false, "dataFilter called" ); return string; @@ -1751,7 +1747,7 @@ QUnit.module( "ajax", { ajaxTest( "#11426 - jQuery.ajax() - loading binary data shouldn't throw an exception in IE", 1, function( assert ) { return { - url: url( "data/1x1.jpg" ), + url: url( "1x1.jpg" ), success: function( data ) { assert.ok( data === undefined || /JFIF/.test( data ), "success callback reached" ); } @@ -1772,7 +1768,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "gh-2498 - jQuery.ajax() - binary data shouldn't throw an exception", 2, function( assert ) { return { - url: url( "data/1x1.jpg" ), + url: url( "1x1.jpg" ), dataType: "arraybuffer", success: function( data, s, jqxhr ) { assert.ok( data instanceof window.ArrayBuffer, "correct data type" ); @@ -1790,7 +1786,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re QUnit.start(); }; jQuery.ajax( { - url: "data/badjson.js", + url: baseURL + "badjson.js", dataType: "script", throws: true } ); @@ -1799,7 +1795,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re jQuery.each( [ "method", "type" ], function( _, globalOption ) { function request( assert, option ) { var options = { - url: url( "data/echoData.php" ), + url: url( "mock.php?action=echoData" ), data: "hello", success: function( msg ) { assert.strictEqual( msg, "hello", "Check for POST (no override)" ); @@ -1836,7 +1832,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "#13276 - jQuery.ajax() - compatibility between XML documents from ajax requests and parsed string", 1, function( assert ) { return { - url: "data/dashboard.xml", + url: baseURL + "dashboard.xml", dataType: "xml", success: function( ajaxXML ) { var parsedXML = jQuery( jQuery.parseXML( "blibli" ) ).find( "tab" ); @@ -1854,7 +1850,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "#13292 - jQuery.ajax() - converter is bypassed for 204 requests", 3, function( assert ) { return { - url: "data/nocontent.php", + url: baseURL + "mock.php?action=status&code=204&text=No+Content", dataType: "testing", converters: { "* testing": function() { @@ -1876,7 +1872,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "#13388 - jQuery.ajax() - responseXML", 3, function( assert ) { return { - url: url( "data/with_fries.xml" ), + url: url( "with_fries.xml" ), dataType: "xml", success: function( resp, _, jqXHR ) { assert.notStrictEqual( resp, undefined, "XML document exists" ); @@ -1888,7 +1884,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "#13922 - jQuery.ajax() - converter is bypassed for HEAD requests", 3, function( assert ) { return { - url: "data/json.php", + url: baseURL + "mock.php?action=json", method: "HEAD", data: { header: "yes" @@ -1922,7 +1918,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "#14683 - jQuery.ajax() - Exceptions thrown synchronously by xhr.send should be caught", 4, function( assert ) { return [ { - url: "data/params_html.php", + url: baseURL + "mock.php?action=echoData", method: "POST", data: { toString: function() { @@ -1951,9 +1947,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "gh-2587 - when content-type not xml, but looks like one", 1, function( assert ) { return { - url: url( "data/ajax/content-type.php" ), + url: url( "mock.php?action=contentType" ), data: { - "content-type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "response": "" }, success: function( result ) { @@ -1968,9 +1964,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "gh-2587 - when content-type not xml, but looks like one", 1, function( assert ) { return { - url: url( "data/ajax/content-type.php" ), + url: url( "mock.php?action=contentType" ), data: { - "content-type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "response": "" }, success: function( result ) { @@ -1985,9 +1981,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "gh-2587 - when content-type not json, but looks like one", 1, function( assert ) { return { - url: url( "data/ajax/content-type.php" ), + url: url( "mock.php?action=contentType" ), data: { - "content-type": "test/jsontest", + contentType: "test/jsontest", "response": JSON.stringify( { test: "test" } ) }, success: function( result ) { @@ -2002,9 +1998,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "gh-2587 - when content-type not html, but looks like one", 1, function( assert ) { return { - url: url( "data/ajax/content-type.php" ), + url: url( "mock.php?action=contentType" ), data: { - "content-type": "test/htmltest", + contentType: "test/htmltest", "response": "

                    test

                    " }, success: function( result ) { @@ -2019,9 +2015,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "gh-2587 - when content-type not javascript, but looks like one", 1, function( assert ) { return { - url: url( "data/ajax/content-type.php" ), + url: url( "mock.php?action=contentType" ), data: { - "content-type": "test/testjavascript", + contentType: "test/testjavascript", "response": "alert(1)" }, success: function( result ) { @@ -2036,9 +2032,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ajaxTest( "gh-2587 - when content-type not ecmascript, but looks like one", 1, function( assert ) { return { - url: url( "data/ajax/content-type.php" ), + url: url( "mock.php?action=contentType" ), data: { - "content-type": "test/testjavascript", + contentType: "test/testjavascript", "response": "alert(1)" }, success: function( result ) { @@ -2079,7 +2075,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re QUnit.asyncTest( "jQuery.ajaxSetup()", 1, function( assert ) { jQuery.ajaxSetup( { - url: url( "data/name.php?name=foo" ), + url: url( "mock.php?action=name&name=foo" ), success: function( msg ) { assert.strictEqual( msg, "bar", "Check for GET" ); QUnit.start(); @@ -2110,7 +2106,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re jQuery.ajax( { type: "GET", - url: url( "data/name.php?wait=5" ), + url: url( "mock.php?action=wait&wait=5" ), error: pass, success: fail } ); @@ -2123,7 +2119,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re jQuery.ajax( { type: "GET", timeout: 15000, - url: url( "data/name.php?wait=1" ), + url: url( "mock.php?action=wait&wait=1" ), error: function() { assert.ok( false, "Check for local timeout failed" ); QUnit.start(); @@ -2148,7 +2144,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re assert.ok( false, "Global event triggered" ); } ); - jQuery( "#qunit-fixture" ).append( "" ); + jQuery( "#qunit-fixture" ).append( "" ); jQuery( document ).off( "ajaxStart ajaxStop" ); } ); @@ -2162,7 +2158,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re type: "POST" } ); - jQuery( "#qunit-fixture" ).load( "data/ajax/method.php", function( method ) { + jQuery( "#qunit-fixture" ).load( baseURL + "mock.php?action=echoMethod", function( method ) { assert.equal( method, "GET" ); done(); } ); @@ -2178,7 +2174,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ps.appendTo( "#qunit-fixture" ); - ps.load( "data/ajax/method.php", function() { + ps.load( baseURL + "mock.php?action=echoMethod", function() { assert.strictEqual( this, ps[ i++ ] ); if ( i === 2 ) { @@ -2191,14 +2187,14 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re QUnit.test( "#11402 - jQuery.domManip() - script in comments are properly evaluated", 2, function( assert ) { - jQuery( "#qunit-fixture" ).load( "data/cleanScript.html", assert.async() ); + jQuery( "#qunit-fixture" ).load( baseURL + "cleanScript.html", assert.async() ); } ); //----------- jQuery.get() QUnit.asyncTest( "jQuery.get( String, Hash, Function ) - parse xml and use text() on nodes", 2, function( assert ) { - jQuery.get( url( "data/dashboard.xml" ), function( xml ) { + jQuery.get( url( "dashboard.xml" ), function( xml ) { var content = []; jQuery( "tab", xml ).each( function() { content.push( jQuery( this ).text() ); @@ -2213,7 +2209,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re jQuery.ajaxSetup( { data: "helloworld" } ); - jQuery.get( url( "data/echoQuery.php" ), function( data ) { + jQuery.get( url( "mock.php?action=echoQuery" ), function( data ) { assert.ok( /helloworld$/.test( data ), "Data from ajaxSettings was used" ); QUnit.start(); } ); @@ -2223,9 +2219,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re QUnit.asyncTest( "jQuery.getJSON( String, Hash, Function ) - JSON array", 5, function( assert ) { jQuery.getJSON( - url( "data/json.php" ), + url( "mock.php?action=json" ), { - "json": "array" + "array": "1" }, function( json ) { assert.ok( json.length >= 2, "Check length" ); @@ -2239,7 +2235,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re } ); QUnit.asyncTest( "jQuery.getJSON( String, Function ) - JSON object", 2, function( assert ) { - jQuery.getJSON( url( "data/json.php" ), function( json ) { + jQuery.getJSON( url( "mock.php?action=json" ), function( json ) { if ( json && json[ "data" ] ) { assert.strictEqual( json[ "data" ][ "lang" ], "en", "Check JSON: lang" ); assert.strictEqual( json[ "data" ].length, 25, "Check JSON: length" ); @@ -2249,7 +2245,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re } ); QUnit.asyncTest( "jQuery.getJSON( String, Function ) - JSON object with absolute url to local content", 2, function( assert ) { - jQuery.getJSON( url( window.location.href.replace( /[^\/]*$/, "" ) + "data/json.php" ), function( json ) { + jQuery.getJSON( window.location.href.replace( /[^\/]*$/, "" ) + url( "mock.php?action=json" ), function( json ) { assert.strictEqual( json.data.lang, "en", "Check JSON: lang" ); assert.strictEqual( json.data.length, 25, "Check JSON: length" ); QUnit.start(); @@ -2263,7 +2259,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re var done = assert.async(); Globals.register( "testBar" ); - jQuery.getScript( url( "data/testbar.php" ), function() { + jQuery.getScript( url( "mock.php?action=testbar" ), function() { assert.strictEqual( window[ "testBar" ], "bar", "Check if script was evaluated" ); done(); } ); @@ -2272,14 +2268,14 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re QUnit.test( "jQuery.getScript( String, Function ) - no callback", 1, function( assert ) { Globals.register( "testBar" ); - jQuery.getScript( url( "data/testbar.php" ) ).done( assert.async() ); + jQuery.getScript( url( "mock.php?action=testbar" ) ).done( assert.async() ); } ); QUnit.test( "#8082 - jQuery.getScript( String, Function ) - source as responseText", 2, function( assert ) { var done = assert.async(); Globals.register( "testBar" ); - jQuery.getScript( url( "data/testbar.php" ), function( data, _, jqXHR ) { + jQuery.getScript( url( "mock.php?action=testbar" ), function( data, _, jqXHR ) { assert.strictEqual( data, jqXHR.responseText, "Same-domain script requests returns the source of the script" ); done(); } ); @@ -2294,7 +2290,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re assert.strictEqual( this.type, "GET", "no data means GET request" ); } } ); - jQuery( "#first" ).load( "data/name.html", assert.async() ); + jQuery( "#first" ).load( baseURL + "name.html", assert.async() ); } ); QUnit.test( "jQuery.fn.load() - 404 error callbacks", function( assert ) { @@ -2303,7 +2299,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re addGlobalEvents( "ajaxStart ajaxStop ajaxSend ajaxComplete ajaxError", assert )(); jQuery( document ).ajaxStop( done ); - jQuery( "
                    " ).load( "data/404.html", function() { + jQuery( "
                    " ).load( baseURL + "404.txt", function() { assert.ok( true, "complete" ); } ); } ); @@ -2315,7 +2311,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re assert.strictEqual( this.type, "GET", "no data means GET request" ); } } ); - jQuery( "#first" ).load( "data/name.html", null, assert.async() ); + jQuery( "#first" ).load( baseURL + "name.html", null, assert.async() ); } ); // check if load can be called with url and undefined data @@ -2325,12 +2321,12 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re assert.strictEqual( this.type, "GET", "no data means GET request" ); } } ); - jQuery( "#first" ).load( "data/name.html", undefined, assert.async() ); + jQuery( "#first" ).load( baseURL + "name.html", undefined, assert.async() ); } ); // check if load can be called with only url QUnit.asyncTest( "jQuery.fn.load( URL_SELECTOR )", 1, function( assert ) { - jQuery( "#first" ).load( "data/test3.html div.user", function() { + jQuery( "#first" ).load( baseURL + "test3.html div.user", function() { assert.strictEqual( jQuery( this ).children( "div" ).length, 2, "Verify that specific elements were injected" ); QUnit.start(); } ); @@ -2338,7 +2334,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re // Selector should be trimmed to avoid leading spaces (#14773) QUnit.asyncTest( "jQuery.fn.load( URL_SELECTOR with spaces )", 1, function( assert ) { - jQuery( "#first" ).load( "data/test3.html #superuser ", function() { + jQuery( "#first" ).load( baseURL + "test3.html #superuser ", function() { assert.strictEqual( jQuery( this ).children( "div" ).length, 1, "Verify that specific elements were injected" ); QUnit.start(); } ); @@ -2349,14 +2345,14 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re QUnit.test( "jQuery.fn.load( URL_SELECTOR with non-HTML whitespace(#3003) )", function( assert ) { assert.expect( 1 ); var done = assert.async(); - jQuery( "#first" ).load( "data/test3.html #whitespace\\\\xA0 ", function() { + jQuery( "#first" ).load( baseURL + "test3.html #whitespace\\\\xA0 ", function() { assert.strictEqual( jQuery( this ).children( "div" ).length, 1, "Verify that specific elements were injected" ); done(); } ); } ); QUnit.asyncTest( "jQuery.fn.load( String, Function ) - simple: inject text into DOM", 2, function( assert ) { - jQuery( "#first" ).load( url( "data/name.html" ), function() { + jQuery( "#first" ).load( url( "name.html" ), function() { assert.ok( /^ERROR/.test( jQuery( "#first" ).text() ), "Check if content was injected into the DOM" ); QUnit.start(); } ); @@ -2372,7 +2368,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re Globals.register( "testFoo" ); Globals.register( "testBar" ); - jQuery( "#first" ).load( url( "data/test.html" ), function() { + jQuery( "#first" ).load( url( "mock.php?action=testHTML&baseURL=" + baseURL ), function() { assert.ok( jQuery( "#first" ).html().match( /^html text/ ), "Check content after loading html" ); assert.strictEqual( jQuery( "#foo" ).html(), "foo", "Check if script evaluation has modified DOM" ); assert.strictEqual( window[ "testFoo" ], "foo", "Check if script was evaluated after load" ); @@ -2383,7 +2379,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re QUnit.asyncTest( "jQuery.fn.load( String, Function ) - check file with only a script tag", 3, function( assert ) { Globals.register( "testFoo" ); - jQuery( "#first" ).load( url( "data/test2.html" ), function() { + jQuery( "#first" ).load( url( "test2.html" ), function() { assert.strictEqual( jQuery( "#foo" ).html(), "foo", "Check if script evaluation has modified DOM" ); assert.strictEqual( window[ "testFoo" ], "foo", "Check if script was evaluated after load" ); QUnit.start(); @@ -2396,7 +2392,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re return "Hello World"; } } ); - jQuery( "
                    " ).load( url( "data/name.html" ), function( responseText ) { + jQuery( "
                    " ).load( url( "name.html" ), function( responseText ) { assert.strictEqual( jQuery( this ).html(), "Hello World", "Test div was filled with filtered data" ); assert.strictEqual( responseText, "Hello World", "Test callback receives filtered data" ); QUnit.start(); @@ -2404,13 +2400,12 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re } ); QUnit.asyncTest( "jQuery.fn.load( String, Object, Function )", 2, function( assert ) { - jQuery( "
                    " ).load( url( "data/params_html.php" ), { - "foo": 3, + jQuery( "
                    " ).load( url( "mock.php?action=echoHtml" ), { "bar": "ok" }, function() { - var $post = jQuery( this ).find( "#post" ); - assert.strictEqual( $post.find( "#foo" ).text(), "3", "Check if a hash of data is passed correctly" ); - assert.strictEqual( $post.find( "#bar" ).text(), "ok", "Check if a hash of data is passed correctly" ); + var $node = jQuery( this ); + assert.strictEqual( $node.find( "#method" ).text(), "POST", "Check method" ); + assert.strictEqual( $node.find( "#data" ).text(), "bar=ok", "Check if data is passed correctly" ); QUnit.start(); } ); } ); @@ -2418,10 +2413,10 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re QUnit.test( "jQuery.fn.load( String, String, Function )", 2, function( assert ) { var done = assert.async(); - jQuery( "
                    " ).load( url( "data/params_html.php" ), "foo=3&bar=ok", function() { - var $get = jQuery( this ).find( "#get" ); - assert.strictEqual( $get.find( "#foo" ).text(), "3", "Check if a string of data is passed correctly" ); - assert.strictEqual( $get.find( "#bar" ).text(), "ok", "Check if a of data is passed correctly" ); + jQuery( "
                    " ).load( url( "mock.php?action=echoHtml" ), "foo=3&bar=ok", function() { + var $node = jQuery( this ); + assert.strictEqual( $node.find( "#method" ).text(), "GET", "Check method" ); + assert.ok( $node.find( "#query" ).text().match( /foo=3&bar=ok/ ), "Check if a string of data is passed correctly" ); done(); } ); } ); @@ -2444,11 +2439,11 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re jQuery.map( [ { type: "success", - url: "data/echoQuery.php?arg=pop" + url: baseURL + "mock.php?action=echoQuery&arg=pop" }, { type: "error", - url: "data/404.php" + url: baseURL + "404.txt" } ], function( options ) { @@ -2477,7 +2472,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re jQuery( document ).off( "ajaxComplete" ); done(); } ); - jQuery( "#first" ).load( "data/test3.html" ); + jQuery( "#first" ).load( baseURL + "test3.html" ); } ); QUnit.test( "#10524 - jQuery.fn.load() - data specified in ajaxSettings is merged in", 1, function( assert ) { @@ -2491,7 +2486,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re "foo": "bar" } } ); - jQuery( "#foo" ).load( "data/echoQuery.php", data ); + jQuery( "#foo" ).load( baseURL + "mock.php?action=echoQuery", data ); jQuery( document ).ajaxComplete( function( event, jqXHR, options ) { assert.ok( ~options.data.indexOf( "foo=bar" ), "Data from ajaxSettings was used" ); done(); @@ -2505,10 +2500,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re jQuery.when( jQuery.post( - url( "data/name.php" ), + url( "mock.php?action=xml" ), { - xml: "5-2", - length: 3 + cal: "5-2" }, function( xml ) { jQuery( "math", xml ).each( function() { @@ -2518,7 +2512,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re } ), jQuery.ajax( { - url: url( "data/echoData.php" ), + url: url( "mock.php?action=echoData" ), type: "POST", data: { "test": { @@ -2530,9 +2524,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re assert.strictEqual( data, "test%5Blength%5D=7&test%5Bfoo%5D=bar", "Check if a sub-object with a length param is serialized correctly" ); } } ) - ).always( function() { - done(); - } ); + ).always( done ); } ); QUnit.test( "jQuery.post( String, Hash, Function ) - simple with xml", 4, function( assert ) { @@ -2540,9 +2532,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re jQuery.when( jQuery.post( - url( "data/name.php" ), + url( "mock.php?action=xml" ), { - "xml": "5-2" + cal: "5-2" }, function( xml ) { jQuery( "math", xml ).each( function() { @@ -2551,7 +2543,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re } ); } ), - jQuery.post( url( "data/name.php?xml=5-2" ), {}, function( xml ) { + jQuery.post( url( "mock.php?action=xml&cal=5-2" ), {}, function( xml ) { jQuery( "math", xml ).each( function() { assert.strictEqual( jQuery( "calculation", this ).text(), "5-2", "Check for XML" ); assert.strictEqual( jQuery( "result", this ).text(), "3", "Check for XML" ); @@ -2568,9 +2560,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re jQuery.when.apply( jQuery, jQuery.map( [ "get", "post" ], function( method ) { return jQuery[ method ]( { - url: url( "data/name.php" ), + url: url( "mock.php?action=xml" ), data: { - "xml": "5-2" + cal: "5-2" }, success: function( xml ) { jQuery( "math", xml ).each( function() { diff --git a/test/unit/attributes.js b/test/unit/attributes.js index 1284ffd18..3ab023eaf 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -127,7 +127,7 @@ QUnit.test( "attr(String)", function( assert ) { assert.equal( jQuery( option ).prop( "selected" ), true, "Make sure that a single option is selected, even when in an optgroup." ); - $img = jQuery( "" ).appendTo( "body" ); + $img = jQuery( "" ).appendTo( "body" ); assert.equal( $img.attr( "width" ), "215", "Retrieve width attribute on an element with display:none." ); assert.equal( $img.attr( "height" ), "53", "Retrieve height attribute on an element with display:none." ); @@ -780,7 +780,7 @@ QUnit.test( "prop('tabindex')", function( assert ) { QUnit.test( "image.prop( 'tabIndex' )", function( assert ) { assert.expect( 1 ); - var image = jQuery( "" ) + var image = jQuery( "" ) .appendTo( "#qunit-fixture" ); assert.equal( image.prop( "tabIndex" ), -1, "tabIndex on image" ); } ); diff --git a/test/unit/basic.js b/test/unit/basic.js index 15bca8ff6..b46a04c28 100644 --- a/test/unit/basic.js +++ b/test/unit/basic.js @@ -8,7 +8,7 @@ QUnit.test( "ajax", function( assert ) { jQuery.ajax( { type: "GET", - url: url( "data/name.php?name=foo" ), + url: url( "mock.php?action=name&name=foo" ), success: function( msg ) { assert.strictEqual( msg, "bar", "Check for GET" ); done.pop()(); @@ -17,7 +17,7 @@ QUnit.test( "ajax", function( assert ) { jQuery.ajax( { type: "POST", - url: url( "data/name.php" ), + url: url( "mock.php?action=name" ), data: "name=peter", success: function( msg ) { assert.strictEqual( msg, "pan", "Check for POST" ); @@ -25,7 +25,7 @@ QUnit.test( "ajax", function( assert ) { } } ); - jQuery( "#first" ).load( url( "data/name.html" ), function() { + jQuery( "#first" ).load( url( "name.html" ), function() { assert.ok( /^ERROR/.test( jQuery( "#first" ).text() ), "Check if content was injected into the DOM" ); done.pop()(); diff --git a/test/unit/core.js b/test/unit/core.js index 322b21dc6..3229f78d5 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -188,7 +188,7 @@ QUnit.test( "globalEval execution after script injection (#7862)", function( ass var now, script = document.createElement( "script" ); - script.src = "data/longLoadScript.php?sleep=2"; + script.src = baseURL + "mock.php?action=wait&wait=2&script=1"; now = jQuery.now(); document.body.appendChild( script ); @@ -1627,10 +1627,10 @@ QUnit.test( "jQuery.parseHTML", function( assert ) { QUnit.test( "jQuery.parseHTML() - gh-2965", function( assert ) { assert.expect( 1 ); - var html = "", + var html = "", href = jQuery.parseHTML( html )[ 0 ].href; - assert.ok( /\/test\.html$/.test( href ), "href is not lost after parsing anchor" ); + assert.ok( /\/example\.html$/.test( href ), "href is not lost after parsing anchor" ); } ); if ( jQuery.support.createHTMLDocument ) { diff --git a/test/unit/css.js b/test/unit/css.js index 2ca72c1aa..20f8195aa 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -1066,7 +1066,7 @@ QUnit.test( "can't get css for disconnected in IE<9, see #10254 and #8388", func assert.expect( 2 ); var span, div; - span = jQuery( "" ).css( "background-image", "url(data/1x1.jpg)" ); + span = jQuery( "" ).css( "background-image", "url(" + baseURL + "1x1.jpg)" ); assert.notEqual( span.css( "background-image" ), null, "can't get background-image in IE<9, see #10254" ); div = jQuery( "
                    " ).css( "top", 10 ); @@ -1374,7 +1374,6 @@ QUnit.test( "Clearing a Cloned Element's Style Shouldn't Clear the Original Element's Style (#8908)", 24, function( assert ) { - var baseUrl = document.location.href.replace( /([^\/]*)$/, "" ); var done = assert.async(); var styles = [ { name: "backgroundAttachment", @@ -1388,7 +1387,7 @@ QUnit.test( // Firefox returns auto's value name: "backgroundImage", - value: [ "url('test.png')", "url(" + baseUrl + "test.png)", "url(\"" + baseUrl + "test.png\")" ], + value: [ "url('test.png')", "url(" + baseURL + "test.png)", "url(\"" + baseURL + "test.png\")" ], expected: [ "none", "url(\"http://static.jquery.com/files/rocker/images/logo_jquery_215x53.gif\")" ] }, { name: "backgroundPosition", diff --git a/test/unit/data.js b/test/unit/data.js index 0eb1d5149..248878eda 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -777,7 +777,7 @@ QUnit.test( ".removeData should not throw exceptions. (#10080)", function( asser } ); // change the url to trigger unload - frame.attr( "src", "data/iframe.html?param=true" ); + frame.attr( "src", baseURL + "iframe.html?param=true" ); } ); QUnit.test( ".data only checks element attributes once. #8909", function( assert ) { diff --git a/test/unit/effects.js b/test/unit/effects.js index ec1669f54..c5f8d53c3 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -33,7 +33,7 @@ QUnit.module( "effects", { QUnit[ jQuery.find.compile ? "test" : "skip" ]( "sanity check", function( assert ) { assert.expect( 1 ); - assert.equal( jQuery( "#dl:visible, #qunit-fixture:visible, #foo:visible" ).length, 3, "QUnit state is correct for testing effects" ); + assert.equal( jQuery( "#qunit-fixture:visible, #foo:visible" ).length, 2, "QUnit state is correct for testing effects" ); } ); QUnit.test( "show() basic", function( assert ) { diff --git a/test/unit/event.js b/test/unit/event.js index d7290a6a4..ccaf72514 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -1281,8 +1281,7 @@ QUnit.test( ".trigger() doesn't bubble load event (#10717)", function( assert ) assert.ok( false, "load fired on window" ); } ); - // It's not an image, but as long as it fires load... - jQuery( "" ) + jQuery( "" ) .appendTo( "body" ) .on( "load", function() { assert.ok( true, "load fired on img" ); @@ -1443,7 +1442,7 @@ QUnit.test( "Submit event can be stopped (#11049)", function( assert ) { // handler making it impossible to feature-detect the support. QUnit[ /(ipad|iphone|ipod)/i.test( navigator.userAgent ) ? "skip" : "test" ]( "on(beforeunload)", 1, function( assert ) { - var iframe = jQuery( jQuery.parseHTML( "