summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/dom/ranges/Range-deleteContents.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/dom/ranges/Range-deleteContents.html')
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-deleteContents.html337
1 files changed, 337 insertions, 0 deletions
diff --git a/testing/web-platform/tests/dom/ranges/Range-deleteContents.html b/testing/web-platform/tests/dom/ranges/Range-deleteContents.html
new file mode 100644
index 0000000000..40dc400125
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-deleteContents.html
@@ -0,0 +1,337 @@
+<!doctype html>
+<title>Range.deleteContents() tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+<p>To debug test failures, add a query parameter "subtest" with the test id (like
+"?subtest=5"). Only that test will be run. Then you can look at the resulting
+iframe in the DOM.
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+testDiv.parentNode.removeChild(testDiv);
+
+var actualIframe = document.createElement("iframe");
+actualIframe.style.display = "none";
+document.body.appendChild(actualIframe);
+
+var expectedIframe = document.createElement("iframe");
+expectedIframe.style.display = "none";
+document.body.appendChild(expectedIframe);
+
+function restoreIframe(iframe, i) {
+ // Most of this function is designed to work around the fact that Opera
+ // doesn't let you add a doctype to a document that no longer has one, in
+ // any way I can figure out. I eventually compromised on something that
+ // will still let Opera pass most tests that don't actually involve
+ // doctypes.
+ while (iframe.contentDocument.firstChild
+ && iframe.contentDocument.firstChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.firstChild);
+ }
+
+ while (iframe.contentDocument.lastChild
+ && iframe.contentDocument.lastChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
+ iframe.contentDocument.removeChild(iframe.contentDocument.lastChild);
+ }
+
+ if (!iframe.contentDocument.firstChild) {
+ // This will throw an exception in Opera if we reach here, which is why
+ // I try to avoid it. It will never happen in a browser that obeys the
+ // spec, so it's really just insurance. I don't think it actually gets
+ // hit by anything.
+ iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", ""));
+ }
+ iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true));
+ iframe.contentWindow.setupRangeTests();
+ iframe.contentWindow.testRangeInput = testRanges[i];
+ iframe.contentWindow.run();
+}
+
+function myDeleteContents(range) {
+ // "If the context object's start and end are the same, abort this method."
+ if (range.startContainer == range.endContainer
+ && range.startOffset == range.endOffset) {
+ return;
+ }
+
+ // "Let original start node, original start offset, original end node, and
+ // original end offset be the context object's start and end nodes and
+ // offsets, respectively."
+ var originalStartNode = range.startContainer;
+ var originalStartOffset = range.startOffset;
+ var originalEndNode = range.endContainer;
+ var originalEndOffset = range.endOffset;
+
+ // "If original start node and original end node are the same, and they are
+ // a Text, ProcessingInstruction, or Comment node, replace data with node
+ // original start node, offset original start offset, count original end
+ // offset minus original start offset, and data the empty string, and then
+ // terminate these steps"
+ if (originalStartNode == originalEndNode
+ && (range.startContainer.nodeType == Node.TEXT_NODE
+ || range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || range.startContainer.nodeType == Node.COMMENT_NODE)) {
+ originalStartNode.deleteData(originalStartOffset, originalEndOffset - originalStartOffset);
+ return;
+ }
+
+ // "Let nodes to remove be a list of all the Nodes that are contained in
+ // the context object, in tree order, omitting any Node whose parent is
+ // also contained in the context object."
+ //
+ // We rely on the fact that the contained nodes must lie in tree order
+ // between the start node, and the end node's last descendant (inclusive).
+ var nodesToRemove = [];
+ var stop = nextNodeDescendants(range.endContainer);
+ for (var node = range.startContainer; node != stop; node = nextNode(node)) {
+ if (isContained(node, range)
+ && !(node.parentNode && isContained(node.parentNode, range))) {
+ nodesToRemove.push(node);
+ }
+ }
+
+ // "If original start node is an ancestor container of original end node,
+ // set new node to original start node and new offset to original start
+ // offset."
+ var newNode;
+ var newOffset;
+ if (originalStartNode == originalEndNode
+ || originalEndNode.compareDocumentPosition(originalStartNode) & Node.DOCUMENT_POSITION_CONTAINS) {
+ newNode = originalStartNode;
+ newOffset = originalStartOffset;
+ // "Otherwise:"
+ } else {
+ // "Let reference node equal original start node."
+ var referenceNode = originalStartNode;
+
+ // "While reference node's parent is not null and is not an ancestor
+ // container of original end node, set reference node to its parent."
+ while (referenceNode.parentNode
+ && referenceNode.parentNode != originalEndNode
+ && !(originalEndNode.compareDocumentPosition(referenceNode.parentNode) & Node.DOCUMENT_POSITION_CONTAINS)) {
+ referenceNode = referenceNode.parentNode;
+ }
+
+ // "Set new node to the parent of reference node, and new offset to one
+ // plus the index of reference node."
+ newNode = referenceNode.parentNode;
+ newOffset = 1 + indexOf(referenceNode);
+ }
+
+ // "If original start node is a Text, ProcessingInstruction, or Comment node,
+ // replace data with node original start node, offset original start offset,
+ // count original start node's length minus original start offset, data the
+ // empty start"
+ if (originalStartNode.nodeType == Node.TEXT_NODE
+ || originalStartNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || originalStartNode.nodeType == Node.COMMENT_NODE) {
+ originalStartNode.deleteData(originalStartOffset, nodeLength(originalStartNode) - originalStartOffset);
+ }
+
+ // "For each node in nodes to remove, in order, remove node from its
+ // parent."
+ for (var i = 0; i < nodesToRemove.length; i++) {
+ nodesToRemove[i].parentNode.removeChild(nodesToRemove[i]);
+ }
+
+ // "If original end node is a Text, ProcessingInstruction, or Comment node,
+ // replace data with node original end node, offset 0, count original end
+ // offset, and data the empty string."
+ if (originalEndNode.nodeType == Node.TEXT_NODE
+ || originalEndNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE
+ || originalEndNode.nodeType == Node.COMMENT_NODE) {
+ originalEndNode.deleteData(0, originalEndOffset);
+ }
+
+ // "Set the context object's start and end to (new node, new offset)."
+ range.setStart(newNode, newOffset);
+ range.setEnd(newNode, newOffset);
+}
+
+function testDeleteContents(i) {
+ restoreIframe(actualIframe, i);
+ restoreIframe(expectedIframe, i);
+
+ var actualRange = actualIframe.contentWindow.testRange;
+ var expectedRange = expectedIframe.contentWindow.testRange;
+ var actualRoots, expectedRoots;
+
+ domTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual deleteContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated deleteContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+
+ // Just to be pedantic, we'll test not only that the tree we're
+ // modifying is the same in expected vs. actual, but also that all the
+ // nodes originally in it were the same. Typically some nodes will
+ // become detached when the algorithm is run, but they still exist and
+ // references can still be kept to them, so they should also remain the
+ // same.
+ //
+ // We initialize the list to all nodes, and later on remove all the
+ // ones which still have parents, since the parents will presumably be
+ // tested for isEqualNode() and checking the children would be
+ // redundant.
+ var actualAllNodes = [];
+ var node = furthestAncestor(actualRange.startContainer);
+ do {
+ actualAllNodes.push(node);
+ } while (node = nextNode(node));
+
+ var expectedAllNodes = [];
+ var node = furthestAncestor(expectedRange.startContainer);
+ do {
+ expectedAllNodes.push(node);
+ } while (node = nextNode(node));
+
+ actualRange.deleteContents();
+ myDeleteContents(expectedRange);
+
+ actualRoots = [];
+ for (var j = 0; j < actualAllNodes.length; j++) {
+ if (!actualAllNodes[j].parentNode) {
+ actualRoots.push(actualAllNodes[j]);
+ }
+ }
+
+ expectedRoots = [];
+ for (var j = 0; j < expectedAllNodes.length; j++) {
+ if (!expectedAllNodes[j].parentNode) {
+ expectedRoots.push(expectedAllNodes[j]);
+ }
+ }
+
+ for (var j = 0; j < actualRoots.length; j++) {
+ if (!actualRoots[j].isEqualNode(expectedRoots[j])) {
+ var msg = j ? "detached node #" + j : "tree root";
+ msg = "Actual and expected mismatch for " + msg + ". ";
+
+ // Find the specific error
+ var actual = actualRoots[j];
+ var expected = expectedRoots[j];
+
+ while (actual && expected) {
+ assert_equals(actual.nodeType, expected.nodeType,
+ msg + "First difference: differing nodeType");
+ assert_equals(actual.nodeName, expected.nodeName,
+ msg + "First difference: differing nodeName");
+ assert_equals(actual.nodeValue, expected.nodeValue,
+ msg + 'First difference: differing nodeValue (nodeName = "' + actual.nodeName + '")');
+ assert_equals(actual.childNodes.length, expected.childNodes.length,
+ msg + 'First difference: differing number of children (nodeName = "' + actual.nodeName + '")');
+ actual = nextNode(actual);
+ expected = nextNode(expected);
+ }
+
+ assert_unreached("DOMs were not equal but we couldn't figure out why");
+ }
+
+ if (j == 0) {
+ // Clearly something is wrong if the node lists are different
+ // lengths. We want to report this only after we've already
+ // checked the main tree for equality, though, so it doesn't
+ // mask more interesting errors.
+ assert_equals(actualRoots.length, expectedRoots.length,
+ "Actual and expected DOMs were broken up into a different number of pieces by deleteContents() (this probably means you created or detached nodes when you weren't supposed to)");
+ }
+ }
+ });
+ domTests[i].done();
+
+ positionTests[i].step(function() {
+ assert_equals(actualIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for actual deleteContents()");
+ assert_equals(expectedIframe.contentWindow.unexpectedException, null,
+ "Unexpected exception thrown when setting up Range for simulated deleteContents()");
+ assert_equals(typeof actualRange, "object",
+ "typeof Range produced in actual iframe");
+ assert_equals(typeof expectedRange, "object",
+ "typeof Range produced in expected iframe");
+ assert_true(actualRoots[0].isEqualNode(expectedRoots[0]),
+ "The resulting DOMs were not equal, so comparing positions makes no sense");
+
+ assert_equals(actualRange.startContainer, actualRange.endContainer,
+ "startContainer and endContainer must always be the same after deleteContents()");
+ assert_equals(actualRange.startOffset, actualRange.endOffset,
+ "startOffset and endOffset must always be the same after deleteContents()");
+ assert_equals(expectedRange.startContainer, expectedRange.endContainer,
+ "Test bug! Expected startContainer and endContainer must always be the same after deleteContents()");
+ assert_equals(expectedRange.startOffset, expectedRange.endOffset,
+ "Test bug! Expected startOffset and endOffset must always be the same after deleteContents()");
+
+ assert_equals(actualRange.startOffset, expectedRange.startOffset,
+ "Unexpected startOffset after deleteContents()");
+ // How do we decide that the two nodes are equal, since they're in
+ // different trees? Since the DOMs are the same, it's enough to check
+ // that the index in the parent is the same all the way up the tree.
+ // But we can first cheat by just checking they're actually equal.
+ assert_true(actualRange.startContainer.isEqualNode(expectedRange.startContainer),
+ "Unexpected startContainer after deleteContents(), expected " +
+ expectedRange.startContainer.nodeName.toLowerCase() + " but got " +
+ actualRange.startContainer.nodeName.toLowerCase());
+ var currentActual = actualRange.startContainer;
+ var currentExpected = expectedRange.startContainer;
+ var actual = "";
+ var expected = "";
+ while (currentActual && currentExpected) {
+ actual = indexOf(currentActual) + "-" + actual;
+ expected = indexOf(currentExpected) + "-" + expected;
+
+ currentActual = currentActual.parentNode;
+ currentExpected = currentExpected.parentNode;
+ }
+ actual = actual.substr(0, actual.length - 1);
+ expected = expected.substr(0, expected.length - 1);
+ assert_equals(actual, expected,
+ "startContainer superficially looks right but is actually the wrong node if you trace back its index in all its ancestors (I'm surprised this actually happened");
+ });
+ positionTests[i].done();
+}
+
+// First test a detached Range, synchronously
+test(function() {
+ var range = document.createRange();
+ range.detach();
+ range.deleteContents();
+}, "Detached Range");
+
+var iStart = 0;
+var iStop = testRanges.length;
+
+if (/subtest=[0-9]+/.test(location.search)) {
+ var matches = /subtest=([0-9]+)/.exec(location.search);
+ iStart = Number(matches[1]);
+ iStop = Number(matches[1]) + 1;
+}
+
+var domTests = [];
+var positionTests = [];
+
+for (var i = iStart; i < iStop; i++) {
+ domTests[i] = async_test("Resulting DOM for range " + i + " " + testRanges[i]);
+ positionTests[i] = async_test("Resulting cursor position for range " + i + " " + testRanges[i]);
+}
+
+var referenceDoc = document.implementation.createHTMLDocument("");
+referenceDoc.removeChild(referenceDoc.documentElement);
+
+actualIframe.onload = function() {
+ expectedIframe.onload = function() {
+ for (var i = iStart; i < iStop; i++) {
+ testDeleteContents(i);
+ }
+ }
+ expectedIframe.src = "Range-test-iframe.html";
+ referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
+}
+actualIframe.src = "Range-test-iframe.html";
+</script>