]> source.dussan.org Git - jquery.git/commitdiff
Selector: Backport jQuery selection context logic to selector-native
authorMichał Gołębiowski-Owczarek <m.goleb@gmail.com>
Mon, 13 Feb 2023 17:34:41 +0000 (18:34 +0100)
committerGitHub <noreply@github.com>
Mon, 13 Feb 2023 17:34:41 +0000 (18:34 +0100)
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

23 files changed:
src/selector-native.js
src/selector.js
src/selector/createCache.js [new file with mode: 0644]
src/selector/filterMatchExpr.js [new file with mode: 0644]
src/selector/preFilter.js [new file with mode: 0644]
src/selector/selectorError.js [new file with mode: 0644]
src/selector/testContext.js [new file with mode: 0644]
src/selector/toSelector.js [new file with mode: 0644]
src/selector/tokenize.js [new file with mode: 0644]
src/selector/unescapeSelector.js [new file with mode: 0644]
src/selector/var/attributes.js [new file with mode: 0644]
src/selector/var/booleans.js [new file with mode: 0644]
src/selector/var/identifier.js [new file with mode: 0644]
src/selector/var/matches.js [new file with mode: 0644]
src/selector/var/pseudos.js [new file with mode: 0644]
src/selector/var/rcomma.js [new file with mode: 0644]
src/selector/var/rdescend.js [new file with mode: 0644]
src/selector/var/rleadingCombinator.js [new file with mode: 0644]
src/selector/var/rpseudo.js [new file with mode: 0644]
src/selector/var/rsibling.js [new file with mode: 0644]
test/unit/core.js
test/unit/selector.js
test/unit/traversing.js

index 7ca500515a9c443b30484e42fec5d5b565e83613..07da6f37ac879dd88ec2f50f839833313a2840d2 100644 (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)
 
 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
        }
 } );
 
index 117ee30517c64bd93639eed73d018e115b04465c..328eca45ffc0a2bbd473e60899f991ebeb1791ad 100644 (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
                                ) ) ) {
diff --git a/src/selector/createCache.js b/src/selector/createCache.js
new file mode 100644 (file)
index 0000000..18e255d
--- /dev/null
@@ -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;
diff --git a/src/selector/filterMatchExpr.js b/src/selector/filterMatchExpr.js
new file mode 100644 (file)
index 0000000..17056a5
--- /dev/null
@@ -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;
diff --git a/src/selector/preFilter.js b/src/selector/preFilter.js
new file mode 100644 (file)
index 0000000..4a2fb48
--- /dev/null
@@ -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;
diff --git a/src/selector/selectorError.js b/src/selector/selectorError.js
new file mode 100644 (file)
index 0000000..a02e516
--- /dev/null
@@ -0,0 +1,5 @@
+function selectorError( msg ) {
+       throw new Error( "Syntax error, unrecognized expression: " + msg );
+}
+
+export default selectorError;
diff --git a/src/selector/testContext.js b/src/selector/testContext.js
new file mode 100644 (file)
index 0000000..a54351e
--- /dev/null
@@ -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;
diff --git a/src/selector/toSelector.js b/src/selector/toSelector.js
new file mode 100644 (file)
index 0000000..bd0c15f
--- /dev/null
@@ -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;
diff --git a/src/selector/tokenize.js b/src/selector/tokenize.js
new file mode 100644 (file)
index 0000000..34bc9ad
--- /dev/null
@@ -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;
diff --git a/src/selector/unescapeSelector.js b/src/selector/unescapeSelector.js
new file mode 100644 (file)
index 0000000..3a01f94
--- /dev/null
@@ -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;
diff --git a/src/selector/var/attributes.js b/src/selector/var/attributes.js
new file mode 100644 (file)
index 0000000..f9813ac
--- /dev/null
@@ -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 + "*\\]";
diff --git a/src/selector/var/booleans.js b/src/selector/var/booleans.js
new file mode 100644 (file)
index 0000000..9dc3c97
--- /dev/null
@@ -0,0 +1,2 @@
+export default "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" +
+       "loop|multiple|open|readonly|required|scoped";
diff --git a/src/selector/var/identifier.js b/src/selector/var/identifier.js
new file mode 100644 (file)
index 0000000..03af0dd
--- /dev/null
@@ -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])+";
diff --git a/src/selector/var/matches.js b/src/selector/var/matches.js
new file mode 100644 (file)
index 0000000..6f79452
--- /dev/null
@@ -0,0 +1,5 @@
+import documentElement from "../../var/documentElement.js";
+
+// Support: IE 9 - 11+
+// IE requires a prefix.
+export default documentElement.matches || documentElement.msMatchesSelector;
diff --git a/src/selector/var/pseudos.js b/src/selector/var/pseudos.js
new file mode 100644 (file)
index 0000000..fdf2882
--- /dev/null
@@ -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)
+       ".*" +
+       ")\\)|)";
diff --git a/src/selector/var/rcomma.js b/src/selector/var/rcomma.js
new file mode 100644 (file)
index 0000000..f5803f9
--- /dev/null
@@ -0,0 +1,3 @@
+import whitespace from "../../var/whitespace.js";
+
+export default new RegExp( "^" + whitespace + "*," + whitespace + "*" );
diff --git a/src/selector/var/rdescend.js b/src/selector/var/rdescend.js
new file mode 100644 (file)
index 0000000..bab6009
--- /dev/null
@@ -0,0 +1,3 @@
+import whitespace from "../../var/whitespace.js";
+
+export default new RegExp( whitespace + "|>" );
diff --git a/src/selector/var/rleadingCombinator.js b/src/selector/var/rleadingCombinator.js
new file mode 100644 (file)
index 0000000..f802dc8
--- /dev/null
@@ -0,0 +1,4 @@
+import whitespace from "../../var/whitespace.js";
+
+export default new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" +
+       whitespace + "*" );
diff --git a/src/selector/var/rpseudo.js b/src/selector/var/rpseudo.js
new file mode 100644 (file)
index 0000000..9ed943a
--- /dev/null
@@ -0,0 +1,3 @@
+import pseudos from "./pseudos.js";
+
+export default new RegExp( pseudos );
diff --git a/src/selector/var/rsibling.js b/src/selector/var/rsibling.js
new file mode 100644 (file)
index 0000000..9aa8158
--- /dev/null
@@ -0,0 +1 @@
+export default /[+~]/;
index 49b7aa607d9da0d09d80b3582580beb57979f5bf..0029ee7175f7fafc2d21f7ee62a6927a7e530e52 100644 (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" );
index 649b1e225b107472dcbbe7fcc281ea9c95b394e4..e368e827c4216ac9ea13a708091018d62a8dbaaa 100644 (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 ];
index eea81240fd1512c2d1bce0a5f6f08daaabb59487..3cdd9470f5de7432026d53d9d42861e693adac1c 100644 (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(),