Skip to content

Commit

Permalink
Selector: Re-expose jQuery.find.{tokenize,select,compile,setDocument}
Browse files Browse the repository at this point in the history
`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 jquerygh-5259
Ref jquerygh-5260
Ref jquery/sizzle#242
Ref jquerygh-5113
Ref jquerygh-4395
Ref jquerygh-4406
  • Loading branch information
mgol committed Jun 1, 2023
1 parent 27303c6 commit 7c6a45b
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 5 deletions.
11 changes: 6 additions & 5 deletions src/selector-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@
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";
Expand All @@ -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 + "*[>+~]" )
Expand Down Expand Up @@ -142,5 +142,6 @@ jQuery.extend( jQuery.find, {
},
matchesSelector: function( elem, expr ) {
return matches.call( elem, expr );
}
},
tokenize: tokenize
} );
7 changes: 7 additions & 0 deletions src/selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

} )();
229 changes: 229 additions & 0 deletions test/unit/selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -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" );
} );

0 comments on commit 7c6a45b

Please sign in to comment.