Fix #13596; #13722: .replaceWith consistency. Close gh-1216.

This commit is contained in:
Richard Gibson 2013-04-03 13:17:02 -04:00
parent 0c927172b8
commit bdc4f3ebbe
7 changed files with 108 additions and 77 deletions

View File

@ -126,23 +126,25 @@ jQuery.fn.extend({
}, },
append: function() { append: function() {
return this.domManip(arguments, true, function( elem ) { return this.domManip( arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
this.appendChild( elem ); var target = manipulationTarget( this, elem );
target.appendChild( elem );
} }
}); });
}, },
prepend: function() { prepend: function() {
return this.domManip(arguments, true, function( elem ) { return this.domManip( arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
this.insertBefore( elem, this.firstChild ); var target = manipulationTarget( this, elem );
target.insertBefore( elem, target.firstChild );
} }
}); });
}, },
before: function() { before: function() {
return this.domManip( arguments, false, function( elem ) { return this.domManip( arguments, function( elem ) {
if ( this.parentNode ) { if ( this.parentNode ) {
this.parentNode.insertBefore( elem, this ); this.parentNode.insertBefore( elem, this );
} }
@ -150,7 +152,7 @@ jQuery.fn.extend({
}, },
after: function() { after: function() {
return this.domManip( arguments, false, function( elem ) { return this.domManip( arguments, function( elem ) {
if ( this.parentNode ) { if ( this.parentNode ) {
this.parentNode.insertBefore( elem, this.nextSibling ); this.parentNode.insertBefore( elem, this.nextSibling );
} }
@ -257,33 +259,35 @@ jQuery.fn.extend({
}, null, value, arguments.length ); }, null, value, arguments.length );
}, },
replaceWith: function( value ) { replaceWith: function() {
var isFunc = jQuery.isFunction( value ); var
// Snapshot the DOM in case .domManip sweeps something relevant into its fragment
args = jQuery.map( this, function( elem ) {
return [ elem.nextSibling, elem.parentNode ];
}),
i = 0;
// Make sure that the elements are removed from the DOM before they are inserted // Make the changes, replacing each context element with the new content
// this can help fix replacing a parent with child elements this.domManip( arguments, function( elem ) {
if ( !isFunc && typeof value !== "string" ) { var next = args[ i++ ],
value = jQuery( value ).not( this ).detach(); parent = args[ i++ ];
}
return value !== "" ? if ( parent ) {
this.domManip( [ value ], true, function( elem ) { jQuery( this ).remove();
var next = this.nextSibling, parent.insertBefore( elem, next );
parent = this.parentNode; }
// Allow new content to include elements from the context set
}, true );
if ( parent ) { // Force removal if there was no new content (e.g., from empty arguments)
jQuery( this ).remove(); return i ? this : this.remove();
parent.insertBefore( elem, next );
}
}) :
this.remove();
}, },
detach: function( selector ) { detach: function( selector ) {
return this.remove( selector, true ); return this.remove( selector, true );
}, },
domManip: function( args, table, callback ) { domManip: function( args, callback, allowIntersection ) {
// Flatten any nested arrays // Flatten any nested arrays
args = core_concat.apply( [], args ); args = core_concat.apply( [], args );
@ -302,14 +306,14 @@ jQuery.fn.extend({
return this.each(function( index ) { return this.each(function( index ) {
var self = set.eq( index ); var self = set.eq( index );
if ( isFunction ) { if ( isFunction ) {
args[0] = value.call( this, index, table ? self.html() : undefined ); args[0] = value.call( this, index, self.html() );
} }
self.domManip( args, table, callback ); self.domManip( args, callback, allowIntersection );
}); });
} }
if ( l ) { if ( l ) {
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );
first = fragment.firstChild; first = fragment.firstChild;
if ( fragment.childNodes.length === 1 ) { if ( fragment.childNodes.length === 1 ) {
@ -317,7 +321,6 @@ jQuery.fn.extend({
} }
if ( first ) { if ( first ) {
table = table && jQuery.nodeName( first, "tr" );
scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
hasScripts = scripts.length; hasScripts = scripts.length;
@ -335,13 +338,7 @@ jQuery.fn.extend({
} }
} }
callback.call( callback.call( this[i], node, i );
table && jQuery.nodeName( this[i], "table" ) ?
findOrAppend( this[i], "tbody" ) :
this[i],
node,
i
);
} }
if ( hasScripts ) { if ( hasScripts ) {
@ -382,8 +379,15 @@ jQuery.fn.extend({
} }
}); });
function findOrAppend( elem, tag ) { // Support: IE<8
return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); // Manipulating tables requires a tbody
function manipulationTarget( elem, content ) {
return jQuery.nodeName( elem, "table" ) &&
jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ?
elem.getElementsByTagName("tbody")[0] ||
elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
elem;
} }
// Replace/restore the type attribute of script elements for safe DOM manipulation // Replace/restore the type attribute of script elements for safe DOM manipulation

@ -1 +1 @@
Subproject commit 2dca62f52cc86a56e36753ab52e1dffe3c257940 Subproject commit 26d2cef803e613fcab99d049c1f947340af968f5

View File

@ -1,3 +1,3 @@
qunit/ qunit/
data/badjson.js data/badjson.js
data/jquery-1.8.2.ajax_xhr.min.js data/jquery-1.9.1.ajax_xhr.min.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@
<!-- Includes --> <!-- Includes -->
<!-- Allows us to fetch submodule tests when using a no-ajax build --> <!-- Allows us to fetch submodule tests when using a no-ajax build -->
<script src="data/jquery-1.8.2.ajax_xhr.min.js"></script> <script src="data/jquery-1.9.1.ajax_xhr.min.js"></script>
<script src="data/testinit.js"></script> <script src="data/testinit.js"></script>

View File

@ -592,42 +592,35 @@ test( "append(Function) with incoming value", function() {
QUnit.reset(); QUnit.reset();
}); });
test( "replaceWith on XML document (#9960)", function() { test( "XML DOM manipulation (#9960)", function() {
expect( 1 ); expect( 5 );
var newNode, var
xmlDoc1 = jQuery.parseXML("<scxml xmlns='http://www.w3.org/2005/07/scxml' version='1.0'><state x='100' y='100' initial='actions' id='provisioning'></state><state x='100' y='100' id='error'></state><state x='100' y='100' id='finished' final='true'></state></scxml>"), xmlDoc1 = jQuery.parseXML("<scxml xmlns='http://www.w3.org/2005/07/scxml' version='1.0'><state x='100' y='100' initial='actions' id='provisioning'></state><state x='100' y='100' id='error'></state><state x='100' y='100' id='finished' final='true'></state></scxml>"),
xmlDoc2 = jQuery.parseXML("<scxml xmlns='http://www.w3.org/2005/07/scxml' version='1.0'><state id='provisioning3'></state></scxml>"), xmlDoc2 = jQuery.parseXML("<scxml xmlns='http://www.w3.org/2005/07/scxml' version='1.0'><state id='provisioning3'></state></scxml>"),
xml1 = jQuery( xmlDoc1 ), xml1 = jQuery( xmlDoc1 ),
xml2 = jQuery( xmlDoc2 ), xml2 = jQuery( xmlDoc2 ),
scxml1 = jQuery( "scxml", xml1 ), scxml1 = jQuery( "scxml", xml1 ),
scxml2 = jQuery( "scxml", xml2 ); scxml2 = jQuery( "scxml", xml2 ),
state = scxml2.find("state");
scxml1.replaceWith( scxml2 ); scxml1.append( state );
strictEqual( scxml1[0].lastChild, state[0], "append" );
newNode = jQuery( "scxml>state[id='provisioning3']", xml1 ); scxml1.prepend( state );
strictEqual( scxml1[0].firstChild, state[0], "prepend" );
equal( newNode.length, 1, "ReplaceWith not working on document nodes." ); scxml1.find("#finished").after( state );
strictEqual( scxml1[0].lastChild, state[0], "after" );
scxml1.find("#provisioning").before( state );
strictEqual( scxml1[0].firstChild, state[0], "before" );
scxml2.replaceWith( scxml1 );
deepEqual( jQuery( "state", xml2 ).get(), scxml1.find("state").get(), "replaceWith" );
}); });
// #12449
test( "replaceWith([]) where replacing element requires cloning", function () {
expect(2);
jQuery("#qunit-fixture").append(
"<div class='replaceable'></div><div class='replaceable'></div>"
);
// replacing set needs to be cloned so it can cover 3 replacements
jQuery("#qunit-fixture .replaceable").replaceWith(
jQuery("<span class='replaced'></span><span class='replaced'></span>")
);
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() { test( "append the same fragment with events (Bug #6997, 5566)", function() {
var element, clone, var element, clone,
@ -1141,7 +1134,7 @@ test( "insertAfter(String|Element|Array<Element>|jQuery)", function() {
function testReplaceWith( val ) { function testReplaceWith( val ) {
var tmp, y, child, child2, set, non_existent, $div, var tmp, y, child, child2, set, non_existent, $div,
expected = 23; expected = 26;
expect( expected ); expect( expected );
@ -1158,15 +1151,22 @@ function testReplaceWith( val ) {
equal( jQuery("#bar").text(),"Baz", "Replace element with text" ); equal( jQuery("#bar").text(),"Baz", "Replace element with text" );
ok( !jQuery("#baz")[ 0 ], "Verify that original element is gone, after element" ); ok( !jQuery("#baz")[ 0 ], "Verify that original element is gone, after element" );
jQuery("#bar").replaceWith( "<div id='yahoo'></div>", "...", "<div id='baz'></div>" );
deepEqual( jQuery("#yahoo, #baz").get(), q( "yahoo", "baz" ), "Replace element with multiple arguments (#13722)" );
strictEqual( jQuery("#yahoo")[0].nextSibling, jQuery("#baz")[0].previousSibling, "Argument order preserved" );
deepEqual( jQuery("#bar").get(), [], "Verify that original element is gone, after multiple arguments" );
jQuery("#google").replaceWith( val([ document.getElementById("first"), document.getElementById("mark") ]) ); jQuery("#google").replaceWith( val([ document.getElementById("first"), document.getElementById("mark") ]) );
ok( jQuery("#first")[ 0 ], "Replace element with array of elements" ); deepEqual( jQuery("#mark, #first").get(), q( "first", "mark" ), "Replace element with array of elements" );
ok( jQuery("#mark")[ 0 ], "Replace element with array of elements" );
ok( !jQuery("#google")[ 0 ], "Verify that original element is gone, after array of elements" ); ok( !jQuery("#google")[ 0 ], "Verify that original element is gone, after array of elements" );
jQuery("#groups").replaceWith( val(jQuery("#mark, #first")) ); jQuery("#groups").replaceWith( val(jQuery("#mark, #first")) );
ok( jQuery("#first")[ 0 ], "Replace element with set of elements" ); deepEqual( jQuery("#mark, #first").get(), q( "first", "mark" ), "Replace element with jQuery collection" );
ok( jQuery("#mark")[ 0 ], "Replace element with set of elements" ); ok( !jQuery("#groups")[ 0 ], "Verify that original element is gone, after jQuery collection" );
ok( !jQuery("#groups")[ 0 ], "Verify that original element is gone, after set of elements" );
jQuery("#mark, #first").replaceWith( val("<span class='replacement'></span><span class='replacement'></span>") );
equal( jQuery("#qunit-fixture .replacement").length, 4, "Replace multiple elements (#12449)" );
deepEqual( jQuery("#mark, #first").get(), [], "Verify that original elements are gone, after replace multiple" );
tmp = jQuery("<b>content</b>")[0]; tmp = jQuery("<b>content</b>")[0];
jQuery("#anchor1").contents().replaceWith( val(tmp) ); jQuery("#anchor1").contents().replaceWith( val(tmp) );
@ -1253,13 +1253,22 @@ test( "replaceWith(string) for more than one element", function() {
equal(jQuery("#foo p").length, 0, "verify that all the three original element have been replaced"); equal(jQuery("#foo p").length, 0, "verify that all the three original element have been replaced");
}); });
test( "replaceWith(\"\") (#13401)", 4, function() { test( "empty replaceWith (#13401; #13596)", 4, function() {
expect( 1 ); expect( 6 );
var div = jQuery("<div><p></p></div>"); var $el = jQuery("<div/>"),
tests = {
"empty string": "",
"empty array": [],
"empty collection": jQuery("#nonexistent")
};
div.children().replaceWith(""); jQuery.each( tests, function( label, input ) {
equal( div.html().toLowerCase(), "", "Replacing with empty string removes element" ); $el.html("<a/>").children().replaceWith( input );
strictEqual( $el.html(), "", "replaceWith(" + label + ")" );
$el.html("<b/>").children().replaceWith(function() { return input; });
strictEqual( $el.html(), "", "replaceWith(function returning " + label + ")" );
});
}); });
test( "replaceAll(String|Element|Array<Element>|jQuery)", function() { test( "replaceAll(String|Element|Array<Element>|jQuery)", function() {
@ -1311,6 +1320,21 @@ test( "append to multiple elements (#8070)", function() {
equal( selects[ 1 ].childNodes.length, 2, "Second select got two nodes" ); equal( selects[ 1 ].childNodes.length, 2, "Second select got two nodes" );
}); });
test( "table manipulation", function() {
expect( 2 );
var table = jQuery("<table style='font-size:16px'></table>").appendTo("#qunit-fixture").empty(),
height = table[0].offsetHeight;
table.append("<tr><td>DATA</td></tr>");
ok( table[0].offsetHeight - height >= 15, "appended rows are visible" );
table.empty();
height = table[0].offsetHeight;
table.prepend("<tr><td>DATA</td></tr>");
ok( table[0].offsetHeight - height >= 15, "prepended rows are visible" );
});
test( "clone()", function() { test( "clone()", function() {
expect( 45 ); expect( 45 );