diff --git a/.editorconfig b/.editorconfig index b5bd7f60ec..c0bf181129 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,7 +10,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[package.json] +[*.{json,yml}] indent_style = space indent_size = 2 diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 3ee82bba37..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,14 +0,0 @@ -external -node_modules -*.min.js -dist/** -!dist/jquery.js -test/data/jquery-1.9.1.js -test/data/badcall.js -test/data/badjson.js -test/data/json_obj.js -test/data/readywaitasset.js -test/data/readywaitloader.js -test/data/support/csp.js -test/data/support/getComputedSupport.js -test/data/core/jquery-iterability-transpiled.js diff --git a/.eslintrc-browser.json b/.eslintrc-browser.json deleted file mode 100644 index ffd5b64674..0000000000 --- a/.eslintrc-browser.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "root": true, - - "extends": "jquery", - - // Support: IE <=9 only, Android <=4.0 only - // The above browsers are failing a lot of tests in the ES5 - // test suite at http://test262.ecmascript.org. - "parserOptions": { - "ecmaVersion": 3 - }, - - // The browser env is not enabled on purpose so that code takes - // all browser-only globals from window instead of assuming - // they're available as globals. This makes it possible to use - // jQuery with tools like jsdom which provide a custom window - // implementation. - "env": {}, - - "globals": { - "window": true, - "define": true, - "module": true - }, - - "rules": { - "strict": ["error", "function"] - } -} diff --git a/.eslintrc-node.json b/.eslintrc-node.json deleted file mode 100644 index 589144272c..0000000000 --- a/.eslintrc-node.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - - "extends": "jquery", - - "parserOptions": { - "ecmaVersion": 2017 - }, - - "env": { - "es6": true, - "node": true - } -} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index d2c977ca85..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "root": true, - - "extends": "./.eslintrc-node.json" -} diff --git a/.gitattributes b/.gitattributes index b7ca95b5b7..5832a0194d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,4 +2,7 @@ * text=auto # JS files must always use LF for tools to work +# JS files may have mjs or cjs extensions now as well *.js eol=lf +*.cjs eol=lf +*.mjs eol=lf diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 58b8cbea56..bd4bae5ef8 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -14,7 +14,7 @@ Bug Reports: We prefer test cases on JS Bin (https://jsbin.com/qawicop/edit?html,css,js,output) or CodePen (https://codepen.io/mgol/pen/wNWJbZ) Frequently Reported Issues: - * Selectors with '#' break: See https://github.com/jquery/jquery/issues/2824 + * Self-closing tags broken in jQuery 3.5.0 or newer: please read the [jQuery 3.5.0 blog post](https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/) & the [upgrade guide](https://jquery.com/upgrade-guide/3.5/); see also issue #4681. --> ### Description ### diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 98d2331b7f..ec0910b10e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,9 +10,7 @@ should start with an issue. Mention the issue number here. Mark an `[x]` for completed items, if you're not sure leave them unchecked and we can assist. --> -* [ ] All authors have signed the CLA at https://cla.js.foundation/jquery/jquery * [ ] New tests have been added to show the fix or feature works -* [ ] Grunt build and unit tests pass locally with these changes * [ ] If needed, a docs issue/PR was created at https://github.com/jquery/api.jquery.com diff --git a/test/data/core/.babelrc.json b/test/data/core/.babelrc.json new file mode 100644 index 0000000000..4328ab218f --- /dev/null +++ b/test/data/core/.babelrc.json @@ -0,0 +1,5 @@ +{ + "plugins": ["@babel/transform-for-of"], + "retainLines": true, + "sourceMaps": "inline" +} diff --git a/test/data/core/aliased.html b/test/data/core/aliased.html index 87b5871f46..519fccfb10 100644 --- a/test/data/core/aliased.html +++ b/test/data/core/aliased.html @@ -2,7 +2,7 @@ - alias-masked DOM properties (#14074) + alias-masked DOM properties (trac-14074) - - - - - - - diff --git a/test/data/core/dynamic_ready.html b/test/data/core/dynamic_ready.html index e8180cd2b0..fade7adc2d 100644 --- a/test/data/core/dynamic_ready.html +++ b/test/data/core/dynamic_ready.html @@ -2,7 +2,7 @@ - + diff --git a/test/data/core/globaleval-context.html b/test/data/core/globaleval-context.html new file mode 100644 index 0000000000..1b75e3a0aa --- /dev/null +++ b/test/data/core/globaleval-context.html @@ -0,0 +1,15 @@ + + + + + body + + +
+ + + + + diff --git a/test/data/core/jquery-iterability-transpiled.html b/test/data/core/jquery-iterability-transpiled.html index 69ac18252b..0a73629746 100644 --- a/test/data/core/jquery-iterability-transpiled.html +++ b/test/data/core/jquery-iterability-transpiled.html @@ -3,7 +3,7 @@ jQuery objects transpiled iterability test page - + diff --git a/test/data/core/onready.html b/test/data/core/onready.html index 88ba94a077..1898ea7e75 100644 --- a/test/data/core/onready.html +++ b/test/data/core/onready.html @@ -2,7 +2,7 @@ - alias-masked DOM properties (#14074) + alias-masked DOM properties (trac-14074) - \ No newline at end of file + diff --git a/test/data/csp-ajax-script-downloaded.js b/test/data/csp-ajax-script-downloaded.js new file mode 100644 index 0000000000..4bd46cb655 --- /dev/null +++ b/test/data/csp-ajax-script-downloaded.js @@ -0,0 +1 @@ +window.downloadedScriptCalled = true; diff --git a/test/data/csp-ajax-script.html b/test/data/csp-ajax-script.html new file mode 100644 index 0000000000..e3e7507274 --- /dev/null +++ b/test/data/csp-ajax-script.html @@ -0,0 +1,13 @@ + + + + + jQuery.ajax() - script, CSP script-src compat (gh-3969) + + + + + +

CSP Test Page

+ + diff --git a/test/data/csp-ajax-script.js b/test/data/csp-ajax-script.js new file mode 100644 index 0000000000..c6821a24e5 --- /dev/null +++ b/test/data/csp-ajax-script.js @@ -0,0 +1,25 @@ +/* global startIframeTest */ + +var timeoutId, type; + +function finalize() { + startIframeTest( type, window.downloadedScriptCalled ); +} + +timeoutId = setTimeout( function() { + finalize(); +}, 1000 ); + +jQuery + .ajax( { + url: "csp-ajax-script-downloaded.js", + dataType: "script", + method: "POST", + beforeSend: function( _jqXhr, settings ) { + type = settings.type; + } + } ) + .then( function() { + clearTimeout( timeoutId ); + finalize(); + } ); diff --git a/test/data/csp.include.html b/test/data/csp.include.html index 17e2ef0d85..59b87f212a 100644 --- a/test/data/csp.include.html +++ b/test/data/csp.include.html @@ -3,7 +3,7 @@ CSP Test Page - + diff --git a/test/data/css/cssComputeStyleTests.html b/test/data/css/cssComputeStyleTests.html new file mode 100644 index 0000000000..9010d70fd2 --- /dev/null +++ b/test/data/css/cssComputeStyleTests.html @@ -0,0 +1,34 @@ + + + + Test computeStyleTests for hidden iframe + + + + +
+ + +
+ + + + + diff --git a/test/data/data/dataAttrs.html b/test/data/data/dataAttrs.html index 785150eb8f..854143fe85 100644 --- a/test/data/data/dataAttrs.html +++ b/test/data/data/dataAttrs.html @@ -2,7 +2,7 @@ - IE11 onpageshow strangeness (#14894) + IE11 onpageshow strangeness (trac-14894) - Test for #14894 + Test for trac-14894 diff --git a/test/data/event/focusElem.html b/test/data/event/focusElem.html index 1940e8b61d..2c09b6d83e 100644 --- a/test/data/event/focusElem.html +++ b/test/data/event/focusElem.html @@ -2,7 +2,7 @@ - .focus() (activeElement access #13393) + .focus() (activeElement access trac-13393) diff --git a/test/data/event/focusinCrossFrame.html b/test/data/event/focusinCrossFrame.html index 6dd187e902..0230eb8a59 100644 --- a/test/data/event/focusinCrossFrame.html +++ b/test/data/event/focusinCrossFrame.html @@ -2,7 +2,7 @@ - focusin event cross-frame (#14180) + focusin event cross-frame (trac-14180) diff --git a/test/data/event/promiseReady.html b/test/data/event/promiseReady.html index c6e086245a..7ed656b6f2 100644 --- a/test/data/event/promiseReady.html +++ b/test/data/event/promiseReady.html @@ -2,7 +2,7 @@ -Test case for jQuery ticket #11470 +Test case for jQuery ticket trac-11470 diff --git a/test/data/inner_nomodule.js b/test/data/inner_nomodule.js index c37b4ed422..a84492b687 100644 --- a/test/data/inner_nomodule.js +++ b/test/data/inner_nomodule.js @@ -1 +1 @@ -QUnit.assert.ok( !QUnit.moduleTypeSupported, "evaluated: inner nomodule script with src" ); +QUnit.assert.ok( QUnit.isIE, "evaluated: inner nomodule script with src" ); diff --git a/test/data/jquery-1.9.1.js b/test/data/jquery-1.9.1.js index 80c97a226a..ae116a43e0 100644 --- a/test/data/jquery-1.9.1.js +++ b/test/data/jquery-1.9.1.js @@ -2051,7 +2051,6 @@ jQuery.fn.extend( { }, // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ delay: function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; @@ -2559,7 +2558,6 @@ jQuery.extend( { get: function( elem ) { // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ var attributeNode = elem.getAttributeNode( "tabindex" ); return attributeNode && attributeNode.specified ? diff --git a/test/data/manipulation/iframe-denied.html b/test/data/manipulation/iframe-denied.html index e74872ad08..030ca717bc 100644 --- a/test/data/manipulation/iframe-denied.html +++ b/test/data/manipulation/iframe-denied.html @@ -11,7 +11,7 @@ diff --git a/test/data/offset/body.html b/test/data/offset/body.html index 75757d7e60..a27e242aff 100644 --- a/test/data/offset/body.html +++ b/test/data/offset/body.html @@ -12,13 +12,13 @@ diff --git a/test/data/offset/fixed.html b/test/data/offset/fixed.html index 48a2c47975..9016f87a98 100644 --- a/test/data/offset/fixed.html +++ b/test/data/offset/fixed.html @@ -15,10 +15,10 @@ diff --git a/test/data/offset/scroll.html b/test/data/offset/scroll.html index 28cade1c37..3b9848fe49 100644 --- a/test/data/offset/scroll.html +++ b/test/data/offset/scroll.html @@ -17,16 +17,16 @@ diff --git a/test/data/offset/static.html b/test/data/offset/static.html index 1f4c3dc3dc..913f9d260d 100644 --- a/test/data/offset/static.html +++ b/test/data/offset/static.html @@ -13,15 +13,15 @@ diff --git a/test/data/offset/table.html b/test/data/offset/table.html index eeac19b77c..1650e72972 100644 --- a/test/data/offset/table.html +++ b/test/data/offset/table.html @@ -13,13 +13,13 @@ diff --git a/test/data/qunit-fixture.html b/test/data/qunit-fixture.html index e0fd3e60e9..f3c99dfd64 100644 --- a/test/data/qunit-fixture.html +++ b/test/data/qunit-fixture.html @@ -1,15 +1,15 @@ -

See this blog entry for more information.

+

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 + 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

+

This is a normal link: Yahoo

+

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

@@ -78,11 +78,11 @@ - + - + "'台北Táiběi"' - + test element @@ -116,6 +116,11 @@ +
+
+ +
+
+
+
+ + +
+
+ + +
C
+
+
+ + + + + +
@@ -194,6 +218,53 @@
+
+
+ + + + + + Neither enabled nor disabled +
+
+ + + + + + + + + + Neither enabled nor disabled +
+ + +
+
1 2 @@ -208,7 +279,7 @@
-
fadeIn
fadeIn
+
fadeIn
fadeIn
fadeOut
fadeOut
show
show
@@ -235,3 +306,4 @@
+
diff --git a/test/data/qunit-fixture.js b/test/data/qunit-fixture.js deleted file mode 100644 index 46f0c3c26d..0000000000 --- a/test/data/qunit-fixture.js +++ /dev/null @@ -1,2 +0,0 @@ -// 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"; diff --git a/test/data/readywait.html b/test/data/readywait.html index d7de0b082b..8f88245d52 100644 --- a/test/data/readywait.html +++ b/test/data/readywait.html @@ -45,9 +45,9 @@

    This is a test page for jQuery.readyWait and jQuery.holdReady, see - #6781 + trac-6781 and - #8803. + trac-8803.

    Test for jQuery.holdReady, which can be used diff --git a/test/data/selector/sizzle_cache.html b/test/data/selector/cache.html similarity index 92% rename from test/data/selector/sizzle_cache.html rename to test/data/selector/cache.html index 513390cabb..d88875ba17 100644 --- a/test/data/selector/sizzle_cache.html +++ b/test/data/selector/cache.html @@ -2,7 +2,7 @@ - jQuery selector - sizzle cache + jQuery selector - cache diff --git a/test/data/selector/mixed_sort.html b/test/data/selector/mixed_sort.html new file mode 100644 index 0000000000..03d6d79efb --- /dev/null +++ b/test/data/selector/mixed_sort.html @@ -0,0 +1,22 @@ + + + + + jQuery selector - cross-window uniqueSort + + + + + + + + diff --git a/test/data/support/bootstrap.html b/test/data/support/bootstrap.html new file mode 100644 index 0000000000..731a474317 --- /dev/null +++ b/test/data/support/bootstrap.html @@ -0,0 +1,20 @@ + + + + + + + +

    + + + +
    + + + diff --git a/test/data/testinit-jsdom.js b/test/data/testinit-jsdom.js index e0830cc925..95012a3929 100644 --- a/test/data/testinit-jsdom.js +++ b/test/data/testinit-jsdom.js @@ -4,6 +4,9 @@ // jsdom implements a throwing `window.scrollTo`. QUnit.config.scrolltop = false; +QUnit.isIE = false; +QUnit.testUnlessIE = QUnit.test; + const FILEPATH = "/test/data/testinit-jsdom.js"; const activeScript = document.currentScript; const parentUrl = activeScript && activeScript.src ? @@ -34,7 +37,27 @@ window.original$ = this.$ = "replaced"; * @example url("mock.php?foo=bar") * @result "data/mock.php?foo=bar&10538358345554" */ -function url( value ) { +this.url = function( value ) { return baseURL + value + ( /\?/.test( value ) ? "&" : "?" ) + new Date().getTime() + "" + parseInt( Math.random() * 100000, 10 ); -} +}; + +// We only run basic tests in jsdom so we don't need to repeat the logic +// from the regular testinit.js +this.includesModule = function() { + return true; +}; + +// The file-loading part of testinit.js#loadTests is handled by +// jsdom Karma config; here we just need to trigger relevant APIs. +this.loadTests = function() { + + // Delay the initialization until after all the files are loaded + // as in the JSDOM case we load them via Karma (see Gruntfile.js) + // instead of directly in testinit.js. + window.addEventListener( "load", function() { + window.__karma__.start(); + jQuery.noConflict(); + QUnit.start(); + } ); +}; diff --git a/test/data/testinit.js b/test/data/testinit.js index e1d81c5c7a..23cea8a405 100644 --- a/test/data/testinit.js +++ b/test/data/testinit.js @@ -1,4 +1,5 @@ /* eslint no-multi-str: "off" */ +"use strict"; var FILEPATH = "/test/data/testinit.js", activeScript = [].slice.call( document.getElementsByTagName( "script" ), -1 )[ 0 ], @@ -14,9 +15,17 @@ var FILEPATH = "/test/data/testinit.js", baseURL = parentUrl + "test/data/", supportjQuery = this.jQuery, - // see RFC 2606 - externalHost = "example.com"; - + // NOTE: keep it in sync with build/tasks/lib/slim-build-flags.js + excludedFromSlim = [ + "ajax", + "callbacks", + "deferred", + "effects", + "queue" + ]; + +// see RFC 2606 +this.externalHost = "example.com"; this.hasPHP = true; this.isLocal = window.location.protocol === "file:"; @@ -43,28 +52,22 @@ this.q = function() { /** * Asserts that a select matches the given IDs * @param {String} message - Assertion name - * @param {String} selector - Sizzle selector + * @param {String} selector - jQuery selector * @param {String} expectedIds - Array of ids to construct what is expected * @param {(String|Node)=document} context - Selector context * @example match("Check for something", "p", ["foo", "bar"]); */ function match( message, selector, expectedIds, context, assert ) { - var f = jQuery( selector, context ).get(), - s = "", - i = 0; - - for ( ; i < f.length; i++ ) { - s += ( s && "," ) + "\"" + f[ i ].id + "\""; - } + var elems = jQuery( selector, context ).get(); - assert.deepEqual( f, q.apply( q, expectedIds ), message + " (" + selector + ")" ); + assert.deepEqual( elems, q.apply( q, expectedIds ), message + " (" + selector + ")" ); } /** * Asserts that a select matches the given IDs. * The select is not bound by a context. * @param {String} message - Assertion name - * @param {String} selector - Sizzle selector + * @param {String} selector - jQuery selector * @param {String} expectedIds - Array of ids to construct what is expected * @example t("Check for something", "p", ["foo", "bar"]); */ @@ -76,7 +79,7 @@ QUnit.assert.t = function( message, selector, expectedIds ) { * Asserts that a select matches the given IDs. * The select is performed within the `#qunit-fixture` context. * @param {String} message - Assertion name - * @param {String} selector - Sizzle selector + * @param {String} selector - jQuery selector * @param {String} expectedIds - Array of ids to construct what is expected * @example selectInFixture("Check for something", "p", ["foo", "bar"]); */ @@ -106,11 +109,11 @@ this.createWithFriesXML = function() { xmlns:xsd='http://www.w3.org/2001/XMLSchema' \ xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> \ \ - \ - \ + \ + \ \ \ - \ + \ \ \ 1 \ @@ -127,16 +130,12 @@ this.createWithFriesXML = function() { \ "; - return jQuery.parseXML( string.replace( /\{\{\s*externalHost\s*\}\}/g, externalHost ) ); + return jQuery.parseXML( string ); }; this.createXMLFragment = function() { - var xml, frag; - if ( window.ActiveXObject ) { - xml = new window.ActiveXObject( "msxml2.domdocument" ); - } else { + var frag, xml = document.implementation.createDocument( "", "", null ); - } if ( xml ) { frag = xml.createElement( "data" ); @@ -145,16 +144,12 @@ this.createXMLFragment = function() { return frag; }; -window.fireNative = document.createEvent ? - function( node, type ) { - var event = document.createEvent( "HTMLEvents" ); +window.fireNative = function( node, type ) { + var event = document.createEvent( "HTMLEvents" ); - event.initEvent( type, true, true ); - node.dispatchEvent( event ); - } : - function( node, type ) { - node.fireEvent( "on" + type, document.createEventObject() ); - }; + event.initEvent( type, true, true ); + node.dispatchEvent( event ); +}; /** * Add random number to url to stop caching @@ -173,8 +168,11 @@ function url( value ) { } // Ajax testing helper -this.ajaxTest = function( title, expect, options ) { - QUnit.test( title, function( assert ) { +this.ajaxTest = function( title, expect, options, wrapper ) { + if ( !wrapper ) { + wrapper = QUnit.test; + } + wrapper.call( QUnit, title, function( assert ) { assert.expect( expect ); var requestOptions; @@ -237,7 +235,7 @@ this.ajaxTest = function( title, expect, options ) { completed = true; delete ajaxTest.abort; assert.ok( false, "aborted " + reason ); - jQuery.each( requests, function( i, request ) { + jQuery.each( requests, function( _i, request ) { request.abort(); } ); } @@ -245,16 +243,21 @@ this.ajaxTest = function( title, expect, options ) { } ); }; -this.testIframe = function( title, fileName, func, wrapper ) { +this.testIframe = function( title, fileName, func, wrapper, iframeStyles ) { if ( !wrapper ) { wrapper = QUnit.test; } wrapper.call( QUnit, title, function( assert ) { var done = assert.async(), - $iframe = supportjQuery( "" ) .css( { position: "absolute", top: "0", left: "-600px", width: "500px" } ) .attr( { id: "qunit-fixture-iframe", src: url( fileName ) } ); + // Add other iframe styles + if ( iframeStyles ) { + $iframe.css( iframeStyles ); + } + // Test iframes are expected to invoke this via startIframeTest (cf. iframeTest.js) window.iframeCallback = function() { var args = Array.prototype.slice.call( arguments ); @@ -262,12 +265,24 @@ this.testIframe = function( title, fileName, func, wrapper ) { args.unshift( assert ); setTimeout( function() { + var result; + this.iframeCallback = undefined; - func.apply( this, args ); - func = function() {}; - $iframe.remove(); - done(); + result = func.apply( this, args ); + + function finish() { + func = function() {}; + $iframe.remove(); + done(); + } + + // Wait for promises returned by `func`. + if ( result && result.then ) { + result.then( finish ); + } else { + finish(); + } } ); }; @@ -288,36 +303,99 @@ if ( !window.__karma__ ) { QUnit.isSwarm = ( QUnit.urlParams.swarmURL + "" ).indexOf( "http" ) === 0; QUnit.basicTests = ( QUnit.urlParams.module + "" ) === "basic"; -// Async test for module script type support -function moduleTypeSupported() { - var script = document.createElement( "script" ); - script.type = "module"; - script.text = "QUnit.moduleTypeSupported = true"; - document.head.appendChild( script ).parentNode.removeChild( script ); -} -moduleTypeSupported(); +// Support: IE 11+ +// A variable to make it easier to skip specific tests in IE, mostly +// testing integrations with newer Web features not supported by it. +QUnit.isIE = !!window.document.documentMode; +QUnit.testUnlessIE = QUnit.isIE ? QUnit.skip : QUnit.test; + +// Returns whether a particular module like "ajax" or "deprecated" +// is included in the current jQuery build; it handles the slim build +// as well. The util was created so that we don't treat presence of +// particular APIs to decide whether to run a test as then if we +// accidentally remove an API, the tests would still not fail. +this.includesModule = function( moduleName ) { + + var excludedModulesPart, excludedModules; + + // A short-cut for the slim build, e.g. "4.0.0-pre+slim" + if ( jQuery.fn.jquery.indexOf( "+slim" ) > -1 ) { + + // The module is included if it does NOT exist on the list + // of modules excluded in the slim build + return excludedFromSlim.indexOf( moduleName ) === -1; + } + + // example version for `npm run build -- -e deprecated`: + // "v4.0.0-pre+14dc9347 -deprecated,-deprecated/ajax-event-alias,-deprecated/event" + excludedModulesPart = jQuery.fn.jquery + + // Take the flags out of the version string. + // Example: "-deprecated,-deprecated/ajax-event-alias,-deprecated/event" + .split( " " )[ 1 ]; + + if ( !excludedModulesPart ) { + + // No build part => the full build where everything is included. + return true; + } + + excludedModules = excludedModulesPart + + // Turn to an array. + // Example: [ "-deprecated", "-deprecated/ajax-event-alias", "-deprecated/event" ] + .split( "," ) + + // Remove the leading "-". + // Example: [ "deprecated", "deprecated/ajax-event-alias", "deprecated/event" ] + .map( function( moduleName ) { + return moduleName.slice( 1 ); + } ) + + // Filter out deep names - ones that contain a slash. + // Example: [ "deprecated" ] + .filter( function( moduleName ) { + return moduleName.indexOf( "/" ) === -1; + } ); + + return excludedModules.indexOf( moduleName ) === -1; +}; this.loadTests = function() { - // Directly load tests that need synchronous evaluation - if ( !QUnit.urlParams.amd || document.readyState === "loading" ) { + // QUnit.config is populated from QUnit.urlParams but only at the beginning + // of the test run. We need to read both. + var esmodules = QUnit.config.esmodules || QUnit.urlParams.esmodules; + + // Directly load tests that need evaluation before DOMContentLoaded. + if ( !esmodules || document.readyState === "loading" ) { document.write( " + + + + diff --git a/test/data/trusted-types-attributes.html b/test/data/trusted-types-attributes.html new file mode 100644 index 0000000000..1767226ccf --- /dev/null +++ b/test/data/trusted-types-attributes.html @@ -0,0 +1,61 @@ + + + + + Trusted HTML attribute tests + + +
    + + + + + diff --git a/test/data/trusted-types-attributes.js b/test/data/trusted-types-attributes.js new file mode 100644 index 0000000000..907edf8c83 --- /dev/null +++ b/test/data/trusted-types-attributes.js @@ -0,0 +1 @@ +window.testMessage = "script run"; diff --git a/test/delegatetest.html b/test/delegatetest.html index d3225196ee..53efe54ce5 100644 --- a/test/delegatetest.html +++ b/test/delegatetest.html @@ -125,103 +125,95 @@

    Submit Tests

    diff --git a/test/index.html b/test/index.html index 7b5b302a98..f2c468a079 100644 --- a/test/index.html +++ b/test/index.html @@ -19,9 +19,9 @@ - + - + @@ -29,7 +29,14 @@ // Load tests if they have not been loaded // This is in a different script tag to ensure that // jQuery is on the page when the testrunner executes - if ( !QUnit.urlParams.amd ) { + // QUnit.config is populated from QUnit.urlParams but only at the beginning + // of the test run. We need to read both. + var esmodules = QUnit.config.esmodules || QUnit.urlParams.esmodules; + + // Workaround: Remove call to `window.__karma__.loaded()` + // in favor of calling `window.__karma__.start()` from `loadTests()` + // because tests such as unit/ready.js should run after document ready. + if ( !esmodules ) { loadTests(); } diff --git a/test/jquery.js b/test/jquery.js index 6b1aef42f6..0340fb6e0d 100644 --- a/test/jquery.js +++ b/test/jquery.js @@ -2,55 +2,76 @@ ( function() { /* global loadTests: false */ - var FILEPATH = "/test/jquery.js", + var dynamicImportSource, config, src, + FILEPATH = "/test/jquery.js", activeScript = [].slice.call( document.getElementsByTagName( "script" ), -1 )[ 0 ], parentUrl = activeScript && activeScript.src ? activeScript.src.replace( /[?#].*/, "" ) + FILEPATH.replace( /[^/]+/g, ".." ) + "/" : "../", - QUnit = window.QUnit || parent.QUnit, - require = window.require || parent.require, + QUnit = window.QUnit, + require = window.require; - // Default to unminified jQuery for directly-opened iframes - urlParams = QUnit ? - QUnit.urlParams : - { dev: true }, - src = urlParams.dev ? - "dist/jquery.js" : - "dist/jquery.min.js"; + function getQUnitConfig() { + var config = Object.create( null ); - // Define configuration parameters controlling how jQuery is loaded - if ( QUnit ) { + // Default to unminified jQuery for directly-opened iframes + if ( !QUnit ) { + config.dev = true; + } else { - // AMD loading is asynchronous and incompatible with synchronous test loading in Karma - if ( !window.__karma__ ) { - QUnit.config.urlConfig.push( { - id: "amd", - label: "Load with AMD", - tooltip: "Load the AMD jQuery file (and its dependencies)" + // QUnit.config is populated from QUnit.urlParams but only at the beginning + // of the test run. We need to read both. + QUnit.config.urlConfig.forEach( function( entry ) { + config[ entry.id ] = QUnit.config[ entry.id ] != null ? + QUnit.config[ entry.id ] : + QUnit.urlParams[ entry.id ]; } ); } + return config; + } + + // Define configuration parameters controlling how jQuery is loaded + if ( QUnit ) { QUnit.config.urlConfig.push( { + id: "esmodules", + label: "Load as modules", + tooltip: "Load the jQuery module file (and its dependencies)" + }, { id: "dev", label: "Load unminified", tooltip: "Load the development (unminified) jQuery file" } ); } - // Honor AMD loading on the main window (detected by seeing QUnit on it). + config = getQUnitConfig(); + + src = config.dev ? + "dist/jquery.js" : + "dist/jquery.min.js"; + + // Honor ES modules loading on the main window (detected by seeing QUnit on it). // This doesn't apply to iframes because they synchronously expect jQuery to be there. - if ( urlParams.amd && window.QUnit ) { - require.config( { - baseUrl: parentUrl - } ); - src = "src/jquery"; + if ( config.esmodules && QUnit ) { - // Include tests if specified - if ( typeof loadTests !== "undefined" ) { - require( [ src ], loadTests ); - } else { - require( [ src ] ); - } + // Support: IE 11+ + // IE doesn't support the dynamic import syntax so it would crash + // with a SyntaxError here. + dynamicImportSource = "" + + "import( `${ parentUrl }src/jquery.js` )\n" + + " .then( ( { jQuery } ) => {\n" + + " window.jQuery = jQuery;\n" + + " if ( typeof loadTests === \"function\" ) {\n" + + " // Include tests if specified\n" + + " loadTests();\n" + + " }\n" + + " } )\n" + + " .catch( error => {\n" + + " console.error( error );\n" + + " QUnit.done();\n" + + " } );"; + + eval( dynamicImportSource ); // Otherwise, load synchronously } else { diff --git a/test/karma.context.html b/test/karma.context.html index e4f8a4f885..a8977b7659 100644 --- a/test/karma.context.html +++ b/test/karma.context.html @@ -1,45 +1,43 @@ - - CONTEXT - - + + CONTEXT + + -
    +
    - - - -
    - + + + +
    + - - - + - %SCRIPTS% - + %MAPPINGS% + + %SCRIPTS% + - - + + // Workaround: Remove call to `window.__karma__.loaded()` + // in favor of calling `window.__karma__.start()` from `loadTests()` + // because tests such as unit/ready.js should run after document ready. + if ( !esmodules ) { + loadTests(); + } + diff --git a/test/karma.debug.html b/test/karma.debug.html index 950064db72..93351adef0 100644 --- a/test/karma.debug.html +++ b/test/karma.debug.html @@ -2,46 +2,44 @@ %X_UA_COMPATIBLE% - DEBUG - - - - + DEBUG + + + + -
    +
    - - - -
    - + + + +
    + - - - - + + - %SCRIPTS% - + %MAPPINGS% + + %SCRIPTS% + - - + + // Workaround: Remove call to `window.__karma__.loaded()` + // in favor of calling `window.__karma__.start()` from `loadTests()` + // because tests such as unit/ready.js should run after document ready. + if ( !esmodules ) { + loadTests(); + } + diff --git a/test/localfile.html b/test/localfile.html deleted file mode 100644 index fbafe02ccc..0000000000 --- a/test/localfile.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - jQuery Local File Test - - - - - -

    jQuery Local File Test

    -

    - Introduction -

    - -

    - Results -

    - -

    - Logs: -

    - - - diff --git a/test/middleware-mockserver.cjs b/test/middleware-mockserver.cjs new file mode 100644 index 0000000000..a7a7a47b93 --- /dev/null +++ b/test/middleware-mockserver.cjs @@ -0,0 +1,415 @@ +"use strict"; + +const url = require( "url" ); +const fs = require( "fs" ); +const getRawBody = require( "raw-body" ); +const multiparty = require( "multiparty" ); + +let cspLog = ""; + +/** + * Keep in sync with /test/mock.php + */ +function cleanCallback( callback ) { + return callback.replace( /[^a-z0-9_]/gi, "" ); +} + +const mocks = { + contentType: function( req, resp ) { + resp.writeHead( 200, { + "content-type": req.query.contentType + } ); + resp.end( req.query.response ); + }, + wait: function( req, resp ) { + const 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 ) { + const 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 ) { + resp.writeHead( 200, { "content-type": "atom+xml" } ); + resp.end( "" ); + }, + script: function( req, resp ) { + const headers = {}; + if ( req.query.header === "ecma" ) { + headers[ "content-type" ] = "application/ecmascript"; + } else if ( "header" in req.query ) { + headers[ "content-type" ] = "text/javascript"; + } else { + headers[ "content-type" ] = "text/html"; + } + + if ( req.query.cors ) { + headers[ "access-control-allow-origin" ] = "*"; + } + + if ( resp.set ) { + resp.set( headers ); + } else { + for ( const key in headers ) { + resp.writeHead( 200, { [ key ]: headers[ key ] } ); + } + } + + if ( req.query.callback ) { + resp.end( `${ cleanCallback( req.query.callback ) }(${ JSON.stringify( { + headers: req.headers + } ) })` ); + } else { + resp.end( "QUnit.assert.ok( true, \"mock executed\" );" ); + } + }, + testbar: function( _req, resp ) { + resp.writeHead( 200 ); + resp.end( + "this.testBar = 'bar'; " + + "jQuery('#ap').html('bar'); " + + "QUnit.assert.ok( true, 'mock executed');" + ); + }, + json: function( req, resp ) { + if ( req.query.header ) { + resp.writeHead( 200, { "content-type": "application/json" } ); + } + if ( req.query.cors ) { + resp.writeHead( 200, { "access-control-allow-origin": "*" } ); + } + 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 ) { + let callback; + if ( Array.isArray( req.query.callback ) ) { + callback = Promise.resolve( req.query.callback[ req.query.callback.length - 1 ] ); + } else 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=", "" ); + } ); + } + const 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( `${ cleanCallback( cb ) }(${ json })` ); + }, next ); + }, + xmlOverJsonp: function( req, resp ) { + const callback = req.query.callback; + const body = fs.readFileSync( `${ __dirname }/data/with_fries.xml` ).toString(); + resp.writeHead( 200 ); + resp.end( `${ cleanCallback( callback ) }(${ JSON.stringify( body ) })\n` ); + }, + formData: function( req, resp, next ) { + const prefix = "multipart/form-data; boundary=--"; + const contentTypeValue = req.headers[ "content-type" ]; + resp.writeHead( 200 ); + if ( ( prefix || "" ).startsWith( prefix ) ) { + getMultiPartContent( req ).then( function( { fields = {} } ) { + resp.end( `key1 -> ${ fields.key1 }, key2 -> ${ fields.key2 }` ); + }, next ); + } else { + resp.end( `Incorrect Content-Type: ${ contentTypeValue + }\nExpected prefix: ${ prefix }` ); + } + }, + 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 ) { + const headers = { + "Sample-Header": "Hello World", + "Empty-Header": "", + "Sample-Header2": "Hello World 2", + "List-Header": "Item 1", + "list-header": "Item 2", + "constructor": "prototype collision (constructor)" + }; + + // Use resp.append in express to + // avoid overwriting List-Header + if ( resp.append ) { + + for ( const key in headers ) { + resp.append( key, headers[ key ] ); + } + } else { + resp.writeHead( 200, headers ); + } + req.query.keys.split( "|" ).forEach( function( key ) { + if ( key.toLowerCase() in req.headers ) { + 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 ) { + const hash = Number( req.query.ts ).toString( 36 ); + const 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 ) { + const 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 ) { + resp.writeHead( Number( req.query.code ) ); + resp.end(); + }, + testHTML: function( req, resp ) { + resp.writeHead( 200, { "Content-Type": "text/html" } ); + const body = fs + .readFileSync( `${ __dirname }/data/test.include.html` ) + .toString() + .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'; require-trusted-types-for 'script'; " + + "report-uri /base/test/data/mock.php?action=cspLog" + } ); + const body = fs.readFileSync( `${ __dirname }/data/csp.include.html` ).toString(); + resp.end( body ); + }, + cspNonce: function( req, resp ) { + const testParam = req.query.test ? `-${ req.query.test }` : ""; + resp.writeHead( 200, { + "Content-Type": "text/html", + "Content-Security-Policy": "script-src 'nonce-jquery+hardcoded+nonce'; " + + "report-uri /base/test/data/mock.php?action=cspLog" + } ); + const body = fs.readFileSync( + `${ __dirname }/data/csp-nonce${ testParam }.html` ).toString(); + resp.end( body ); + }, + cspAjaxScript: function( _req, resp ) { + resp.writeHead( 200, { + "Content-Type": "text/html", + "Content-Security-Policy": "script-src 'self'; " + + "report-uri /base/test/data/mock.php?action=cspLog" + } ); + const body = fs.readFileSync( + `${ __dirname }/data/csp-ajax-script.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(); + }, + trustedHtml: function( _req, resp ) { + resp.writeHead( 200, { + "Content-Type": "text/html", + "Content-Security-Policy": "require-trusted-types-for 'script'; " + + "report-uri /base/test/data/mock.php?action=cspLog" + } ); + const body = fs.readFileSync( `${ __dirname }/data/trusted-html.html` ).toString(); + resp.end( body ); + }, + trustedTypesAttributes: function( _req, resp ) { + resp.writeHead( 200, { + "Content-Type": "text/html", + "Content-Security-Policy": "require-trusted-types-for 'script'; " + + "report-uri /base/test/data/mock.php?action=cspLog" + } ); + const body = fs.readFileSync( + `${ __dirname }/data/trusted-types-attributes.html` ).toString(); + resp.end( body ); + }, + errorWithScript: function( req, resp ) { + if ( req.query.withScriptContentType ) { + resp.writeHead( 404, { "Content-Type": "application/javascript" } ); + } else { + resp.writeHead( 404, { "Content-Type": "text/html; charset=UTF-8" } ); + } + if ( req.query.callback ) { + resp.end( `${ cleanCallback( req.query.callback ) + }( {"status": 404, "msg": "Not Found"} )` ); + } else { + resp.end( "QUnit.assert.ok( false, \"Mock return erroneously executed\" );" ); + } + } +}; +const 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 ) { + const parsed = url.parse( req.url, /* parseQuery */ true ); + let path = parsed.pathname.replace( /^\/base\//, "" ); + const query = parsed.query; + const 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; + } + + // console.log( "Mock handling", req.method, parsed.href ); + handlers[ path ]( subReq, resp, next ); + }; +} + +function getBody( req ) { + return req.method !== "POST" ? + Promise.resolve( "" ) : + getRawBody( req, { + encoding: true + } ); +} + +function getMultiPartContent( req ) { + return new Promise( function( resolve ) { + if ( req.method !== "POST" ) { + resolve( "" ); + return; + } + + const form = new multiparty.Form(); + form.parse( req, function( _err, fields, files ) { + resolve( { fields, files } ); + } ); + } ); +} + +module.exports = MockserverMiddlewareFactory; diff --git a/test/middleware-mockserver.js b/test/middleware-mockserver.js deleted file mode 100644 index 20a6607140..0000000000 --- a/test/middleware-mockserver.js +++ /dev/null @@ -1,297 +0,0 @@ -/* 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( "QUnit.assert.ok( true, \"mock executed\" );" ); - }, - testbar: function( req, resp ) { - resp.writeHead( 200 ); - resp.end( - "this.testBar = 'bar'; " + - "jQuery('#ap').html('bar'); " + - "QUnit.assert.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", - "List-Header": "Item 1", - "list-header": "Item 2", - "constructor": "prototype collision (constructor)" - } ); - 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 ); - }, - cspNonce: function( req, resp ) { - var testParam = req.query.test ? "-" + req.query.test : ""; - resp.writeHead( 200, { - "Content-Type": "text/html", - "Content-Security-Policy": "script-src 'nonce-jquery+hardcoded+nonce'; report-uri /base/test/data/mock.php?action=cspLog" - } ); - var body = fs.readFileSync( - __dirname + "/data/csp-nonce" + testParam + ".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/networkerror.html b/test/networkerror.html index f666ee0480..40848bce58 100644 --- a/test/networkerror.html +++ b/test/networkerror.html @@ -1,7 +1,7 @@ " ); assert.ok( j.length >= 2, "Check node,textnode,comment creation (some browsers delete comments)" ); - assert.ok( !jQuery( "" )[ 0 ].selected, "Make sure that options are auto-selected #2050" ); + assert.ok( !jQuery( "" )[ 0 ].selected, "Make sure that options are auto-selected trac-2050" ); assert.ok( jQuery( "
    " )[ 0 ], "Create a div with closing tag." ); assert.ok( jQuery( "
    " )[ 0 ], "Create a table with closing tag." ); @@ -446,7 +487,7 @@ QUnit.test( "jQuery('html')", function( assert ) { //equal( jQuery( "element[attribute=
    ]" ).length, 0, // "When html is within brackets, do not recognize as html." ); - if ( jQuery.find.compile ) { + if ( QUnit.jQuerySelectors ) { assert.equal( jQuery( "element:not(
    )" ).length, 0, "When html is within parens, do not recognize as html." ); } else { @@ -477,7 +518,7 @@ QUnit.test( "jQuery(element with non-alphanumeric name)", function( assert ) { } ); } ); -QUnit.test( "jQuery('massive html #7990')", function( assert ) { +QUnit.test( "jQuery('massive html trac-7990')", function( assert ) { assert.expect( 3 ); var i, @@ -497,9 +538,9 @@ QUnit.test( "jQuery('massive html #7990')", function( assert ) { QUnit.test( "jQuery('html', context)", function( assert ) { assert.expect( 1 ); - var $div = jQuery( "
    " )[ 0 ], - $span = jQuery( "", $div ); - assert.equal( $span.length, 1, "verify a span created with a div context works, #1763" ); + var $div = jQuery( "
    " )[ 0 ], + $span = jQuery( "", $div ); + assert.equal( $span.length, 1, "verify a span created with a div context works, trac-1763" ); } ); QUnit.test( "jQuery(selector, xml).text(str) - loaded via xml document", function( assert ) { @@ -507,7 +548,7 @@ QUnit.test( "jQuery(selector, xml).text(str) - loaded via xml document", functio var xml = createDashboardXML(), - // tests for #1419 where ie was a problem + // tests for trac-1419 where ie was a problem tab = jQuery( "tab", xml ).eq( 0 ); assert.equal( tab.text(), "blabla", "verify initial text correct" ); tab.text( "newtext" ); @@ -545,20 +586,20 @@ QUnit.test( "inArray()", function( assert ) { assert.expect( 19 ); var selections = { - p: q( "firstp", "sap", "ap", "first" ), - em: q( "siblingnext", "siblingfirst" ), + p: q( "firstp", "sap", "ap", "first" ), + em: q( "siblingnext", "siblingfirst" ), div: q( "qunit-testrunner-toolbar", "nothiddendiv", "nothiddendivchild", "foo" ), - a: q( "mark", "groups", "google", "simon1" ), + a: q( "mark", "groups", "google", "simon1" ), empty: [] }, tests = { - p: { elem: jQuery( "#ap" )[ 0 ], index: 2 }, - em: { elem: jQuery( "#siblingfirst" )[ 0 ], index: 1 }, - div: { elem: jQuery( "#nothiddendiv" )[ 0 ], index: 1 }, - a: { elem: jQuery( "#simon1" )[ 0 ], index: 3 } + p: { elem: jQuery( "#ap" )[ 0 ], index: 2 }, + em: { elem: jQuery( "#siblingfirst" )[ 0 ], index: 1 }, + div: { elem: jQuery( "#nothiddendiv" )[ 0 ], index: 1 }, + a: { elem: jQuery( "#simon1" )[ 0 ], index: 3 } }, falseTests = { - p: jQuery( "#liveSpan1" )[ 0 ], + p: jQuery( "#liveSpan1" )[ 0 ], em: jQuery( "#nothiddendiv" )[ 0 ], empty: "" }; @@ -595,7 +636,9 @@ QUnit.test( "each(Function)", function( assert ) { var div, pass, i; div = jQuery( "div" ); - div.each( function() {this.foo = "zoo";} ); + div.each( function() { +this.foo = "zoo"; +} ); pass = true; for ( i = 0; i < div.length; i++ ) { if ( div.get( i ).foo !== "zoo" ) { @@ -632,6 +675,18 @@ QUnit.test( "first()/last()", function( assert ) { assert.deepEqual( $none.last().get(), [], "last() none" ); } ); +QUnit.test( "even()/odd()", function( assert ) { + assert.expect( 4 ); + + var $links = jQuery( "#ap a" ), $none = jQuery( "asdf" ); + + assert.deepEqual( $links.even().get(), q( "google", "anchor1" ), "even()" ); + assert.deepEqual( $links.odd().get(), q( "groups", "mark" ), "odd()" ); + + assert.deepEqual( $none.even().get(), [], "even() none" ); + assert.deepEqual( $none.odd().get(), [], "odd() none" ); +} ); + QUnit.test( "map()", function( assert ) { assert.expect( 2 ); @@ -653,7 +708,7 @@ QUnit.test( "map()", function( assert ) { } ); QUnit.test( "jQuery.map", function( assert ) { - assert.expect( 25 ); + assert.expect( 28 ); var i, label, result, callback; @@ -689,8 +744,12 @@ QUnit.test( "jQuery.map", function( assert ) { result = { Zero: function() {}, - One: function( a ) { a = a; }, - Two: function( a, b ) { a = a; b = b; } + One: function( a ) { + a = a; + }, + Two: function( a, b ) { + a = a; b = b; + } }; callback = function( v, k ) { assert.equal( k, "foo", label + "-argument function treated like object" ); @@ -752,7 +811,24 @@ QUnit.test( "jQuery.map", function( assert ) { result = jQuery.map( Array( 4 ), function( v, k ) { return k % 2 ? k : [ k, k, k ]; } ); - assert.equal( result.join( "" ), "00012223", "Array results flattened (#2616)" ); + assert.equal( result.join( "" ), "00012223", "Array results flattened (trac-2616)" ); + + result = jQuery.map( [ [ [ 1, 2 ], 3 ], 4 ], function( v, k ) { + return v; + } ); + assert.equal( result.length, 3, "Array flatten only one level down" ); + assert.ok( Array.isArray( result[ 0 ] ), "Array flatten only one level down" ); + + // Support: IE 11+ + // IE doesn't have Array#flat so it'd fail the test. + if ( !QUnit.isIE ) { + result = jQuery.map( Array( 300000 ), function( v, k ) { + return k; + } ); + assert.equal( result.length, 300000, "Able to map 300000 records without any problems (gh-4320)" ); + } else { + assert.ok( "skip", "Array#flat isn't supported in IE" ); + } } ); QUnit.test( "jQuery.merge()", function( assert ) { @@ -786,14 +862,14 @@ QUnit.test( "jQuery.merge()", function( assert ) { "First empty" ); - // Fixed at [5998], #3641 + // Fixed at [5998], trac-3641 assert.deepEqual( jQuery.merge( [ -2, -1 ], [ 0, 1, 2 ] ), [ -2, -1, 0, 1, 2 ], "Second array including a zero (falsy)" ); - // After fixing #5527 + // After fixing trac-5527 assert.deepEqual( jQuery.merge( [], [ null, undefined ] ), [ null, undefined ], @@ -931,25 +1007,25 @@ QUnit.test( "jQuery.extend(Object, Object)", function( assert ) { assert.deepEqual( options, optionsCopy, "Check if not modified: options must not be modified" ); jQuery.extend( true, deep1, deep2 ); - assert.deepEqual( deep1[ "foo" ], deepmerged[ "foo" ], "Check if foo: settings must be extended" ); - assert.deepEqual( deep2[ "foo" ], deep2copy[ "foo" ], "Check if not deep2: options must not be modified" ); - assert.equal( deep1[ "foo2" ], document, "Make sure that a deep clone was not attempted on the document" ); + assert.deepEqual( deep1.foo, deepmerged.foo, "Check if foo: settings must be extended" ); + assert.deepEqual( deep2.foo, deep2copy.foo, "Check if not deep2: options must not be modified" ); + assert.equal( deep1.foo2, document, "Make sure that a deep clone was not attempted on the document" ); - assert.ok( jQuery.extend( true, {}, nestedarray )[ "arr" ] !== arr, "Deep extend of object must clone child array" ); + assert.ok( jQuery.extend( true, {}, nestedarray ).arr !== arr, "Deep extend of object must clone child array" ); - // #5991 - assert.ok( Array.isArray( jQuery.extend( true, { "arr": {} }, nestedarray )[ "arr" ] ), "Cloned array have to be an Array" ); - assert.ok( jQuery.isPlainObject( jQuery.extend( true, { "arr": arr }, { "arr": {} } )[ "arr" ] ), "Cloned object have to be an plain object" ); + // trac-5991 + assert.ok( Array.isArray( jQuery.extend( true, { "arr": {} }, nestedarray ).arr ), "Cloned array have to be an Array" ); + assert.ok( jQuery.isPlainObject( jQuery.extend( true, { "arr": arr }, { "arr": {} } ).arr ), "Cloned object have to be an plain object" ); empty = {}; optionsWithLength = { "foo": { "length": -1 } }; jQuery.extend( true, empty, optionsWithLength ); - assert.deepEqual( empty[ "foo" ], optionsWithLength[ "foo" ], "The length property must copy correctly" ); + assert.deepEqual( empty.foo, optionsWithLength.foo, "The length property must copy correctly" ); empty = {}; optionsWithDate = { "foo": { "date": new Date() } }; jQuery.extend( true, empty, optionsWithDate ); - assert.deepEqual( empty[ "foo" ], optionsWithDate[ "foo" ], "Dates copy correctly" ); + assert.deepEqual( empty.foo, optionsWithDate.foo, "Dates copy correctly" ); /** @constructor */ myKlass = function() {}; @@ -957,13 +1033,13 @@ QUnit.test( "jQuery.extend(Object, Object)", function( assert ) { optionsWithCustomObject = { "foo": { "date": customObject } }; empty = {}; jQuery.extend( true, empty, optionsWithCustomObject ); - assert.ok( empty[ "foo" ] && empty[ "foo" ][ "date" ] === customObject, "Custom objects copy correctly (no methods)" ); + assert.ok( empty.foo && empty.foo.date === customObject, "Custom objects copy correctly (no methods)" ); // Makes the class a little more realistic myKlass.prototype = { "someMethod": function() {} }; empty = {}; jQuery.extend( true, empty, optionsWithCustomObject ); - assert.ok( empty[ "foo" ] && empty[ "foo" ][ "date" ] === customObject, "Custom objects copy correctly" ); + assert.ok( empty.foo && empty.foo.date === customObject, "Custom objects copy correctly" ); MyNumber = Number; @@ -971,30 +1047,30 @@ QUnit.test( "jQuery.extend(Object, Object)", function( assert ) { assert.ok( parseInt( ret.foo, 10 ) === 5, "Wrapped numbers copy correctly" ); nullUndef = jQuery.extend( {}, options, { "xnumber2": null } ); - assert.ok( nullUndef[ "xnumber2" ] === null, "Check to make sure null values are copied" ); + assert.ok( nullUndef.xnumber2 === null, "Check to make sure null values are copied" ); nullUndef = jQuery.extend( {}, options, { "xnumber2": undefined } ); - assert.ok( nullUndef[ "xnumber2" ] === options[ "xnumber2" ], "Check to make sure undefined values are not copied" ); + assert.ok( nullUndef.xnumber2 === options.xnumber2, "Check to make sure undefined values are not copied" ); nullUndef = jQuery.extend( {}, options, { "xnumber0": null } ); - assert.ok( nullUndef[ "xnumber0" ] === null, "Check to make sure null values are inserted" ); + assert.ok( nullUndef.xnumber0 === null, "Check to make sure null values are inserted" ); target = {}; - recursive = { foo:target, bar:5 }; + recursive = { foo: target, bar: 5 }; jQuery.extend( true, target, recursive ); - assert.deepEqual( target, { bar:5 }, "Check to make sure a recursive obj doesn't go never-ending loop by not copying it over" ); + assert.deepEqual( target, { bar: 5 }, "Check to make sure a recursive obj doesn't go never-ending loop by not copying it over" ); ret = jQuery.extend( true, { foo: [] }, { foo: [ 0 ] } ); // 1907 - assert.equal( ret.foo.length, 1, "Check to make sure a value with coercion 'false' copies over when necessary to fix #1907" ); + assert.equal( ret.foo.length, 1, "Check to make sure a value with coercion 'false' copies over when necessary to fix trac-1907" ); ret = jQuery.extend( true, { foo: "1,2,3" }, { foo: [ 1, 2, 3 ] } ); assert.ok( typeof ret.foo !== "string", "Check to make sure values equal with coercion (but not actually equal) overwrite correctly" ); - ret = jQuery.extend( true, { foo:"bar" }, { foo:null } ); - assert.ok( typeof ret.foo !== "undefined", "Make sure a null value doesn't crash with deep extend, for #1908" ); + ret = jQuery.extend( true, { foo: "bar" }, { foo: null } ); + assert.ok( typeof ret.foo !== "undefined", "Make sure a null value doesn't crash with deep extend, for trac-1908" ); - obj = { foo:null }; - jQuery.extend( true, obj, { foo:"notnull" } ); + obj = { foo: null }; + jQuery.extend( true, obj, { foo: "notnull" } ); assert.equal( obj.foo, "notnull", "Make sure a null value can be overwritten" ); function func() {} @@ -1104,8 +1180,12 @@ QUnit.test( "jQuery.each(Object,Function)", function( assert ) { seen = { Zero: function() {}, - One: function( a ) { a = a; }, - Two: function( a, b ) { a = a; b = b; } + One: function( a ) { + a = a; + }, + Two: function( a, b ) { + a = a; b = b; + } }; callback = function( k ) { assert.equal( k, "foo", label + "-argument function treated like object" ); @@ -1214,7 +1294,9 @@ QUnit.test( "jQuery.makeArray", function( assert ) { assert.equal( jQuery.makeArray( document.getElementsByName( "PWD" ) ).slice( 0, 1 )[ 0 ].name, "PWD", "Pass makeArray a nodelist" ); - assert.equal( ( function() { return jQuery.makeArray( arguments ); } )( 1, 2 ).join( "" ), "12", "Pass makeArray an arguments array" ); + assert.equal( ( function() { + return jQuery.makeArray( arguments ); + } )( 1, 2 ).join( "" ), "12", "Pass makeArray an arguments array" ); assert.equal( jQuery.makeArray( [ 1, 2, 3 ] ).join( "" ), "123", "Pass makeArray a real array" ); @@ -1228,12 +1310,14 @@ QUnit.test( "jQuery.makeArray", function( assert ) { assert.equal( jQuery.makeArray( document.createElement( "div" ) )[ 0 ].nodeName.toUpperCase(), "DIV", "Pass makeArray a single node" ); - assert.equal( jQuery.makeArray( { length:2, 0:"a", 1:"b" } ).join( "" ), "ab", "Pass makeArray an array like map (with length)" ); + assert.equal( jQuery.makeArray( { length: 2, 0: "a", 1: "b" } ).join( "" ), "ab", "Pass makeArray an array like map (with length)" ); assert.ok( !!jQuery.makeArray( document.documentElement.childNodes ).slice( 0, 1 )[ 0 ].nodeName, "Pass makeArray a childNodes array" ); // function, is tricky as it has length - assert.equal( jQuery.makeArray( function() { return 1;} )[ 0 ](), 1, "Pass makeArray a function" ); + assert.equal( jQuery.makeArray( function() { + return 1; + } )[ 0 ](), 1, "Pass makeArray a function" ); //window, also has length assert.equal( jQuery.makeArray( window )[ 0 ], window, "Pass makeArray the window" ); @@ -1259,7 +1343,7 @@ QUnit.test( "jQuery.isEmptyObject", function( assert ) { assert.expect( 2 ); assert.equal( true, jQuery.isEmptyObject( {} ), "isEmptyObject on empty object literal" ); - assert.equal( false, jQuery.isEmptyObject( { a:1 } ), "isEmptyObject on non-empty object literal" ); + assert.equal( false, jQuery.isEmptyObject( { a: 1 } ), "isEmptyObject on non-empty object literal" ); // What about this ? // equal(true, jQuery.isEmptyObject(null), "isEmptyObject on null" ); @@ -1297,7 +1381,7 @@ QUnit.test( "jQuery.parseHTML", function( assert ) { assert.equal( jQuery.parseHTML( "text" )[ 0 ].nodeType, 3, "Parsing text returns a text node" ); assert.equal( jQuery.parseHTML( "\t
    " )[ 0 ].nodeValue, "\t", "Preserve leading whitespace" ); - assert.equal( jQuery.parseHTML( "
    " )[ 0 ].nodeType, 3, "Leading spaces are treated as text nodes (#11290)" ); + assert.equal( jQuery.parseHTML( "
    " )[ 0 ].nodeType, 3, "Leading spaces are treated as text nodes (trac-11290)" ); html = jQuery.parseHTML( "
    test div
    " ); @@ -1319,22 +1403,20 @@ QUnit.test( "jQuery.parseHTML() - gh-2965", function( assert ) { assert.ok( /\/example\.html$/.test( href ), "href is not lost after parsing anchor" ); } ); -if ( jQuery.support.createHTMLDocument ) { - QUnit.test( "jQuery.parseHTML", function( assert ) { - var done = assert.async(); - assert.expect( 1 ); +QUnit.test( "jQuery.parseHTML error handling", function( assert ) { + var done = assert.async(); + assert.expect( 1 ); - Globals.register( "parseHTMLError" ); + Globals.register( "parseHTMLError" ); - jQuery.globalEval( "parseHTMLError = false;" ); - jQuery.parseHTML( "" ); + jQuery.globalEval( "parseHTMLError = false;" ); + jQuery.parseHTML( "" ); - window.setTimeout( function() { - assert.equal( window.parseHTMLError, false, "onerror eventhandler has not been called." ); - done(); - }, 2000 ); - } ); -} + window.setTimeout( function() { + assert.equal( window.parseHTMLError, false, "onerror eventhandler has not been called." ); + done(); + }, 2000 ); +} ); QUnit.test( "jQuery.parseXML", function( assert ) { assert.expect( 8 ); @@ -1352,9 +1434,9 @@ QUnit.test( "jQuery.parseXML", function( assert ) { } try { xml = jQuery.parseXML( "

    Not a <well-formed xml string

    " ); - assert.ok( false, "invalid xml not detected" ); + assert.ok( false, "invalid XML not detected" ); } catch ( e ) { - assert.strictEqual( e.message, "Invalid XML:

    Not a <well-formed xml string

    ", "invalid xml detected" ); + assert.ok( e.message.indexOf( "Invalid XML:" ) === 0, "invalid XML detected" ); } try { xml = jQuery.parseXML( "" ); @@ -1370,33 +1452,40 @@ QUnit.test( "jQuery.parseXML", function( assert ) { } } ); +// Support: IE 11+ +// IE throws an error when parsing invalid XML instead of reporting the error +// in a `parsererror` element, skip the test there. +QUnit.testUnlessIE( "jQuery.parseXML - error reporting", function( assert ) { + assert.expect( 2 ); + + var errorArg, lineMatch, line, columnMatch, column; + + this.sandbox.stub( jQuery, "error" ); + + jQuery.parseXML( "

    Not a <well-formed xml string

    " ); + errorArg = jQuery.error.firstCall.lastArg.toLowerCase(); + console.log( "errorArg", errorArg ); + + lineMatch = errorArg.match( /line\s*(?:number)?\s*(\d+)/ ); + line = lineMatch && lineMatch[ 1 ]; + columnMatch = errorArg.match( /column\s*(\d+)/ ); + column = columnMatch && columnMatch[ 1 ]; + + assert.strictEqual( line, "1", "reports error line" ); + assert.strictEqual( column, "11", "reports error column" ); +} ); + testIframe( - "Conditional compilation compatibility (#13274)", - "core/cc_on.html", - function( assert, jQuery, window, document, cc_on, errors ) { - assert.expect( 3 ); - assert.ok( true, "JScript conditional compilation " + ( cc_on ? "supported" : "not supported" ) ); - assert.deepEqual( errors, [], "No errors" ); - assert.ok( jQuery(), "jQuery executes" ); + "document ready when jQuery loaded asynchronously (trac-13655)", + "core/dynamic_ready.html", + function( assert, jQuery, window, document, ready ) { + assert.expect( 1 ); + assert.equal( true, ready, "document ready correctly fired when jQuery is loaded after DOMContentLoaded" ); } ); -// iOS7 doesn't fire the load event if the long-loading iframe gets its source reset to about:blank. -// This makes this test fail but it doesn't seem to cause any real-life problems so blacklisting -// this test there is preferred to complicating the hard-to-test core/ready code further. -if ( !/iphone os 7_/i.test( navigator.userAgent ) ) { - testIframe( - "document ready when jQuery loaded asynchronously (#13655)", - "core/dynamic_ready.html", - function( assert, jQuery, window, document, ready ) { - assert.expect( 1 ); - assert.equal( true, ready, "document ready correctly fired when jQuery is loaded after DOMContentLoaded" ); - } - ); -} - testIframe( - "Tolerating alias-masked DOM properties (#14074)", + "Tolerating alias-masked DOM properties (trac-14074)", "core/aliased.html", function( assert, jQuery, window, document, errors ) { assert.expect( 1 ); @@ -1405,7 +1494,7 @@ testIframe( ); testIframe( - "Don't call window.onready (#14802)", + "Don't call window.onready (trac-14802)", "core/onready.html", function( assert, jQuery, window, document, error ) { assert.expect( 1 ); @@ -1443,12 +1532,12 @@ testIframe( } ); -QUnit[ jQuery.Deferred ? "test" : "skip" ]( "jQuery.readyException (original)", function( assert ) { +QUnit[ includesModule( "deferred" ) ? "test" : "skip" ]( "jQuery.readyException (original)", function( assert ) { assert.expect( 1 ); var message; - this.sandbox.stub( window, "setTimeout", function( fn ) { + this.sandbox.stub( window, "setTimeout" ).callsFake( function( fn ) { try { fn(); } catch ( error ) { @@ -1466,12 +1555,12 @@ QUnit[ jQuery.Deferred ? "test" : "skip" ]( "jQuery.readyException (original)", ); } ); -QUnit[ jQuery.Deferred ? "test" : "skip" ]( "jQuery.readyException (custom)", function( assert ) { +QUnit[ includesModule( "deferred" ) ? "test" : "skip" ]( "jQuery.readyException (custom)", function( assert ) { assert.expect( 1 ); var done = assert.async(); - this.sandbox.stub( jQuery, "readyException", function( error ) { + this.sandbox.stub( jQuery, "readyException" ).callsFake( function( error ) { assert.strictEqual( error.message, "Error in jQuery ready", @@ -1484,3 +1573,59 @@ QUnit[ jQuery.Deferred ? "test" : "skip" ]( "jQuery.readyException (custom)", fu throw new Error( "Error in jQuery ready" ); } ); } ); + +QUnit.test( "jQuery.contains", function( assert ) { + assert.expect( 16 ); + + var container = document.getElementById( "nonnodes" ), + element = container.firstChild, + text = element.nextSibling, + nonContained = container.nextSibling, + detached = document.createElement( "a" ); + assert.ok( element && element.nodeType === 1, "preliminary: found element" ); + assert.ok( text && text.nodeType === 3, "preliminary: found text" ); + assert.ok( nonContained, "preliminary: found non-descendant" ); + assert.ok( jQuery.contains( container, element ), "child" ); + assert.ok( jQuery.contains( container.parentNode, element ), "grandchild" ); + assert.ok( jQuery.contains( container, text ), "text child" ); + assert.ok( jQuery.contains( container.parentNode, text ), "text grandchild" ); + assert.ok( !jQuery.contains( container, container ), "self" ); + assert.ok( !jQuery.contains( element, container ), "parent" ); + assert.ok( !jQuery.contains( container, nonContained ), "non-descendant" ); + assert.ok( !jQuery.contains( container, document ), "document" ); + assert.ok( !jQuery.contains( container, document.documentElement ), "documentElement (negative)" ); + assert.ok( !jQuery.contains( container, null ), "Passing null does not throw an error" ); + assert.ok( jQuery.contains( document, document.documentElement ), "documentElement (positive)" ); + assert.ok( jQuery.contains( document, element ), "document container (positive)" ); + assert.ok( !jQuery.contains( document, detached ), "document container (negative)" ); +} ); + +QUnit.test( "jQuery.contains in SVG (jQuery trac-10832)", function( assert ) { + assert.expect( 4 ); + + var svg = jQuery( + "" + + "" + + "" + ).appendTo( "#qunit-fixture" )[ 0 ]; + + assert.ok( jQuery.contains( svg, svg.firstChild ), "root child" ); + assert.ok( jQuery.contains( svg.firstChild, svg.firstChild.firstChild ), "element child" ); + assert.ok( jQuery.contains( svg, svg.firstChild.firstChild ), "root granchild" ); + assert.ok( !jQuery.contains( svg.firstChild.firstChild, svg.firstChild ), + "parent (negative)" ); +} ); + +QUnit.testUnlessIE( "jQuery.contains within