Selector: Backport jQuery selection context logic to selector-native

This makes:
```js
$div.find("div > *")
```
no longer matching children of `$div`.

Also, leading combinators now work, e.g.:
```js
$div.find( "> *" );
```
returns children of `$div`.

As a result of that, a number of tests are no longer skipped in the
`selector-native` mode.

Also, rename `rcombinators` to `rleadingCombinator`.

Fixes gh-5185
Closes gh-5186
Ref gh-5085
This commit is contained in:
Michał Gołębiowski-Owczarek 2023-02-13 18:34:41 +01:00 committed by GitHub
parent 7e7bd06207
commit 2e644e8450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 516 additions and 414 deletions

View File

@ -8,13 +8,10 @@
* * Positional selectors (:first; :eq(n); :odd; etc.)
* * Type selectors (:input; :checkbox; :button; etc.)
* * State-based selectors (:animated; :visible; :hidden; etc.)
* * :has(selector)
* * :not(complex selector)
* * :has(selector) in browsers without native support
* * :not(complex selector) in IE
* * custom selectors via jQuery extensions
* * Leading combinators (e.g., $collection.find("> *"))
* * Reliable functionality on XML fragments
* * Requiring all parts of a selector to match elements under context
* (e.g., $div.find("div > *") now matches children of $div)
* * Matching against non-elements
* * Reliable sorting of disconnected nodes
* * querySelectorAll bug fixes (e.g., unreliable :focus on WebKit)
@ -26,20 +23,35 @@
import jQuery from "./core.js";
import document from "./var/document.js";
import documentElement from "./var/documentElement.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";
import rdescend from "./selector/var/rdescend.js";
import rsibling from "./selector/var/rsibling.js";
import matches from "./selector/var/matches.js";
import testContext from "./selector/testContext.js";
import filterMatchExpr from "./selector/filterMatchExpr.js";
import preFilter from "./selector/preFilter.js";
import tokenize from "./selector/tokenize.js";
import toSelector from "./selector/toSelector.js";
// Support: IE 9 - 11+
// IE requires a prefix.
var matches = documentElement.matches || documentElement.msMatchesSelector;
var matchExpr = jQuery.extend( {
bool: new RegExp( "^(?:" + booleans + ")$", "i" ),
needsContext: new RegExp( "^" + whitespace + "*[>+~]" )
}, filterMatchExpr );
jQuery.extend( {
find: function( selector, context, results, seed ) {
var elem, nodeType,
var elem, nid, groups, newSelector,
newContext = context && context.ownerDocument,
// nodeType defaults to 9, since context defaults to document
nodeType = context ? context.nodeType : 9,
i = 0;
results = results || [];
@ -51,7 +63,7 @@ jQuery.extend( {
}
// Early return if context is not an element, document or document fragment
if ( ( nodeType = context.nodeType ) !== 1 && nodeType !== 9 && nodeType !== 11 ) {
if ( nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
return [];
}
@ -62,18 +74,65 @@ jQuery.extend( {
}
}
} else {
jQuery.merge( results, context.querySelectorAll( selector ) );
newSelector = selector;
newContext = context;
// qSA considers elements outside a scoping root when evaluating child or
// descendant combinators, which is not what we want.
// In such cases, we work around the behavior by prefixing every selector in the
// list with an ID selector referencing the scope context.
// The technique has to be used as well when a leading combinator is used
// as such selectors are not recognized by querySelectorAll.
// Thanks to Andrew Dupont for this technique.
if ( nodeType === 1 &&
( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {
// Expand context for sibling selectors
newContext = rsibling.test( selector ) &&
testContext( context.parentNode ) ||
context;
// Outside of IE, if we're not changing the context we can
// use :scope instead of an ID.
if ( newContext !== context || isIE ) {
// Capture the context ID, setting it first if necessary
if ( ( nid = context.getAttribute( "id" ) ) ) {
nid = jQuery.escapeSelector( nid );
} else {
context.setAttribute( "id", ( nid = jQuery.expando ) );
}
}
// Prefix every selector in the list
groups = tokenize( selector );
i = groups.length;
while ( i-- ) {
groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
toSelector( groups[ i ] );
}
newSelector = groups.join( "," );
}
try {
jQuery.merge( results, newContext.querySelectorAll( newSelector ) );
} finally {
if ( nid === jQuery.expando ) {
context.removeAttribute( "id" );
}
}
}
return results;
},
expr: {
attrHandle: {},
match: {
bool: new RegExp( "^(?:checked|selected|async|autofocus|autoplay|controls|defer" +
"|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped)$", "i" ),
needsContext: new RegExp( "^" + whitespace + "*[>+~]" )
}
// Can be adjusted by the user
cacheLength: 50,
match: matchExpr,
preFilter: preFilter
}
} );

View File

@ -1,7 +1,6 @@
import jQuery from "./core.js";
import nodeName from "./core/nodeName.js";
import document from "./var/document.js";
import documentElement from "./var/documentElement.js";
import indexOf from "./var/indexOf.js";
import pop from "./var/pop.js";
import push from "./var/push.js";
@ -9,19 +8,31 @@ import whitespace from "./var/whitespace.js";
import rbuggyQSA from "./selector/rbuggyQSA.js";
import rtrim from "./var/rtrim.js";
import isIE from "./var/isIE.js";
import identifier from "./selector/var/identifier.js";
import booleans from "./selector/var/booleans.js";
import rleadingCombinator from "./selector/var/rleadingCombinator.js";
import rdescend from "./selector/var/rdescend.js";
import rsibling from "./selector/var/rsibling.js";
import matches from "./selector/var/matches.js";
import createCache from "./selector/createCache.js";
import testContext from "./selector/testContext.js";
import filterMatchExpr from "./selector/filterMatchExpr.js";
import preFilter from "./selector/preFilter.js";
import selectorError from "./selector/selectorError.js";
import unescapeSelector from "./selector/unescapeSelector.js";
import tokenize from "./selector/tokenize.js";
import toSelector from "./selector/toSelector.js";
import support from "./selector/support.js";
// The following utils are attached directly to the jQuery object.
import "./selector/escapeSelector.js";
import "./selector/uniqueSort.js";
var preferredDoc = document,
matches = documentElement.matches || documentElement.msMatchesSelector;
var preferredDoc = document;
( function() {
var i,
Expr,
outermostContext,
// Local document vars
@ -30,67 +41,20 @@ var i,
documentIsHTML,
// Instance-specific data
expando = jQuery.expando,
dirruns = 0,
done = 0,
classCache = createCache(),
tokenCache = createCache(),
compilerCache = createCache(),
nonnativeSelectorCache = createCache(),
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" +
"loop|multiple|open|readonly|required|scoped",
// Regular expressions
// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",
// Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors
attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
// Operator (capture 2)
"*([*^$|!~]?=)" + whitespace +
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
whitespace + "*\\]",
pseudos = ":(" + identifier + ")(?:\\((" +
// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
// 1. quoted (capture 3; capture 4 or capture 5)
"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
// 2. simple (capture 6)
"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
// 3. anything else (capture 2)
".*" +
")\\)|)",
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
rwhitespace = new RegExp( whitespace + "+", "g" ),
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" +
whitespace + "*" ),
rdescend = new RegExp( whitespace + "|>" ),
rpseudo = new RegExp( pseudos ),
ridentifier = new RegExp( "^" + identifier + "$" ),
matchExpr = {
ID: new RegExp( "^#(" + identifier + ")" ),
CLASS: new RegExp( "^\\.(" + identifier + ")" ),
TAG: new RegExp( "^(" + identifier + "|[*])" ),
ATTR: new RegExp( "^" + attributes ),
PSEUDO: new RegExp( "^" + pseudos ),
CHILD: new RegExp(
"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
matchExpr = jQuery.extend( {
bool: new RegExp( "^(?:" + booleans + ")$", "i" ),
// For use in libraries implementing .is()
@ -98,7 +62,7 @@ var i,
needsContext: new RegExp( "^" + whitespace +
"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
"*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
},
}, filterMatchExpr ),
rinputs = /^(?:input|select|textarea|button)$/i,
rheader = /^h\d$/i,
@ -106,30 +70,6 @@ var i,
// Easily-parseable/retrievable ID or TAG or CLASS selectors
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
rsibling = /[+~]/,
// CSS escapes
// https://www.w3.org/TR/CSS21/syndata.html#escaped-characters
runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace +
"?|\\\\([^\\r\\n\\f])", "g" ),
funescape = function( escape, nonHex ) {
var high = "0x" + escape.slice( 1 ) - 0x10000;
if ( nonHex ) {
// Strip the backslash prefix from a non-hex escape sequence
return nonHex;
}
// Replace a hexadecimal escape sequence with the encoded Unicode code point
// Support: IE <=11+
// For values outside the Basic Multilingual Plane (BMP), manually construct a
// surrogate pair
return high < 0 ?
String.fromCharCode( high + 0x10000 ) :
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
},
// Used for iframes; see `setDocument`.
// Support: IE 9 - 11+
// Removing the function wrapper causes a "Permission Denied"
@ -145,10 +85,6 @@ var i,
{ dir: "parentNode", next: "legend" }
);
function selectorError( msg ) {
throw new Error( "Syntax error, unrecognized expression: " + msg );
}
function find( selector, context, results, seed ) {
var m, i, elem, nid, match, groups, newSelector,
newContext = context && context.ownerDocument,
@ -223,10 +159,11 @@ function find( selector, context, results, seed ) {
// as such selectors are not recognized by querySelectorAll.
// Thanks to Andrew Dupont for this technique.
if ( nodeType === 1 &&
( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {
( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {
// Expand context for sibling selectors
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
newContext = rsibling.test( selector ) &&
testContext( context.parentNode ) ||
context;
// Outside of IE, if we're not changing the context we can
@ -237,7 +174,7 @@ function find( selector, context, results, seed ) {
if ( ( nid = context.getAttribute( "id" ) ) ) {
nid = jQuery.escapeSelector( nid );
} else {
context.setAttribute( "id", ( nid = expando ) );
context.setAttribute( "id", ( nid = jQuery.expando ) );
}
}
@ -285,7 +222,7 @@ function find( selector, context, results, seed ) {
} catch ( qsaError ) {
nonnativeSelectorCache( selector, true );
} finally {
if ( nid === expando ) {
if ( nid === jQuery.expando ) {
context.removeAttribute( "id" );
}
}
@ -297,35 +234,12 @@ function find( selector, context, results, seed ) {
return select( selector.replace( rtrim, "$1" ), context, results, seed );
}
/**
* Create key-value caches of limited size
* @returns {function(string, object)} Returns the Object data after storing it on itself with
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
* deleting the oldest entry
*/
function createCache() {
var keys = [];
function cache( key, value ) {
// Use (key + " ") to avoid collision with native prototype properties
// (see https://github.com/jquery/sizzle/issues/157)
if ( keys.push( key + " " ) > Expr.cacheLength ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
return ( cache[ key + " " ] = value );
}
return cache;
}
/**
* Mark a function for special use by jQuery selector module
* @param {Function} fn The function to mark
*/
function markFunction( fn ) {
fn[ expando ] = true;
fn[ jQuery.expando ] = true;
return fn;
}
@ -427,15 +341,6 @@ function createPositionalPseudo( fn ) {
} );
}
/**
* Checks a node for validity as a jQuery selector context
* @param {Element|Object=} context
* @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
*/
function testContext( context ) {
return context && typeof context.getElementsByTagName !== "undefined" && context;
}
/**
* Sets document-related variables once based on the current document
* @param {Element|Object} [node] An element or document object to use to set the document
@ -491,7 +396,7 @@ find.matchesSelector = function( elem, expr ) {
return find( expr, document, null, [ elem ] ).length > 0;
};
Expr = jQuery.expr = {
jQuery.expr = {
// Can be adjusted by the user
cacheLength: 50,
@ -532,99 +437,18 @@ Expr = jQuery.expr = {
"~": { dir: "previousSibling" }
},
preFilter: {
ATTR: function( match ) {
match[ 1 ] = match[ 1 ].replace( runescape, funescape );
// Move the given value to match[3] whether quoted or unquoted
match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" )
.replace( runescape, funescape );
if ( match[ 2 ] === "~=" ) {
match[ 3 ] = " " + match[ 3 ] + " ";
}
return match.slice( 0, 4 );
},
CHILD: function( match ) {
/* matches from matchExpr["CHILD"]
1 type (only|nth|...)
2 what (child|of-type)
3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
4 xn-component of xn+y argument ([+-]?\d*n|)
5 sign of xn-component
6 x of xn-component
7 sign of y-component
8 y of y-component
*/
match[ 1 ] = match[ 1 ].toLowerCase();
if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
// nth-* requires argument
if ( !match[ 3 ] ) {
selectorError( match[ 0 ] );
}
// numeric x and y parameters for Expr.filter.CHILD
// remember that false/true cast respectively to 0/1
match[ 4 ] = +( match[ 4 ] ?
match[ 5 ] + ( match[ 6 ] || 1 ) :
2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" )
);
match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
// other types prohibit arguments
} else if ( match[ 3 ] ) {
selectorError( match[ 0 ] );
}
return match;
},
PSEUDO: function( match ) {
var excess,
unquoted = !match[ 6 ] && match[ 2 ];
if ( matchExpr.CHILD.test( match[ 0 ] ) ) {
return null;
}
// Accept quoted arguments as-is
if ( match[ 3 ] ) {
match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
// Strip excess characters from unquoted arguments
} else if ( unquoted && rpseudo.test( unquoted ) &&
// Get excess from tokenize (recursively)
( excess = tokenize( unquoted, true ) ) &&
// advance to the next closing parenthesis
( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {
// excess is a negative index
match[ 0 ] = match[ 0 ].slice( 0, excess );
match[ 2 ] = unquoted.slice( 0, excess );
}
// Return only captures needed by the pseudo filter method (type and argument)
return match.slice( 0, 3 );
}
},
preFilter: preFilter,
filter: {
ID: function( id ) {
var attrId = id.replace( runescape, funescape );
var attrId = unescapeSelector( id );
return function( elem ) {
return elem.getAttribute( "id" ) === attrId;
};
},
TAG: function( nodeNameSelector ) {
var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
var expectedNodeName = unescapeSelector( nodeNameSelector ).toLowerCase();
return nodeNameSelector === "*" ?
function() {
@ -739,7 +563,8 @@ Expr = jQuery.expr = {
if ( forward && useCache ) {
// Seek `elem` from a previously-cached index
outerCache = parent[ expando ] || ( parent[ expando ] = {} );
outerCache = parent[ jQuery.expando ] ||
( parent[ jQuery.expando ] = {} );
cache = outerCache[ type ] || [];
nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
diff = nodeIndex && cache[ 2 ];
@ -761,7 +586,8 @@ Expr = jQuery.expr = {
// Use previously-cached element index if available
if ( useCache ) {
outerCache = elem[ expando ] || ( elem[ expando ] = {} );
outerCache = elem[ jQuery.expando ] ||
( elem[ jQuery.expando ] = {} );
cache = outerCache[ type ] || [];
nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
diff = nodeIndex;
@ -782,8 +608,8 @@ Expr = jQuery.expr = {
// Cache the index of each encountered element
if ( useCache ) {
outerCache = node[ expando ] ||
( node[ expando ] = {} );
outerCache = node[ jQuery.expando ] ||
( node[ jQuery.expando ] = {} );
outerCache[ type ] = [ dirruns, diff ];
}
@ -808,13 +634,14 @@ Expr = jQuery.expr = {
// https://www.w3.org/TR/selectors/#pseudo-classes
// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
// Remember that setFilters inherits from pseudos
var fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
var fn = jQuery.expr.pseudos[ pseudo ] ||
jQuery.expr.setFilters[ pseudo.toLowerCase() ] ||
selectorError( "unsupported pseudo: " + pseudo );
// The user may use createPseudo to indicate that
// arguments are needed to create the filter function
// just as jQuery does
if ( fn[ expando ] ) {
if ( fn[ jQuery.expando ] ) {
return fn( argument );
}
@ -834,7 +661,7 @@ Expr = jQuery.expr = {
results = [],
matcher = compile( selector.replace( rtrim, "$1" ) );
return matcher[ expando ] ?
return matcher[ jQuery.expando ] ?
markFunction( function( seed, matches, _context, xml ) {
var elem,
unmatched = matcher( seed, null, xml, [] ),
@ -865,7 +692,7 @@ Expr = jQuery.expr = {
} ),
contains: markFunction( function( text ) {
text = text.replace( runescape, funescape );
text = unescapeSelector( text );
return function( elem ) {
return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1;
};
@ -884,7 +711,7 @@ Expr = jQuery.expr = {
if ( !ridentifier.test( lang || "" ) ) {
selectorError( "unsupported lang: " + lang );
}
lang = lang.replace( runescape, funescape ).toLowerCase();
lang = unescapeSelector( lang ).toLowerCase();
return function( elem ) {
var elemLang;
do {
@ -958,7 +785,7 @@ Expr = jQuery.expr = {
},
parent: function( elem ) {
return !Expr.pseudos.empty( elem );
return !jQuery.expr.pseudos.empty( elem );
},
// Element/input types
@ -1035,102 +862,20 @@ Expr = jQuery.expr = {
}
};
Expr.pseudos.nth = Expr.pseudos.eq;
jQuery.expr.pseudos.nth = jQuery.expr.pseudos.eq;
// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
Expr.pseudos[ i ] = createInputPseudo( i );
jQuery.expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
Expr.pseudos[ i ] = createButtonPseudo( i );
jQuery.expr.pseudos[ i ] = createButtonPseudo( i );
}
// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();
function tokenize( selector, parseOnly ) {
var matched, match, tokens, type,
soFar, groups, preFilters,
cached = tokenCache[ selector + " " ];
if ( cached ) {
return parseOnly ? 0 : cached.slice( 0 );
}
soFar = selector;
groups = [];
preFilters = Expr.preFilter;
while ( soFar ) {
// Comma and first run
if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
if ( match ) {
// Don't consume trailing commas as valid
soFar = soFar.slice( match[ 0 ].length ) || soFar;
}
groups.push( ( tokens = [] ) );
}
matched = false;
// Combinators
if ( ( match = rcombinators.exec( soFar ) ) ) {
matched = match.shift();
tokens.push( {
value: matched,
// Cast descendant combinators to space
type: match[ 0 ].replace( rtrim, " " )
} );
soFar = soFar.slice( matched.length );
}
// Filters
for ( type in Expr.filter ) {
if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
( match = preFilters[ type ]( match ) ) ) ) {
matched = match.shift();
tokens.push( {
value: matched,
type: type,
matches: match
} );
soFar = soFar.slice( matched.length );
}
}
if ( !matched ) {
break;
}
}
// Return the length of the invalid excess
// if we're just parsing
// Otherwise, throw an error or return tokens
if ( parseOnly ) {
return soFar.length;
}
return soFar ?
selectorError( selector ) :
// Cache the tokens
tokenCache( selector, groups ).slice( 0 );
}
function toSelector( tokens ) {
var i = 0,
len = tokens.length,
selector = "";
for ( ; i < len; i++ ) {
selector += tokens[ i ].value;
}
return selector;
}
setFilters.prototype = jQuery.expr.filters = jQuery.expr.pseudos;
jQuery.expr.setFilters = new setFilters();
function addCombinator( matcher, combinator, base ) {
var dir = combinator.dir,
@ -1168,7 +913,7 @@ function addCombinator( matcher, combinator, base ) {
} else {
while ( ( elem = elem[ dir ] ) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
outerCache = elem[ expando ] || ( elem[ expando ] = {} );
outerCache = elem[ jQuery.expando ] || ( elem[ jQuery.expando ] = {} );
if ( skip && nodeName( elem, skip ) ) {
elem = elem[ dir ] || elem;
@ -1239,10 +984,10 @@ function condense( unmatched, map, filter, context, xml ) {
}
function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
if ( postFilter && !postFilter[ expando ] ) {
if ( postFilter && !postFilter[ jQuery.expando ] ) {
postFilter = setMatcher( postFilter );
}
if ( postFinder && !postFinder[ expando ] ) {
if ( postFinder && !postFinder[ jQuery.expando ] ) {
postFinder = setMatcher( postFinder, postSelector );
}
return markFunction( function( seed, results, context, xml ) {
@ -1340,8 +1085,8 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS
function matcherFromTokens( tokens ) {
var checkContext, matcher, j,
len = tokens.length,
leadingRelative = Expr.relative[ tokens[ 0 ].type ],
implicitRelative = leadingRelative || Expr.relative[ " " ],
leadingRelative = jQuery.expr.relative[ tokens[ 0 ].type ],
implicitRelative = leadingRelative || jQuery.expr.relative[ " " ],
i = leadingRelative ? 1 : 0,
// The foundational matcher ensures that elements are reachable from top-level context(s)
@ -1364,18 +1109,18 @@ function matcherFromTokens( tokens ) {
} ];
for ( ; i < len; i++ ) {
if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
if ( ( matcher = jQuery.expr.relative[ tokens[ i ].type ] ) ) {
matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
} else {
matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
matcher = jQuery.expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
// Return special upon seeing a positional matcher
if ( matcher[ expando ] ) {
if ( matcher[ jQuery.expando ] ) {
// Find the next relative operator (if any) for proper handling
j = ++i;
for ( ; j < len; j++ ) {
if ( Expr.relative[ tokens[ j ].type ] ) {
if ( jQuery.expr.relative[ tokens[ j ].type ] ) {
break;
}
}
@ -1412,7 +1157,7 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
contextBackup = outermostContext,
// We must always have either seed elements or outermost context
elems = seed || byElement && Expr.find.TAG( "*", outermost ),
elems = seed || byElement && jQuery.expr.find.TAG( "*", outermost ),
// Use integer dirruns iff this is the outermost matcher
dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 );
@ -1537,7 +1282,7 @@ function compile( selector, match /* Internal Use Only */ ) {
i = match.length;
while ( i-- ) {
cached = matcherFromTokens( match[ i ] );
if ( cached[ expando ] ) {
if ( cached[ jQuery.expando ] ) {
setMatchers.push( cached );
} else {
elementMatchers.push( cached );
@ -1577,10 +1322,11 @@ function select( selector, context, results, seed ) {
// Reduce context if the leading compound selector is an ID
tokens = match[ 0 ] = match[ 0 ].slice( 0 );
if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {
context.nodeType === 9 && documentIsHTML &&
jQuery.expr.relative[ tokens[ 1 ].type ] ) {
context = ( Expr.find.ID(
token.matches[ 0 ].replace( runescape, funescape ),
context = ( jQuery.expr.find.ID(
unescapeSelector( token.matches[ 0 ] ),
context
) || [] )[ 0 ];
if ( !context ) {
@ -1600,14 +1346,14 @@ function select( selector, context, results, seed ) {
token = tokens[ i ];
// Abort if we hit a combinator
if ( Expr.relative[ ( type = token.type ) ] ) {
if ( jQuery.expr.relative[ ( type = token.type ) ] ) {
break;
}
if ( ( find = Expr.find[ type ] ) ) {
if ( ( find = jQuery.expr.find[ type ] ) ) {
// Search, expanding context for leading sibling combinators
if ( ( seed = find(
token.matches[ 0 ].replace( runescape, funescape ),
unescapeSelector( token.matches[ 0 ] ),
rsibling.test( tokens[ 0 ].type ) &&
testContext( context.parentNode ) || context
) ) ) {

View File

@ -0,0 +1,26 @@
import jQuery from "../core.js";
/**
* Create key-value caches of limited size
* @returns {function(string, object)} Returns the Object data after storing it on itself with
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
* deleting the oldest entry
*/
function createCache() {
var keys = [];
function cache( key, value ) {
// Use (key + " ") to avoid collision with native prototype properties
// (see https://github.com/jquery/sizzle/issues/157)
if ( keys.push( key + " " ) > jQuery.expr.cacheLength ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
return ( cache[ key + " " ] = value );
}
return cache;
}
export default createCache;

View File

@ -0,0 +1,18 @@
import whitespace from "../var/whitespace.js";
import identifier from "./var/identifier.js";
import attributes from "./var/attributes.js";
import pseudos from "./var/pseudos.js";
var filterMatchExpr = {
ID: new RegExp( "^#(" + identifier + ")" ),
CLASS: new RegExp( "^\\.(" + identifier + ")" ),
TAG: new RegExp( "^(" + identifier + "|[*])" ),
ATTR: new RegExp( "^" + attributes ),
PSEUDO: new RegExp( "^" + pseudos ),
CHILD: new RegExp(
"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" )
};
export default filterMatchExpr;

90
src/selector/preFilter.js Normal file
View File

@ -0,0 +1,90 @@
import rpseudo from "./var/rpseudo.js";
import filterMatchExpr from "./filterMatchExpr.js";
import unescapeSelector from "./unescapeSelector.js";
import selectorError from "./selectorError.js";
import tokenize from "./tokenize.js";
var preFilter = {
ATTR: function( match ) {
match[ 1 ] = unescapeSelector( match[ 1 ] );
// Move the given value to match[3] whether quoted or unquoted
match[ 3 ] = unescapeSelector( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" );
if ( match[ 2 ] === "~=" ) {
match[ 3 ] = " " + match[ 3 ] + " ";
}
return match.slice( 0, 4 );
},
CHILD: function( match ) {
/* matches from filterMatchExpr["CHILD"]
1 type (only|nth|...)
2 what (child|of-type)
3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
4 xn-component of xn+y argument ([+-]?\d*n|)
5 sign of xn-component
6 x of xn-component
7 sign of y-component
8 y of y-component
*/
match[ 1 ] = match[ 1 ].toLowerCase();
if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
// nth-* requires argument
if ( !match[ 3 ] ) {
selectorError( match[ 0 ] );
}
// numeric x and y parameters for jQuery.expr.filter.CHILD
// remember that false/true cast respectively to 0/1
match[ 4 ] = +( match[ 4 ] ?
match[ 5 ] + ( match[ 6 ] || 1 ) :
2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" )
);
match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
// other types prohibit arguments
} else if ( match[ 3 ] ) {
selectorError( match[ 0 ] );
}
return match;
},
PSEUDO: function( match ) {
var excess,
unquoted = !match[ 6 ] && match[ 2 ];
if ( filterMatchExpr.CHILD.test( match[ 0 ] ) ) {
return null;
}
// Accept quoted arguments as-is
if ( match[ 3 ] ) {
match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
// Strip excess characters from unquoted arguments
} else if ( unquoted && rpseudo.test( unquoted ) &&
// Get excess from tokenize (recursively)
( excess = tokenize( unquoted, true ) ) &&
// advance to the next closing parenthesis
( excess = unquoted.indexOf( ")", unquoted.length - excess ) -
unquoted.length ) ) {
// excess is a negative index
match[ 0 ] = match[ 0 ].slice( 0, excess );
match[ 2 ] = unquoted.slice( 0, excess );
}
// Return only captures needed by the pseudo filter method (type and argument)
return match.slice( 0, 3 );
}
};
export default preFilter;

View File

@ -0,0 +1,5 @@
function selectorError( msg ) {
throw new Error( "Syntax error, unrecognized expression: " + msg );
}
export default selectorError;

View File

@ -0,0 +1,10 @@
/**
* Checks a node for validity as a jQuery selector context
* @param {Element|Object=} context
* @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
*/
function testContext( context ) {
return context && typeof context.getElementsByTagName !== "undefined" && context;
}
export default testContext;

View File

@ -0,0 +1,11 @@
function toSelector( tokens ) {
var i = 0,
len = tokens.length,
selector = "";
for ( ; i < len; i++ ) {
selector += tokens[ i ].value;
}
return selector;
}
export default toSelector;

83
src/selector/tokenize.js Normal file
View File

@ -0,0 +1,83 @@
import jQuery from "../core.js";
import rcomma from "./var/rcomma.js";
import rleadingCombinator from "./var/rleadingCombinator.js";
import rtrim from "../var/rtrim.js";
import createCache from "./createCache.js";
import selectorError from "./selectorError.js";
import filterMatchExpr from "./filterMatchExpr.js";
var tokenCache = createCache();
function tokenize( selector, parseOnly ) {
var matched, match, tokens, type,
soFar, groups, preFilters,
cached = tokenCache[ selector + " " ];
if ( cached ) {
return parseOnly ? 0 : cached.slice( 0 );
}
soFar = selector;
groups = [];
preFilters = jQuery.expr.preFilter;
while ( soFar ) {
// Comma and first run
if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
if ( match ) {
// Don't consume trailing commas as valid
soFar = soFar.slice( match[ 0 ].length ) || soFar;
}
groups.push( ( tokens = [] ) );
}
matched = false;
// Combinators
if ( ( match = rleadingCombinator.exec( soFar ) ) ) {
matched = match.shift();
tokens.push( {
value: matched,
// Cast descendant combinators to space
type: match[ 0 ].replace( rtrim, " " )
} );
soFar = soFar.slice( matched.length );
}
// Filters
for ( type in filterMatchExpr ) {
if ( ( match = jQuery.expr.match[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
( match = preFilters[ type ]( match ) ) ) ) {
matched = match.shift();
tokens.push( {
value: matched,
type: type,
matches: match
} );
soFar = soFar.slice( matched.length );
}
}
if ( !matched ) {
break;
}
}
// Return the length of the invalid excess
// if we're just parsing
// Otherwise, throw an error or return tokens
if ( parseOnly ) {
return soFar.length;
}
return soFar ?
selectorError( selector ) :
// Cache the tokens
tokenCache( selector, groups ).slice( 0 );
}
export default tokenize;

View File

@ -0,0 +1,29 @@
// CSS escapes
// https://www.w3.org/TR/CSS21/syndata.html#escaped-characters
import whitespace from "../var/whitespace.js";
var runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace +
"?|\\\\([^\\r\\n\\f])", "g" ),
funescape = function( escape, nonHex ) {
var high = "0x" + escape.slice( 1 ) - 0x10000;
if ( nonHex ) {
// Strip the backslash prefix from a non-hex escape sequence
return nonHex;
}
// Replace a hexadecimal escape sequence with the encoded Unicode code point
// Support: IE <=11+
// For values outside the Basic Multilingual Plane (BMP), manually construct a
// surrogate pair
return high < 0 ?
String.fromCharCode( high + 0x10000 ) :
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
};
function unescapeSelector( sel ) {
return sel.replace( runescape, funescape );
}
export default unescapeSelector;

View File

@ -0,0 +1,12 @@
import whitespace from "../../var/whitespace.js";
import identifier from "./identifier.js";
// Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors
export default "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
// Operator (capture 2)
"*([*^$|!~]?=)" + whitespace +
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
whitespace + "*\\]";

View File

@ -0,0 +1,2 @@
export default "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" +
"loop|multiple|open|readonly|required|scoped";

View File

@ -0,0 +1,5 @@
import whitespace from "../../var/whitespace.js";
// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
export default "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+";

View File

@ -0,0 +1,5 @@
import documentElement from "../../var/documentElement.js";
// Support: IE 9 - 11+
// IE requires a prefix.
export default documentElement.matches || documentElement.msMatchesSelector;

View File

@ -0,0 +1,15 @@
import identifier from "./identifier.js";
import attributes from "./attributes.js";
export default ":(" + identifier + ")(?:\\((" +
// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
// 1. quoted (capture 3; capture 4 or capture 5)
"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
// 2. simple (capture 6)
"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
// 3. anything else (capture 2)
".*" +
")\\)|)";

View File

@ -0,0 +1,3 @@
import whitespace from "../../var/whitespace.js";
export default new RegExp( "^" + whitespace + "*," + whitespace + "*" );

View File

@ -0,0 +1,3 @@
import whitespace from "../../var/whitespace.js";
export default new RegExp( whitespace + "|>" );

View File

@ -0,0 +1,4 @@
import whitespace from "../../var/whitespace.js";
export default new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" +
whitespace + "*" );

View File

@ -0,0 +1,3 @@
import pseudos from "./pseudos.js";
export default new RegExp( pseudos );

View File

@ -0,0 +1 @@
export default /[+~]/;

View File

@ -153,7 +153,7 @@ QUnit.test( "jQuery()", function( assert ) {
"Empty attributes object is not interpreted as a document (trac-8950)" );
} );
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "jQuery(selector, context)", function( assert ) {
QUnit.test( "jQuery(selector, context)", function( assert ) {
assert.expect( 3 );
assert.deepEqual( jQuery( "div p", "#qunit-fixture" ).get(), q( "sndp", "en", "sap" ), "Basic selector with string as context" );
assert.deepEqual( jQuery( "div p", q( "qunit-fixture" )[ 0 ] ).get(), q( "sndp", "en", "sap" ), "Basic selector with element as context" );

View File

@ -121,19 +121,13 @@ QUnit.test( "element", function( assert ) {
assert.deepEqual( jQuery( "input[id='idTest']", lengthtest ).get(), q( "idTest" ),
"Finding elements with id of ID." );
if ( QUnit.jQuerySelectors ) {
siblingTest = document.getElementById( "siblingTest" );
assert.deepEqual( jQuery( "div em", siblingTest ).get(), [],
"Element-rooted QSA does not select based on document context" );
assert.deepEqual( jQuery( "div em, div em, div em:not(div em)", siblingTest ).get(), [],
"Element-rooted QSA does not select based on document context" );
assert.deepEqual( jQuery( "div em, em\\,", siblingTest ).get(), [],
"Escaped commas do not get treated with an id in element-rooted QSA" );
} else {
assert.ok( "skip", "Element-rooted QSA behavior different in selector-native" );
assert.ok( "skip", "Element-rooted QSA behavior different in selector-native" );
assert.ok( "skip", "Element-rooted QSA behavior different in selector-native" );
}
siblingTest = document.getElementById( "siblingTest" );
assert.deepEqual( jQuery( "div em", siblingTest ).get(), [],
"Element-rooted QSA does not select based on document context" );
assert.deepEqual( jQuery( "div em, div em, div em:not(div em)", siblingTest ).get(), [],
"Element-rooted QSA does not select based on document context" );
assert.deepEqual( jQuery( "div em, em\\,", siblingTest ).get(), [],
"Escaped commas do not get treated with an id in element-rooted QSA" );
html = "";
for ( i = 0; i < 100; i++ ) {
@ -263,12 +257,8 @@ QUnit.test( "id", function( assert ) {
fiddle = jQuery( "<div id='fiddle\\Foo'><span id='fiddleSpan'></span></div>" )
.appendTo( "#qunit-fixture" );
if ( QUnit.jQuerySelectors ) {
assert.deepEqual( jQuery( "> span", jQuery( "#fiddle\\\\Foo" )[ 0 ] ).get(),
q( [ "fiddleSpan" ] ), "Escaped ID as context" );
} else {
assert.ok( "skip", "leading > not supported in selector-native" );
}
assert.deepEqual( jQuery( "> span", jQuery( "#fiddle\\\\Foo" )[ 0 ] ).get(),
q( [ "fiddleSpan" ] ), "Escaped ID as context" );
fiddle.remove();
@ -473,15 +463,10 @@ QUnit.test( "child and adjacent", function( assert ) {
siblingFirst = document.getElementById( "siblingfirst" );
if ( QUnit.jQuerySelectors ) {
assert.deepEqual( jQuery( "+ em", siblingFirst ).get(), q( "siblingnext" ),
"Element Directly Preceded By with a context." );
assert.deepEqual( jQuery( "~ em", siblingFirst ).get(), q( "siblingnext", "siblingthird" ),
"Element Preceded By with a context." );
} else {
assert.ok( "skip", "leading + not supported in selector-native" );
assert.ok( "skip", "leading ~ not supported in selector-native" );
}
assert.deepEqual( jQuery( "+ em", siblingFirst ).get(), q( "siblingnext" ),
"Element Directly Preceded By with a context." );
assert.deepEqual( jQuery( "~ em", siblingFirst ).get(), q( "siblingnext", "siblingthird" ),
"Element Preceded By with a context." );
if ( QUnit.jQuerySelectorsPos ) {
assert.deepEqual( jQuery( "~ em:first", siblingFirst ).get(), q( "siblingnext" ),
@ -490,16 +475,11 @@ QUnit.test( "child and adjacent", function( assert ) {
assert.ok( "skip", "Positional selectors are not supported" );
}
if ( QUnit.jQuerySelectors ) {
en = document.getElementById( "en" );
assert.deepEqual( jQuery( "+ p, a", en ).get(), q( "yahoo", "sap" ),
"Compound selector with context, beginning with sibling test." );
assert.deepEqual( jQuery( "a, + p", en ).get(), q( "yahoo", "sap" ),
"Compound selector with context, containing sibling test." );
} else {
assert.ok( "skip", "leading + not supported in selector-native" );
assert.ok( "skip", "leading + not supported in selector-native" );
}
en = document.getElementById( "en" );
assert.deepEqual( jQuery( "+ p, a", en ).get(), q( "yahoo", "sap" ),
"Compound selector with context, beginning with sibling test." );
assert.deepEqual( jQuery( "a, + p", en ).get(), q( "yahoo", "sap" ),
"Compound selector with context, containing sibling test." );
if ( QUnit.jQuerySelectors ) {
assert.t( "Element Preceded By, Containing", "#liveHandlerOrder ~ div em:contains('1')", [ "siblingfirst" ] );
@ -1412,16 +1392,12 @@ QUnit.test( "pseudo - :(dis|en)abled, explicitly disabled", function( assert ) {
"disabled-select", "disabled-optgroup", "disabled-option" ]
);
if ( QUnit.jQuerySelectors ) {
assert.t(
"Enabled elements",
"#enabled-fieldset :enabled",
[ "enabled-input", "enabled-textarea", "enabled-button",
"enabled-select", "enabled-optgroup", "enabled-option" ]
);
} else {
assert.ok( "skip", ":enabled broken in Chrome in selector-native" );
}
assert.t(
"Enabled elements",
"#enabled-fieldset :enabled",
[ "enabled-input", "enabled-textarea", "enabled-button",
"enabled-select", "enabled-optgroup", "enabled-option" ]
);
} );
QUnit.test( "pseudo - :(dis|en)abled, optgroup and option", function( assert ) {
@ -1637,11 +1613,7 @@ QUnit.test( "context", function( assert ) {
context = document.getElementById( "nothiddendiv" );
assert.deepEqual( jQuery( "*", context ).get(), q( "nothiddendivchild" ), "<div> context" );
if ( QUnit.jQuerySelectors ) {
assert.deepEqual( jQuery( "* > *", context ).get(), [], "<div> context (no results)" );
} else {
assert.ok( "skip", "The whole selector not required to be under context in selector-native" );
}
assert.deepEqual( jQuery( "* > *", context ).get(), [], "<div> context (no results)" );
context.removeAttribute( "id" );
assert.deepEqual( jQuery( "*", context ).get(), q( "nothiddendivchild" ), "no-id element context" );
@ -1670,20 +1642,14 @@ QUnit.test( "context", function( assert ) {
expected = q( "siblingnext", "siblingspan" );
context.appendChild( document.getElementById( "siblingTest" ) );
if ( QUnit.jQuerySelectors ) {
assert.deepEqual(
jQuery( "em:nth-child(2)", context ).get(),
expected.slice( 0, 1 ),
"DocumentFragment context"
);
assert.deepEqual( jQuery( "span", context ).get(), expected.slice( 1 ),
"DocumentFragment context by tag name" );
assert.deepEqual( jQuery( "p", context ).get(), [], "DocumentFragment context (no results)" );
} else {
assert.ok( "skip", "selection on document fragments not supported in selector-native" );
assert.ok( "skip", "selection on document fragments not supported in selector-native" );
assert.ok( "skip", "selection on document fragments not supported in selector-native" );
}
assert.deepEqual(
jQuery( "em:nth-child(2)", context ).get(),
expected.slice( 0, 1 ),
"DocumentFragment context"
);
assert.deepEqual( jQuery( "span", context ).get(), expected.slice( 1 ),
"DocumentFragment context by tag name" );
assert.deepEqual( jQuery( "p", context ).get(), [], "DocumentFragment context (no results)" );
if ( QUnit.jQuerySelectors ) {
assert.deepEqual(
@ -1775,9 +1741,7 @@ QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "disconnected nodes", function
// Support: IE 11+
// IE doesn't support Shadow DOM.
// selector-native doesn't support querying inside of Shadow DOM.
QUnit[ QUnit.jQuerySelectors && !QUnit.isIE ? "test" : "skip" ](
"Shadow DOM nodes supported as root", function( assert ) {
QUnit.testUnlessIE( "Shadow DOM nodes supported as root", function( assert ) {
assert.expect( 2 );
var shadowHost = jQuery( "<div></div>" ).appendTo( "#qunit-fixture" )[ 0 ];

View File

@ -13,7 +13,7 @@ QUnit.test( "find(String) under non-elements", function( assert ) {
assert.equal( j.find( "div" ).addBack().length, 3, "Check node,textnode,comment to find zero divs, but preserves pushStack" );
} );
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "find(leading combinator)", function( assert ) {
QUnit.test( "find(leading combinator)", function( assert ) {
assert.expect( 4 );
assert.deepEqual( jQuery( "#qunit-fixture" ).find( "> div" ).get(), q( "foo", "nothiddendiv", "moretests", "tabindex-tests", "liveHandlerOrder", "siblingTest", "fx-test-group" ), "find child elements" );
@ -445,7 +445,9 @@ QUnit.test( "closest(jQuery)", function( assert ) {
assert.ok( $child.closest( $body.add( $parent ) ).is( "#nothiddendiv" ), "Closest ancestor retrieved." );
} );
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "not(Selector)", function( assert ) {
// Support: IE 11+
// IE doesn't support complex selectors inside `:not()`.
QUnit.testUnlessIE( "not(Selector)", function( assert ) {
assert.expect( 7 );
assert.equal( jQuery( "#qunit-fixture > p#ap > a" ).not( "#google" ).length, 2, "not('selector')" );
@ -478,11 +480,15 @@ QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "not(Selector)", function( ass
assert.deepEqual( jQuery( "#ap *" ).not( "code, #mark" ).get(), q( "google", "groups", "anchor1" ), "not('tag, ID selector')" );
assert.deepEqual( jQuery( "#ap *" ).not( "#mark, code" ).get(), q( "google", "groups", "anchor1" ), "not('ID, tag selector')" );
assert.deepEqual(
jQuery( "#form option" ).not( "option.emptyopt:contains('Nothing'),optgroup *,[value='1']" ).get(),
q( "option1c", "option1d", "option2c", "option2d", "option3c", "option3d", "option3e", "option4d", "option4e", "option5a", "option5b" ),
"not('complex selector')"
);
if ( QUnit.jQuerySelectors ) {
assert.deepEqual(
jQuery( "#form option" ).not( "option.emptyopt:contains('Nothing'),optgroup *,[value='1']" ).get(),
q( "option1c", "option1d", "option2c", "option2d", "option3c", "option3d", "option3e", "option4d", "option4e", "option5a", "option5b" ),
"not('complex selector')"
);
} else {
assert.ok( "skip", ":contains not supported in selector-native" );
}
} );
QUnit.test( "not(undefined)", function( assert ) {
@ -525,7 +531,9 @@ QUnit.test( "not(jQuery)", function( assert ) {
);
} );
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "not(Selector) excludes non-element nodes (gh-2808)", function( assert ) {
// Support: IE 11+
// IE doesn't support complex selectors inside `:not()`.
QUnit.testUnlessIE( "not(Selector) excludes non-element nodes (gh-2808)", function( assert ) {
assert.expect( 3 );
var mixedContents = jQuery( "#nonnodes" ).contents(),