]> source.dussan.org Git - jquery.git/commitdiff
Selector: Re-expose jQuery.find.{tokenize,select,compile,setDocument}
authorMichał Gołębiowski-Owczarek <m.goleb@gmail.com>
Mon, 12 Jun 2023 20:58:55 +0000 (22:58 +0200)
committerGitHub <noreply@github.com>
Mon, 12 Jun 2023 20:58:55 +0000 (22:58 +0200)
`Sizzle.tokenize` is an internal Sizzle API, but exposed. As a result,
it has historically been available in jQuery via `jQuery.find.tokenize`.
That got dropped during Sizzle removal; this change restores the API.

Some other APIs so far only exposed on the `3.x` line are also added
back:
* `jQuery.find.select`
* `jQuery.find.compile`
* `jQuery.find.setDocument`

In addition to that, Sizzle tests have been backported for the following
APIs:
* `jQuery.find.matchesSelector`
* `jQuery.find.matches`
* `jQuery.find.compile`
* `jQuery.find.select`

A new test was also added for `jQuery.find.tokenize` - even Sizzle was
missing one.

Fixes gh-5259
Closes gh-5263
Ref gh-5260
Ref jquery/sizzle#242
Ref gh-5113
Ref gh-4395
Ref gh-4406

src/selector-native.js
src/selector.js
test/unit/selector.js

index 07da6f37ac879dd88ec2f50f839833313a2840d2..5eb582cfd8b46f7c9077ae936c8b224a13b5a6d5 100644 (file)
 import jQuery from "./core.js";
 import document from "./var/document.js";
 import whitespace from "./var/whitespace.js";
-
-// The following utils are attached directly to the jQuery object.
-import "./selector/escapeSelector.js";
-import "./selector/uniqueSort.js";
 import isIE from "./var/isIE.js";
 import booleans from "./selector/var/booleans.js";
 import rleadingCombinator from "./selector/var/rleadingCombinator.js";
@@ -40,6 +36,10 @@ import preFilter from "./selector/preFilter.js";
 import tokenize from "./selector/tokenize.js";
 import toSelector from "./selector/toSelector.js";
 
+// The following utils are attached directly to the jQuery object.
+import "./selector/escapeSelector.js";
+import "./selector/uniqueSort.js";
+
 var matchExpr = jQuery.extend( {
        bool: new RegExp( "^(?:" + booleans + ")$", "i" ),
        needsContext: new RegExp( "^" + whitespace + "*[>+~]" )
@@ -142,5 +142,6 @@ jQuery.extend( jQuery.find, {
        },
        matchesSelector: function( elem, expr ) {
                return matches.call( elem, expr );
-       }
+       },
+       tokenize: tokenize
 } );
index c995a65feaff227e70994d2f641807b7b26b8314..52e975c905b3a6847fdb162d928de026fefdd38e 100644 (file)
@@ -1362,4 +1362,11 @@ setDocument();
 
 jQuery.find = find;
 
+// These have always been private, but they used to be documented as part of
+// Sizzle so let's maintain them for now for backwards compatibility purposes.
+find.compile = compile;
+find.select = select;
+find.setDocument = setDocument;
+find.tokenize = tokenize;
+
 } )();
index e368e827c4216ac9ea13a708091018d62a8dbaaa..57e21ce6bb1dd3fce3a5b80f9fa732eb1e192bcf 100644 (file)
@@ -2211,3 +2211,232 @@ QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "custom pseudos", function( as
                delete jQuery.expr.filters.slice;
        }
 } );
+
+QUnit.test( "jQuery.find.matchesSelector", function( assert ) {
+       assert.expect( 15 );
+
+       var link = document.getElementById( "simon1" ),
+               input = document.getElementById( "text1" ),
+               option = document.getElementById( "option1a" ),
+               disconnected = document.createElement( "div" );
+
+       link.title = "Don't click me";
+       assert.ok( jQuery.find.matchesSelector( link, "[rel='bookmark']" ), "attribute-equals string" );
+       assert.ok( jQuery.find.matchesSelector( link, "[rel=bookmark]" ), "attribute-equals identifier" );
+       assert.ok( jQuery.find.matchesSelector( link, "[\nrel = bookmark\t]" ),
+               "attribute-equals identifier (whitespace ignored)" );
+       assert.ok( jQuery.find.matchesSelector( link, "a[title=\"Don't click me\"]" ),
+               "attribute-equals string containing single quote" );
+
+       // trac-12303
+       input.setAttribute( "data-pos", ":first" );
+       assert.ok( jQuery.find.matchesSelector( input, "input[data-pos=\\:first]" ),
+               "attribute-equals POS in identifier" );
+       assert.ok( jQuery.find.matchesSelector( input, "input[data-pos=':first']" ),
+               "attribute-equals POS in string" );
+       if ( QUnit.jQuerySelectors ) {
+               assert.ok( jQuery.find.matchesSelector( input, ":input[data-pos=':first']" ),
+                       "attribute-equals POS in string after pseudo" );
+       } else {
+               assert.ok( "skip", ":input not supported in selector-native" );
+       }
+
+       option.setAttribute( "test", "" );
+       assert.ok( jQuery.find.matchesSelector( option, "[id=option1a]" ),
+               "id attribute-equals identifier" );
+       if ( QUnit.jQuerySelectors ) {
+               assert.ok( jQuery.find.matchesSelector( option, "[id*=option1][type!=checkbox]" ),
+                       "attribute-not-equals identifier" );
+       } else {
+               assert.ok( "skip", "[key!=value] not supported in selector-native" );
+       }
+       assert.ok( jQuery.find.matchesSelector( option, "[id*=option1]" ), "attribute-contains identifier" );
+       assert.ok( !jQuery.find.matchesSelector( option, "[test^='']" ),
+               "attribute-starts-with empty string (negative)" );
+
+       option.className = "=]";
+       assert.ok( jQuery.find.matchesSelector( option, ".\\=\\]" ),
+               "class selector with attribute-equals confusable" );
+
+       assert.ok( jQuery.find.matchesSelector( disconnected, "div" ), "disconnected element" );
+       assert.ok( jQuery.find.matchesSelector( link, "* > *" ), "child combinator matches in document" );
+       assert.ok( !jQuery.find.matchesSelector( disconnected, "* > *" ), "child combinator fails in fragment" );
+} );
+
+QUnit.test( "jQuery.find.matches", function( assert ) {
+       assert.expect( 4 );
+
+       var iframeChild,
+               input = document.getElementById( "text1" ),
+               div = document.createElement( "div" ),
+               iframe = document.getElementById( "iframe" ),
+               iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
+
+       assert.deepEqual( jQuery.find.matches( "input", [ input ] ), [ input ],
+               "jQuery.find.matches with seed of input element" );
+       assert.deepEqual( jQuery.find.matches( "div", [ div ] ), [ div ],
+               "jQuery.find.matches with disconnected element" );
+
+       iframeDoc.open();
+       iframeDoc.write( "<body><div id='foo'><div id='bar'></div></div></body>" );
+       iframeDoc.close();
+
+       iframeChild = iframeDoc.getElementById( "bar" );
+
+       assert.deepEqual(
+               jQuery.find.matches( ":root > body > #foo > #bar", [ iframeChild ] ),
+               [ iframeChild ],
+               "jQuery.find.matches infers context from element"
+       );
+
+       assert.deepEqual(
+               jQuery.find.matches( ":root *", [ div, iframeChild, input ] ),
+               [ iframeChild, input ],
+               "jQuery.find.matches infers context from each seed element"
+       );
+} );
+
+QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "jQuery.find.select with pre-compiled function", function( assert ) {
+       assert.expect( 6 );
+
+       supportjQuery.each( [
+               "#qunit-fixture #first",
+               "ol#listWithTabIndex > li[tabindex]",
+               "#liveSpan1"
+       ], function( i, selector ) {
+               var compiled = jQuery.find.compile( selector );
+               assert.equal( jQuery.find.select( compiled, document ).length,
+                       1, "Should match using a compiled selector function" );
+               assert.equal(
+                       jQuery.find.select( compiled, jQuery( "#first" )[ 0 ] ).length,
+                       0, "Should not match with different context" );
+       } );
+} );
+
+// Internal, but we test it for backwards compatibility for edge cases
+QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "jQuery.find.tokenize", function( assert ) {
+       assert.expect( 1 );
+
+       var selector = "#id .class > div[prop=\"value\"] + input:nth-child(1):button, span:contains(\"Text\") ~ div:has(div:has(span)):not(.not-this.not-that > div)",
+               tokens = [
+                       [
+                               {
+                                       "value": "#id",
+                                       "type": "ID",
+                                       "matches": [
+                                               "id"
+                                       ]
+                               },
+                               {
+                                       "value": " ",
+                                       "type": " "
+                               },
+                               {
+                                       "value": ".class",
+                                       "type": "CLASS",
+                                       "matches": [
+                                               "class"
+                                       ]
+                               },
+                               {
+                                       "value": " > ",
+                                       "type": ">"
+                               },
+                               {
+                                       "value": "div",
+                                       "type": "TAG",
+                                       "matches": [
+                                               "div"
+                                       ]
+                               },
+                               {
+                                       "value": "[prop=\"value\"]",
+                                       "type": "ATTR",
+                                       "matches": [
+                                               "prop",
+                                               "=",
+                                               "value"
+                                       ]
+                               },
+                               {
+                                       "value": " + ",
+                                       "type": "+"
+                               },
+                               {
+                                       "value": "input",
+                                       "type": "TAG",
+                                       "matches": [
+                                               "input"
+                                       ]
+                               },
+                               {
+                                       "value": ":nth-child(1)",
+                                       "type": "CHILD",
+                                       "matches": [
+                                               "nth",
+                                               "child",
+                                               "1",
+                                               0,
+                                               1,
+                                               undefined,
+                                               "",
+                                               "1"
+                                       ]
+                               },
+                               {
+                                       "value": ":button",
+                                       "type": "PSEUDO",
+                                       "matches": [
+                                               "button",
+                                               undefined
+                                       ]
+                               }
+                       ],
+                       [
+                               {
+                                       "value": "span",
+                                       "type": "TAG",
+                                       "matches": [
+                                               "span"
+                                       ]
+                               },
+                               {
+                                       "value": ":contains(\"Text\")",
+                                       "type": "PSEUDO",
+                                       "matches": [
+                                               "contains",
+                                               "Text"
+                                       ]
+                               },
+                               {
+                                       "value": " ~ ",
+                                       "type": "~"
+                               },
+                               {
+                                       "value": "div",
+                                       "type": "TAG",
+                                       "matches": [
+                                               "div"
+                                       ]
+                               },
+                               {
+                                       "value": ":has(div:has(span))",
+                                       "type": "PSEUDO",
+                                       "matches": [
+                                               "has",
+                                               "div:has(span)"
+                                       ]
+                               },
+                               {
+                                       "value": ":not(.not-this.not-that > div)",
+                                       "type": "PSEUDO",
+                                       "matches": [
+                                               "not",
+                                               ".not-this.not-that > div"
+                                       ]
+                               }
+                       ]
+               ];
+
+       assert.deepEqual( jQuery.find.tokenize( selector ), tokens, "Tokenization successful" );
+} );