@@ -2219,3 +2219,130 @@ QUnit.test( "jQuery.find.attr (HTML)", function( assert ) {
22192219QUnit . test ( "jQuery.find.attr (XML)" , function ( assert ) {
22202220testAttr ( jQuery . parseXML ( "<root/>" ) , assert ) ;
22212221} ) ;
2222+
2223+ QUnit [ QUnit . jQuerySelectors ?"test" :"skip" ] ( "custom pseudos" , function ( assert ) {
2224+ assert . expect ( 6 ) ;
2225+
2226+ try {
2227+ jQuery . expr . filters . foundation = jQuery . expr . filters . root ;
2228+ assert . deepEqual ( jQuery . find ( ":foundation" ) , [ document . documentElement ] , "Copy element filter with new name" ) ;
2229+ } finally {
2230+ delete jQuery . expr . filters . foundation ;
2231+ }
2232+
2233+ try {
2234+ jQuery . expr . setFilters . primary = jQuery . expr . setFilters . first ;
2235+ assert . t ( "Copy set filter with new name" , "div#qunit-fixture :primary" , [ "firstp" ] ) ;
2236+ } finally {
2237+ delete jQuery . expr . setFilters . primary ;
2238+ }
2239+
2240+ try {
2241+ jQuery . expr . filters . aristotlean = jQuery . expr . createPseudo ( function ( ) {
2242+ return function ( elem ) {
2243+ return ! ! elem . id ;
2244+ } ;
2245+ } ) ;
2246+ assert . t ( "Custom element filter" , "#foo :aristotlean" , [ "sndp" , "en" , "yahoo" , "sap" , "anchor2" , "simon" ] ) ;
2247+ } finally {
2248+ delete jQuery . expr . filters . aristotlean ;
2249+ }
2250+
2251+ try {
2252+ jQuery . expr . filters . endswith = jQuery . expr . createPseudo ( function ( text ) {
2253+ return function ( elem ) {
2254+ return jQuery . text ( elem ) . slice ( - text . length ) === text ;
2255+ } ;
2256+ } ) ;
2257+ assert . t ( "Custom element filter with argument" , "a:endswith(ogle)" , [ "google" ] ) ;
2258+ } finally {
2259+ delete jQuery . expr . filters . endswith ;
2260+ }
2261+
2262+ try {
2263+ jQuery . expr . setFilters . second = jQuery . expr . createPseudo ( function ( ) {
2264+ return jQuery . expr . createPseudo ( function ( seed , matches ) {
2265+ if ( seed [ 1 ] ) {
2266+ matches [ 1 ] = seed [ 1 ] ;
2267+ seed [ 1 ] = false ;
2268+ }
2269+ } ) ;
2270+ } ) ;
2271+ assert . t ( "Custom set filter" , "#qunit-fixture p:second" , [ "ap" ] ) ;
2272+ } finally {
2273+ delete jQuery . expr . filters . second ;
2274+ }
2275+
2276+ try {
2277+ jQuery . expr . setFilters . slice = jQuery . expr . createPseudo ( function ( argument ) {
2278+ var bounds = argument . split ( ":" ) ;
2279+ return jQuery . expr . createPseudo ( function ( seed , matches ) {
2280+ var i = bounds [ 1 ] ;
2281+
2282+ // Match elements found at the specified indexes
2283+ while ( -- i >= bounds [ 0 ] ) {
2284+ if ( seed [ i ] ) {
2285+ matches [ i ] = seed [ i ] ;
2286+ seed [ i ] = false ;
2287+ }
2288+ }
2289+ } ) ;
2290+ } ) ;
2291+ assert . t ( "Custom set filter with argument" , "#qunit-fixture p:slice(1:3)" , [ "ap" , "sndp" ] ) ;
2292+ } finally {
2293+ delete jQuery . expr . filters . slice ;
2294+ }
2295+ } ) ;
2296+
2297+ QUnit [ QUnit . jQuerySelectors ?"test" :"skip" ] ( "backwards-compatible custom pseudos" , function ( assert ) {
2298+ assert . expect ( 3 ) ;
2299+
2300+ try {
2301+ jQuery . expr . filters . icontains = function ( elem , i , match ) {
2302+ return jQuery . text ( elem ) . toLowerCase ( ) . indexOf ( ( match [ 3 ] || "" ) . toLowerCase ( ) ) > - 1 ;
2303+ } ;
2304+ assert . t ( "Custom element filter with argument" , "a:icontains(THIS BLOG ENTRY)" , [ "simon1" ] ) ;
2305+ } finally {
2306+ delete jQuery . expr . filters . icontains ;
2307+ }
2308+
2309+ try {
2310+ jQuery . expr . setFilters . podium = function ( elements , argument ) {
2311+ var count = argument == null || argument === "" ?3 :+ argument ;
2312+ return elements . slice ( 0 , count ) ;
2313+ } ;
2314+ // Using TAG as the first token here forces this setMatcher into a fail state
2315+ // Where the descendent combinator was lost
2316+ assert . t ( "Custom setFilter" , "form#form :PODIUM" , [ "label-for" , "text1" , "text2" ] ) ;
2317+ assert . t ( "Custom setFilter with argument" , "#form input:Podium(1)" , [ "text1" ] ) ;
2318+ } finally {
2319+ delete jQuery . expr . setFilters . podium ;
2320+ }
2321+ } ) ;
2322+
2323+ // The jQuery.expr.attrHandle API is only maintained for backwards compatibility
2324+ // on the `3.x-stable` branch.
2325+ QUnit [ QUnit . jQuerySelectors ?"test" :"skip" ] ( "custom attribute getters" , function ( assert ) {
2326+ assert . expect ( 2 ) ;
2327+
2328+ var original = jQuery . expr . attrHandle . hreflang ,
2329+ selector = "a:contains('mark')[hreflang='http://diveintomark.org/en']" ;
2330+
2331+ try {
2332+ jQuery . expr . attrHandle . hreflang = function ( elem , name ) {
2333+ var href = elem . getAttribute ( "href" ) ,
2334+ lang = elem . getAttribute ( name ) ;
2335+ return lang && ( href + lang ) ;
2336+ } ;
2337+
2338+ assert . deepEqual ( jQuery . find ( selector , createWithFriesXML ( ) ) , [ ] , "Custom attrHandle (preferred document)" ) ;
2339+ assert . t ( "Custom attrHandle (preferred document)" , selector , [ "mark" ] ) ;
2340+ } finally {
2341+ jQuery . expr . attrHandle . hreflang = original ;
2342+ }
2343+ } ) ;
2344+ QUnit [ QUnit . jQuerySelectors ?"test" :"skip" ] ( "Ensure no 'undefined' handler is added" , function ( assert ) {
2345+ assert . expect ( 1 ) ;
2346+ assert . ok ( ! jQuery . expr . attrHandle . hasOwnProperty ( "undefined" ) ,
2347+ "Extra attr handlers are not added to Expr.attrHandle (https://github.com/jquery/sizzle/issues/353)" ) ;
2348+ } ) ;