Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<title>Node.moveBefore</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<!-- First test shared pre-insertion checks that work similarly for replaceChild
and moveBefore -->
<script>
var insertFunc = Node.prototype.moveBefore;
</script>
<script src="../../pre-insertion-validation-hierarchy.js"></script>
<script>
preInsertionValidateHierarchy("moveBefore");
test(function() {
// WebIDL: first argument.
assert_throws_js(TypeError, function() { document.body.moveBefore(null, null) })
assert_throws_js(TypeError, function() { document.body.moveBefore(null, document.body.firstChild) })
assert_throws_js(TypeError, function() { document.body.moveBefore({'a':'b'}, document.body.firstChild) })
}, "Calling moveBefore with a non-Node first argument must throw TypeError.")
test(function() {
// WebIDL: second argument.
assert_throws_js(TypeError, function() { document.body.moveBefore(document.createTextNode("child")) })
assert_throws_js(TypeError, function() { document.body.moveBefore(document.createTextNode("child"), {'a':'b'}) })
}, "Calling moveBefore with second argument missing, or other than Node, null, or undefined, must throw TypeError.")
test(() => {
assert_false("moveBefore" in document.doctype, "moveBefore() not on DocumentType");
assert_false("moveBefore" in document.createTextNode("text"), "moveBefore() not on TextNode");
assert_false("moveBefore" in new Comment("comment"), "moveBefore() not on CommentNode");
assert_false("moveBefore" in document.createProcessingInstruction("foo", "bar"), "moveBefore() not on ProcessingInstruction");
}, "moveBefore() method does not exist on non-ParentNode Nodes");
// Pre-move validity, step 1:
// "If either parent or node are not connected, then throw a
// "HierarchyRequestError" DOMException."
//
test(t => {
const connectedTarget = document.body.appendChild(document.createElement('div'));
const disconnectedDestination = document.createElement('div');
t.add_cleanup(() => connectedTarget.remove());
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
disconnectedDestination.moveBefore(connectedTarget, null);
});
}, "moveBefore() on disconnected parent throws a HierarchyRequestError");
test(t => {
const connectedDestination = document.body.appendChild(document.createElement('div'));
const disconnectedTarget = document.createElement('div');
t.add_cleanup(() => connectedDestination.remove());
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
connectedDestination.moveBefore(disconnectedTarget, null);
});
}, "moveBefore() with disconnected target node throws a HierarchyRequestError");
// Pre-move validity, step 2:
// "If parent’s shadow-including root is not the same as node’s shadow-including
// "root, then throw a "HierarchyRequestError" DOMException."
//
test(t => {
const iframe = document.createElement('iframe');
document.body.append(iframe);
const connectedCrossDocChild = iframe.contentDocument.createElement('div');
const connectedLocalParent = document.querySelector('div');
t.add_cleanup(() => iframe.remove());
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
connectedLocalParent.moveBefore(connectedCrossDocChild, null);
});
}, "moveBefore() on a cross-document target node throws a HierarchyRequestError");
// Pre-move validity, step 3:
// "If parent is not a Document, DocumentFragment, or Element node, then throw a
// "HierarchyRequestError" DOMException."
//
test(t => {
const iframe = document.body.appendChild(document.createElement('iframe'));
const innerBody = iframe.contentDocument.querySelector('body');
assert_throws_dom("HIERARCHY_REQUEST_ERR", iframe.contentWindow.DOMException, () => {
// Moving the body into the same place that it already is, which is a valid
// action in the normal case, when moving an Element directly under the
// document. This is not `moveBefore()`-specific behavior; it is consistent
// with traditional Document insertion rules, just like `insertBefore()`.
iframe.contentDocument.moveBefore(innerBody, null);
});
}, "moveBefore() into a Document throws a HierarchyRequestError");
test(t => {
const iframe = document.body.appendChild(document.createElement('iframe'));
const comment = iframe.contentDocument.createComment("comment");
iframe.contentDocument.body.append(comment);
iframe.contentDocument.moveBefore(comment, null);
assert_equals(comment.parentNode, iframe.contentDocument);
}, "moveBefore() CharacterData into a Document");
// Pre-move validity, step 4:
// "If node is a host-including inclusive ancestor of parent, then throw a
// "HierarchyRequestError" DOMException."
//
test(t => {
const parentDiv = document.body.appendChild(document.createElement('div'));
const childDiv = parentDiv.appendChild(document.createElement('div'));
t.add_cleanup(() => {
parentDiv.remove();
childDiv.remove();
});
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
parentDiv.moveBefore(parentDiv, null);
}, "parent moving itself");
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
childDiv.moveBefore(parentDiv, null);
}, "Moving parent into immediate child");
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
childDiv.moveBefore(document.body, null);
}, "Moving grandparent into grandchild");
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
document.body.moveBefore(document.documentElement, childDiv);
}, "Moving documentElement (<html>) into a deeper child");
}, "moveBefore() with node being an inclusive ancestor of parent throws a " +
"HierarchyRequestError");
// Pre-move validity, step 5:
// "If node is not an Element or a CharacterData node, then throw a
// "HierarchyRequestError" DOMException."
//
test(t => {
assert_true(document.doctype.isConnected);
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
document.body.moveBefore(document.doctype, null);
}, "DocumentType throws");
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
document.body.moveBefore(new DocumentFragment(), null);
}, "DocumentFragment throws");
const doc = document.implementation.createHTMLDocument("title");
assert_true(doc.isConnected);
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
document.body.moveBefore(doc, null);
});
}, "moveBefore() with a non-{Element, CharacterData} throws a HierarchyRequestError");
test(t => {
const text = new Text("child text");
document.body.prepend(text);
const childElement = document.createElement('p');
document.body.prepend(childElement);
t.add_cleanup(() => {
text.remove();
childElement.remove();
});
document.body.moveBefore(text, null);
assert_equals(document.body.lastChild, text);
document.body.moveBefore(childElement, null);
assert_equals(document.body.lastChild, childElement);
document.body.moveBefore(text, null);
assert_equals(document.body.lastChild, text);
}, "moveBefore with an Element or CharacterData succeeds");
test(t => {
const p = document.createElement('p');
p.textContent = "Some content";
document.body.prepend(p);
const text_node = p.firstChild;
// The Text node is *inside* the paragraph.
assert_equals(text_node.textContent, "Some content");
assert_not_equals(document.body.lastChild, text_node);
t.add_cleanup(() => {
text_node.remove();
p.remove();
});
document.body.moveBefore(p.firstChild, null);
assert_equals(document.body.lastChild, text_node);
}, "moveBefore on a paragraph's Text node child");
// Pre-move validity, step 6:
// "If child is non-null and its parent is not parent, then throw a
// "NotFoundError" DOMException."
//
test(t => {
const a = document.body.appendChild(document.createElement("div"));
const b = document.body.appendChild(document.createElement("div"));
const c = document.body.appendChild(document.createElement("div"));
t.add_cleanup(() => {
a.remove();
b.remove();
c.remove();
});
assert_throws_dom("NotFoundError", () => {
a.moveBefore(b, c);
});
}, "moveBefore with reference child whose parent is NOT the destination " +
"parent (context node) throws a NotFoundError.")
test(() => {
const a = document.body.appendChild(document.createElement("div"));
const b = document.createElement("div");
const c = document.createElement("div");
a.append(b);
a.append(c);
assert_array_equals(a.childNodes, [b, c]);
assert_equals(a.moveBefore(c, b), undefined, "moveBefore() returns undefined");
assert_array_equals(a.childNodes, [c, b]);
}, "moveBefore() returns undefined");
test(() => {
const a = document.body.appendChild(document.createElement("div"));
const b = document.createElement("div");
const c = document.createElement("div");
a.append(b);
a.append(c);
assert_array_equals(a.childNodes, [b, c]);
a.moveBefore(b, b);
assert_array_equals(a.childNodes, [b, c]);
a.moveBefore(c, c);
assert_array_equals(a.childNodes, [b, c]);
}, "Moving a node before itself should not move the node");
test(() => {
const disconnectedOrigin = document.createElement('div');
const disconnectedDestination = document.createElement('div');
const p = disconnectedOrigin.appendChild(document.createElement('p'));
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
disconnectedDestination.moveBefore(p, null);
});
}, "Moving a node from a disconnected container to a disconnected new parent " +
"without a shared ancestor throws a HIERARCHY_REQUEST_ERR");
test(() => {
const disconnectedOrigin = document.createElement('div');
const disconnectedDestination = disconnectedOrigin.appendChild(document.createElement('div'));
const p = disconnectedOrigin.appendChild(document.createElement('p'));
disconnectedDestination.moveBefore(p, null);
assert_equals(disconnectedDestination.firstChild, p, "<p> Was successfully moved");
}, "Moving a node from a disconnected container to a disconnected new parent in the same tree succeeds");
test(() => {
const disconnectedOrigin = document.createElement('div');
const disconnectedHost = disconnectedOrigin.appendChild(document.createElement('div'));
const p = disconnectedOrigin.appendChild(document.createElement('p'));
const shadow = disconnectedHost.attachShadow({mode: "closed"});
const disconnectedDestination = shadow.appendChild(document.createElement('div'));
disconnectedDestination.moveBefore(p, null);
assert_equals(disconnectedDestination.firstChild, p, "<p> Was successfully moved");
}, "Moving a node from a disconnected container to a disconnected new parent in the same tree succeeds," +
"also across shadow-roots");
test(() => {
const disconnectedOrigin = document.createElement('div');
const connectedDestination = document.body.appendChild(document.createElement('div'));
const p = disconnectedOrigin.appendChild(document.createElement('p'));
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => connectedDestination.moveBefore(p, null));
}, "Moving a node from disconnected->connected throws a HIERARCHY_REQUEST_ERR");
test(() => {
const connectedOrigin = document.body.appendChild(document.createElement('div'));
const disconnectedDestination = document.createElement('div');
const p = connectedOrigin.appendChild(document.createElement('p'));
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => disconnectedDestination.moveBefore(p, null));
}, "Moving a node from connected->disconnected throws a HIERARCHY_REQUEST_ERR");
promise_test(async t => {
let reactions = [];
const element_name = `ce-${performance.now()}`;
customElements.define(element_name,
class MockCustomElement extends HTMLElement {
connectedMoveCallback() { reactions.push("connectedMove"); }
connectedCallback() { reactions.push("connected"); }
disconnectedCallback() { reactions.push("disconnected"); }
});
const oldParent = document.createElement('div');
const newParent = oldParent.appendChild(document.createElement('div'));
const element = oldParent.appendChild(document.createElement(element_name));
t.add_cleanup(() => {
element.remove();
newParent.remove();
oldParent.remove();
});
// Wait a microtask to let any custom element reactions run (should be none,
// since the initial parent is disconnected).
await Promise.resolve();
newParent.moveBefore(element, null);
await Promise.resolve();
assert_array_equals(reactions, []);
}, "No custom element callbacks are run during disconnected moveBefore()");
</script>