From 13a870b60e2042cf2c5df45589ec160e19168531 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Micha=C5=82=20Go=C5=82=C4=99biowski-Owczarek?= Date: Mon, 12 Jun 2023 22:58:58 +0200 Subject: [PATCH] Selector: Re-expose jQuery.find.tokenize (3.x version) `Sizzle.tokenize` is an internal Sizzle API, but exposed. As a result, it has historically been available in jQuery via `jQuery.find.tokenize`. That got dropped during Sizzle removal; this change restores the API. In addition to that, Sizzle tests have been backported for the following APIs: * `jQuery.find.matchesSelector` * `jQuery.find.matches` * `jQuery.find.compile` * `jQuery.find.select` A new test was also added for `jQuery.find.tokenize` - even Sizzle was missing one. Fixes gh-5259 Closes gh-5260 Ref gh-5263 Ref jquery/sizzle#242 Ref gh-5113 Ref gh-4395 Ref gh-4406 --- src/selector.js | 6 +- test/unit/selector.js | 229 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 3 deletions(-) diff --git a/src/selector.js b/src/selector.js index 406bf49f4..924846ff6 100644 --- a/src/selector.js +++ b/src/selector.js @@ -2091,12 +2091,12 @@ jQuery.find = find; jQuery.expr[ ":" ] = jQuery.expr.pseudos; jQuery.unique = jQuery.uniqueSort; -// These have always been private, but they used to be documented -// as part of Sizzle so let's maintain them in the 3.x line -// for backwards compatibility purposes. +// These have always been private, but they used to be documented as part of +// Sizzle so let's maintain them for now for backwards compatibility purposes. find.compile = compile; find.select = select; find.setDocument = setDocument; +find.tokenize = tokenize; find.escape = jQuery.escapeSelector; find.getText = jQuery.text; diff --git a/test/unit/selector.js b/test/unit/selector.js index d94eb76ea..0bd35e6bd 100644 --- a/test/unit/selector.js +++ b/test/unit/selector.js @@ -2448,3 +2448,232 @@ QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "Ensure no 'undefined' handler assert.ok( !jQuery.expr.attrHandle.hasOwnProperty( "undefined" ), "Extra attr handlers are not added to Expr.attrHandle (https://github.com/jquery/sizzle/issues/353)" ); } ); + +QUnit.test( "jQuery.find.matchesSelector", function( assert ) { + assert.expect( 15 ); + + var link = document.getElementById( "simon1" ), + input = document.getElementById( "text1" ), + option = document.getElementById( "option1a" ), + disconnected = document.createElement( "div" ); + + link.title = "Don't click me"; + assert.ok( jQuery.find.matchesSelector( link, "[rel='bookmark']" ), "attribute-equals string" ); + assert.ok( jQuery.find.matchesSelector( link, "[rel=bookmark]" ), "attribute-equals identifier" ); + assert.ok( jQuery.find.matchesSelector( link, "[\nrel = bookmark\t]" ), + "attribute-equals identifier (whitespace ignored)" ); + assert.ok( jQuery.find.matchesSelector( link, "a[title=\"Don't click me\"]" ), + "attribute-equals string containing single quote" ); + + // trac-12303 + input.setAttribute( "data-pos", ":first" ); + assert.ok( jQuery.find.matchesSelector( input, "input[data-pos=\\:first]" ), + "attribute-equals POS in identifier" ); + assert.ok( jQuery.find.matchesSelector( input, "input[data-pos=':first']" ), + "attribute-equals POS in string" ); + if ( QUnit.jQuerySelectors ) { + assert.ok( jQuery.find.matchesSelector( input, ":input[data-pos=':first']" ), + "attribute-equals POS in string after pseudo" ); + } else { + assert.ok( "skip", ":input not supported in selector-native" ); + } + + option.setAttribute( "test", "" ); + assert.ok( jQuery.find.matchesSelector( option, "[id=option1a]" ), + "id attribute-equals identifier" ); + if ( QUnit.jQuerySelectors ) { + assert.ok( jQuery.find.matchesSelector( option, "[id*=option1][type!=checkbox]" ), + "attribute-not-equals identifier" ); + } else { + assert.ok( "skip", "[key!=value] not supported in selector-native" ); + } + assert.ok( jQuery.find.matchesSelector( option, "[id*=option1]" ), "attribute-contains identifier" ); + assert.ok( !jQuery.find.matchesSelector( option, "[test^='']" ), + "attribute-starts-with empty string (negative)" ); + + option.className = "=]"; + assert.ok( jQuery.find.matchesSelector( option, ".\\=\\]" ), + "class selector with attribute-equals confusable" ); + + assert.ok( jQuery.find.matchesSelector( disconnected, "div" ), "disconnected element" ); + assert.ok( jQuery.find.matchesSelector( link, "* > *" ), "child combinator matches in document" ); + assert.ok( !jQuery.find.matchesSelector( disconnected, "* > *" ), "child combinator fails in fragment" ); +} ); + +QUnit.test( "jQuery.find.matches", function( assert ) { + assert.expect( 4 ); + + var iframeChild, + input = document.getElementById( "text1" ), + div = document.createElement( "div" ), + iframe = document.getElementById( "iframe" ), + iframeDoc = iframe.contentDocument || iframe.contentWindow.document; + + assert.deepEqual( jQuery.find.matches( "input", [ input ] ), [ input ], + "jQuery.find.matches with seed of input element" ); + assert.deepEqual( jQuery.find.matches( "div", [ div ] ), [ div ], + "jQuery.find.matches with disconnected element" ); + + iframeDoc.open(); + iframeDoc.write( "
" ); + iframeDoc.close(); + + iframeChild = iframeDoc.getElementById( "bar" ); + + assert.deepEqual( + jQuery.find.matches( ":root > body > #foo > #bar", [ iframeChild ] ), + [ iframeChild ], + "jQuery.find.matches infers context from element" + ); + + assert.deepEqual( + jQuery.find.matches( ":root *", [ div, iframeChild, input ] ), + [ iframeChild, input ], + "jQuery.find.matches infers context from each seed element" + ); +} ); + +QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "jQuery.find.select with pre-compiled function", function( assert ) { + assert.expect( 6 ); + + supportjQuery.each( [ + "#qunit-fixture #first", + "ol#listWithTabIndex > li[tabindex]", + "#liveSpan1" + ], function( i, selector ) { + var compiled = jQuery.find.compile( selector ); + assert.equal( jQuery.find.select( compiled, document ).length, + 1, "Should match using a compiled selector function" ); + assert.equal( + jQuery.find.select( compiled, jQuery( "#first" )[ 0 ] ).length, + 0, "Should not match with different context" ); + } ); +} ); + +// Internal, but we test it for backwards compatibility for edge cases +QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "jQuery.find.tokenize", function( assert ) { + assert.expect( 1 ); + + var selector = "#id .class > div[prop=\"value\"] + input:nth-child(1):button, span:contains(\"Text\") ~ div:has(div:has(span)):not(.not-this.not-that > div)", + tokens = [ + [ + { + "value": "#id", + "type": "ID", + "matches": [ + "id" + ] + }, + { + "value": " ", + "type": " " + }, + { + "value": ".class", + "type": "CLASS", + "matches": [ + "class" + ] + }, + { + "value": " > ", + "type": ">" + }, + { + "value": "div", + "type": "TAG", + "matches": [ + "div" + ] + }, + { + "value": "[prop=\"value\"]", + "type": "ATTR", + "matches": [ + "prop", + "=", + "value" + ] + }, + { + "value": " + ", + "type": "+" + }, + { + "value": "input", + "type": "TAG", + "matches": [ + "input" + ] + }, + { + "value": ":nth-child(1)", + "type": "CHILD", + "matches": [ + "nth", + "child", + "1", + 0, + 1, + undefined, + "", + "1" + ] + }, + { + "value": ":button", + "type": "PSEUDO", + "matches": [ + "button", + undefined + ] + } + ], + [ + { + "value": "span", + "type": "TAG", + "matches": [ + "span" + ] + }, + { + "value": ":contains(\"Text\")", + "type": "PSEUDO", + "matches": [ + "contains", + "Text" + ] + }, + { + "value": " ~ ", + "type": "~" + }, + { + "value": "div", + "type": "TAG", + "matches": [ + "div" + ] + }, + { + "value": ":has(div:has(span))", + "type": "PSEUDO", + "matches": [ + "has", + "div:has(span)" + ] + }, + { + "value": ":not(.not-this.not-that > div)", + "type": "PSEUDO", + "matches": [ + "not", + ".not-this.not-that > div" + ] + } + ] + ]; + + assert.deepEqual( jQuery.find.tokenize( selector ), tokens, "Tokenization successful" ); +} ); -- 2.39.5