From b734666f4d2e9a92b8ebb99db5b05bd4c82e71f2 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sun, 27 Jan 2013 13:24:50 -0500 Subject: [PATCH] Fix #13265 #13332: traversing methods with text nodes. Close gh-1145. --- src/traversing.js | 59 ++++++++++++++++++++++++----------------- test/index.html | 2 +- test/unit/traversing.js | 44 +++++++++++++++++++++--------- 3 files changed, 68 insertions(+), 37 deletions(-) diff --git a/src/traversing.js b/src/traversing.js index ff590504f..615de1f8a 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -80,14 +80,18 @@ jQuery.fn.extend({ 0; for ( ; i < l; i++ ) { - cur = this[ i ]; + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : - while ( cur && cur.ownerDocument && cur !== context ) { - if ( pos ? pos.index( cur ) > -1 : jQuery.find.matchesSelector( cur, selectors ) ) { - matched.push( cur ); + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + cur = matched.push( cur ); break; } - cur = cur.parentElement; } } @@ -134,42 +138,46 @@ jQuery.fn.extend({ jQuery.fn.andSelf = jQuery.fn.addBack; +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + + return cur; +} + jQuery.each({ parent: function( elem ) { - return elem.parentElement; + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { - return jQuery.dir( elem, "parentElement" ); + return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentElement", until ); + return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { - return elem.nextElementSibling; + return sibling( elem, "nextSibling" ); }, prev: function( elem ) { - return elem.previousElementSibling; + return sibling( elem, "previousSibling" ); }, nextAll: function( elem ) { - return jQuery.dir( elem, "nextElementSibling" ); + return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { - return jQuery.dir( elem, "previousElementSibling" ); + return jQuery.dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextElementSibling", until ); + return jQuery.dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousElementSibling", until ); + return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { - var children = elem.children; - - // documentFragment or document does not have children property - return children ? jQuery.merge( [], children ) : jQuery.sibling( elem.firstChild ); + return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? @@ -214,14 +222,17 @@ jQuery.extend({ }, dir: function( elem, dir, until ) { - var cur = elem[ dir ], - matched = []; + var matched = [], + truncate = until !== undefined; - while ( cur && ( !until || !jQuery( cur ).is( until ) ) ) { - matched.push( cur ); - cur = cur[ dir ]; + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } } - return matched; }, diff --git a/test/index.html b/test/index.html index 62ccfe12e..14e686034 100644 --- a/test/index.html +++ b/test/index.html @@ -247,7 +247,7 @@ Z -
hi there
+
hi there
diff --git a/test/unit/traversing.js b/test/unit/traversing.js index 72301ec08..087abdc37 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -279,7 +279,9 @@ test("filter() with positional selectors", function() { }); test("closest()", function() { - expect( 14 ); + expect( 15 ); + + var jq; deepEqual( jQuery("body").closest("body").get(), q("body"), "closest(body)" ); deepEqual( jQuery("body").closest("html").get(), q("html"), "closest(html)" ); @@ -290,7 +292,7 @@ test("closest()", function() { deepEqual( jQuery("#qunit-fixture div").closest("body:first div:last").get(), q("fx-tests"), "closest(body:first div:last)" ); // Test .closest() limited by the context - var jq = jQuery("#nothiddendivchild"); + jq = jQuery("#nothiddendivchild"); deepEqual( jq.closest("html", document.body).get(), [], "Context limited." ); deepEqual( jq.closest("body", document.body).get(), [], "Context limited." ); deepEqual( jq.closest("#nothiddendiv", document.body).get(), q("nothiddendiv"), "Context not reached." ); @@ -306,6 +308,9 @@ test("closest()", function() { equal( jQuery("
text
").closest("[lang]").length, 0, "Disconnected nodes with text and non-existent attribute selector" ); ok( !jQuery(document).closest("#foo").length, "Calling closest on a document fails silently" ); + + jq = jQuery("
text
"); + deepEqual( jq.contents().closest("*").get(), jq.get(), "Text node input (#13332)" ); }); test("closest(jQuery)", function() { @@ -430,8 +435,9 @@ test("addBack()", function() { }); test("siblings([String])", function() { - expect(7); + expect(8); deepEqual( jQuery("#en").siblings().get(), q("sndp", "sap"), "Check for siblings" ); + deepEqual( jQuery("#nonnodes").contents().eq(1).siblings().get(), q("nonnodesElement"), "Check for text node siblings" ); deepEqual( jQuery("#sndp").siblings(":has(code)").get(), q("sap"), "Check for filtered siblings (has code child element)" ); deepEqual( jQuery("#sndp").siblings(":has(a)").get(), q("en", "sap"), "Check for filtered siblings (has anchor child element)" ); deepEqual( jQuery("#foo").siblings("form, b").get(), q("form", "floatTest", "lengthtest", "name-tests", "testForm"), "Check for multiple filters" ); @@ -449,17 +455,24 @@ test("children([String])", function() { }); test("parent([String])", function() { - expect(5); + expect(6); + + var $el; + equal( jQuery("#groups").parent()[0].id, "ap", "Simple parent check" ); equal( jQuery("#groups").parent("p")[0].id, "ap", "Filtered parent check" ); equal( jQuery("#groups").parent("div").length, 0, "Filtered parent check, no match" ); equal( jQuery("#groups").parent("div, p")[0].id, "ap", "Check for multiple filters" ); deepEqual( jQuery("#en, #sndp").parent().get(), q("foo"), "Check for unique results from parent" ); + + $el = jQuery("
text
"); + deepEqual( $el.contents().parent().get(), $el.get(), "Check for parent of text node (#13265)" ); }); test("parents([String])", function() { - expect(5); + expect(6); equal( jQuery("#groups").parents()[0].id, "ap", "Simple parents check" ); + deepEqual( jQuery("#nonnodes").contents().eq(1).parents().eq(0).get(), q("nonnodes"), "Text node parents check" ); equal( jQuery("#groups").parents("p")[0].id, "ap", "Filtered parents check" ); equal( jQuery("#groups").parents("div")[0].id, "qunit-fixture", "Filtered parents check2" ); deepEqual( jQuery("#groups").parents("p, div").get(), q("ap", "qunit-fixture"), "Check for multiple filters" ); @@ -467,7 +480,7 @@ test("parents([String])", function() { }); test("parentsUntil([String])", function() { - expect(9); + expect(10); var parents = jQuery("#groups").parents(); @@ -475,6 +488,7 @@ test("parentsUntil([String])", function() { deepEqual( jQuery("#groups").parentsUntil(".foo").get(), parents.get(), "parentsUntil with invalid selector (nextAll)" ); deepEqual( jQuery("#groups").parentsUntil("#html").get(), parents.not(":last").get(), "Simple parentsUntil check" ); equal( jQuery("#groups").parentsUntil("#ap").length, 0, "Simple parentsUntil check" ); + deepEqual( jQuery("#nonnodes").contents().eq(1).parentsUntil("#html").eq(0).get(), q("nonnodes"), "Text node parentsUntil check" ); deepEqual( jQuery("#groups").parentsUntil("#html, #body").get(), parents.slice( 0, 3 ).get(), "Less simple parentsUntil check" ); deepEqual( jQuery("#groups").parentsUntil("#html", "div").get(), jQuery("#qunit-fixture").get(), "Filtered parentsUntil check" ); deepEqual( jQuery("#groups").parentsUntil("#html", "p,div,dl").get(), parents.slice( 0, 3 ).get(), "Multiple-filtered parentsUntil check" ); @@ -483,8 +497,9 @@ test("parentsUntil([String])", function() { }); test("next([String])", function() { - expect(5); + expect(6); equal( jQuery("#ap").next()[0].id, "foo", "Simple next check" ); + equal( jQuery("
text
").contents().eq(0).next().attr("id"), "element", "Text node next check" ); equal( jQuery("#ap").next("div")[0].id, "foo", "Filtered next check" ); equal( jQuery("#ap").next("p").length, 0, "Filtered next check, no match" ); equal( jQuery("#ap").next("div, p")[0].id, "foo", "Multiple filters" ); @@ -492,41 +507,45 @@ test("next([String])", function() { }); test("prev([String])", function() { - expect(4); + expect(5); equal( jQuery("#foo").prev()[0].id, "ap", "Simple prev check" ); + deepEqual( jQuery("#nonnodes").contents().eq(1).prev().get(), q("nonnodesElement"), "Text node prev check" ); equal( jQuery("#foo").prev("p")[0].id, "ap", "Filtered prev check" ); equal( jQuery("#foo").prev("div").length, 0, "Filtered prev check, no match" ); equal( jQuery("#foo").prev("p, div")[0].id, "ap", "Multiple filters" ); }); test("nextAll([String])", function() { - expect(4); + expect(5); var elems = jQuery("#form").children(); deepEqual( jQuery("#label-for").nextAll().get(), elems.not(":first").get(), "Simple nextAll check" ); + equal( jQuery("
text
").contents().eq(0).nextAll().attr("id"), "element", "Text node nextAll check" ); deepEqual( jQuery("#label-for").nextAll("input").get(), elems.not(":first").filter("input").get(), "Filtered nextAll check" ); deepEqual( jQuery("#label-for").nextAll("input,select").get(), elems.not(":first").filter("input,select").get(), "Multiple-filtered nextAll check" ); deepEqual( jQuery("#label-for, #hidden1").nextAll("input,select").get(), elems.not(":first").filter("input,select").get(), "Multi-source, multiple-filtered nextAll check" ); }); test("prevAll([String])", function() { - expect(4); + expect(5); var elems = jQuery( jQuery("#form").children().slice(0, 12).get().reverse() ); deepEqual( jQuery("#area1").prevAll().get(), elems.get(), "Simple prevAll check" ); + deepEqual( jQuery("#nonnodes").contents().eq(1).prevAll().get(), q("nonnodesElement"), "Text node prevAll check" ); deepEqual( jQuery("#area1").prevAll("input").get(), elems.filter("input").get(), "Filtered prevAll check" ); deepEqual( jQuery("#area1").prevAll("input,select").get(), elems.filter("input,select").get(), "Multiple-filtered prevAll check" ); deepEqual( jQuery("#area1, #hidden1").prevAll("input,select").get(), elems.filter("input,select").get(), "Multi-source, multiple-filtered prevAll check" ); }); test("nextUntil([String])", function() { - expect(11); + expect(12); var elems = jQuery("#form").children().slice( 2, 12 ); deepEqual( jQuery("#text1").nextUntil().get(), jQuery("#text1").nextAll().get(), "nextUntil with no selector (nextAll)" ); + equal( jQuery("
text
").contents().eq(0).nextUntil().attr("id"), "element", "Text node nextUntil with no selector (nextAll)" ); deepEqual( jQuery("#text1").nextUntil(".foo").get(), jQuery("#text1").nextAll().get(), "nextUntil with invalid selector (nextAll)" ); deepEqual( jQuery("#text1").nextUntil("#area1").get(), elems.get(), "Simple nextUntil check" ); equal( jQuery("#text1").nextUntil("#text2").length, 0, "Simple nextUntil check" ); @@ -541,11 +560,12 @@ test("nextUntil([String])", function() { }); test("prevUntil([String])", function() { - expect(10); + expect(11); var elems = jQuery("#area1").prevAll(); deepEqual( jQuery("#area1").prevUntil().get(), elems.get(), "prevUntil with no selector (prevAll)" ); + deepEqual( jQuery("#nonnodes").contents().eq(1).prevUntil().get(), q("nonnodesElement"), "Text node prevUntil with no selector (prevAll)" ); deepEqual( jQuery("#area1").prevUntil(".foo").get(), elems.get(), "prevUntil with invalid selector (prevAll)" ); deepEqual( jQuery("#area1").prevUntil("label").get(), elems.not(":last").get(), "Simple prevUntil check" ); equal( jQuery("#area1").prevUntil("#button").length, 0, "Simple prevUntil check" ); -- 2.39.5