From 3ae8ed63b1e9c8f3b4e8eeb22d6a2509055599bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Mon, 4 Nov 2024 00:11:21 +0100 Subject: [PATCH 01/11] Core: Remove support for jQuery 4.x --- .github/workflows/browserstack-git.yml | 68 ----- ...{browserstack-3.x.yml => browserstack.yml} | 4 +- .github/workflows/filestash.yml | 4 +- CONTRIBUTING.md | 2 +- README.md | 16 +- jtr-3.x.yml => jtr-ci.yml | 0 jtr-local.yml | 4 - src/jquery/ajax.js | 133 ++-------- src/jquery/attributes.js | 16 +- src/jquery/core.js | 101 +------- src/jquery/css.js | 71 +----- src/jquery/deferred.js | 10 +- src/jquery/selector.js | 69 +++++ src/main.js | 6 +- test/data/ajax-jsonp-callback-name.html | 2 +- test/data/event-fixHooks.html | 2 +- test/data/event-lateload.html | 2 +- test/data/event-props-concat.html | 2 +- test/data/event-props.html | 2 +- test/data/event-ready.html | 2 +- test/data/testinit.js | 16 +- test/unit/jquery/ajax.js | 240 ++++++++---------- test/unit/jquery/attributes.js | 37 +-- test/unit/jquery/core.js | 159 ------------ test/unit/jquery/css.js | 9 +- test/unit/jquery/deferred.js | 24 +- test/unit/jquery/selector.js | 103 ++++++++ warnings.md | 16 +- 28 files changed, 368 insertions(+), 752 deletions(-) delete mode 100644 .github/workflows/browserstack-git.yml rename .github/workflows/{browserstack-3.x.yml => browserstack.yml} (96%) rename jtr-3.x.yml => jtr-ci.yml (100%) diff --git a/.github/workflows/browserstack-git.yml b/.github/workflows/browserstack-git.yml deleted file mode 100644 index 40247557..00000000 --- a/.github/workflows/browserstack-git.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Browserstack (Core git) - -on: - push: - branches: - - main - # Once a week every Tuesday - schedule: - - cron: "42 1 * * 2" - -jobs: - test: - runs-on: ubuntu-latest - environment: browserstack - env: - BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} - BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - NODE_VERSION: 22.x - name: ${{ matrix.BROWSER }} - concurrency: - group: ${{ matrix.BROWSER }} - ${{ github.sha }} - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - BROWSER: - - 'IE_11' - - 'Safari_latest' - - 'Safari_latest-1' - - 'Chrome_latest' - - 'Chrome_latest-1' - - 'Opera_latest' - - 'Edge_latest' - - 'Edge_latest-1' - - 'Firefox_latest' - - 'Firefox_latest-1' - - 'Firefox_115' - - '__iOS_18' - - '__iOS_17' - - '__iOS_16' - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Cache - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock- - - - name: Install dependencies - run: npm install - - - name: Pretest script - run: npm run pretest - - - name: Test - run: | - npm run test:unit -- -v -c jtr-git.yml \ - --browserstack "${{ matrix.BROWSER }}" \ - --run-id ${{ github.run_id }} \ diff --git a/.github/workflows/browserstack-3.x.yml b/.github/workflows/browserstack.yml similarity index 96% rename from .github/workflows/browserstack-3.x.yml rename to .github/workflows/browserstack.yml index a9748638..03c58b0b 100644 --- a/.github/workflows/browserstack-3.x.yml +++ b/.github/workflows/browserstack.yml @@ -1,4 +1,4 @@ -name: Browserstack (Core 3.x) +name: Browserstack on: push: @@ -79,6 +79,6 @@ jobs: - name: Test run: | - npm run test:unit -- -v -c jtr-3.x.yml \ + npm run test:unit -- -v -c jtr-ci.yml \ --browserstack "${{ matrix.BROWSER }}" \ --run-id ${{ github.run_id }} \ diff --git a/.github/workflows/filestash.yml b/.github/workflows/filestash.yml index 12f8b18b..4f1a9279 100644 --- a/.github/workflows/filestash.yml +++ b/.github/workflows/filestash.yml @@ -47,5 +47,5 @@ jobs: - name: Upload to Filestash run: | - rsync dist/jquery-migrate.js filestash@"${{ secrets.FILESTASH_SERVER }}":jquery-migrate-git.js - rsync dist/jquery-migrate.min.js filestash@"${{ secrets.FILESTASH_SERVER }}":jquery-migrate-git.min.js + rsync dist/jquery-migrate.js filestash@"${{ secrets.FILESTASH_SERVER }}":jquery-migrate-3.x-git.js + rsync dist/jquery-migrate.min.js filestash@"${{ secrets.FILESTASH_SERVER }}":jquery-migrate-3.x-git.min.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb18176c..69865b1d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,7 +47,7 @@ Make sure you have reproduced the bug with all browser extensions and add-ons di ### Try the latest version of jQuery Migrate -Bugs in old versions of jQuery Migrate may have already been fixed. In order to avoid reporting known issues, make sure you are always testing against the [latest build](https://releases.jquery.com/git/jquery-migrate-git.js). We cannot fix bugs in older released files, if a bug has been fixed in a subsequent version of jQuery Migrate the site should upgrade. +Bugs in old versions of jQuery Migrate may have already been fixed. In order to avoid reporting known issues, make sure you are always testing against the [latest build](https://releases.jquery.com/git/jquery-migrate-3.x-git.js). We cannot fix bugs in older released files, if a bug has been fixed in a subsequent version of jQuery Migrate the site should upgrade. ### Simplify the test case diff --git a/README.md b/README.md index 5512c8e4..bd615a60 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,14 @@ That way you can spot and fix what otherwise would have been errors, until you n The following table indicates which jQuery Migrate versions can be used with which jQuery versions: -| jQuery version | jQuery Migrate version | -|----------------|-------------------------| -| 1.x | 1.x | -| 2.x | 1.x | -| 3.x | 3.x / 4.x[1] | -| 4.x | 3.x / 4.x[1] | +| jQuery version | jQuery Migrate version | +|----------------|------------------------| +| 1.x | 1.x | +| 2.x | 1.x | +| 3.x | 3.x | +| 4.x | 4.x | -[1] NOTE: jQuery Migrate 4.x only supports the same browser as jQuery 4.x does. If you need to support Edge Legacy, Internet Explorer 9-10 or iOS 7+ (and not just 3 latest versions), use jQuery Migrate 3.x. +Each jQuery Migrate version supports the same browsers that the jQuery version used with it. ## Usage @@ -40,7 +40,7 @@ The production build is minified and does not generate console warnings. It will | Debugging enabled |

| | | Minified | |

| | Latest release (*may be hotlinked if desired*) | [jquery-migrate-3.5.2.js](https://code.jquery.com/jquery-migrate-3.5.2.js) | [jquery-migrate-3.5.2.min.js](https://code.jquery.com/jquery-migrate-3.5.2.min.js) | -| \* Latest work-in-progress build | [jquery-migrate-git.js](https://releases.jquery.com/git/jquery-migrate-git.js) | [jquery-migrate-git.min.js](https://releases.jquery.com/git/jquery-migrate-git.min.js) | +| \* Latest work-in-progress build | [jquery-migrate-3.x-git.js](https://releases.jquery.com/git/jquery-migrate-3.x-git.js) | [jquery-migrate-3.x-git.min.js](https://releases.jquery.com/git/jquery-migrate-3.x-git.min.js) | \* **Work-in-progress build:** Although this file represents the most recent updates to the plugin, it may not have been thoroughly tested. We do not recommend using this file on production sites since it may be unstable; use the released production version instead. diff --git a/jtr-3.x.yml b/jtr-ci.yml similarity index 100% rename from jtr-3.x.yml rename to jtr-ci.yml diff --git a/jtr-local.yml b/jtr-local.yml index fa5058e1..2fdfd1c8 100644 --- a/jtr-local.yml +++ b/jtr-local.yml @@ -2,10 +2,6 @@ version: 1 flags: jquery: - - git - - git.min - - git.slim - - git.slim.min - 3.x-git - 3.x-git.min - 3.x-git.slim diff --git a/src/jquery/ajax.js b/src/jquery/ajax.js index 91555749..9c2bf679 100644 --- a/src/jquery/ajax.js +++ b/src/jquery/ajax.js @@ -1,4 +1,3 @@ -import { jQueryVersionSince } from "../compareVersions.js"; import { migrateWarn, migratePatchAndWarnFunc, migratePatchFunc } from "../main.js"; // Support jQuery slim which excludes the ajax module @@ -8,8 +7,7 @@ var oldAjax = jQuery.ajax, oldCallbacks = [], guid = "migrate-" + Date.now(), origJsonpCallback = jQuery.ajaxSettings.jsonpCallback, - rjsonp = /(=)\?(?=&|$)|\?\?/, - rquery = /\?/; + rjsonp = /(=)\?(?=&|$)|\?\?/; migratePatchFunc( jQuery, "ajax", function() { var jQXHR = oldAjax.apply( this, arguments ); @@ -45,120 +43,23 @@ jQuery.ajaxSetup( { // Register this prefilter before the jQuery one. Otherwise, a promoted // request is transformed into one with the script dataType, and we can't // catch it anymore. -if ( jQueryVersionSince( "4.0.0" ) ) { - - // Code mostly from: - // https://github.com/jquery/jquery/blob/fa0058af426c4e482059214c29c29f004254d9a1/src/ajax/jsonp.js#L20-L97 - jQuery.ajaxPrefilter( "+json", function( s, originalSettings, jqXHR ) { - - if ( !jQuery.migrateIsPatchEnabled( "jsonp-promotion" ) ) { - return; - } - - var callbackName, overwritten, responseContainer, - jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ? - "url" : - typeof s.data === "string" && - ( s.contentType || "" ) - .indexOf( "application/x-www-form-urlencoded" ) === 0 && - rjsonp.test( s.data ) && "data" - ); - - // Handle iff the expected data type is "jsonp" or we have a parameter to set - if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { - migrateWarn( "jsonp-promotion", "JSON-to-JSONP auto-promotion is deprecated" ); - - // Get callback name, remembering preexisting value associated with it - callbackName = s.jsonpCallback = typeof s.jsonpCallback === "function" ? - s.jsonpCallback() : - s.jsonpCallback; - - // Insert callback into url or form data - if ( jsonProp ) { - s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); - } else if ( s.jsonp !== false ) { - s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; - } - - // Use data converter to retrieve json after script execution - s.converters[ "script json" ] = function() { - if ( !responseContainer ) { - jQuery.error( callbackName + " was not called" ); - } - return responseContainer[ 0 ]; - }; - - // Force json dataType - s.dataTypes[ 0 ] = "json"; - - // Install callback - overwritten = window[ callbackName ]; - window[ callbackName ] = function() { - responseContainer = arguments; - }; - - // Clean-up function (fires after converters) - jqXHR.always( function() { - - // If previous value didn't exist - remove it - if ( overwritten === undefined ) { - jQuery( window ).removeProp( callbackName ); - - // Otherwise restore preexisting value - } else { - window[ callbackName ] = overwritten; - } - - // Save back as free - if ( s[ callbackName ] ) { - - // Make sure that re-using the options doesn't screw things around - s.jsonpCallback = originalSettings.jsonpCallback; - - // Save the callback name for future use - oldCallbacks.push( callbackName ); - } - - // Call if it was a function and we have a response - if ( responseContainer && typeof overwritten === "function" ) { - overwritten( responseContainer[ 0 ] ); - } - - responseContainer = overwritten = undefined; - } ); - - // Delegate to script - return "script"; - } - } ); -} else { - - // jQuery <4 already contains this prefixer; don't duplicate the whole logic, - // but only enough to know when to warn. - jQuery.ajaxPrefilter( "+json", function( s ) { - - if ( !jQuery.migrateIsPatchEnabled( "jsonp-promotion" ) ) { - return; - } - - // Warn if JSON-to-JSONP auto-promotion happens. - if ( s.jsonp !== false && ( rjsonp.test( s.url ) || - typeof s.data === "string" && - ( s.contentType || "" ) - .indexOf( "application/x-www-form-urlencoded" ) === 0 && - rjsonp.test( s.data ) - ) ) { - migrateWarn( "jsonp-promotion", "JSON-to-JSONP auto-promotion is deprecated" ); - } - } ); -} +// jQuery <4 already contains this prefixer; don't duplicate the whole logic, +// but only enough to know when to warn. +jQuery.ajaxPrefilter( "+json", function( s ) { + if ( !jQuery.migrateIsPatchEnabled( "jsonp-promotion" ) ) { + return; + } -// Don't trigger the above logic in jQuery >=4 by default as the JSON-to-JSONP -// auto-promotion behavior is gone in jQuery 4.0 and as it has security implications, -// we don't want to restore the legacy behavior by default. -if ( jQueryVersionSince( "4.0.0" ) ) { - jQuery.migrateDisablePatches( "jsonp-promotion" ); -} + // Warn if JSON-to-JSONP auto-promotion happens. + if ( s.jsonp !== false && ( rjsonp.test( s.url ) || + typeof s.data === "string" && + ( s.contentType || "" ) + .indexOf( "application/x-www-form-urlencoded" ) === 0 && + rjsonp.test( s.data ) + ) ) { + migrateWarn( "jsonp-promotion", "JSON-to-JSONP auto-promotion is deprecated" ); + } +} ); } diff --git a/src/jquery/attributes.js b/src/jquery/attributes.js index f76b1499..be029948 100644 --- a/src/jquery/attributes.js +++ b/src/jquery/attributes.js @@ -1,5 +1,4 @@ import { migratePatchFunc, migrateWarn } from "../main.js"; -import { jQueryVersionSince } from "../compareVersions.js"; var oldRemoveAttr = jQuery.fn.removeAttr, oldJQueryAttr = jQuery.attr, @@ -36,15 +35,6 @@ jQuery.each( booleans.split( "|" ), function( _i, name ) { migrateWarn( "boolean-attributes", "Boolean attribute '" + name + "' value is different from its lowercased name" ); - - // jQuery <4 attr hooks setup is complex: there are attr - // hooks, bool hooks and selector attr handles. Only - // implement the logic in jQuery >=4 where it's missing - // and there are only attr hooks. - if ( jQueryVersionSince( "4.0.0" ) ) { - return name.toLowerCase(); - } - return null; } } @@ -72,12 +62,12 @@ jQuery.each( booleans.split( "|" ), function( _i, name ) { } return name; } - } else if ( !jQueryVersionSince( "4.0.0" ) ) { + } else { // jQuery <4 uses a private `boolHook` for the boolean attribute // setter. It's only activated if `attrHook` is not set, but we set - // it here in Migrate. Since we cannot access it, let's just repeat - // its contents here. + // it here in Migrate so jQuery would not use it. Since we cannot + // access it, let's just repeat its contents here. if ( value === false ) { // Remove boolean attributes when set to false diff --git a/src/jquery/core.js b/src/jquery/core.js index 7d92fefd..c1f8b311 100644 --- a/src/jquery/core.js +++ b/src/jquery/core.js @@ -1,24 +1,10 @@ import { jQueryVersionSince } from "../compareVersions.js"; -import { - migratePatchFunc, - migrateWarn, - migratePatchAndWarnFunc, - migrateWarnProp -} from "../main.js"; +import { migratePatchAndWarnFunc } from "../main.js"; import "../disablePatches.js"; -var findProp, - arr = [], - push = arr.push, +var arr = [], slice = arr.slice, - sort = arr.sort, - splice = arr.splice, class2type = {}, - oldInit = jQuery.fn.init, - oldFind = jQuery.find, - - rattrHashTest = /\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/, - rattrHashGlob = /\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/g, // Require that the "whitespace run" starts from a non-whitespace // to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position. @@ -37,68 +23,6 @@ function isFunction( obj ) { typeof obj.item !== "function"; } -migratePatchFunc( jQuery.fn, "init", function( arg1 ) { - var args = Array.prototype.slice.call( arguments ); - - if ( jQuery.migrateIsPatchEnabled( "selector-empty-id" ) && - typeof arg1 === "string" && arg1 === "#" ) { - - // JQuery( "#" ) is a bogus ID selector, but it returned an empty set - // before jQuery 3.0 - migrateWarn( "selector-empty-id", "jQuery( '#' ) is not a valid selector" ); - args[ 0 ] = []; - } - - return oldInit.apply( this, args ); -}, "selector-empty-id" ); - -// This is already done in Core but the above patch will lose this assignment -// so we need to redo it. It doesn't matter whether the patch is enabled or not -// as the method is always going to be a Migrate-created wrapper. -jQuery.fn.init.prototype = jQuery.fn; - -migratePatchFunc( jQuery, "find", function( selector ) { - var args = Array.prototype.slice.call( arguments ); - - // Support: PhantomJS 1.x - // String#match fails to match when used with a //g RegExp, only on some strings - if ( typeof selector === "string" && rattrHashTest.test( selector ) ) { - - // The nonstandard and undocumented unquoted-hash was removed in jQuery 1.12.0 - // First see if qS thinks it's a valid selector, if so avoid a false positive - try { - window.document.querySelector( selector ); - } catch ( err1 ) { - - // Didn't *look* valid to qSA, warn and try quoting what we think is the value - selector = selector.replace( rattrHashGlob, function( _, attr, op, value ) { - return "[" + attr + op + "\"" + value + "\"]"; - } ); - - // If the regexp *may* have created an invalid selector, don't update it - // Note that there may be false alarms if selector uses jQuery extensions - try { - window.document.querySelector( selector ); - migrateWarn( "selector-hash", - "Attribute selector with '#' must be quoted: " + args[ 0 ] ); - args[ 0 ] = selector; - } catch ( err2 ) { - migrateWarn( "selector-hash", - "Attribute selector with '#' was not fixed: " + args[ 0 ] ); - } - } - } - - return oldFind.apply( this, args ); -}, "selector-hash" ); - -// Copy properties attached to original jQuery.find method (e.g. .attr, .isXML) -for ( findProp in oldFind ) { - if ( Object.prototype.hasOwnProperty.call( oldFind, findProp ) ) { - jQuery.find[ findProp ] = oldFind[ findProp ]; - } -} - // The number of elements contained in the matched element set migratePatchAndWarnFunc( jQuery.fn, "size", function() { return this.length; @@ -176,11 +100,10 @@ if ( jQueryVersionSince( "3.3.0" ) ) { }, "type", "jQuery.type is deprecated" ); - migratePatchAndWarnFunc( jQuery, "isFunction", - function( obj ) { - return typeof obj === "function"; - }, "isFunction", - "jQuery.isFunction() is deprecated" ); + migratePatchAndWarnFunc( jQuery, "isFunction", function( obj ) { + return isFunction( obj ); + }, "isFunction", + "jQuery.isFunction() is deprecated" ); migratePatchAndWarnFunc( jQuery, "isWindow", function( obj ) { @@ -224,15 +147,3 @@ if ( jQueryVersionSince( "3.3.0" ) ) { ); } - -if ( jQueryVersionSince( "4.0.0" ) ) { - - // `push`, `sort` & `splice` are used internally by jQuery <4, so we only - // warn in jQuery 4+. - migrateWarnProp( jQuery.fn, "push", push, "push", - "jQuery.fn.push() is deprecated and removed; use .add or convert to an array" ); - migrateWarnProp( jQuery.fn, "sort", sort, "sort", - "jQuery.fn.sort() is deprecated and removed; convert to an array before sorting" ); - migrateWarnProp( jQuery.fn, "splice", splice, "splice", - "jQuery.fn.splice() is deprecated and removed; use .slice or .not with .eq" ); -} diff --git a/src/jquery/css.js b/src/jquery/css.js index 2ae10feb..f06853fc 100644 --- a/src/jquery/css.js +++ b/src/jquery/css.js @@ -2,7 +2,7 @@ import { jQueryVersionSince } from "../compareVersions.js"; import { migrateWarn, migratePatchFunc } from "../main.js"; import { camelCase } from "../utils.js"; -var origFnCss, internalCssNumber, +var origFnCss, internalSwapCall = false, ralphaStart = /^[a-z]/, @@ -80,71 +80,6 @@ if ( jQueryVersionSince( "3.4.0" ) && typeof Proxy !== "undefined" ) { } ); } -// In jQuery >=4 where jQuery.cssNumber is missing fill it with the latest 3.x version: -// https://github.com/jquery/jquery/blob/3.7.1/src/css.js#L216-L246 -// This way, number values for the CSS properties below won't start triggering -// Migrate warnings when jQuery gets updated to >=4.0.0 (gh-438). -if ( jQueryVersionSince( "4.0.0" ) ) { - - // We need to keep this as a local variable as we need it internally - // in a `jQuery.fn.css` patch and this usage shouldn't warn. - internalCssNumber = { - animationIterationCount: true, - aspectRatio: true, - borderImageSlice: true, - columnCount: true, - flexGrow: true, - flexShrink: true, - fontWeight: true, - gridArea: true, - gridColumn: true, - gridColumnEnd: true, - gridColumnStart: true, - gridRow: true, - gridRowEnd: true, - gridRowStart: true, - lineHeight: true, - opacity: true, - order: true, - orphans: true, - scale: true, - widows: true, - zIndex: true, - zoom: true, - - // SVG-related - fillOpacity: true, - floodOpacity: true, - stopOpacity: true, - strokeMiterlimit: true, - strokeOpacity: true - }; - - if ( typeof Proxy !== "undefined" ) { - jQuery.cssNumber = new Proxy( internalCssNumber, { - get: function() { - migrateWarn( "css-number", "jQuery.cssNumber is deprecated" ); - return Reflect.get.apply( this, arguments ); - }, - set: function() { - migrateWarn( "css-number", "jQuery.cssNumber is deprecated" ); - return Reflect.set.apply( this, arguments ); - } - } ); - } else { - - // Support: IE 9-11+ - // IE doesn't support proxies, but we still want to restore the legacy - // jQuery.cssNumber there. - jQuery.cssNumber = internalCssNumber; - } -} else { - - // Make `internalCssNumber` defined for jQuery <4 as well as it's needed - // in the `jQuery.fn.css` patch below. - internalCssNumber = jQuery.cssNumber; -} - function isAutoPx( prop ) { // The first test is used to ensure that: @@ -170,9 +105,7 @@ migratePatchFunc( jQuery.fn, "css", function( name, value ) { if ( typeof value === "number" ) { camelName = camelCase( name ); - // Use `internalCssNumber` to avoid triggering our warnings in this - // internal check. - if ( !isAutoPx( camelName ) && !internalCssNumber[ camelName ] ) { + if ( !isAutoPx( camelName ) && !jQuery.cssNumber[ camelName ] ) { migrateWarn( "css-number", "Number-typed values are deprecated for jQuery.fn.css( \"" + name + "\", value )" ); diff --git a/src/jquery/deferred.js b/src/jquery/deferred.js index 7f036efe..bb78cd3e 100644 --- a/src/jquery/deferred.js +++ b/src/jquery/deferred.js @@ -3,7 +3,6 @@ import { migratePatchAndWarnFunc, migrateWarn } from "../main.js"; -import { jQueryVersionSince } from "../compareVersions.js"; // Support jQuery slim which excludes the deferred module in jQuery 4.0+ if ( jQuery.Deferred ) { @@ -80,13 +79,8 @@ Object.defineProperty( jQuery.Deferred, "getStackHook", { get: function() { if ( jQuery.migrateIsPatchEnabled( "deferred-getStackHook" ) ) { - // jQuery 3.x checks `getStackHook` if `getErrorHook` missing; - // don't warn on the getter there. - if ( jQueryVersionSince( "4.0.0" ) ) { - migrateWarn( "deferred-getStackHook", - "jQuery.Deferred.getStackHook is deprecated; " + - "use jQuery.Deferred.getErrorHook" ); - } + // jQuery 3.x checks `getStackHook` if `getErrorHook` is missing, + // so don't warn on the getter. return jQuery.Deferred.getErrorHook; } else { return unpatchedGetStackHookValue; diff --git a/src/jquery/selector.js b/src/jquery/selector.js index 44237d30..c1625a12 100644 --- a/src/jquery/selector.js +++ b/src/jquery/selector.js @@ -1,6 +1,75 @@ import { jQueryVersionSince } from "../compareVersions.js"; import { migratePatchFunc, migrateWarnProp, migrateWarn } from "../main.js"; +var findProp, + oldInit = jQuery.fn.init, + oldFind = jQuery.find, + + rattrHashTest = /\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/, + rattrHashGlob = /\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/g; + +migratePatchFunc( jQuery.fn, "init", function( arg1 ) { + var args = Array.prototype.slice.call( arguments ); + + if ( jQuery.migrateIsPatchEnabled( "selector-empty-id" ) && + typeof arg1 === "string" && arg1 === "#" ) { + + // JQuery( "#" ) is a bogus ID selector, but it returned an empty set + // before jQuery 3.0 + migrateWarn( "selector-empty-id", "jQuery( '#' ) is not a valid selector" ); + args[ 0 ] = []; + } + + return oldInit.apply( this, args ); +}, "selector-empty-id" ); + +// This is already done in Core but the above patch will lose this assignment +// so we need to redo it. It doesn't matter whether the patch is enabled or not +// as the method is always going to be a Migrate-created wrapper. +jQuery.fn.init.prototype = jQuery.fn; + +migratePatchFunc( jQuery, "find", function( selector ) { + var args = Array.prototype.slice.call( arguments ); + + // Support: PhantomJS 1.x + // String#match fails to match when used with a //g RegExp, only on some strings + if ( typeof selector === "string" && rattrHashTest.test( selector ) ) { + + // The nonstandard and undocumented unquoted-hash was removed in jQuery 1.12.0 + // First see if qS thinks it's a valid selector, if so avoid a false positive + try { + window.document.querySelector( selector ); + } catch ( err1 ) { + + // Didn't *look* valid to qSA, warn and try quoting what we think is the value + selector = selector.replace( rattrHashGlob, function( _, attr, op, value ) { + return "[" + attr + op + "\"" + value + "\"]"; + } ); + + // If the regexp *may* have created an invalid selector, don't update it + // Note that there may be false alarms if selector uses jQuery extensions + try { + window.document.querySelector( selector ); + migrateWarn( "selector-hash", + "Attribute selector with '#' must be quoted: " + args[ 0 ] ); + args[ 0 ] = selector; + } catch ( err2 ) { + migrateWarn( "selector-hash", + "Attribute selector with '#' was not fixed: " + args[ 0 ] ); + } + } + } + + return oldFind.apply( this, args ); +}, "selector-hash" ); + +// Copy properties attached to original jQuery.find method (e.g. .attr, .isXML) +for ( findProp in oldFind ) { + if ( Object.prototype.hasOwnProperty.call( oldFind, findProp ) ) { + jQuery.find[ findProp ] = oldFind[ findProp ]; + } +} + // Now jQuery.expr.pseudos is the standard incantation migrateWarnProp( jQuery.expr, "filters", jQuery.expr.pseudos, "expr-pre-pseudos", "jQuery.expr.filters is deprecated; use jQuery.expr.pseudos" ); diff --git a/src/main.js b/src/main.js index ca6ce5c7..909b6203 100644 --- a/src/main.js +++ b/src/main.js @@ -10,10 +10,10 @@ if ( !window.console || !window.console.log ) { return; } -// Need jQuery 3.x-4.x and no older Migrate loaded +// Need jQuery 3.x and no older Migrate loaded if ( !jQuery || !jQueryVersionSince( "3.0.0" ) || - jQueryVersionSince( "5.0.0" ) ) { - window.console.log( "JQMIGRATE: jQuery 3.x-4.x REQUIRED" ); + jQueryVersionSince( "4.0.0" ) ) { + window.console.log( "JQMIGRATE: jQuery 3.x REQUIRED" ); } if ( jQuery.migrateWarnings ) { window.console.log( "JQMIGRATE: Migrate plugin loaded multiple times" ); diff --git a/test/data/ajax-jsonp-callback-name.html b/test/data/ajax-jsonp-callback-name.html index 2d10331a..fd9766b9 100644 --- a/test/data/ajax-jsonp-callback-name.html +++ b/test/data/ajax-jsonp-callback-name.html @@ -7,7 +7,7 @@ diff --git a/test/data/testinit.js b/test/data/testinit.js index 54ea2686..450f0f19 100644 --- a/test/data/testinit.js +++ b/test/data/testinit.js @@ -8,7 +8,7 @@ * dev Uncompressed development version: source files in the project /dist dir * esmodules Non-combined dev version: source files from the project /src dir * min Minified version in the project /dist dir - * VER Version from code.jquery.com, e.g.: git, 1.8.2.min or 1.7rc1 + * VER Version from code.jquery.com, e.g.: 3.x-git, 3.7.1.min or 3.0.0-rc1 * else Full or relative path to be used for script src */ loadProject: function( projectName, defaultVersion, isSelf ) { @@ -28,8 +28,7 @@ file = projectRoot + "/dist/" + projectName + ".js"; } else if ( version === "min" ) { file = projectRoot + "/dist/" + projectName + ".min.js"; - } else if ( version.indexOf( "git" ) === 0 || - version.indexOf( "3.x-git" ) === 0 ) { + } else if ( version.indexOf( "3.x-git" ) === 0 ) { file = "https://releases.jquery.com/git/" + projectName + "-" + version + ".js"; } else if ( /^[\w\.\-]+$/.test( version ) ) { file = "https://code.jquery.com/" + projectName + "-" + version + ".js"; @@ -234,9 +233,6 @@ // Re-disable patches disabled by default jQuery.migrateDisablePatches( "self-closed-tags" ); - if ( jQueryVersionSince( "4.0.0" ) ) { - jQuery.migrateDisablePatches( "jsonp-promotion" ); - } } } ); } @@ -247,10 +243,6 @@ TestManager.init( { choices: [ "dev", "min", - "git", - "git.min", - "git.slim", - "git.slim.min", "3.x-git", "3.x-git.min", "3.x-git.slim", @@ -278,7 +270,9 @@ TestManager.init( { choices: [ "dev", "min", - "git", + "3.x-git", + "3.5.2", + "3.5.0", "3.4.1", "3.4.0", "3.3.2", diff --git a/test/unit/jquery/ajax.js b/test/unit/jquery/ajax.js index 8ff10073..a1818517 100644 --- a/test/unit/jquery/ajax.js +++ b/test/unit/jquery/ajax.js @@ -27,141 +27,121 @@ QUnit.test( "jQuery.ajax() deprecations on jqXHR", function( assert ) { } ); [ " - Same Domain", " - Cross Domain" ].forEach( function( label, crossDomain ) { - function runTests( options ) { - var forceEnablePatch = ( options || {} ).forceEnablePatch || false; - - // Support: IE <10 only - // IE 9 doesn't support CORS, skip cross-domain tests there. - QUnit[ - document.documentMode < 10 && crossDomain ? "skip" : "test" - ]( "jQuery.ajax() JSON-to-JSONP auto-promotion" + label + ( - forceEnablePatch ? ", patch force-enabled" : "" - ), function( assert ) { - - assert.expect( 10 ); - - if ( forceEnablePatch ) { - jQuery.migrateEnablePatches( "jsonp-promotion" ); - } - - var done = assert.async(), - patchEnabled = forceEnablePatch || !jQueryVersionSince( "4.0.0" ), - tests = [ - function() { - var testName = "dataType: \"json\""; - return expectNoWarning( assert, testName, function() { - return jQuery.ajax( { - url: url( "null.json" ), - context: { testName: testName }, - crossDomain: crossDomain, - dataType: "json", - jsonpCallback: "customJsonpCallback" - } ).then( function() { - assert.ok( true, this.testName + " (success)" ); - } ).catch( function() { - assert.ok( false, this.testName + " (failure)" ); - } ); + + // Support: IE <10 only + // IE 9 doesn't support CORS, skip cross-domain tests there. + QUnit[ + document.documentMode < 10 && crossDomain ? "skip" : "test" + ]( "jQuery.ajax() JSON-to-JSONP auto-promotion" + label, function( assert ) { + + assert.expect( 10 ); + + var done = assert.async(), + tests = [ + function() { + var testName = "dataType: \"json\""; + return expectNoWarning( assert, testName, function() { + return jQuery.ajax( { + url: url( "null.json" ), + context: { testName: testName }, + crossDomain: crossDomain, + dataType: "json", + jsonpCallback: "customJsonpCallback" + } ).then( function() { + assert.ok( true, this.testName + " (success)" ); + } ).catch( function() { + assert.ok( false, this.testName + " (failure)" ); } ); - }, - - function() { - var testName = "dataType: \"json\", URL callback"; - return expectWarning( assert, testName, patchEnabled ? 1 : 0, function() { - return jQuery.ajax( { - url: url( "jsonpScript.js?callback=?" ), - context: { testName: testName }, - crossDomain: crossDomain, - dataType: "json", - jsonpCallback: "customJsonpCallback" - } ).then( function() { - assert.ok( patchEnabled, this.testName + " (success)" ); - } ).catch( function() { - assert.ok( !patchEnabled, this.testName + " (failure)" ); - } ); + } ); + }, + + function() { + var testName = "dataType: \"json\", URL callback"; + return expectWarning( assert, testName, 1, function() { + return jQuery.ajax( { + url: url( "jsonpScript.js?callback=?" ), + context: { testName: testName }, + crossDomain: crossDomain, + dataType: "json", + jsonpCallback: "customJsonpCallback" + } ).then( function() { + assert.ok( true, this.testName + " (success)" ); + } ).catch( function() { + assert.ok( false, this.testName + " (failure)" ); } ); - }, - - function() { - var testName = "dataType: \"json\", data callback"; - return expectWarning( assert, testName, patchEnabled ? 1 : 0, function() { - return jQuery.ajax( { - url: url( "jsonpScript.js" ), - context: { testName: testName }, - crossDomain: crossDomain, - data: "callback=?", - dataType: "json", - jsonpCallback: "customJsonpCallback" - } ).then( function() { - assert.ok( patchEnabled, this.testName + " (success)" ); - } ).catch( function() { - assert.ok( !patchEnabled, this.testName + " (failure)" ); - } ); + } ); + }, + + function() { + var testName = "dataType: \"json\", data callback"; + return expectWarning( assert, testName, 1, function() { + return jQuery.ajax( { + url: url( "jsonpScript.js" ), + context: { testName: testName }, + crossDomain: crossDomain, + data: "callback=?", + dataType: "json", + jsonpCallback: "customJsonpCallback" + } ).then( function() { + assert.ok( true, this.testName + " (success)" ); + } ).catch( function() { + assert.ok( false, this.testName + " (failure)" ); } ); - }, - - function() { - var testName = "dataType: \"jsonp\", URL callback"; - return expectNoWarning( assert, testName, function() { - return jQuery.ajax( { - url: url( "jsonpScript.js?callback=?" ), - context: { testName: testName }, - crossDomain: crossDomain, - dataType: "jsonp", - jsonpCallback: "customJsonpCallback" - } ).then( function() { - assert.ok( true, this.testName + " (success)" ); - } ).catch( function() { - assert.ok( false, this.testName + " (failure)" ); - } ); + } ); + }, + + function() { + var testName = "dataType: \"jsonp\", URL callback"; + return expectNoWarning( assert, testName, function() { + return jQuery.ajax( { + url: url( "jsonpScript.js?callback=?" ), + context: { testName: testName }, + crossDomain: crossDomain, + dataType: "jsonp", + jsonpCallback: "customJsonpCallback" + } ).then( function() { + assert.ok( true, this.testName + " (success)" ); + } ).catch( function() { + assert.ok( false, this.testName + " (failure)" ); } ); - }, - - function() { - var testName = "dataType: \"jsonp\", data callback"; - return expectNoWarning( assert, testName, function() { - return jQuery.ajax( { - url: url( "jsonpScript.js" ), - context: { testName: testName }, - crossDomain: crossDomain, - data: "callback=?", - dataType: "jsonp", - jsonpCallback: "customJsonpCallback" - } ).then( function() { - assert.ok( true, this.testName + " (success)" ); - } ).catch( function() { - assert.ok( false, this.testName + " (failure)" ); - } ); + } ); + }, + + function() { + var testName = "dataType: \"jsonp\", data callback"; + return expectNoWarning( assert, testName, function() { + return jQuery.ajax( { + url: url( "jsonpScript.js" ), + context: { testName: testName }, + crossDomain: crossDomain, + data: "callback=?", + dataType: "jsonp", + jsonpCallback: "customJsonpCallback" + } ).then( function() { + assert.ok( true, this.testName + " (success)" ); + } ).catch( function() { + assert.ok( false, this.testName + " (failure)" ); } ); - } - ]; - - // Invoke tests sequentially as they're async and early tests could get warnings - // from later ones. - function run( tests ) { - var test = tests[ 0 ]; - return test().then( function() { - if ( tests.length > 1 ) { - return run( tests.slice( 1 ) ); - } - } ); - } - - run( tests ) - .then( function() { - done(); - } ); - } ); - } - - if ( jQueryVersionSince( "4.0.0" ) ) { - - // In jQuery 4+, this behavior is disabled by default for security - // reasons, re-enable for this test, but test default behavior as well. - runTests( { forceEnablePatch: true } ); - runTests( { forceEnablePatch: false } ); - } else { - runTests(); - } + } ); + } + ]; + + // Invoke tests sequentially as they're async and early tests could get warnings + // from later ones. + function run( tests ) { + var test = tests[ 0 ]; + return test().then( function() { + if ( tests.length > 1 ) { + return run( tests.slice( 1 ) ); + } + } ); + } + + run( tests ) + .then( function() { + done(); + } ); + } ); } ); TestManager.runIframeTest( diff --git a/test/unit/jquery/attributes.js b/test/unit/jquery/attributes.js index a80e758f..216e29ee 100644 --- a/test/unit/jquery/attributes.js +++ b/test/unit/jquery/attributes.js @@ -3,7 +3,6 @@ QUnit.module( "attributes" ); ( function() { function runTests( options ) { var patchEnabled = options.patchEnabled; - var stockJq4 = jQueryVersionSince( "4.0.0" ) && !patchEnabled; function ifOn( warningsCount ) { return patchEnabled ? warningsCount : 0; @@ -12,7 +11,7 @@ QUnit.module( "attributes" ); QUnit.test( ".attr( boolean attribute ) - patch " + ( patchEnabled ? "enabled" : "disabled" ), function( assert ) { - assert.expect( 33 ); + assert.expect( 32 ); if ( !patchEnabled ) { jQuery.migrateDisablePatches( "boolean-attributes" ); @@ -35,7 +34,7 @@ QUnit.module( "attributes" ); $checkbox.prop( "checked", true ).prop( "checked", false ).attr( "checked", true ); assert.equal( $checkbox.attr( "checked" ), - stockJq4 ? "true" : "checked", + "checked", "Set checked (verified by .attr)" ); } ); @@ -51,10 +50,8 @@ QUnit.module( "attributes" ); try { assert.strictEqual( $checkbox.attr( original ), - stockJq4 ? "" : lowercased, - "The '" + this + - "' attribute getter should return " + - ( stockJq4 ? "an empty string" : "the lowercased name" ) + lowercased, + "The '" + this + "' attribute getter should return the lowercased name" ); } catch ( _ ) { assert.ok( false, "The '" + this + "' attribute getter threw" ); @@ -70,7 +67,7 @@ QUnit.module( "attributes" ); .attr( "checked", true ); assert.equal( $checkbox.attr( "checked" ), - stockJq4 ? "true" : "checked", + "checked", "Set checked (verified by .attr)" ); } ); @@ -95,7 +92,7 @@ QUnit.module( "attributes" ); .attr( "readonly", true ); assert.equal( $input.attr( "readonly" ), - stockJq4 ? "true" : "readonly", + "readonly", "Set readonly (verified by .attr)" ); } ); @@ -137,7 +134,7 @@ QUnit.module( "attributes" ); "Clear checked property (verified by .prop)" ); assert.equal( $checkbox.attr( "checked" ), - stockJq4 ? "true" : "checked", + "checked", "Clearing checked property doesn't affect checked attribute" ); } ); @@ -150,7 +147,7 @@ QUnit.module( "attributes" ); } ); assert.equal( $input.attr( "autofocus" ), - stockJq4 ? "true" : "autofocus", + "autofocus", "Reading autofocus attribute yields 'autofocus'" ); assert.equal( @@ -160,7 +157,7 @@ QUnit.module( "attributes" ); ); assert.equal( $input.attr( "required" ), - stockJq4 ? "true" : "required", + "required", "Reading required attribute yields 'required'" ); assert.equal( @@ -191,18 +188,12 @@ QUnit.module( "attributes" ); expectNoWarning( assert, "extra ex-boolean attrs values", function() { var $input = jQuery( "" ); + // Extra ex-boolean attrs values not supported under jQuery 3.x; + // just make sure this setter doesn't warn. We do not want to warn + // here as under jQuery 3.x this value would be changed to "hidden" + // which is most likely a bug for such code. Updating to jQuery 4.x + // will fix it. $input.attr( "hidden", "until-found" ); - - if ( jQueryVersionSince( "4.0.0" ) ) { - assert.equal( - $input.attr( "hidden" ), - "until-found", - "Extra values of ex-boolean attributes are not changed" - ); - } else { - assert.ok( true, - "Extra ex-boolean attrs values not supported under jQuery 3.x" ); - } } ); } ); } diff --git a/test/unit/jquery/core.js b/test/unit/jquery/core.js index 37c6bb26..b9c95b48 100644 --- a/test/unit/jquery/core.js +++ b/test/unit/jquery/core.js @@ -1,12 +1,6 @@ QUnit.module( "core" ); -function getTagNames( elem ) { - return elem.toArray().map( function( node ) { - return node.tagName.toLowerCase(); - } ); -} - QUnit.test( "jQuery(html, props)", function( assert ) { assert.expect( 2 ); @@ -16,109 +10,6 @@ QUnit.test( "jQuery(html, props)", function( assert ) { assert.equal( $el.val(), "value", "Call setter method" ); } ); -QUnit.test( "jQuery( '#' )", function( assert ) { - assert.expect( 2 ); - - expectWarning( assert, "Selector, through the jQuery constructor, nothing but hash", - function() { - var set = jQuery( "#" ); - assert.equal( set.length, 0, "empty set" ); - } ); -} ); - -QUnit.test( "Attribute selectors with unquoted hashes", function( assert ) { - assert.expect( 31 ); - - var markup = jQuery( - "
" + - "
" + - "test" + - "" + - "

" + - "anchor2" + - "" + - "anchor" + - "

" + - "
" ).appendTo( "#qunit-fixture" ), - - // No warning, no need to fix - okays = [ - "a[href='#some-anchor']", - "[data-id=\"#junk\"]", - "div[data-selector='a[href=#main]']", - "input[value~= '[strange*=#stuff]']" - ], - - // Fixable, and gives warning - fixables = [ - "a[href=#]", - "a[href*=#]:not([href=#]):first-child", - ".space a[href=#]", - "a[href=#some-anchor]", - "link[rel*=#stuff]", - "p[class *= #junk]", - "a[href=space#junk]" - ], - - // False positives that still work - positives = [ - "div[data-selector='a[href=#main]']:first", - "input[value= '[strange*=#stuff]']:eq(0)" - ], - - // Failures due to quotes and jQuery extensions combined - failures = [ - "p[class ^= #junk]:first", - "a[href=space#junk]:eq(1)" - ]; - - expectNoWarning( assert, "Perfectly cromulent selectors are unchanged", function() { - okays.forEach( function( okay ) { - assert.equal( jQuery( okay, markup ).length, 1, okay ); - assert.equal( markup.find( okay ).length, 1, okay ); - } ); - } ); - - expectWarning( assert, "Values with unquoted hashes are quoted", - fixables.length * 2, function() { - fixables.forEach( function( fixable ) { - assert.equal( jQuery( fixable, markup ).length, 1, fixable ); - assert.equal( markup.find( fixable ).length, 1, fixable ); - } ); - } ); - - expectWarning( assert, "False positives", positives.length * 2, function() { - positives.forEach( function( positive ) { - assert.equal( jQuery( positive, markup ).length, 1, positive ); - assert.equal( markup.find( positive ).length, 1, positive ); - } ); - } ); - - expectWarning( assert, "Unfixable cases", failures.length * 2, function() { - failures.forEach( function( failure ) { - try { - jQuery( failure, markup ); - assert.ok( false, "Expected jQuery() to die!" ); - } catch ( err1 ) { } - try { - markup.find( failure ); - assert.ok( false, "Expected .find() to die!" ); - } catch ( err2 ) { } - } ); - } ); - - // Ensure we don't process jQuery( x ) when x is a function - expectNoWarning( assert, "ready function with attribute selector", function() { - try { - jQuery( function() { - if ( jQuery.thisIsNeverTrue ) { - jQuery( "a[href=#]" ); - } - } ); - } catch ( e ) {} - } ); -} ); - QUnit.test( "XSS injection (leading hash)", function( assert ) { assert.expect( 1 ); @@ -427,56 +318,6 @@ TestManager.runIframeTest( "old pre-3.0 jQuery", "core-jquery2.html", assert.ok( /jQuery 3/.test( log ), "logged: " + log ); } ); -QUnit[ jQueryVersionSince( "4.0.0" ) ? "test" : "skip" ]( "jQuery.fn.push", function( assert ) { - assert.expect( 2 ); - - expectWarning( assert, "jQuery.fn.push", 1, function() { - var node = jQuery( "
" )[ 0 ], - elem = jQuery( "

" ); - - elem.push( node ); - - assert.deepEqual( getTagNames( elem ), [ "p", "span", "div" ], - "div added in-place" ); - } ); -} ); - -QUnit[ jQueryVersionSince( "4.0.0" ) ? "test" : "skip" ]( "jQuery.fn.sort", function( assert ) { - assert.expect( 2 ); - - expectWarning( assert, "jQuery.fn.sort", 1, function() { - var elem = jQuery( "

" ); - - elem.sort( function( node1, node2 ) { - var tag1 = node1.tagName.toLowerCase(), - tag2 = node2.tagName.toLowerCase(); - if ( tag1 < tag2 ) { - return -1; - } - if ( tag1 > tag2 ) { - return 1; - } - return 0; - } ); - - assert.deepEqual( getTagNames( elem ), [ "div", "p", "span" ], - "element sorted in-place" ); - } ); -} ); - -QUnit[ jQueryVersionSince( "4.0.0" ) ? "test" : "skip" ]( "jQuery.fn.splice", function( assert ) { - assert.expect( 2 ); - - expectWarning( assert, "jQuery.fn.splice", 1, function() { - var elem = jQuery( "

" ); - - elem.splice( 1, 1, jQuery( "" )[ 0 ], jQuery( "" )[ 0 ] ); - - assert.deepEqual( getTagNames( elem ), [ "span", "i", "b", "p" ], - "splice removed & added in-place" ); - } ); -} ); - QUnit[ jQueryVersionSince( "3.3.0" ) ? "test" : "skip" ]( "jQuery.proxy", function( assert ) { assert.expect( 10 ); diff --git a/test/unit/jquery/css.js b/test/unit/jquery/css.js index 2304164c..0c164375 100644 --- a/test/unit/jquery/css.js +++ b/test/unit/jquery/css.js @@ -63,8 +63,7 @@ QUnit.test( "jQuery.css with arrays", function( assert ) { QUnit[ typeof Proxy !== "undefined" ? "test" : "skip" ]( "jQuery.css with numbers", function( assert ) { - var jQuery3OrOlder = compareVersions( jQuery.fn.jquery, "4.0.0" ) < 0, - allowlist = [ + var allowlist = [ "margin", "marginTop", "marginRight", @@ -97,7 +96,7 @@ QUnit[ "borderLeftWidth" ]; - assert.expect( jQuery3OrOlder ? 8 : 7 ); + assert.expect( 8 ); function kebabCase( string ) { return string.replace( /[A-Z]/g, function( match ) { @@ -144,9 +143,7 @@ QUnit[ jQuery( "
" ).css( prop, 1 ); jQuery( "
" ).css( kebabCase( prop ), 1 ); } - if ( jQuery3OrOlder ) { - assert.strictEqual( assertionFired, true, "jQuery.cssNumber property was accessed" ); - } + assert.strictEqual( assertionFired, true, "jQuery.cssNumber property was accessed" ); } ); // z-index is tested explicitly as raw jQuery 4.0 will not have `jQuery.cssNumber` diff --git a/test/unit/jquery/deferred.js b/test/unit/jquery/deferred.js index 9e5f7114..07a0ec9a 100644 --- a/test/unit/jquery/deferred.js +++ b/test/unit/jquery/deferred.js @@ -39,11 +39,8 @@ QUnit.test( "jQuery.Deferred.getStackHook - getter", function( assert ) { exceptionHookSpy = this.sandbox.spy( jQuery.Deferred, "exceptionHook" ); - expectWarning( assert, "jQuery.Deferred.getStackHook - getter", - - // The getter only warns in jQuery 4+ as jQuery 3.x reads it internally. - jQueryVersionSince( "4.0.0" ) ? 1 : 0, - function() { + // The getter doesn't warn as jQuery 3.x reads it internally. + expectNoWarning( assert, "jQuery.Deferred.getStackHook - getter", function() { assert.strictEqual( jQuery.Deferred.getStackHook, jQuery.Deferred.getErrorHook, "getStackHook mirrors getErrorHook (getter)" ); } ); @@ -173,7 +170,7 @@ QUnit[ } ); QUnit.test( "jQuery.Deferred.getStackHook - disabled patch, setter", function( assert ) { - assert.expect( jQueryVersionSince( "4.0.0" ) ? 4 : 5 ); + assert.expect( 5 ); var exceptionHookSpy, done = assert.async(); @@ -222,16 +219,11 @@ QUnit.test( "jQuery.Deferred.getStackHook - disabled patch, setter", function( a .catch( function() { var asyncError = exceptionHookSpy.lastCall.args[ 1 ]; - if ( jQueryVersionSince( "4.0.0" ) ) { - assert.strictEqual( asyncError, undefined, - "Error not passed to exceptionHook" ); - } else { - assert.ok( asyncError instanceof Error, - "Error passed to exceptionHook (instance)" ); - assert.strictEqual( asyncError.message, - "Different exception in jQuery.Deferred", - "Error passed to exceptionHook (message)" ); - } + assert.ok( asyncError instanceof Error, + "Error passed to exceptionHook (instance)" ); + assert.strictEqual( asyncError.message, + "Different exception in jQuery.Deferred", + "Error passed to exceptionHook (message)" ); done(); } ); diff --git a/test/unit/jquery/selector.js b/test/unit/jquery/selector.js index e15426f1..78b4c7df 100644 --- a/test/unit/jquery/selector.js +++ b/test/unit/jquery/selector.js @@ -57,6 +57,109 @@ function testSelector( assert, message, selector, expectedIds ) { assert.deepEqual( elems, r, message + " (" + selector + ")" ); } +QUnit.test( "jQuery( '#' )", function( assert ) { + assert.expect( 2 ); + + expectWarning( assert, "Selector, through the jQuery constructor, nothing but hash", + function() { + var set = jQuery( "#" ); + assert.equal( set.length, 0, "empty set" ); + } ); +} ); + +QUnit.test( "Attribute selectors with unquoted hashes", function( assert ) { + assert.expect( 31 ); + + var markup = jQuery( + "
" + + "
" + + "test" + + "" + + "

" + + "anchor2" + + "" + + "anchor" + + "

" + + "
" ).appendTo( "#qunit-fixture" ), + + // No warning, no need to fix + okays = [ + "a[href='#some-anchor']", + "[data-id=\"#junk\"]", + "div[data-selector='a[href=#main]']", + "input[value~= '[strange*=#stuff]']" + ], + + // Fixable, and gives warning + fixables = [ + "a[href=#]", + "a[href*=#]:not([href=#]):first-child", + ".space a[href=#]", + "a[href=#some-anchor]", + "link[rel*=#stuff]", + "p[class *= #junk]", + "a[href=space#junk]" + ], + + // False positives that still work + positives = [ + "div[data-selector='a[href=#main]']:first", + "input[value= '[strange*=#stuff]']:eq(0)" + ], + + // Failures due to quotes and jQuery extensions combined + failures = [ + "p[class ^= #junk]:first", + "a[href=space#junk]:eq(1)" + ]; + + expectNoWarning( assert, "Perfectly cromulent selectors are unchanged", function() { + okays.forEach( function( okay ) { + assert.equal( jQuery( okay, markup ).length, 1, okay ); + assert.equal( markup.find( okay ).length, 1, okay ); + } ); + } ); + + expectWarning( assert, "Values with unquoted hashes are quoted", + fixables.length * 2, function() { + fixables.forEach( function( fixable ) { + assert.equal( jQuery( fixable, markup ).length, 1, fixable ); + assert.equal( markup.find( fixable ).length, 1, fixable ); + } ); + } ); + + expectWarning( assert, "False positives", positives.length * 2, function() { + positives.forEach( function( positive ) { + assert.equal( jQuery( positive, markup ).length, 1, positive ); + assert.equal( markup.find( positive ).length, 1, positive ); + } ); + } ); + + expectWarning( assert, "Unfixable cases", failures.length * 2, function() { + failures.forEach( function( failure ) { + try { + jQuery( failure, markup ); + assert.ok( false, "Expected jQuery() to die!" ); + } catch ( err1 ) { } + try { + markup.find( failure ); + assert.ok( false, "Expected .find() to die!" ); + } catch ( err2 ) { } + } ); + } ); + + // Ensure we don't process jQuery( x ) when x is a function + expectNoWarning( assert, "ready function with attribute selector", function() { + try { + jQuery( function() { + if ( jQuery.thisIsNeverTrue ) { + jQuery( "a[href=#]" ); + } + } ); + } catch ( e ) {} + } ); +} ); + QUnit.test( "jQuery.expr.pseudos aliases", function( assert ) { assert.expect( 7 ); diff --git a/warnings.md b/warnings.md index 42ca9d61..ae975ccb 100644 --- a/warnings.md +++ b/warnings.md @@ -1,6 +1,6 @@ # jQuery Migrate Plugin - Warning Messages -**NOTE: This page lists the messages for jQuery Migrate 3.0. If you are using an earlier version, see the documentation on [the 1.x-stable branch](https://github.com/jquery/jquery-migrate/blob/1.x-stable/warnings.md).** +**NOTE: This page lists the messages for jQuery Migrate 3.x. If you are using an earlier version, see the documentation on [the 1.x-stable branch](https://github.com/jquery/jquery-migrate/blob/1.x-stable/warnings.md).** To allow developers to identify and fix compatibility issues when migrating older jQuery code, the development (uncompressed) version of the plugin generates console warning messages whenever any of its functionality is called. The messages only appear once on the console for each unique message. @@ -15,9 +15,9 @@ All messages generated by this plugin start with the text "JQMIGRATE" for easy i This is _not_ a warning, but a console log message the plugin shows when it first loads to indicate whether warnings will be shown on the console when appropriate. As of version 1.4.0 this message is also shown with production builds. The use jQuery Migrate in production has performance impacts and can complicate debugging as it modifies the normal behavior of the version of jQuery being used. -### JQMIGRATE: jQuery 3.0.0+ REQUIRED +### JQMIGRATE: jQuery 3.x REQUIRED -**Cause:** The page does not have a version of jQuery installed, or is using a version of jQuery older than 3.0.0. The jQuery Migrate plugin is not intended to be used for those cases. Any messages that follow this one may not be accurate, or the page may not run properly at all. +**Cause:** The page does not have a version of jQuery installed, or is using a version of jQuery 4.0.0 or newer or older than 3.0.0. The jQuery Migrate plugin is not intended to be used for those cases. Any messages that follow this one may not be accurate, or the page may not run properly at all. **Solution:** See the [README](https://github.com/jquery/jquery-migrate/#readme) for more information on usage and upgrading from older versions. @@ -191,14 +191,6 @@ See jQuery-ui [commit](https://github.com/jquery/jquery-ui/commit/c0093b599fcd58 **Solution**: Review code that uses `jQuery.type()` and use a type check that is appropriate for the situation. For example. if the code expects a plain function, check for `typeof arg === "function"`. -### \[push\] JQMIGRATE: jQuery.fn.push() is deprecated and removed; use .add or convert to an array -### \[sort\] JQMIGRATE: jQuery.fn.sort() is deprecated and removed; convert to an array before sorting -### \[splice\] JQMIGRATE: jQuery.fn.splice() is deprecated and removed; use .slice or .not with .eq - -**Cause**: jQuery used to add the Array `push`, `sort` & `splice` methods to the jQuery prototype. They behaved differently to other jQuery APIs - they modify the jQuery collections in place, they don't play nice with APIs like `.end()`, they were also never documented. - -**Solution**: Replace `.push( node )` with `.add( node )`, `.splice( index )` with `.not( elem.eq( index ) )`. In more complex cases, call `.toArray()` first, manipulate the resulting array and convert back to the jQuery object by passing the resulting array to `$()`. - ### \[unique\] JQMIGRATE: jQuery.unique is deprecated; use jQuery.uniqueSort **Cause**: The fact that `jQuery.unique` sorted its results in DOM order was surprising to many who did not read the documentation carefully. As of jQuery 3.0 this function is being renamed to make it clear. @@ -230,7 +222,7 @@ See jQuery-ui [commit](https://github.com/jquery/jquery-ui/commit/c0093b599fcd58 **Cause:** The calling code has attempted to attach a `load` event to `window` after the page has already loaded. That means the handler will never run and so is probably not what the caller intended. This can occur when the event attachment is made too late, for example, in a jQuery ready handler. It can also occur when a file is loaded dynamically with jQuery after the page has loaded, for example using the `$.getScript()` method. -**Solution:** If a function `fn` does not actually depend on all page assets being fully loaded, switch to a ready handler `$( fn )` which runs earlier and will aways run `fn` even if the script that contains the code loads long after the page has fully loaded. If `fn` actually does depend on the script being fully loaded, check `document.readyState`. If the value is `"complete"` run the function immediately, otherwise use `$(window).on( "load", fn )`. +**Solution:** If a function `fn` does not actually depend on all page assets being fully loaded, switch to a ready handler `$( fn )` which runs earlier and will always run `fn` even if the script that contains the code loads long after the page has fully loaded. If `fn` actually does depend on the script being fully loaded, check `document.readyState`. If the value is `"complete"` run the function immediately, otherwise use `$(window).on( "load", fn )`. ### \[holdReady\] JQMIGRATE: jQuery.holdReady() is deprecated From 8d20816194c6c5b1ff4c8eb82c25f652bf3db873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Wed, 6 Nov 2024 09:51:51 +0100 Subject: [PATCH 02/11] Core: Don't reimplement deprecated but not removed APIs This will save space and avoid potential divergence from Core. To minimize risk, this only handles APIs still present in jQuery 4.x. --- src/jquery/core.js | 35 +++-------------------------------- src/jquery/event.js | 33 ++++++++++++--------------------- 2 files changed, 15 insertions(+), 53 deletions(-) diff --git a/src/jquery/core.js b/src/jquery/core.js index c1f8b311..26d47a0f 100644 --- a/src/jquery/core.js +++ b/src/jquery/core.js @@ -2,9 +2,7 @@ import { jQueryVersionSince } from "../compareVersions.js"; import { migratePatchAndWarnFunc } from "../main.js"; import "../disablePatches.js"; -var arr = [], - slice = arr.slice, - class2type = {}, +var class2type = {}, // Require that the "whitespace run" starts from a non-whitespace // to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position. @@ -116,34 +114,7 @@ if ( jQueryVersionSince( "3.3.0" ) ) { // arguments. // jQuery.proxy is deprecated to promote standards (specifically Function#bind) // However, it is not slated for removal any time soon - migratePatchAndWarnFunc( jQuery, "proxy", - function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, "proxy", - "jQuery.proxy() is deprecated" - ); + migratePatchAndWarnFunc( jQuery, "proxy", jQuery.proxy, + "proxy", "DEPRECATED: jQuery.proxy()" ); } diff --git a/src/jquery/event.js b/src/jquery/event.js index 5a5ea9a1..e7a6c4b8 100644 --- a/src/jquery/event.js +++ b/src/jquery/event.js @@ -96,13 +96,9 @@ jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + function( _i, name ) { // Handle event binding - migratePatchAndWarnFunc( jQuery.fn, name, function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }, - "shorthand-deprecated-v3", - "jQuery.fn." + name + "() event shorthand is deprecated" ); + migratePatchAndWarnFunc( jQuery.fn, name, jQuery.fn[ name ], "shorthand-deprecated-v3", + "DEPRECATED: jQuery.fn." + name + "() event shorthand" ); + } ); // Trigger "ready" event only once, on document ready @@ -118,20 +114,15 @@ jQuery.event.special.ready = { } }; -migratePatchAndWarnFunc( jQuery.fn, "bind", function( types, data, fn ) { - return this.on( types, null, data, fn ); -}, "pre-on-methods", "jQuery.fn.bind() is deprecated" ); -migratePatchAndWarnFunc( jQuery.fn, "unbind", function( types, fn ) { - return this.off( types, null, fn ); -}, "pre-on-methods", "jQuery.fn.unbind() is deprecated" ); -migratePatchAndWarnFunc( jQuery.fn, "delegate", function( selector, types, data, fn ) { - return this.on( types, selector, data, fn ); -}, "pre-on-methods", "jQuery.fn.delegate() is deprecated" ); -migratePatchAndWarnFunc( jQuery.fn, "undelegate", function( selector, types, fn ) { - return arguments.length === 1 ? - this.off( selector, "**" ) : - this.off( types, selector || "**", fn ); -}, "pre-on-methods", "jQuery.fn.undelegate() is deprecated" ); +migratePatchAndWarnFunc( jQuery.fn, "bind", jQuery.fn.bind, + "pre-on-methods", "jQuery.fn.bind() is deprecated" ); +migratePatchAndWarnFunc( jQuery.fn, "unbind", jQuery.fn.unbind, + "pre-on-methods", "jQuery.fn.unbind() is deprecated" ); +migratePatchAndWarnFunc( jQuery.fn, "delegate", jQuery.fn.delegate, + "pre-on-methods", "jQuery.fn.delegate() is deprecated" ); +migratePatchAndWarnFunc( jQuery.fn, "undelegate", jQuery.fn.undelegate, + "pre-on-methods", "jQuery.fn.undelegate() is deprecated" ); + migratePatchAndWarnFunc( jQuery.fn, "hover", function( fnOver, fnOut ) { return this.on( "mouseenter", fnOver ).on( "mouseleave", fnOut || fnOver ); }, "pre-on-methods", "jQuery.fn.hover() is deprecated" ); From 75c35bcc2ec27d52a354026d1c7722acad36c7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Tue, 19 Nov 2024 23:14:47 +0100 Subject: [PATCH 03/11] Attributes: Update warnings.md to not mention jQuery 4.0 --- warnings.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/warnings.md b/warnings.md index ae975ccb..465be03c 100644 --- a/warnings.md +++ b/warnings.md @@ -106,13 +106,13 @@ This is _not_ a warning, but a console log message the plugin shows when it firs ### \[boolean-attributes\] JQMIGRATE: Boolean attribute 'NAME' value is different from its lowercased name ### \[boolean-attributes\] JQMIGRATE: Boolean attribute 'NAME' value is not set to its lowercased name -**Cause**: Prior to jQuery 4.0, when calling `.attr( name, value )` with any non-`false` non-`null` `value`, jQuery would actually set it to `name`. Similarly, regardless of the actual value, `.attr( name )` used to return `name` lowercased. jQuery 4.0 removes this special behavior. +**Cause**: When calling `.attr( name, value )` with any non-`false` non-`null` `value`, jQuery would actually set it to `name`. Similarly, regardless of the actual value, `.attr( name )` used to return `name` lowercased. This behavior is deprecated. **Solution**: Always set boolean attributes to their names, whether when using jQuery (`.attr( name, name )`), native APIs (`.setAttribute( name, name )`) or directly in HTML (``). ### \[attr-false\] JQMIGRATE: Setting the non-ARIA non-boolean attribute 'NAME' to false -**Cause**: Prior to jQuery 4.0, calling `.attr( name, false )` was only removing the attribute when `name` was a boolean attribute; otherwise, it was setting the attribute value to `"false"`. In jQuery 4.x, it will remove any non-ARIA attribute. +**Cause**: Calling `.attr( name, false )` only removes the attribute when `name` is a boolean attribute; otherwise, it sets the attribute value to `"false"`. This behavior is deprecated for non-ARIA attributes. **Solution**: If you want to set the value of an attribute to `"false"`, wrap it in quotes: `.attr( name, "false" )`. From 946c36fb8f41bfcb83a0417f3759026d6687527f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Sat, 4 Jan 2025 13:53:57 +0100 Subject: [PATCH 04/11] Build: Rename more `main`s to `3.x-stable`s --- .github/workflows/browser-tests.yml | 2 +- .github/workflows/browserstack.yml | 2 +- .github/workflows/filestash.yml | 2 +- CONTRIBUTING.md | 6 +++--- README.md | 6 +++--- build/release.js | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/browser-tests.yml b/.github/workflows/browser-tests.yml index 10426204..e6890e41 100644 --- a/.github/workflows/browser-tests.yml +++ b/.github/workflows/browser-tests.yml @@ -4,7 +4,7 @@ on: pull_request: push: branches: - - main + - 3.x-stable env: NODE_VERSION: 22.x diff --git a/.github/workflows/browserstack.yml b/.github/workflows/browserstack.yml index 03c58b0b..705625f1 100644 --- a/.github/workflows/browserstack.yml +++ b/.github/workflows/browserstack.yml @@ -3,7 +3,7 @@ name: Browserstack on: push: branches: - - main + - 3.x-stable # Once a week every Tuesday schedule: - cron: "12 2 * * 2" diff --git a/.github/workflows/filestash.yml b/.github/workflows/filestash.yml index 4f1a9279..34f83b68 100644 --- a/.github/workflows/filestash.yml +++ b/.github/workflows/filestash.yml @@ -3,7 +3,7 @@ name: Filestash on: push: branches: - - main + - 3.x-stable permissions: contents: read # to fetch code (actions/checkout) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69865b1d..dd91e121 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,16 +78,16 @@ Change directory to the newly created dir `jquery-migrate/`: $ cd jquery-migrate ``` -Add the jQuery Migrate `main` as a remote (e.g. `upstream`): +Add the jQuery Migrate `3.x-stable` as a remote (e.g. `upstream`): ```bash $ git remote add upstream git@github.com:jquery/jquery-migrate.git ``` -Get in the habit of pulling in the "upstream" main to stay up to date as jQuery Migrate receives new commits: +Get in the habit of pulling in the "upstream" `3.x-stable` to stay up to date as jQuery Migrate receives new commits: ```bash -$ git pull upstream main +$ git pull upstream 3.x-stable ``` Install the necessary dependencies: diff --git a/README.md b/README.md index bd615a60..3eb27665 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![CI Status](https://github.com/jquery/jquery-migrate/actions/workflows/node.js.yml/badge.svg?branch=main) +![CI Status](https://github.com/jquery/jquery-migrate/actions/workflows/node.js.yml/badge.svg?branch=3.x-stable) #### NOTE: To upgrade to jQuery 3.0, you first need version 1.12.x or 2.2.x. If you're using an older version, first upgrade to one of these versions using [jQuery Migrate 1.x](https://github.com/jquery/jquery-migrate/tree/1.x-stable#readme), to resolve any compatibility issues. For more information about the changes made in jQuery 3.0, see the [upgrade guide](https://jquery.com/upgrade-guide/3.0/) and [blog post](https://blog.jquery.com/2016/06/09/jquery-3-0-final-released/). @@ -50,7 +50,7 @@ The production build is minified and does not generate console warnings. It will The development version of the plugin displays warnings in the browser console. Older browsers such as IE9 doesn't support the console interface. No messages will be generated unless you include a debugging library such as [Firebug Lite](https://getfirebug.com/firebuglite) before including the jQuery Migrate plugin. Developers can also inspect the `jQuery.migrateWarnings` array to see what error messages have been generated. -All warnings generated by this plugin start with the string "JQMIGRATE". A list of the warnings you may see are in [warnings.md](https://github.com/jquery/jquery-migrate/blob/main/warnings.md). +All warnings generated by this plugin start with the string "JQMIGRATE". A list of the warnings you may see are in [warnings.md](https://github.com/jquery/jquery-migrate/blob/3.x-stable/warnings.md). ## Migrate Plugin API @@ -69,7 +69,7 @@ This plugin adds some properties to the `jQuery` object that can be used to prog `jQuery.migrateDeduplicateWarnings`: By default, Migrate only gives a specific warning once. If you set this property to `false` it will give a warning for every occurrence each time it happens. Note that this can generate a lot of output, for example when a warning occurs in a loop. -`jQuery.migrateDisablePatches`: Disables patches by their codes. You can find a code for each patch in square brackets in [warnings.md](https://github.com/jquery/jquery-migrate/blob/main/warnings.md). A limited number of warnings doesn't have codes defined and cannot be disabled. These are mostly setup issues like using an incorrect version of jQuery or loading Migrate multiple times. +`jQuery.migrateDisablePatches`: Disables patches by their codes. You can find a code for each patch in square brackets in [warnings.md](https://github.com/jquery/jquery-migrate/blob/3.x-stable/warnings.md). A limited number of warnings doesn't have codes defined and cannot be disabled. These are mostly setup issues like using an incorrect version of jQuery or loading Migrate multiple times. `jQuery.migrateDisablePatches`: Disables patches by their codes. diff --git a/build/release.js b/build/release.js index 92242abe..6dd74e05 100644 --- a/build/release.js +++ b/build/release.js @@ -22,7 +22,7 @@ var releaseVersion, prompt = enquirer.prompt, repoURL = "git@github.com:jquery/jquery-migrate.git", - branch = "main", + branch = "3.x-stable", // Windows needs the .cmd version but will find the non-.cmd // On Windows, also ensure the HOME environment variable is set @@ -211,7 +211,7 @@ async function publishToNPM( next ) { function setNextVersion( next ) { updateSourceVersion( nextVersion ); - updatePackageVersion( nextVersion, "main" ); + updatePackageVersion( nextVersion, "3.x-stable" ); git( [ "commit", "-a", "--no-verify", "-m", "Updating the source version to " + nextVersion ], next ); } @@ -281,7 +281,7 @@ function updateReadmeVersion() { } function setBlobVersion( s, v ) { - return s.replace( /\/blob\/(?:(\d+\.\d+[^\/]+)|main)/, "/blob/" + v ); + return s.replace( /\/blob\/(?:(\d+\.\d+[^\/]+)|3.x-stable)/, "/blob/" + v ); } function writeJsonSync( fname, json ) { From 7f1aa2163b65ae9d1944f1ce0b9bf38ab4910e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Sun, 5 Jan 2025 00:19:52 +0100 Subject: [PATCH 05/11] Event: Reimplement APIs deprecated in jQuery 3.0/3.1 This fixes tests with 3.0/3.1 slim builds. --- src/jquery/event.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/jquery/event.js b/src/jquery/event.js index e7a6c4b8..c0cce65e 100644 --- a/src/jquery/event.js +++ b/src/jquery/event.js @@ -114,15 +114,25 @@ jQuery.event.special.ready = { } }; -migratePatchAndWarnFunc( jQuery.fn, "bind", jQuery.fn.bind, - "pre-on-methods", "jQuery.fn.bind() is deprecated" ); -migratePatchAndWarnFunc( jQuery.fn, "unbind", jQuery.fn.unbind, - "pre-on-methods", "jQuery.fn.unbind() is deprecated" ); -migratePatchAndWarnFunc( jQuery.fn, "delegate", jQuery.fn.delegate, - "pre-on-methods", "jQuery.fn.delegate() is deprecated" ); -migratePatchAndWarnFunc( jQuery.fn, "undelegate", jQuery.fn.undelegate, - "pre-on-methods", "jQuery.fn.undelegate() is deprecated" ); - +// Support: jQuery <3.2.0 only +// jQuery 3.0.x & 3.1.x used to not include the deprecated module in the slim build. +// To maintain compatibility with those versions, we need to reimplement APIs +// deprecated in them. +// See https://github.com/jquery/jquery/blob/3.1.1/src/deprecated.js +migratePatchAndWarnFunc( jQuery.fn, "bind", function( types, data, fn ) { + return this.on( types, null, data, fn ); +}, "pre-on-methods", "jQuery.fn.bind() is deprecated" ); +migratePatchAndWarnFunc( jQuery.fn, "unbind", function( types, fn ) { + return this.off( types, null, fn ); +}, "pre-on-methods", "jQuery.fn.unbind() is deprecated" ); +migratePatchAndWarnFunc( jQuery.fn, "delegate", function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); +}, "pre-on-methods", "jQuery.fn.delegate() is deprecated" ); +migratePatchAndWarnFunc( jQuery.fn, "undelegate", function( selector, types, fn ) { + return arguments.length === 1 ? + this.off( selector, "**" ) : + this.off( types, selector || "**", fn ); +}, "pre-on-methods", "jQuery.fn.undelegate() is deprecated" ); migratePatchAndWarnFunc( jQuery.fn, "hover", function( fnOver, fnOut ) { return this.on( "mouseenter", fnOver ).on( "mouseleave", fnOut || fnOver ); }, "pre-on-methods", "jQuery.fn.hover() is deprecated" ); From 182359ffb754427eaf4fe78ae8569468d34e6867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Sun, 5 Jan 2025 00:23:44 +0100 Subject: [PATCH 06/11] Tests: Test on jQuery 3.1.1.slim in non-BrowserStack browser tests jQuery <3.2.0 doesn't include the deprecated module in the slim build so it makes sense to test on one of these versions in slim mode even on PRs. --- jtr-local.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/jtr-local.yml b/jtr-local.yml index 2fdfd1c8..e0ab114b 100644 --- a/jtr-local.yml +++ b/jtr-local.yml @@ -14,6 +14,7 @@ flags: - 3.3.1 - 3.2.1 - 3.1.1 + - 3.1.1.slim - 3.0.0 retries: 1 From e891cfc05e610b7a34fa8b729dd110406c05c8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Sun, 5 Jan 2025 00:26:39 +0100 Subject: [PATCH 07/11] Build: Stop testing on iOS 10 As of January 2025, iOS 10 is a tier 4 device on BrowserStack: https://www.browserstack.com/device-tiers That leads to devices with this iOS version often not being available and failing our tests. Remove it from the test matrix. Also, add comments explaining the status of tests on various iOS versions, including iOS 7 that we stopped testing on a long time ago. Ref jquery/jquery#5606 --- .github/workflows/browserstack.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/browserstack.yml b/.github/workflows/browserstack.yml index 705625f1..8708e0a6 100644 --- a/.github/workflows/browserstack.yml +++ b/.github/workflows/browserstack.yml @@ -51,9 +51,19 @@ jobs: - '__iOS_13' - '__iOS_12' - '__iOS_11' - - '__iOS_10' + + # iOS 10 is a tier 4 device as of January 2025 and its availability + # is poor, leading to frequent test timeouts. Skip testing on it. + # See https://www.browserstack.com/device-tiers + # - '__iOS_10' + + # Versions below are not officially supported by BrowserStack as + # they use emulators instead of real devices. We include them as + # long as they still work. - '__iOS_9' - '__iOS_8' + # iOS 7 emulators no longer work properly + # - '__iOS_7' steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 From fb1f74bf790ef5f3103fa1c034732d099723eb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Tue, 14 Jan 2025 12:55:56 +0100 Subject: [PATCH 08/11] Build: Update a vulnerable dependency --- package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 96b7569f..32208064 100644 --- a/package-lock.json +++ b/package-lock.json @@ -879,10 +879,11 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", From 5505f7f134d06304efe5b36d29bd3fbce74d43c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Mon, 27 Jan 2025 19:03:15 +0100 Subject: [PATCH 09/11] Core: Update the package.json description Indicate this version of Migrate helps with updating jQuery to 3.x, not 3.0+. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c52a71ce..4679a577 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jquery-migrate", "title": "jQuery Migrate", - "description": "Migrate older jQuery code to jQuery 3.0+", + "description": "Migrate older jQuery code to jQuery 3.x", "main": "dist/jquery-migrate.js", "version": "3.5.3-pre", "type": "module", From 4a66dab8dcbe72ed929b5f1a8e0e093a3d5cd002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Tue, 28 Jan 2025 00:46:02 +0100 Subject: [PATCH 10/11] Docs: Link to jQuery Browser Support page in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3eb27665..b2dd5339 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The following table indicates which jQuery Migrate versions can be used with whi | 3.x | 3.x | | 4.x | 4.x | -Each jQuery Migrate version supports the same browsers that the jQuery version used with it. +Each jQuery Migrate version supports the same browsers that the jQuery version used with it. See the [jQuery Browser Support page](https://jquery.com/browser-support/) for more information. ## Usage From cf6a09e0d45785bb9bc99a94ff0c80f6d06b7ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Tue, 18 Feb 2025 00:57:44 +0100 Subject: [PATCH 11/11] Docs: Address code review remarks --- README.md | 2 +- src/jquery/ajax.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2dd5339..283bb01f 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The production build is minified and does not generate console warnings. It will | Debugging enabled |

| | | Minified | |

| | Latest release (*may be hotlinked if desired*) | [jquery-migrate-3.5.2.js](https://code.jquery.com/jquery-migrate-3.5.2.js) | [jquery-migrate-3.5.2.min.js](https://code.jquery.com/jquery-migrate-3.5.2.min.js) | -| \* Latest work-in-progress build | [jquery-migrate-3.x-git.js](https://releases.jquery.com/git/jquery-migrate-3.x-git.js) | [jquery-migrate-3.x-git.min.js](https://releases.jquery.com/git/jquery-migrate-3.x-git.min.js) | +| \* Latest work-in-progress 3.x build | [jquery-migrate-3.x-git.js](https://releases.jquery.com/git/jquery-migrate-3.x-git.js) | [jquery-migrate-3.x-git.min.js](https://releases.jquery.com/git/jquery-migrate-3.x-git.min.js) | \* **Work-in-progress build:** Although this file represents the most recent updates to the plugin, it may not have been thoroughly tested. We do not recommend using this file on production sites since it may be unstable; use the released production version instead. diff --git a/src/jquery/ajax.js b/src/jquery/ajax.js index 9c2bf679..8f982626 100644 --- a/src/jquery/ajax.js +++ b/src/jquery/ajax.js @@ -43,7 +43,7 @@ jQuery.ajaxSetup( { // Register this prefilter before the jQuery one. Otherwise, a promoted // request is transformed into one with the script dataType, and we can't // catch it anymore. -// jQuery <4 already contains this prefixer; don't duplicate the whole logic, +// jQuery <4 already contains this prefilter; don't duplicate the whole logic, // but only enough to know when to warn. jQuery.ajaxPrefilter( "+json", function( s ) {