From 551c2c9f4ac776b6d53600c452ad40a4b4d6670b Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 4 Dec 2012 21:50:22 -0500 Subject: [PATCH] Fixes #12449. make replaceWith() clone elements where required. Closes gh-920 --- src/manipulation.js | 36 ++++--- test/unit/manipulation.js | 205 +++++++++++++++++++++----------------- 2 files changed, 128 insertions(+), 113 deletions(-) diff --git a/src/manipulation.js b/src/manipulation.js index 6cde99404..f07d6bbfb 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -258,32 +258,29 @@ jQuery.fn.extend({ value = jQuery( value ).detach(); } - this.each( function( i ) { - var next = this.nextSibling, - parent = this.parentNode, - // HTML argument replaced by "this" element - // 1. There were no supporting tests - // 2. There was no internal code relying on this - // 3. There was no documentation of an html argument - val = !isFunc ? value : value.call( this, i, this ); + return this.domManip( [ value ], true, function( elem, i ) { + var next, parent; if ( isDisconnected( this ) ) { - // for disconnected elements, we replace with the new content in the set. We use - // clone here to ensure that each replaced instance is unique - self[ i ] = jQuery( val ).clone()[ 0 ]; + // for disconnected elements, we simply replace + // with the new content in the set + self[ i ] = elem; return; } - jQuery( this ).remove(); + if ( this.nodeType === 1 || this.nodeType === 11 ) { + next = this.nextSibling; + parent = this.parentNode; - if ( next ) { - jQuery( next ).before( val ); - } else { - jQuery( parent ).append( val ); + jQuery( this ).remove(); + + if ( next ) { + next.parentNode.insertBefore( elem, next ); + } else { + parent.appendChild( elem ); + } } }); - - return this; }, detach: function( selector ) { @@ -344,7 +341,8 @@ jQuery.fn.extend({ table && jQuery.nodeName( this[i], "table" ) ? findOrAppend( this[i], "tbody" ) : this[i], - node + node, + i ); } diff --git a/test/unit/manipulation.js b/test/unit/manipulation.js index 788c75bf0..7e48fb68e 100644 --- a/test/unit/manipulation.js +++ b/test/unit/manipulation.js @@ -30,9 +30,9 @@ var manipulationFunctionReturningObj = function( value ) { */ test( "text()", function() { - + expect( 5 ); - + var expected, frag, $newLineTest; expected = "This link has class=\"blog\": Simon Willison's Weblog"; @@ -56,16 +56,16 @@ test( "text()", function() { }); test( "text(undefined)", function() { - + expect( 1 ); - + equal( jQuery("#foo").text("Hello cruel world!"); @@ -90,7 +90,7 @@ test( "text(Function)", function() { }); test( "text(Function) with incoming value", function() { - + expect( 2 ); var old = "This link has class=\"blog\": Simon Willison's Weblog"; @@ -104,14 +104,14 @@ test( "text(Function) with incoming value", function() { }); var testWrap = function( val ) { - + expect( 19 ); - + var defaultText, result, j, i, cacheLength; defaultText = "Try them out:", result = jQuery("#first").wrap( val("
") ).text(); - + equal( defaultText, result, "Check for wrapping of on-the-fly html" ); ok( jQuery("#first").parent().parent().is(".red"), "Check if wrapper has class 'red'" ); @@ -123,7 +123,7 @@ var testWrap = function( val ) { QUnit.reset(); jQuery("#check1").click(function() { var checkbox = this; - + ok( checkbox.checked, "Checkbox's state is erased after wrap() action, see #769" ); jQuery( checkbox ).wrap( val("") ); ok( checkbox.checked, "Checkbox's state is erased after wrap() action, see #769" ); @@ -221,15 +221,15 @@ test( "wrap(String) consecutive elements (#10177)", function() { }); var testWrapAll = function( val ) { - + expect( 8 ); - + var prev, p, result; - + prev = jQuery("#firstp")[ 0 ].previousSibling; p = jQuery("#firstp,#first")[ 0 ].parentNode; result = jQuery("#firstp,#first").wrapAll( val("
") ); - + equal( result.parent().length, 1, "Check for wrapping of on-the-fly html" ); ok( jQuery("#first").parent().parent().is(".red"), "Check if wrapper has class 'red'" ); ok( jQuery("#firstp").parent().parent().is(".red"), "Check if wrapper has class 'red'" ); @@ -240,7 +240,7 @@ var testWrapAll = function( val ) { prev = jQuery("#firstp")[ 0 ].previousSibling; p = jQuery("#first")[ 0 ].parentNode; result = jQuery("#firstp,#first").wrapAll( val(document.getElementById("empty")) ); - + equal( jQuery("#first").parent()[ 0 ], jQuery("#firstp").parent()[ 0 ], "Same Parent" ); equal( jQuery("#first").parent()[ 0 ].previousSibling, prev, "Correct Previous Sibling" ); equal( jQuery("#first").parent()[ 0 ].parentNode, p, "Correct Parent" ); @@ -251,14 +251,14 @@ test( "wrapAll(String|Element)", function() { }); var testWrapInner = function( val ) { - + expect( 11 ); - + var num, result; num = jQuery("#first").children().length; result = jQuery("#first").wrapInner( val("
") ); - + equal( jQuery("#first").children().length, 1, "Only one child" ); ok( jQuery("#first").children().is(".red"), "Verify Right Element" ); equal( jQuery("#first").children().children().children().length, num, "Verify Elements Intact" ); @@ -292,7 +292,7 @@ test( "wrapInner(Function)", function() { }); test( "unwrap()", function() { - + expect( 9 ); jQuery("body").append(" "); @@ -394,9 +394,9 @@ var testAppendForObject = function( valueObj, isFragment ) { }; var testAppend = function( valueObj ) { - + expect( 59 ); - + testAppendForObject( valueObj, false ); testAppendForObject( valueObj, true ); @@ -502,7 +502,7 @@ test( "append(Function)", function() { }); test( "append(param) to object, see #11280", function() { - + expect( 5 ); var object = jQuery( document.createElement("object") ).appendTo( document.body ); @@ -519,7 +519,7 @@ test( "append(param) to object, see #11280", function() { }); test( "append(Function) with incoming value", function() { - + expect( 12 ); var defaultText, result, select, old, expected; @@ -584,7 +584,7 @@ test( "append(Function) with incoming value", function() { }); test( "replaceWith on XML document (#9960)", function() { - + expect( 1 ); var newNode, @@ -602,13 +602,30 @@ test( "replaceWith on XML document (#9960)", function() { equal( newNode.length, 1, "ReplaceWith not working on document nodes." ); }); +// #12449 +test( "replaceWith([]) where replacing element requires cloning", function () { + expect(2); + jQuery("#qunit-fixture").append( + "
" + ); + // replacing set needs to be cloned so it can cover 3 replacements + jQuery("#qunit-fixture .replaceable").replaceWith( + jQuery("") + ); + equal( jQuery("#qunit-fixture").find(".replaceable").length, 0, + "Make sure replaced elements were removed" ); + equal( jQuery("#qunit-fixture").find(".replaced").length, 4, + "Make sure replacing elements were cloned" ); +}); + + test( "append the same fragment with events (Bug #6997, 5566)", function() { - + expect( 2 + ( doExtra ? 1 : 0 ) ); var element, clone, doExtra = !jQuery.support.noCloneEvent && document["fireEvent"]; - + stop(); // This patch modified the way that cloning occurs in IE; we need to make sure that @@ -644,7 +661,7 @@ test( "append the same fragment with events (Bug #6997, 5566)", function() { }); test( "append HTML5 sectioning elements (Bug #6485)", function() { - + expect( 2 ); var article, aside; @@ -659,7 +676,7 @@ test( "append HTML5 sectioning elements (Bug #6485)", function() { }); test( "jQuery.clean, #12392", function() { - + expect( 6 ); var elems = jQuery.clean( [ "
test div
", "

test p

" ] ); @@ -678,7 +695,7 @@ test( "jQuery.clean, #12392", function() { if ( jQuery.css ) { test( "HTML5 Elements inherit styles from style rules (Bug #10501)", function() { - + expect( 1 ); jQuery("#qunit-fixture").append("
"); @@ -690,7 +707,7 @@ if ( jQuery.css ) { } test( "html(String) with HTML5 (Bug #6485)", function() { - + expect( 2 ); jQuery("#qunit-fixture").html("
"); @@ -699,7 +716,7 @@ test( "html(String) with HTML5 (Bug #6485)", function() { }); test( "IE8 serialization bug", function() { - + expect( 2 ); var wrapper = jQuery("
"); @@ -711,7 +728,7 @@ test( "IE8 serialization bug", function() { }); test( "html() object element #10324", function() { - + expect( 1 ); var object = jQuery("?").appendTo("#qunit-fixture"), @@ -721,7 +738,7 @@ test( "html() object element #10324", function() { }); test( "append(xml)", function() { - + expect( 1 ); var xmlDoc, xml1, xml2; @@ -875,7 +892,7 @@ test( "prepend(Function)", function() { }); test( "prepend(Function) with incoming value", function() { - + expect( 10 ); var defaultText, old, result, expected; @@ -931,9 +948,9 @@ test( "prepend(Function) with incoming value", function() { }); test( "prependTo(String|Element|Array|jQuery)", function() { - + expect( 6 ); - + var defaultText, expected; defaultText = "Try them out:"; @@ -964,9 +981,9 @@ test( "prependTo(String|Element|Array|jQuery)", function() { }); var testBefore = function( val ) { - + expect( 7 ); - + var expected, set; expected = "This is a normal link: bugaYahoo"; @@ -1007,7 +1024,7 @@ test( "before(Function)", function() { }); test( "before and after w/ empty object (#10812)", function() { - + expect( 1 ); var res; @@ -1017,9 +1034,9 @@ test( "before and after w/ empty object (#10812)", function() { }); test( "before and after on disconnected node (#10517)", function() { - + expect( 6 ); - + var expectedBefore = "This is a normal link: bugaYahoo", expectedAfter = "This is a normal link: Yahoobuga"; @@ -1044,11 +1061,11 @@ test( "before and after on disconnected node (#10517)", function() { }); test( "insertBefore(String|Element|Array|jQuery)", function() { - + expect( 4 ); - + var expected; - + expected = "This is a normal link: bugaYahoo"; jQuery("buga").insertBefore("#yahoo"); equal( jQuery("#en").text(), expected, "Insert String before" ); @@ -1070,11 +1087,11 @@ test( "insertBefore(String|Element|Array|jQuery)", function() { }); var testAfter = function( val ) { - + expect( 7 ); - + var set, expected; - + expected = "This is a normal link: Yahoobuga"; jQuery("#yahoo").after( val("buga") ); equal( jQuery("#en").text(), expected, "Insert String after" ); @@ -1113,9 +1130,9 @@ test( "after(Function)", function() { }); test( "insertAfter(String|Element|Array|jQuery)", function() { - + expect( 4 ) ; - + var expected; expected = "This is a normal link: Yahoobuga"; @@ -1139,9 +1156,9 @@ test( "insertAfter(String|Element|Array|jQuery)", function() { }); var testReplaceWith = function( val ) { - + expect( 22 ); - + var tmp, y, child, child2, set, non_existant, $div; jQuery("#yahoo").replaceWith(val( "buga" )); @@ -1261,7 +1278,7 @@ test( "replaceWith(Function)", function() { }); test( "replaceWith(string) for more than one element", function() { - + expect( 3 ); equal( jQuery("#foo p").length, 3, "ensuring that test data has not changed" ); @@ -1272,12 +1289,12 @@ test( "replaceWith(string) for more than one element", function() { }); test( "replaceWith(string) for collection with disconnected element", function() { - + expect( 18 ); var testSet, newSet, elem = jQuery("
"); - + QUnit.reset(); testSet = jQuery("#foo p").add( elem ); @@ -1347,7 +1364,7 @@ test( "jQuery.clone() (#8017)", function() { }); test( "append to multiple elements (#8070)", function() { - + expect( 2 ); var selects = jQuery("").appendTo("#qunit-fixture"); @@ -1358,9 +1375,9 @@ test( "append to multiple elements (#8070)", function() { }); test( "clone()", function() { - + expect( 45 ); - + var div, clone, form, body; equal( jQuery("#en").text(), "This is a normal link: Yahoo", "Assert text for #en" ); @@ -1486,12 +1503,12 @@ test( "clone()", function() { }); test( "clone(script type=non-javascript) (#11359)", function() { - + expect( 3 ); - + var src = jQuery(""), dest = src.clone(); - + equal( dest[ 0 ].text, "Lorem ipsum dolor sit amet", "Cloning preserves script text" ); equal( dest.last().html(), src.last().html(), "Cloning preserves nested script text" ); ok( /^\s*consectetur adipiscing elit<\/scr.pt>\s*$/i.test( dest.last().html() ), "Cloning preserves nested script text" ); @@ -1499,9 +1516,9 @@ test( "clone(script type=non-javascript) (#11359)", function() { }); test( "clone(form element) (Bug #3879, #6655)", function() { - + expect( 5 ); - + var clone, element = jQuery(""); @@ -1526,9 +1543,9 @@ test( "clone(form element) (Bug #3879, #6655)", function() { }); test( "clone(multiple selected options) (Bug #8129)", function() { - + expect( 1 ); - + var element = jQuery(""); equal( element.clone().find("option:selected").length, element.find("option:selected").length, "Multiple selected options cloned correctly" ); @@ -1536,14 +1553,14 @@ test( "clone(multiple selected options) (Bug #8129)", function() { }); test( "clone() on XML nodes", function() { - + expect( 2 ); - + var xml = createDashboardXML(), root = jQuery(xml.documentElement).clone(), origTab = jQuery("tab", xml).eq( 0 ), cloneTab = jQuery("tab", root).eq( 0 ); - + origTab.text("origval"); cloneTab.text("cloneval"); equal( origTab.text(), "origval", "Check original XML node was correctly set" ); @@ -1551,7 +1568,7 @@ test( "clone() on XML nodes", function() { }); test( "clone() on local XML nodes with html5 nodename", function() { - + expect( 2 ); var $xmlDoc = jQuery( jQuery.parseXML( "" ) ), @@ -1562,16 +1579,16 @@ test( "clone() on local XML nodes with html5 nodename", function() { }); test( "html(undefined)", function() { - + expect( 1 ); - + equal( jQuery("#foo").html("test").html(undefined).html().toLowerCase(), "test", ".html(undefined) is chainable (#5571)" ); }); test( "html() on empty set", function() { - + expect( 1 ); - + strictEqual( jQuery().html(), undefined, ".html() returns undefined for empty sets (#11962)" ); }); @@ -1696,7 +1713,7 @@ test( "html(Function)", function() { }); test( "html(Function) with incoming value", function() { - + expect( 18 ); var els, actualhtml, pass, j, $div, $div2, insert; @@ -1770,9 +1787,9 @@ test( "html(Function) with incoming value", function() { }); test( "clone()/html() don't expose jQuery/Sizzle expandos (#12858)", function() { - + expect( 2 ); - + var $content = jQuery("
text
").appendTo("#qunit-fixture"), expected = /^text<\/i><\/b>$/i; @@ -1853,9 +1870,9 @@ test( "detach() event cleaning ", 1, function() { }); test("empty()", function() { - + expect( 3 ); - + equal( jQuery("#ap").children().empty().text().length, 0, "Check text is removed" ); equal( jQuery("#ap").children().length, 4, "Check elements are not removed" ); @@ -1866,7 +1883,7 @@ test("empty()", function() { }); test( "jQuery.cleanData", function() { - + expect( 14 ); var type, pos, div, child; @@ -1944,7 +1961,7 @@ test( "jQuery.cleanData", function() { }); test( "jQuery.buildFragment - no plain-text caching (Bug #6779)", function() { - + expect( 1 ); // DOM manipulation fails if added text matches an Object method @@ -1990,7 +2007,7 @@ test( "jQuery.html - execute scripts escaped with html comment or CDATA (#9221)" }); test( "jQuery.buildFragment - plain objects are not a document #8950", function() { - + expect( 1 ); try { @@ -2000,7 +2017,7 @@ test( "jQuery.buildFragment - plain objects are not a document #8950", function( }); test( "jQuery.clone - no exceptions for object elements #9587", function() { - + expect( 1 ); try { @@ -2012,7 +2029,7 @@ test( "jQuery.clone - no exceptions for object elements #9587", function() { }); test( "jQuery() & wrap[Inner/All]() handle unknown elems (#10667)", function() { - + expect( 2 ); var $wraptarget = jQuery( "
Target
" ).appendTo( "#qunit-fixture" ), @@ -2025,7 +2042,7 @@ test( "jQuery() & wrap[Inner/All]() handle unknown elems (#10667)", functio }); test( "Cloned, detached HTML5 elems (#10667,10670)", function() { - + expect( 7 ); var $clone, @@ -2098,7 +2115,7 @@ test( "Cloned, detached HTML5 elems (#10667,10670)", function() { }); test( "Guard against exceptions when clearing safeChildNodes", function() { - + expect( 1 ); var div; @@ -2111,7 +2128,7 @@ test( "Guard against exceptions when clearing safeChildNodes", function() { }); test( "Ensure oldIE creates a new set on appendTo (#8894)", function() { - + expect( 5 ); strictEqual( jQuery("
").clone().addClass("test").appendTo("
").end().hasClass("test"), false, "Check jQuery.fn.appendTo after jQuery.clone" ); @@ -2122,7 +2139,7 @@ test( "Ensure oldIE creates a new set on appendTo (#8894)", function() { }); test( "html() - script exceptions bubble (#11743)", function() { - + expect( 2 ); raises(function() { @@ -2137,7 +2154,7 @@ test( "html() - script exceptions bubble (#11743)", function() { }); test( "checked state is cloned with clone()", function() { - + expect( 2 ); var elem = jQuery.parseHTML("")[ 0 ]; @@ -2150,7 +2167,7 @@ test( "checked state is cloned with clone()", function() { }); test( "Clearing a Cloned Element's Style Shouldn't Clear the Original Element's Style (#8908)", function() { - + expect( 16 ); var baseUrl = document.location.href.replace( /([^\/]*)$/, "" ), @@ -2217,12 +2234,12 @@ test( "Clearing a Cloned Element's Style Shouldn't Clear the Original Element's }); test( "manipulate mixed jQuery and text (#12384, #12346)", function() { - + expect( 2 ); var div = jQuery("
a
").append( " ", jQuery("b"), " ", jQuery("c") ), nbsp = String.fromCharCode( 160 ); - + equal( div.text(), "a" + nbsp + "b" + nbsp+ "c", "Appending mixed jQuery with text nodes" ); div = jQuery("
") @@ -2239,7 +2256,7 @@ testIframeWithCallback( "buildFragment works even if document[0] is iframe's win }); test( "script evaluation (#11795)", function() { - + expect( 11 ); var scriptsIn, scriptsOut, @@ -2284,7 +2301,7 @@ test( "script evaluation (#11795)", function() { }); test( "wrapping scripts (#10470)", function() { - + expect( 2 ); var script = document.createElement("script"); @@ -2295,4 +2312,4 @@ test( "wrapping scripts (#10470)", function() { jQuery("#qunit-fixture script").wrap(""); strictEqual( script.parentNode, jQuery("#qunit-fixture > b")[ 0 ], "correctly wrapped" ); jQuery( script ).remove(); -}); \ No newline at end of file +}); -- 2.39.5