<!doctype html> <title>Range.extractContents() 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 testExtractContents(i) { restoreIframe(actualIframe, i); restoreIframe(expectedIframe, i); var actualRange = actualIframe.contentWindow.testRange; var expectedRange = expectedIframe.contentWindow.testRange; var actualFrag, expectedFrag; var actualRoots, expectedRoots; domTests[i].step(function() { assert_equals(actualIframe.contentWindow.unexpectedException, null, "Unexpected exception thrown when setting up Range for actual extractContents()"); assert_equals(expectedIframe.contentWindow.unexpectedException, null, "Unexpected exception thrown when setting up Range for simulated extractContents()"); 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)); expectedFrag = myExtractContents(expectedRange); if (typeof expectedFrag == "string") { assert_throws(expectedFrag, function() { actualRange.extractContents(); }); } else { actualFrag = actualRange.extractContents(); } 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++) { assertNodesEqual(actualRoots[j], expectedRoots[j], j ? "detached node #" + j : "tree root"); 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 extractContents() (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 extractContents()"); assert_equals(expectedIframe.contentWindow.unexpectedException, null, "Unexpected exception thrown when setting up Range for simulated extractContents()"); 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"); if (typeof expectedFrag == "string") { // It's no longer true that, e.g., startContainer and endContainer // must always be the same return; } assert_equals(actualRange.startContainer, actualRange.endContainer, "startContainer and endContainer must always be the same after extractContents()"); assert_equals(actualRange.startOffset, actualRange.endOffset, "startOffset and endOffset must always be the same after extractContents()"); assert_equals(expectedRange.startContainer, expectedRange.endContainer, "Test bug! Expected startContainer and endContainer must always be the same after extractContents()"); assert_equals(expectedRange.startOffset, expectedRange.endOffset, "Test bug! Expected startOffset and endOffset must always be the same after extractContents()"); assert_equals(actualRange.startOffset, expectedRange.startOffset, "Unexpected startOffset after extractContents()"); // 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 extractContents(), 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(); fragTests[i].step(function() { assert_equals(actualIframe.contentWindow.unexpectedException, null, "Unexpected exception thrown when setting up Range for actual extractContents()"); assert_equals(expectedIframe.contentWindow.unexpectedException, null, "Unexpected exception thrown when setting up Range for simulated extractContents()"); assert_equals(typeof actualRange, "object", "typeof Range produced in actual iframe"); assert_equals(typeof expectedRange, "object", "typeof Range produced in expected iframe"); if (typeof expectedFrag == "string") { // Comparing makes no sense return; } assertNodesEqual(actualFrag, expectedFrag, "returned fragment"); }); fragTests[i].done(); } // First test a detached Range, synchronously test(function() { var range = document.createRange(); range.detach(); assert_array_equals(range.extractContents().childNodes, []); }, "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 = []; var fragTests = []; 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]); fragTests[i] = async_test("Returned fragment 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++) { testExtractContents(i); } } expectedIframe.src = "Range-test-iframe.html"; referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true)); } actualIframe.src = "Range-test-iframe.html"; </script>