summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/dom/ranges/Range-mutations.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/dom/ranges/Range-mutations.html')
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-mutations.html950
1 files changed, 950 insertions, 0 deletions
diff --git a/testing/web-platform/tests/dom/ranges/Range-mutations.html b/testing/web-platform/tests/dom/ranges/Range-mutations.html
new file mode 100644
index 000000000..ef99ca2ef
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-mutations.html
@@ -0,0 +1,950 @@
+<!doctype html>
+<title>Range mutation tests</title>
+<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
+<meta name=timeout content=long>
+
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=../common.js></script>
+<script>
+"use strict";
+
+// These tests probably use too much abstraction and too little copy-paste.
+// Reader beware.
+//
+// TODO:
+//
+// * Lots and lots and lots more different types of ranges
+// * insertBefore() with DocumentFragments
+// * Fill out other insert/remove tests
+// * normalize() (https://www.w3.org/Bugs/Public/show_bug.cgi?id=13843)
+
+// Give a textual description of the range we're testing, for the test names.
+function describeRange(startContainer, startOffset, endContainer, endOffset) {
+ if (startContainer == endContainer && startOffset == endOffset) {
+ return "range collapsed at (" + startContainer + ", " + startOffset + ")";
+ } else if (startContainer == endContainer) {
+ return "range on " + startContainer + " from " + startOffset + " to " + endOffset;
+ } else {
+ return "range from (" + startContainer + ", " + startOffset + ") to (" + endContainer + ", " + endOffset + ")";
+ }
+}
+
+// Lists of the various types of nodes we'll want to use. We use strings that
+// we can later eval(), so that we can produce legible test names.
+var textNodes = [
+ "paras[0].firstChild",
+ "paras[1].firstChild",
+ "foreignTextNode",
+ "xmlTextNode",
+ "detachedTextNode",
+ "detachedForeignTextNode",
+ "detachedXmlTextNode",
+];
+var commentNodes = [
+ "comment",
+ "foreignComment",
+ "xmlComment",
+ "detachedComment",
+ "detachedForeignComment",
+ "detachedXmlComment",
+];
+var characterDataNodes = textNodes.concat(commentNodes);
+
+// This function is slightly scary, but it works well enough, so . . .
+// sourceTests is an array of test data that will be altered in mysterious ways
+// before being passed off to doTest, descFn is something that takes an element
+// of sourceTests and produces the first part of a human-readable description
+// of the test, testFn is the function that doTest will call to do the actual
+// work and tell it what results to expect.
+function doTests(sourceTests, descFn, testFn) {
+ var tests = [];
+ for (var i = 0; i < sourceTests.length; i++) {
+ var params = sourceTests[i];
+ var len = params.length;
+ tests.push([
+ descFn(params) + ", with unselected " + describeRange(params[len - 4], params[len - 3], params[len - 2], params[len - 1]),
+ // The closure here ensures that the params that testFn get are the
+ // current version of params, not the version from the last
+ // iteration of this loop. We test that none of the parameters
+ // evaluate to undefined to catch bugs in our eval'ing, like
+ // mistyping a property name.
+ function(params) { return function() {
+ var evaledParams = params.map(eval);
+ for (var i = 0; i < evaledParams.length; i++) {
+ assert_true(typeof evaledParams[i] != "undefined",
+ "Test bug: " + params[i] + " is undefined");
+ }
+ return testFn.apply(null, evaledParams);
+ } }(params),
+ false,
+ params[len - 4],
+ params[len - 3],
+ params[len - 2],
+ params[len - 1]
+ ]);
+ tests.push([
+ descFn(params) + ", with selected " + describeRange(params[len - 4], params[len - 3], params[len - 2], params[len - 1]),
+ function(params) { return function(selectedRange) {
+ var evaledParams = params.slice(0, len - 4).map(eval);
+ for (var i = 0; i < evaledParams.length; i++) {
+ assert_true(typeof evaledParams[i] != "undefined",
+ "Test bug: " + params[i] + " is undefined");
+ }
+ // Override input range with the one that was actually selected when computing the expected result.
+ evaledParams = evaledParams.concat([selectedRange.startContainer, selectedRange.startOffset, selectedRange.endContainer, selectedRange.endOffset]);
+ return testFn.apply(null, evaledParams);
+ } }(params),
+ true,
+ params[len - 4],
+ params[len - 3],
+ params[len - 2],
+ params[len - 1]
+ ]);
+ }
+ generate_tests(doTest, tests);
+}
+
+// Set up the range, call the callback function to do the DOM modification and
+// tell us what to expect. The callback function needs to return a
+// four-element array with the expected start/end containers/offsets, and
+// receives no arguments. useSelection tells us whether the Range should be
+// added to a Selection and the Selection tested to ensure that the mutation
+// affects user selections as well as other ranges; every test is run with this
+// both false and true, because when it's set to true WebKit and Opera fail all
+// tests' sanity checks, which is unhelpful. The last four parameters just
+// tell us what range to build.
+function doTest(callback, useSelection, startContainer, startOffset, endContainer, endOffset) {
+ // Recreate all the test nodes in case they were altered by the last test
+ // run.
+ setupRangeTests();
+ startContainer = eval(startContainer);
+ startOffset = eval(startOffset);
+ endContainer = eval(endContainer);
+ endOffset = eval(endOffset);
+
+ var ownerDoc = startContainer.nodeType == Node.DOCUMENT_NODE
+ ? startContainer
+ : startContainer.ownerDocument;
+ var range = ownerDoc.createRange();
+ range.setStart(startContainer, startOffset);
+ range.setEnd(endContainer, endOffset);
+
+ if (useSelection) {
+ getSelection().removeAllRanges();
+ getSelection().addRange(range);
+
+ // Some browsers refuse to add a range unless it results in an actual visible selection.
+ if (!getSelection().rangeCount)
+ return;
+
+ // Override range with the one that was actually selected as it differs in some browsers.
+ range = getSelection().getRangeAt(0);
+ }
+
+ var expected = callback(range);
+
+ assert_equals(range.startContainer, expected[0],
+ "Wrong start container");
+ assert_equals(range.startOffset, expected[1],
+ "Wrong start offset");
+ assert_equals(range.endContainer, expected[2],
+ "Wrong end container");
+ assert_equals(range.endOffset, expected[3],
+ "Wrong end offset");
+}
+
+
+// Now we get to the specific tests.
+
+function testSplitText(oldNode, offset, startContainer, startOffset, endContainer, endOffset) {
+ // Save these for later
+ var originalStartOffset = startOffset;
+ var originalEndOffset = endOffset;
+ var originalLength = oldNode.length;
+
+ var newNode;
+ try {
+ newNode = oldNode.splitText(offset);
+ } catch (e) {
+ // Should only happen if offset is negative
+ return [startContainer, startOffset, endContainer, endOffset];
+ }
+
+ // First we adjust for replacing data:
+ //
+ // "Replace data with offset offset, count count, and data the empty
+ // string."
+ //
+ // That translates to offset = offset, count = originalLength - offset,
+ // data = "". node is oldNode.
+ //
+ // "For every boundary point whose node is node, and whose offset is
+ // greater than offset but less than or equal to offset plus count, set its
+ // offset to offset."
+ if (startContainer == oldNode
+ && startOffset > offset
+ && startOffset <= originalLength) {
+ startOffset = offset;
+ }
+
+ if (endContainer == oldNode
+ && endOffset > offset
+ && endOffset <= originalLength) {
+ endOffset = offset;
+ }
+
+ // "For every boundary point whose node is node, and whose offset is
+ // greater than offset plus count, add the length of data to its offset,
+ // then subtract count from it."
+ //
+ // Can't happen: offset plus count is originalLength.
+
+ // Now we insert a node, if oldNode's parent isn't null: "For each boundary
+ // point whose node is the new parent of the affected node and whose offset
+ // is greater than the new index of the affected node, add one to the
+ // boundary point's offset."
+ if (startContainer == oldNode.parentNode
+ && startOffset > 1 + indexOf(oldNode)) {
+ startOffset++;
+ }
+
+ if (endContainer == oldNode.parentNode
+ && endOffset > 1 + indexOf(oldNode)) {
+ endOffset++;
+ }
+
+ // Finally, the splitText stuff itself:
+ //
+ // "If parent is not null, run these substeps:
+ //
+ // * "For each range whose start node is node and start offset is greater
+ // than offset, set its start node to new node and decrease its start
+ // offset by offset.
+ //
+ // * "For each range whose end node is node and end offset is greater
+ // than offset, set its end node to new node and decrease its end offset
+ // by offset.
+ //
+ // * "For each range whose start node is parent and start offset is equal
+ // to the index of node + 1, increase its start offset by one.
+ //
+ // * "For each range whose end node is parent and end offset is equal to
+ // the index of node + 1, increase its end offset by one."
+ if (oldNode.parentNode) {
+ if (startContainer == oldNode && originalStartOffset > offset) {
+ startContainer = newNode;
+ startOffset = originalStartOffset - offset;
+ }
+
+ if (endContainer == oldNode && originalEndOffset > offset) {
+ endContainer = newNode;
+ endOffset = originalEndOffset - offset;
+ }
+
+ if (startContainer == oldNode.parentNode
+ && startOffset == 1 + indexOf(oldNode)) {
+ startOffset++;
+ }
+
+ if (endContainer == oldNode.parentNode
+ && endOffset == 1 + indexOf(oldNode)) {
+ endOffset++;
+ }
+ }
+
+ return [startContainer, startOffset, endContainer, endOffset];
+}
+
+// The offset argument is unsigned, so per WebIDL -1 should wrap to 4294967295,
+// which is probably longer than the length, so it should throw an exception.
+// This is no different from the other cases where the offset is longer than
+// the length, and the wrapping complicates my testing slightly, so I won't
+// bother testing negative values here or in other cases.
+var splitTextTests = [];
+for (var i = 0; i < textNodes.length; i++) {
+ var node = textNodes[i];
+ splitTextTests.push([node, 376, node, 0, node, 1]);
+ splitTextTests.push([node, 0, node, 0, node, 0]);
+ splitTextTests.push([node, 1, node, 1, node, 1]);
+ splitTextTests.push([node, node + ".length", node, node + ".length", node, node + ".length"]);
+ splitTextTests.push([node, 1, node, 1, node, 3]);
+ splitTextTests.push([node, 2, node, 1, node, 3]);
+ splitTextTests.push([node, 3, node, 1, node, 3]);
+}
+
+splitTextTests.push(
+ ["paras[0].firstChild", 1, "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+function testReplaceDataAlgorithm(node, offset, count, data, callback, startContainer, startOffset, endContainer, endOffset) {
+ // Mutation works the same any time DOM Core's "replace data" algorithm is
+ // invoked. node, offset, count, data are as in that algorithm. The
+ // callback is what does the actual setting. Not to be confused with
+ // testReplaceData, which tests the replaceData() method.
+
+ // Barring any provision to the contrary, the containers and offsets must
+ // not change.
+ var expectedStartContainer = startContainer;
+ var expectedStartOffset = startOffset;
+ var expectedEndContainer = endContainer;
+ var expectedEndOffset = endOffset;
+
+ var originalParent = node.parentNode;
+ var originalData = node.data;
+
+ var exceptionThrown = false;
+ try {
+ callback();
+ } catch (e) {
+ // Should only happen if offset is greater than length
+ exceptionThrown = true;
+ }
+
+ assert_equals(node.parentNode, originalParent,
+ "Sanity check failed: changing data changed the parent");
+
+ // "User agents must run the following steps whenever they replace data of
+ // a CharacterData node, as though they were written in the specification
+ // for that algorithm after all other steps. In particular, the steps must
+ // not be executed if the algorithm threw an exception."
+ if (exceptionThrown) {
+ assert_equals(node.data, originalData,
+ "Sanity check failed: exception thrown but data changed");
+ } else {
+ assert_equals(node.data,
+ originalData.substr(0, offset) + data + originalData.substr(offset + count),
+ "Sanity check failed: data not changed as expected");
+ }
+
+ // "For every boundary point whose node is node, and whose offset is
+ // greater than offset but less than or equal to offset plus count, set
+ // its offset to offset."
+ if (!exceptionThrown
+ && startContainer == node
+ && startOffset > offset
+ && startOffset <= offset + count) {
+ expectedStartOffset = offset;
+ }
+
+ if (!exceptionThrown
+ && endContainer == node
+ && endOffset > offset
+ && endOffset <= offset + count) {
+ expectedEndOffset = offset;
+ }
+
+ // "For every boundary point whose node is node, and whose offset is
+ // greater than offset plus count, add the length of data to its offset,
+ // then subtract count from it."
+ if (!exceptionThrown
+ && startContainer == node
+ && startOffset > offset + count) {
+ expectedStartOffset += data.length - count;
+ }
+
+ if (!exceptionThrown
+ && endContainer == node
+ && endOffset > offset + count) {
+ expectedEndOffset += data.length - count;
+ }
+
+ return [expectedStartContainer, expectedStartOffset, expectedEndContainer, expectedEndOffset];
+}
+
+function testInsertData(node, offset, data, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, offset, 0, data,
+ function() { node.insertData(offset, data) },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var insertDataTests = [];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ insertDataTests.push([node, 376, '"foo"', node, 0, node, 1]);
+ insertDataTests.push([node, 0, '"foo"', node, 0, node, 0]);
+ insertDataTests.push([node, 1, '"foo"', node, 1, node, 1]);
+ insertDataTests.push([node, node + ".length", '"foo"', node, node + ".length", node, node + ".length"]);
+ insertDataTests.push([node, 1, '"foo"', node, 1, node, 3]);
+ insertDataTests.push([node, 2, '"foo"', node, 1, node, 3]);
+ insertDataTests.push([node, 3, '"foo"', node, 1, node, 3]);
+
+ insertDataTests.push([node, 376, '""', node, 0, node, 1]);
+ insertDataTests.push([node, 0, '""', node, 0, node, 0]);
+ insertDataTests.push([node, 1, '""', node, 1, node, 1]);
+ insertDataTests.push([node, node + ".length", '""', node, node + ".length", node, node + ".length"]);
+ insertDataTests.push([node, 1, '""', node, 1, node, 3]);
+ insertDataTests.push([node, 2, '""', node, 1, node, 3]);
+ insertDataTests.push([node, 3, '""', node, 1, node, 3]);
+}
+
+insertDataTests.push(
+ ["paras[0].firstChild", 1, '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+function testAppendData(node, data, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, node.length, 0, data,
+ function() { node.appendData(data) },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var appendDataTests = [];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ appendDataTests.push([node, '"foo"', node, 0, node, 1]);
+ appendDataTests.push([node, '"foo"', node, 0, node, 0]);
+ appendDataTests.push([node, '"foo"', node, 1, node, 1]);
+ appendDataTests.push([node, '"foo"', node, 0, node, node + ".length"]);
+ appendDataTests.push([node, '"foo"', node, 1, node, node + ".length"]);
+ appendDataTests.push([node, '"foo"', node, node + ".length", node, node + ".length"]);
+ appendDataTests.push([node, '"foo"', node, 1, node, 3]);
+
+ appendDataTests.push([node, '""', node, 0, node, 1]);
+ appendDataTests.push([node, '""', node, 0, node, 0]);
+ appendDataTests.push([node, '""', node, 1, node, 1]);
+ appendDataTests.push([node, '""', node, 0, node, node + ".length"]);
+ appendDataTests.push([node, '""', node, 1, node, node + ".length"]);
+ appendDataTests.push([node, '""', node, node + ".length", node, node + ".length"]);
+ appendDataTests.push([node, '""', node, 1, node, 3]);
+}
+
+appendDataTests.push(
+ ["paras[0].firstChild", '""', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", '""', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", '""', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", '""', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", '""', "paras[0]", 0, "paras[0].firstChild", 3],
+
+ ["paras[0].firstChild", '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", '"foo"', "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+function testDeleteData(node, offset, count, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, offset, count, "",
+ function() { node.deleteData(offset, count) },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var deleteDataTests = [];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ deleteDataTests.push([node, 376, 2, node, 0, node, 1]);
+ deleteDataTests.push([node, 0, 2, node, 0, node, 0]);
+ deleteDataTests.push([node, 1, 2, node, 1, node, 1]);
+ deleteDataTests.push([node, node + ".length", 2, node, node + ".length", node, node + ".length"]);
+ deleteDataTests.push([node, 1, 2, node, 1, node, 3]);
+ deleteDataTests.push([node, 2, 2, node, 1, node, 3]);
+ deleteDataTests.push([node, 3, 2, node, 1, node, 3]);
+
+ deleteDataTests.push([node, 376, 0, node, 0, node, 1]);
+ deleteDataTests.push([node, 0, 0, node, 0, node, 0]);
+ deleteDataTests.push([node, 1, 0, node, 1, node, 1]);
+ deleteDataTests.push([node, node + ".length", 0, node, node + ".length", node, node + ".length"]);
+ deleteDataTests.push([node, 1, 0, node, 1, node, 3]);
+ deleteDataTests.push([node, 2, 0, node, 1, node, 3]);
+ deleteDataTests.push([node, 3, 0, node, 1, node, 3]);
+
+ deleteDataTests.push([node, 376, 631, node, 0, node, 1]);
+ deleteDataTests.push([node, 0, 631, node, 0, node, 0]);
+ deleteDataTests.push([node, 1, 631, node, 1, node, 1]);
+ deleteDataTests.push([node, node + ".length", 631, node, node + ".length", node, node + ".length"]);
+ deleteDataTests.push([node, 1, 631, node, 1, node, 3]);
+ deleteDataTests.push([node, 2, 631, node, 1, node, 3]);
+ deleteDataTests.push([node, 3, 631, node, 1, node, 3]);
+}
+
+deleteDataTests.push(
+ ["paras[0].firstChild", 1, 2, "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, 2, "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 2, "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 2, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, 2, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, 2, "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 2, "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, 2, "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, 2, "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+function testReplaceData(node, offset, count, data, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, offset, count, data,
+ function() { node.replaceData(offset, count, data) },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var replaceDataTests = [];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ replaceDataTests.push([node, 376, 0, '"foo"', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 0, '"foo"', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 0, '"foo"', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 0, '"foo"', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 0, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 0, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 0, '"foo"', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 0, '""', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 0, '""', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 0, '""', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 0, '""', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 0, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 0, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 0, '""', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 1, '"foo"', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 1, '"foo"', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 1, '"foo"', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 1, '"foo"', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 1, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 1, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 1, '"foo"', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 1, '""', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 1, '""', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 1, '""', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 1, '""', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 1, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 1, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 1, '""', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 47, '"foo"', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 47, '"foo"', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 47, '"foo"', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 47, '"foo"', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 47, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 47, '"foo"', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 47, '"foo"', node, 1, node, 3]);
+
+ replaceDataTests.push([node, 376, 47, '""', node, 0, node, 1]);
+ replaceDataTests.push([node, 0, 47, '""', node, 0, node, 0]);
+ replaceDataTests.push([node, 1, 47, '""', node, 1, node, 1]);
+ replaceDataTests.push([node, node + ".length", 47, '""', node, node + ".length", node, node + ".length"]);
+ replaceDataTests.push([node, 1, 47, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 2, 47, '""', node, 1, node, 3]);
+ replaceDataTests.push([node, 3, 47, '""', node, 1, node, 3]);
+}
+
+replaceDataTests.push(
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, 0, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, 0, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, 0, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, 0, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 0, "paras[0]", 0],
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 0, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 2, 47, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 3, 47, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1],
+ ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 2, 47, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3],
+ ["paras[0].firstChild", 3, 47, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3]
+);
+
+
+// There are lots of ways to set data, so we pass a callback that does the
+// actual setting.
+function testDataChange(node, attr, op, rval, startContainer, startOffset, endContainer, endOffset) {
+ return testReplaceDataAlgorithm(node, 0, node.length, op == "=" ? rval : node[attr] + rval,
+ function() {
+ if (op == "=") {
+ node[attr] = rval;
+ } else if (op == "+=") {
+ node[attr] += rval;
+ } else {
+ throw "Unknown op " + op;
+ }
+ },
+ startContainer, startOffset, endContainer, endOffset);
+}
+
+var dataChangeTests = [];
+var dataChangeTestAttrs = ["data", "textContent", "nodeValue"];
+for (var i = 0; i < characterDataNodes.length; i++) {
+ var node = characterDataNodes[i];
+ var dataChangeTestRanges = [
+ [node, 0, node, 0],
+ [node, 0, node, 1],
+ [node, 1, node, 1],
+ [node, 0, node, node + ".length"],
+ [node, 1, node, node + ".length"],
+ [node, node + ".length", node, node + ".length"],
+ ];
+
+ for (var j = 0; j < dataChangeTestRanges.length; j++) {
+ for (var k = 0; k < dataChangeTestAttrs.length; k++) {
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"="',
+ '""',
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"="',
+ '"foo"',
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"="',
+ node + "." + dataChangeTestAttrs[k],
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"+="',
+ '""',
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"+="',
+ '"foo"',
+ ].concat(dataChangeTestRanges[j]));
+
+ dataChangeTests.push([
+ node,
+ '"' + dataChangeTestAttrs[k] + '"',
+ '"+="',
+ node + "." + dataChangeTestAttrs[k]
+ ].concat(dataChangeTestRanges[j]));
+ }
+ }
+}
+
+
+// Now we test node insertions and deletions, as opposed to just data changes.
+// To avoid loads of repetition, we define modifyForRemove() and
+// modifyForInsert().
+
+// If we were to remove removedNode from its parent, what would the boundary
+// point [node, offset] become? Returns [new node, new offset]. Must be
+// called BEFORE the node is actually removed, so its parent is not null. (If
+// the parent is null, it will do nothing.)
+function modifyForRemove(removedNode, point) {
+ var oldParent = removedNode.parentNode;
+ var oldIndex = indexOf(removedNode);
+ if (!oldParent) {
+ return point;
+ }
+
+ // "For each boundary point whose node is removed node or a descendant of
+ // it, set the boundary point to (old parent, old index)."
+ if (point[0] == removedNode || isDescendant(point[0], removedNode)) {
+ return [oldParent, oldIndex];
+ }
+
+ // "For each boundary point whose node is old parent and whose offset is
+ // greater than old index, subtract one from its offset."
+ if (point[0] == oldParent && point[1] > oldIndex) {
+ return [point[0], point[1] - 1];
+ }
+
+ return point;
+}
+
+// Update the given boundary point [node, offset] to account for the fact that
+// insertedNode was just inserted into its current position. This must be
+// called AFTER insertedNode was already inserted.
+function modifyForInsert(insertedNode, point) {
+ // "For each boundary point whose node is the new parent of the affected
+ // node and whose offset is greater than the new index of the affected
+ // node, add one to the boundary point's offset."
+ if (point[0] == insertedNode.parentNode && point[1] > indexOf(insertedNode)) {
+ return [point[0], point[1] + 1];
+ }
+
+ return point;
+}
+
+
+function testInsertBefore(newParent, affectedNode, refNode, startContainer, startOffset, endContainer, endOffset) {
+ var expectedStart = [startContainer, startOffset];
+ var expectedEnd = [endContainer, endOffset];
+
+ expectedStart = modifyForRemove(affectedNode, expectedStart);
+ expectedEnd = modifyForRemove(affectedNode, expectedEnd);
+
+ try {
+ newParent.insertBefore(affectedNode, refNode);
+ } catch (e) {
+ // For our purposes, assume that DOM Core is true -- i.e., ignore
+ // mutation events and similar.
+ return [startContainer, startOffset, endContainer, endOffset];
+ }
+
+ expectedStart = modifyForInsert(affectedNode, expectedStart);
+ expectedEnd = modifyForInsert(affectedNode, expectedEnd);
+
+ return expectedStart.concat(expectedEnd);
+}
+
+var insertBeforeTests = [
+ // Moving a node to its current position
+ ["testDiv", "paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 0],
+ ["testDiv", "paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 1],
+ ["testDiv", "paras[0]", "paras[1]", "paras[0]", 1, "paras[0]", 1],
+ ["testDiv", "paras[0]", "paras[1]", "testDiv", 0, "testDiv", 2],
+ ["testDiv", "paras[0]", "paras[1]", "testDiv", 1, "testDiv", 1],
+ ["testDiv", "paras[0]", "paras[1]", "testDiv", 1, "testDiv", 2],
+ ["testDiv", "paras[0]", "paras[1]", "testDiv", 2, "testDiv", 2],
+
+ // Stuff that actually moves something. Note that paras[0] and paras[1]
+ // are both children of testDiv.
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 2],
+ ["paras[0]", "paras[1]", "null", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[1]", "null", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "null", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "null", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "paras[1]", "null", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "paras[1]", "null", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "paras[1]", "null", "testDiv", 1, "testDiv", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 0],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 1, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 0],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 1, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "null", "foreignDoc", 0, "foreignDoc", 1],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1],
+
+ // Stuff that throws exceptions
+ ["paras[0]", "paras[0]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "testDiv", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "foreignDoc", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document.doctype", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+];
+
+
+function testReplaceChild(newParent, newChild, oldChild, startContainer, startOffset, endContainer, endOffset) {
+ var expectedStart = [startContainer, startOffset];
+ var expectedEnd = [endContainer, endOffset];
+
+ expectedStart = modifyForRemove(oldChild, expectedStart);
+ expectedEnd = modifyForRemove(oldChild, expectedEnd);
+
+ if (newChild != oldChild) {
+ // Don't do this twice, if they're the same!
+ expectedStart = modifyForRemove(newChild, expectedStart);
+ expectedEnd = modifyForRemove(newChild, expectedEnd);
+ }
+
+ try {
+ newParent.replaceChild(newChild, oldChild);
+ } catch (e) {
+ return [startContainer, startOffset, endContainer, endOffset];
+ }
+
+ expectedStart = modifyForInsert(newChild, expectedStart);
+ expectedEnd = modifyForInsert(newChild, expectedEnd);
+
+ return expectedStart.concat(expectedEnd);
+}
+
+var replaceChildTests = [
+ // Moving a node to its current position. Doesn't match most browsers'
+ // behavior, but we probably want to keep the spec the same anyway:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=647603
+ ["testDiv", "paras[0]", "paras[0]", "paras[0]", 0, "paras[0]", 0],
+ ["testDiv", "paras[0]", "paras[0]", "paras[0]", 0, "paras[0]", 1],
+ ["testDiv", "paras[0]", "paras[0]", "paras[0]", 1, "paras[0]", 1],
+ ["testDiv", "paras[0]", "paras[0]", "testDiv", 0, "testDiv", 2],
+ ["testDiv", "paras[0]", "paras[0]", "testDiv", 1, "testDiv", 1],
+ ["testDiv", "paras[0]", "paras[0]", "testDiv", 1, "testDiv", 2],
+ ["testDiv", "paras[0]", "paras[0]", "testDiv", 2, "testDiv", 2],
+
+ // Stuff that actually moves something.
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 0],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc", 1, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 0],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 1],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "foreignDoc", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 1, "foreignDoc", 1],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1],
+
+ // Stuff that throws exceptions
+ ["paras[0]", "paras[0]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "testDiv", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "foreignDoc", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document.doctype", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1],
+];
+
+
+function testAppendChild(newParent, affectedNode, startContainer, startOffset, endContainer, endOffset) {
+ var expectedStart = [startContainer, startOffset];
+ var expectedEnd = [endContainer, endOffset];
+
+ expectedStart = modifyForRemove(affectedNode, expectedStart);
+ expectedEnd = modifyForRemove(affectedNode, expectedEnd);
+
+ try {
+ newParent.appendChild(affectedNode);
+ } catch (e) {
+ return [startContainer, startOffset, endContainer, endOffset];
+ }
+
+ // These two lines will actually never do anything, if you think about it,
+ // but let's leave them in so correctness is more obvious.
+ expectedStart = modifyForInsert(affectedNode, expectedStart);
+ expectedEnd = modifyForInsert(affectedNode, expectedEnd);
+
+ return expectedStart.concat(expectedEnd);
+}
+
+var appendChildTests = [
+ // Moving a node to its current position
+ ["testDiv", "testDiv.lastChild", "testDiv.lastChild", 0, "testDiv.lastChild", 0],
+ ["testDiv", "testDiv.lastChild", "testDiv.lastChild", 0, "testDiv.lastChild", 1],
+ ["testDiv", "testDiv.lastChild", "testDiv.lastChild", 1, "testDiv.lastChild", 1],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 2", "testDiv", "testDiv.childNodes.length"],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 2", "testDiv", "testDiv.childNodes.length - 1"],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 1", "testDiv", "testDiv.childNodes.length"],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 1", "testDiv", "testDiv.childNodes.length - 1"],
+ ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length", "testDiv", "testDiv.childNodes.length"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv.lastChild", 0, "detachedDiv.lastChild", 0],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv.lastChild", 0, "detachedDiv.lastChild", 1],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv.lastChild", 1, "detachedDiv.lastChild", 1],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length - 2", "detachedDiv", "detachedDiv.childNodes.length"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length - 2", "detachedDiv", "detachedDiv.childNodes.length - 1"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length - 1", "detachedDiv", "detachedDiv.childNodes.length"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length - 1", "detachedDiv", "detachedDiv.childNodes.length - 1"],
+ ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNodes.length", "detachedDiv", "detachedDiv.childNodes.length"],
+
+ // Stuff that actually moves something
+ ["paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "paras[1]", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "paras[1]", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "paras[1]", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "paras[1]", "testDiv", 1, "testDiv", 2],
+ ["foreignDoc", "detachedComment", "foreignDoc", "foreignDoc.childNodes.length - 1", "foreignDoc", "foreignDoc.childNodes.length"],
+ ["foreignDoc", "detachedComment", "foreignDoc", "foreignDoc.childNodes.length - 1", "foreignDoc", "foreignDoc.childNodes.length - 1"],
+ ["foreignDoc", "detachedComment", "foreignDoc", "foreignDoc.childNodes.length", "foreignDoc", "foreignDoc.childNodes.length"],
+ ["foreignDoc", "detachedComment", "detachedComment", 0, "detachedComment", 5],
+ ["paras[0]", "xmlTextNode", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "xmlTextNode", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "xmlTextNode", "paras[0]", 1, "paras[0]", 1],
+
+ // Stuff that throws exceptions
+ ["paras[0]", "paras[0]", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "testDiv", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "foreignDoc", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "document.doctype", "paras[0]", 0, "paras[0]", 1],
+];
+
+
+function testRemoveChild(affectedNode, startContainer, startOffset, endContainer, endOffset) {
+ var expectedStart = [startContainer, startOffset];
+ var expectedEnd = [endContainer, endOffset];
+
+ expectedStart = modifyForRemove(affectedNode, expectedStart);
+ expectedEnd = modifyForRemove(affectedNode, expectedEnd);
+
+ // We don't test cases where the parent is wrong, so this should never
+ // throw an exception.
+ affectedNode.parentNode.removeChild(affectedNode);
+
+ return expectedStart.concat(expectedEnd);
+}
+
+var removeChildTests = [
+ ["paras[0]", "paras[0]", 0, "paras[0]", 0],
+ ["paras[0]", "paras[0]", 0, "paras[0]", 1],
+ ["paras[0]", "paras[0]", 1, "paras[0]", 1],
+ ["paras[0]", "testDiv", 0, "testDiv", 0],
+ ["paras[0]", "testDiv", 0, "testDiv", 1],
+ ["paras[0]", "testDiv", 1, "testDiv", 1],
+ ["paras[0]", "testDiv", 0, "testDiv", 2],
+ ["paras[0]", "testDiv", 1, "testDiv", 2],
+ ["paras[0]", "testDiv", 2, "testDiv", 2],
+
+ ["foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", "foreignDoc.childNodes.length"],
+];
+
+
+// Finally run everything. All grouped together at the end so that I can
+// easily comment out some of them, so I don't have to wait for all test types
+// to debug only some of them.
+doTests(splitTextTests, function(params) { return params[0] + ".splitText(" + params[1] + ")" }, testSplitText);
+doTests(insertDataTests, function(params) { return params[0] + ".insertData(" + params[1] + ", " + params[2] + ")" }, testInsertData);
+doTests(appendDataTests, function(params) { return params[0] + ".appendData(" + params[1] + ")" }, testAppendData);
+doTests(deleteDataTests, function(params) { return params[0] + ".deleteData(" + params[1] + ", " + params[2] + ")" }, testDeleteData);
+doTests(replaceDataTests, function(params) { return params[0] + ".replaceData(" + params[1] + ", " + params[2] + ", " + params[3] + ")" }, testReplaceData);
+doTests(dataChangeTests, function(params) { return params[0] + "." + eval(params[1]) + " " + eval(params[2]) + ' ' + params[3] }, testDataChange);
+doTests(insertBeforeTests, function(params) { return params[0] + ".insertBefore(" + params[1] + ", " + params[2] + ")" }, testInsertBefore);
+doTests(replaceChildTests, function(params) { return params[0] + ".replaceChild(" + params[1] + ", " + params[2] + ")" }, testReplaceChild);
+doTests(appendChildTests, function(params) { return params[0] + ".appendChild(" + params[1] + ")" }, testAppendChild);
+doTests(removeChildTests, function(params) { return params[0] + ".parentNode.removeChild(" + params[0] + ")" }, testRemoveChild);
+
+
+testDiv.style.display = "none";
+</script>