Skip to content

Commit 338de35

Browse files
authored
Selector: Re-expose jQuery.find.{tokenize,select,compile,setDocument}
`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
1 parent 27303c6 commit 338de35

File tree

3 files changed

+242
-5
lines changed

3 files changed

+242
-5
lines changed

src/selector-native.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@
2424
import jQuery from "./core.js";
2525
import document from "./var/document.js";
2626
import whitespace from "./var/whitespace.js";
27-
28-
// The following utils are attached directly to the jQuery object.
29-
import "./selector/escapeSelector.js";
30-
import "./selector/uniqueSort.js";
3127
import isIE from "./var/isIE.js";
3228
import booleans from "./selector/var/booleans.js";
3329
import rleadingCombinator from "./selector/var/rleadingCombinator.js";
@@ -40,6 +36,10 @@ import preFilter from "./selector/preFilter.js";
4036
import tokenize from "./selector/tokenize.js";
4137
import toSelector from "./selector/toSelector.js";
4238

39+
// The following utils are attached directly to the jQuery object.
40+
import "./selector/escapeSelector.js";
41+
import "./selector/uniqueSort.js";
42+
4343
var matchExpr = jQuery.extend( {
4444
bool: new RegExp( "^(?:" + booleans + ")$", "i" ),
4545
needsContext: new RegExp( "^" + whitespace + "*[>+~]" )
@@ -142,5 +142,6 @@ jQuery.extend( jQuery.find, {
142142
},
143143
matchesSelector: function( elem, expr ) {
144144
return matches.call( elem, expr );
145-
}
145+
},
146+
tokenize: tokenize
146147
} );

src/selector.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,4 +1362,11 @@ setDocument();
13621362

13631363
jQuery.find = find;
13641364

1365+
// These have always been private, but they used to be documented as part of
1366+
// Sizzle so let's maintain them for now for backwards compatibility purposes.
1367+
find.compile = compile;
1368+
find.select = select;
1369+
find.setDocument = setDocument;
1370+
find.tokenize = tokenize;
1371+
13651372
} )();

test/unit/selector.js

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2211,3 +2211,232 @@ QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "custom pseudos", function( as
22112211
delete jQuery.expr.filters.slice;
22122212
}
22132213
} );
2214+
2215+
QUnit.test( "jQuery.find.matchesSelector", function( assert ) {
2216+
assert.expect( 15 );
2217+
2218+
var link = document.getElementById( "simon1" ),
2219+
input = document.getElementById( "text1" ),
2220+
option = document.getElementById( "option1a" ),
2221+
disconnected = document.createElement( "div" );
2222+
2223+
link.title = "Don't click me";
2224+
assert.ok( jQuery.find.matchesSelector( link, "[rel='bookmark']" ), "attribute-equals string" );
2225+
assert.ok( jQuery.find.matchesSelector( link, "[rel=bookmark]" ), "attribute-equals identifier" );
2226+
assert.ok( jQuery.find.matchesSelector( link, "[\nrel = bookmark\t]" ),
2227+
"attribute-equals identifier (whitespace ignored)" );
2228+
assert.ok( jQuery.find.matchesSelector( link, "a[title=\"Don't click me\"]" ),
2229+
"attribute-equals string containing single quote" );
2230+
2231+
// trac-12303
2232+
input.setAttribute( "data-pos", ":first" );
2233+
assert.ok( jQuery.find.matchesSelector( input, "input[data-pos=\\:first]" ),
2234+
"attribute-equals POS in identifier" );
2235+
assert.ok( jQuery.find.matchesSelector( input, "input[data-pos=':first']" ),
2236+
"attribute-equals POS in string" );
2237+
if ( QUnit.jQuerySelectors ) {
2238+
assert.ok( jQuery.find.matchesSelector( input, ":input[data-pos=':first']" ),
2239+
"attribute-equals POS in string after pseudo" );
2240+
} else {
2241+
assert.ok( "skip", ":input not supported in selector-native" );
2242+
}
2243+
2244+
option.setAttribute( "test", "" );
2245+
assert.ok( jQuery.find.matchesSelector( option, "[id=option1a]" ),
2246+
"id attribute-equals identifier" );
2247+
if ( QUnit.jQuerySelectors ) {
2248+
assert.ok( jQuery.find.matchesSelector( option, "[id*=option1][type!=checkbox]" ),
2249+
"attribute-not-equals identifier" );
2250+
} else {
2251+
assert.ok( "skip", "[key!=value] not supported in selector-native" );
2252+
}
2253+
assert.ok( jQuery.find.matchesSelector( option, "[id*=option1]" ), "attribute-contains identifier" );
2254+
assert.ok( !jQuery.find.matchesSelector( option, "[test^='']" ),
2255+
"attribute-starts-with empty string (negative)" );
2256+
2257+
option.className = "=]";
2258+
assert.ok( jQuery.find.matchesSelector( option, ".\\=\\]" ),
2259+
"class selector with attribute-equals confusable" );
2260+
2261+
assert.ok( jQuery.find.matchesSelector( disconnected, "div" ), "disconnected element" );
2262+
assert.ok( jQuery.find.matchesSelector( link, "* > *" ), "child combinator matches in document" );
2263+
assert.ok( !jQuery.find.matchesSelector( disconnected, "* > *" ), "child combinator fails in fragment" );
2264+
} );
2265+
2266+
QUnit.test( "jQuery.find.matches", function( assert ) {
2267+
assert.expect( 4 );
2268+
2269+
var iframeChild,
2270+
input = document.getElementById( "text1" ),
2271+
div = document.createElement( "div" ),
2272+
iframe = document.getElementById( "iframe" ),
2273+
iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
2274+
2275+
assert.deepEqual( jQuery.find.matches( "input", [ input ] ), [ input ],
2276+
"jQuery.find.matches with seed of input element" );
2277+
assert.deepEqual( jQuery.find.matches( "div", [ div ] ), [ div ],
2278+
"jQuery.find.matches with disconnected element" );
2279+
2280+
iframeDoc.open();
2281+
iframeDoc.write( "<body><div id='foo'><div id='bar'></div></div></body>" );
2282+
iframeDoc.close();
2283+
2284+
iframeChild = iframeDoc.getElementById( "bar" );
2285+
2286+
assert.deepEqual(
2287+
jQuery.find.matches( ":root > body > #foo > #bar", [ iframeChild ] ),
2288+
[ iframeChild ],
2289+
"jQuery.find.matches infers context from element"
2290+
);
2291+
2292+
assert.deepEqual(
2293+
jQuery.find.matches( ":root *", [ div, iframeChild, input ] ),
2294+
[ iframeChild, input ],
2295+
"jQuery.find.matches infers context from each seed element"
2296+
);
2297+
} );
2298+
2299+
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "jQuery.find.select with pre-compiled function", function( assert ) {
2300+
assert.expect( 6 );
2301+
2302+
supportjQuery.each( [
2303+
"#qunit-fixture #first",
2304+
"ol#listWithTabIndex > li[tabindex]",
2305+
"#liveSpan1"
2306+
], function( i, selector ) {
2307+
var compiled = jQuery.find.compile( selector );
2308+
assert.equal( jQuery.find.select( compiled, document ).length,
2309+
1, "Should match using a compiled selector function" );
2310+
assert.equal(
2311+
jQuery.find.select( compiled, jQuery( "#first" )[ 0 ] ).length,
2312+
0, "Should not match with different context" );
2313+
} );
2314+
} );
2315+
2316+
// Internal, but we test it for backwards compatibility for edge cases
2317+
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "jQuery.find.tokenize", function( assert ) {
2318+
assert.expect( 1 );
2319+
2320+
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)",
2321+
tokens = [
2322+
[
2323+
{
2324+
"value": "#id",
2325+
"type": "ID",
2326+
"matches": [
2327+
"id"
2328+
]
2329+
},
2330+
{
2331+
"value": " ",
2332+
"type": " "
2333+
},
2334+
{
2335+
"value": ".class",
2336+
"type": "CLASS",
2337+
"matches": [
2338+
"class"
2339+
]
2340+
},
2341+
{
2342+
"value": " > ",
2343+
"type": ">"
2344+
},
2345+
{
2346+
"value": "div",
2347+
"type": "TAG",
2348+
"matches": [
2349+
"div"
2350+
]
2351+
},
2352+
{
2353+
"value": "[prop=\"value\"]",
2354+
"type": "ATTR",
2355+
"matches": [
2356+
"prop",
2357+
"=",
2358+
"value"
2359+
]
2360+
},
2361+
{
2362+
"value": " + ",
2363+
"type": "+"
2364+
},
2365+
{
2366+
"value": "input",
2367+
"type": "TAG",
2368+
"matches": [
2369+
"input"
2370+
]
2371+
},
2372+
{
2373+
"value": ":nth-child(1)",
2374+
"type": "CHILD",
2375+
"matches": [
2376+
"nth",
2377+
"child",
2378+
"1",
2379+
0,
2380+
1,
2381+
undefined,
2382+
"",
2383+
"1"
2384+
]
2385+
},
2386+
{
2387+
"value": ":button",
2388+
"type": "PSEUDO",
2389+
"matches": [
2390+
"button",
2391+
undefined
2392+
]
2393+
}
2394+
],
2395+
[
2396+
{
2397+
"value": "span",
2398+
"type": "TAG",
2399+
"matches": [
2400+
"span"
2401+
]
2402+
},
2403+
{
2404+
"value": ":contains(\"Text\")",
2405+
"type": "PSEUDO",
2406+
"matches": [
2407+
"contains",
2408+
"Text"
2409+
]
2410+
},
2411+
{
2412+
"value": " ~ ",
2413+
"type": "~"
2414+
},
2415+
{
2416+
"value": "div",
2417+
"type": "TAG",
2418+
"matches": [
2419+
"div"
2420+
]
2421+
},
2422+
{
2423+
"value": ":has(div:has(span))",
2424+
"type": "PSEUDO",
2425+
"matches": [
2426+
"has",
2427+
"div:has(span)"
2428+
]
2429+
},
2430+
{
2431+
"value": ":not(.not-this.not-that > div)",
2432+
"type": "PSEUDO",
2433+
"matches": [
2434+
"not",
2435+
".not-this.not-that > div"
2436+
]
2437+
}
2438+
]
2439+
];
2440+
2441+
assert.deepEqual( jQuery.find.tokenize( selector ), tokens, "Tokenization successful" );
2442+
} );

0 commit comments

Comments
 (0)