Update web-platform-tests to revision 0d318188757a9c996e20b82db201fd04de5aa255

This commit is contained in:
James Graham 2015-03-27 09:15:38 +00:00
parent b2a5225831
commit 1a81b18b9f
12321 changed files with 544385 additions and 6 deletions

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<title>Range attributes</title>
<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
<meta name=timeout content=long>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<div id=log></div>
<script>
test(function() {
var r = document.createRange();
assert_equals(r.startContainer, document)
assert_equals(r.endContainer, document)
assert_equals(r.startOffset, 0)
assert_equals(r.endOffset, 0)
assert_true(r.collapsed)
r.detach()
assert_equals(r.startContainer, document)
assert_equals(r.endContainer, document)
assert_equals(r.startOffset, 0)
assert_equals(r.endOffset, 0)
assert_true(r.collapsed)
})
</script>

View file

@ -0,0 +1,454 @@
<!doctype html>
<title>Range.cloneContents() 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 myCloneContents(range) {
// "Let frag be a new DocumentFragment whose ownerDocument is the same as
// the ownerDocument of the context object's start node."
var ownerDoc = range.startContainer.nodeType == Node.DOCUMENT_NODE
? range.startContainer
: range.startContainer.ownerDocument;
var frag = ownerDoc.createDocumentFragment();
// "If the context object's start and end are the same, abort this method,
// returning frag."
if (range.startContainer == range.endContainer
&& range.startOffset == range.endOffset) {
return frag;
}
// "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 or Comment node:"
if (range.startContainer == range.endContainer
&& (range.startContainer.nodeType == Node.TEXT_NODE
|| range.startContainer.nodeType == Node.COMMENT_NODE)) {
// "Let clone be the result of calling cloneNode(false) on original
// start node."
var clone = originalStartNode.cloneNode(false);
// "Set the data of clone to the result of calling
// substringData(original start offset, original end offset original
// start offset) on original start node."
clone.data = originalStartNode.substringData(originalStartOffset,
originalEndOffset - originalStartOffset);
// "Append clone as the last child of frag."
frag.appendChild(clone);
// "Abort this method, returning frag."
return frag;
}
// "Let common ancestor equal original start node."
var commonAncestor = originalStartNode;
// "While common ancestor is not an ancestor container of original end
// node, set common ancestor to its own parent."
while (!isAncestorContainer(commonAncestor, originalEndNode)) {
commonAncestor = commonAncestor.parentNode;
}
// "If original start node is an ancestor container of original end node,
// let first partially contained child be null."
var firstPartiallyContainedChild;
if (isAncestorContainer(originalStartNode, originalEndNode)) {
firstPartiallyContainedChild = null;
// "Otherwise, let first partially contained child be the first child of
// common ancestor that is partially contained in the context object."
} else {
for (var i = 0; i < commonAncestor.childNodes.length; i++) {
if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
firstPartiallyContainedChild = commonAncestor.childNodes[i];
break;
}
}
if (!firstPartiallyContainedChild) {
throw "Spec bug: no first partially contained child!";
}
}
// "If original end node is an ancestor container of original start node,
// let last partially contained child be null."
var lastPartiallyContainedChild;
if (isAncestorContainer(originalEndNode, originalStartNode)) {
lastPartiallyContainedChild = null;
// "Otherwise, let last partially contained child be the last child of
// common ancestor that is partially contained in the context object."
} else {
for (var i = commonAncestor.childNodes.length - 1; i >= 0; i--) {
if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
lastPartiallyContainedChild = commonAncestor.childNodes[i];
break;
}
}
if (!lastPartiallyContainedChild) {
throw "Spec bug: no last partially contained child!";
}
}
// "Let contained children be a list of all children of common ancestor
// that are contained in the context object, in tree order."
//
// "If any member of contained children is a DocumentType, raise a
// HIERARCHY_REQUEST_ERR exception and abort these steps."
var containedChildren = [];
for (var i = 0; i < commonAncestor.childNodes.length; i++) {
if (isContained(commonAncestor.childNodes[i], range)) {
if (commonAncestor.childNodes[i].nodeType
== Node.DOCUMENT_TYPE_NODE) {
return "HIERARCHY_REQUEST_ERR";
}
containedChildren.push(commonAncestor.childNodes[i]);
}
}
// "If first partially contained child is a Text or Comment node:"
if (firstPartiallyContainedChild
&& (firstPartiallyContainedChild.nodeType == Node.TEXT_NODE
|| firstPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
// "Let clone be the result of calling cloneNode(false) on original
// start node."
var clone = originalStartNode.cloneNode(false);
// "Set the data of clone to the result of calling substringData() on
// original start node, with original start offset as the first
// argument and (length of original start node original start offset)
// as the second."
clone.data = originalStartNode.substringData(originalStartOffset,
nodeLength(originalStartNode) - originalStartOffset);
// "Append clone as the last child of frag."
frag.appendChild(clone);
// "Otherwise, if first partially contained child is not null:"
} else if (firstPartiallyContainedChild) {
// "Let clone be the result of calling cloneNode(false) on first
// partially contained child."
var clone = firstPartiallyContainedChild.cloneNode(false);
// "Append clone as the last child of frag."
frag.appendChild(clone);
// "Let subrange be a new Range whose start is (original start node,
// original start offset) and whose end is (first partially contained
// child, length of first partially contained child)."
var subrange = ownerDoc.createRange();
subrange.setStart(originalStartNode, originalStartOffset);
subrange.setEnd(firstPartiallyContainedChild,
nodeLength(firstPartiallyContainedChild));
// "Let subfrag be the result of calling cloneContents() on
// subrange."
var subfrag = myCloneContents(subrange);
// "For each child of subfrag, in order, append that child to clone as
// its last child."
for (var i = 0; i < subfrag.childNodes.length; i++) {
clone.appendChild(subfrag.childNodes[i]);
}
}
// "For each contained child in contained children:"
for (var i = 0; i < containedChildren.length; i++) {
// "Let clone be the result of calling cloneNode(true) of contained
// child."
var clone = containedChildren[i].cloneNode(true);
// "Append clone as the last child of frag."
frag.appendChild(clone);
}
// "If last partially contained child is a Text or Comment node:"
if (lastPartiallyContainedChild
&& (lastPartiallyContainedChild.nodeType == Node.TEXT_NODE
|| lastPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
// "Let clone be the result of calling cloneNode(false) on original
// end node."
var clone = originalEndNode.cloneNode(false);
// "Set the data of clone to the result of calling substringData(0,
// original end offset) on original end node."
clone.data = originalEndNode.substringData(0, originalEndOffset);
// "Append clone as the last child of frag."
frag.appendChild(clone);
// "Otherwise, if last partially contained child is not null:"
} else if (lastPartiallyContainedChild) {
// "Let clone be the result of calling cloneNode(false) on last
// partially contained child."
var clone = lastPartiallyContainedChild.cloneNode(false);
// "Append clone as the last child of frag."
frag.appendChild(clone);
// "Let subrange be a new Range whose start is (last partially
// contained child, 0) and whose end is (original end node, original
// end offset)."
var subrange = ownerDoc.createRange();
subrange.setStart(lastPartiallyContainedChild, 0);
subrange.setEnd(originalEndNode, originalEndOffset);
// "Let subfrag be the result of calling cloneContents() on
// subrange."
var subfrag = myCloneContents(subrange);
// "For each child of subfrag, in order, append that child to clone as
// its last child."
for (var i = 0; i < subfrag.childNodes.length; i++) {
clone.appendChild(subfrag.childNodes[i]);
}
}
// "Return frag."
return frag;
}
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 testCloneContents(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 cloneContents()");
assert_equals(expectedIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for simulated cloneContents()");
assert_equals(typeof actualRange, "object",
"typeof Range produced in actual iframe");
assert_equals(typeof expectedRange, "object",
"typeof Range produced in expected iframe");
// NOTE: We could just assume that cloneContents() doesn't change
// anything. That would simplify these tests, taken in isolation. But
// once we've already set up the whole apparatus for extractContents()
// and deleteContents(), we just reuse it here, on the theory of "why
// not test some more stuff if it's easy to do".
//
// 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 = myCloneContents(expectedRange);
if (typeof expectedFrag == "string") {
assert_throws(expectedFrag, function() {
actualRange.cloneContents();
});
} else {
actualFrag = actualRange.cloneContents();
}
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 cloneContents() (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 cloneContents()");
assert_equals(expectedIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for simulated cloneContents()");
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.startOffset, expectedRange.startOffset,
"Unexpected startOffset after cloneContents()");
// 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 cloneContents(), 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 cloneContents()");
assert_equals(expectedIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for simulated cloneContents()");
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 Range that has the no-op detach() called on it, synchronously
test(function() {
var range = document.createRange();
range.detach();
assert_array_equals(range.cloneContents().childNodes, []);
}, "Range.detach()");
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++) {
testCloneContents(i);
}
}
expectedIframe.src = "Range-test-iframe.html";
referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
}
actualIframe.src = "Range-test-iframe.html";
</script>

View file

@ -0,0 +1,112 @@
<!doctype html>
<title>Range.cloneRange() and document.createRange() 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";
function testCloneRange(rangeEndpoints) {
var range;
if (rangeEndpoints == "detached") {
range = document.createRange();
range.detach();
var clonedRange = range.cloneRange();
assert_equals(clonedRange.startContainer, range.startContainer,
"startContainers must be equal after cloneRange()");
assert_equals(clonedRange.startOffset, range.startOffset,
"startOffsets must be equal after cloneRange()");
assert_equals(clonedRange.endContainer, range.endContainer,
"endContainers must be equal after cloneRange()");
assert_equals(clonedRange.endOffset, range.endOffset,
"endOffsets must be equal after cloneRange()");
return;
}
// Have to account for Ranges involving Documents! We could just create
// the Range from the current document unconditionally, but some browsers
// (WebKit) don't implement setStart() and setEnd() per spec and will throw
// spurious exceptions at the time of this writing. No need to mask other
// bugs.
var ownerDoc = rangeEndpoints[0].nodeType == Node.DOCUMENT_NODE
? rangeEndpoints[0]
: rangeEndpoints[0].ownerDocument;
range = ownerDoc.createRange();
// Here we throw in some createRange() tests, because why not. Have to
// test it someplace.
assert_equals(range.startContainer, ownerDoc,
"doc.createRange() must create Range whose startContainer is doc");
assert_equals(range.endContainer, ownerDoc,
"doc.createRange() must create Range whose endContainer is doc");
assert_equals(range.startOffset, 0,
"doc.createRange() must create Range whose startOffset is 0");
assert_equals(range.endOffset, 0,
"doc.createRange() must create Range whose endOffset is 0");
range.setStart(rangeEndpoints[0], rangeEndpoints[1]);
range.setEnd(rangeEndpoints[2], rangeEndpoints[3]);
// Make sure we bail out now if setStart or setEnd are buggy, so it doesn't
// create misleading failures later.
assert_equals(range.startContainer, rangeEndpoints[0],
"Sanity check on setStart()");
assert_equals(range.startOffset, rangeEndpoints[1],
"Sanity check on setStart()");
assert_equals(range.endContainer, rangeEndpoints[2],
"Sanity check on setEnd()");
assert_equals(range.endOffset, rangeEndpoints[3],
"Sanity check on setEnd()");
var clonedRange = range.cloneRange();
assert_equals(clonedRange.startContainer, range.startContainer,
"startContainers must be equal after cloneRange()");
assert_equals(clonedRange.startOffset, range.startOffset,
"startOffsets must be equal after cloneRange()");
assert_equals(clonedRange.endContainer, range.endContainer,
"endContainers must be equal after cloneRange()");
assert_equals(clonedRange.endOffset, range.endOffset,
"endOffsets must be equal after cloneRange()");
// Make sure that modifying one doesn't affect the other.
var testNode1 = ownerDoc.createTextNode("testing");
var testNode2 = ownerDoc.createTextNode("testing with different length");
range.setStart(testNode1, 1);
range.setEnd(testNode1, 2);
assert_equals(clonedRange.startContainer, rangeEndpoints[0],
"Modifying a Range must not modify its clone's startContainer");
assert_equals(clonedRange.startOffset, rangeEndpoints[1],
"Modifying a Range must not modify its clone's startOffset");
assert_equals(clonedRange.endContainer, rangeEndpoints[2],
"Modifying a Range must not modify its clone's endContainer");
assert_equals(clonedRange.endOffset, rangeEndpoints[3],
"Modifying a Range must not modify its clone's endOffset");
clonedRange.setStart(testNode2, 3);
clonedRange.setStart(testNode2, 4);
assert_equals(range.startContainer, testNode1,
"Modifying a clone must not modify the original Range's startContainer");
assert_equals(range.startOffset, 1,
"Modifying a clone must not modify the original Range's startOffset");
assert_equals(range.endContainer, testNode1,
"Modifying a clone must not modify the original Range's endContainer");
assert_equals(range.endOffset, 2,
"Modifying a clone must not modify the original Range's endOffset");
}
var tests = [];
for (var i = 0; i < testRanges.length; i++) {
tests.push([
"Range " + i + " " + testRanges[i],
eval(testRanges[i])
]);
}
generate_tests(testCloneRange, tests);
testDiv.style.display = "none";
</script>

View file

@ -0,0 +1,75 @@
<!doctype html>
<title>Range.collapse() and .collapsed 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";
function testCollapse(rangeEndpoints, toStart) {
var range;
if (rangeEndpoints == "detached") {
range = document.createRange();
range.detach(); // should be a no-op and therefore the following should not throw
range.collapse(toStart);
assert_equals(true, range.collapsed);
}
// Have to account for Ranges involving Documents!
var ownerDoc = rangeEndpoints[0].nodeType == Node.DOCUMENT_NODE
? rangeEndpoints[0]
: rangeEndpoints[0].ownerDocument;
range = ownerDoc.createRange();
range.setStart(rangeEndpoints[0], rangeEndpoints[1]);
range.setEnd(rangeEndpoints[2], rangeEndpoints[3]);
var expectedContainer = toStart ? range.startContainer : range.endContainer;
var expectedOffset = toStart ? range.startOffset : range.endOffset;
assert_equals(range.collapsed, range.startContainer == range.endContainer
&& range.startOffset == range.endOffset,
"collapsed must be true if and only if the start and end are equal");
if (toStart === undefined) {
range.collapse();
} else {
range.collapse(toStart);
}
assert_equals(range.startContainer, expectedContainer,
"Wrong startContainer");
assert_equals(range.endContainer, expectedContainer,
"Wrong endContainer");
assert_equals(range.startOffset, expectedOffset,
"Wrong startOffset");
assert_equals(range.endOffset, expectedOffset,
"Wrong endOffset");
assert_true(range.collapsed,
".collapsed must be set after .collapsed()");
}
var tests = [];
for (var i = 0; i < testRanges.length; i++) {
tests.push([
"Range " + i + " " + testRanges[i] + ", toStart true",
eval(testRanges[i]),
true
]);
tests.push([
"Range " + i + " " + testRanges[i] + ", toStart false",
eval(testRanges[i]),
false
]);
tests.push([
"Range " + i + " " + testRanges[i] + ", toStart omitted",
eval(testRanges[i]),
undefined
]);
}
generate_tests(testCollapse, tests);
testDiv.style.display = "none";
</script>

View file

@ -0,0 +1,33 @@
<!doctype html>
<title>Range.commonAncestorContainer</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<div id=log></div>
<script>
test(function() {
var range = document.createRange();
range.detach();
assert_equals(range.commonAncestorContainer, document);
}, "Detached Range")
test(function() {
var df = document.createDocumentFragment();
var foo = df.appendChild(document.createElement("foo"));
foo.appendChild(document.createTextNode("Foo"));
var bar = df.appendChild(document.createElement("bar"));
bar.appendChild(document.createComment("Bar"));
[
// start node, start offset, end node, end offset, expected cAC
[foo, 0, bar, 0, df],
[foo, 0, foo.firstChild, 3, foo],
[foo.firstChild, 0, bar, 0, df],
[foo.firstChild, 3, bar.firstChild, 2, df]
].forEach(function(t) {
test(function() {
var range = document.createRange();
range.setStart(t[0], t[1]);
range.setEnd(t[2], t[3]);
assert_equals(range.commonAncestorContainer, t[4]);
})
});
}, "Normal Ranges")
</script>

View file

@ -0,0 +1,40 @@
<!doctype html>
<title>Range.commonAncestorContainer 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";
testRanges.unshift("[detached]");
for (var i = 0; i < testRanges.length; i++) {
test(function() {
var range;
if (i == 0) {
range = document.createRange();
range.detach();
} else {
range = rangeFromEndpoints(eval(testRanges[i]));
}
// "Let container be start node."
var container = range.startContainer;
// "While container is not an inclusive ancestor of end node, let
// container be container's parent."
while (container != range.endContainer
&& !isAncestor(container, range.endContainer)) {
container = container.parentNode;
}
// "Return container."
assert_equals(range.commonAncestorContainer, container);
}, i + ": range " + testRanges[i]);
}
testDiv.style.display = "none";
</script>

View file

@ -0,0 +1,182 @@
<!doctype html>
<title>Range.compareBoundaryPoints() 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";
var testRangesCached = [];
testRangesCached.push(document.createRange());
testRangesCached[0].detach();
for (var i = 0; i < testRangesShort.length; i++) {
try {
testRangesCached.push(rangeFromEndpoints(eval(testRangesShort[i])));
} catch(e) {
testRangesCached.push(null);
}
}
var testRangesCachedClones = [];
testRangesCachedClones.push(document.createRange());
testRangesCachedClones[0].detach();
for (var i = 1; i < testRangesCached.length; i++) {
if (testRangesCached[i]) {
testRangesCachedClones.push(testRangesCached[i].cloneRange());
} else {
testRangesCachedClones.push(null);
}
}
// We want to run a whole bunch of extra tests with invalid "how" values (not
// 0-3), but it's excessive to run them for every single pair of ranges --
// there are too many of them. So just run them for a handful of the tests.
var extraTests = [0, // detached
1 + testRanges.indexOf("[paras[0].firstChild, 2, paras[0].firstChild, 8]"),
1 + testRanges.indexOf("[paras[0].firstChild, 3, paras[3], 1]"),
1 + testRanges.indexOf("[testDiv, 0, comment, 5]"),
1 + testRanges.indexOf("[foreignDoc.documentElement, 0, foreignDoc.documentElement, 1]")];
for (var i = 0; i < testRangesCached.length; i++) {
var range1 = testRangesCached[i];
var range1Desc = i + " " + (i == 0 ? "[detached]" : testRanges[i - 1]);
for (var j = 0; j <= testRangesCachedClones.length; j++) {
var range2;
var range2Desc;
if (j == testRangesCachedClones.length) {
range2 = range1;
range2Desc = "same as first range";
} else {
range2 = testRangesCachedClones[j];
range2Desc = j + " " + (j == 0 ? "[detached]" : testRanges[j - 1]);
}
var hows = [Range.START_TO_START, Range.START_TO_END, Range.END_TO_END,
Range.END_TO_START];
if (extraTests.indexOf(i) != -1 && extraTests.indexOf(j) != -1) {
// TODO: Make some type of reusable utility function to do this
// work.
hows.push(-1, 4, 5, NaN, -0, +Infinity, -Infinity);
[65536, -65536, 65536*65536, 0.5, -0.5, -72.5].forEach(function(addend) {
hows.push(-1 + addend, 0 + addend, 1 + addend,
2 + addend, 3 + addend, 4 + addend);
});
hows.forEach(function(how) { hows.push(String(how)) });
hows.push("6.5536e4", null, undefined, true, false, "", "quasit");
}
for (var k = 0; k < hows.length; k++) {
var how = hows[k];
test(function() {
assert_not_equals(range1, null,
"Creating context range threw an exception");
assert_not_equals(range2, null,
"Creating argument range threw an exception");
// Convert how per WebIDL. TODO: Make some type of reusable
// utility function to do this work.
// "Let number be the result of calling ToNumber on the input
// argument."
var convertedHow = Number(how);
// "If number is NaN, +0, 0, +∞, or −∞, return +0."
if (isNaN(convertedHow)
|| convertedHow == 0
|| convertedHow == Infinity
|| convertedHow == -Infinity) {
convertedHow = 0;
} else {
// "Let posInt be sign(number) * floor(abs(number))."
var posInt = (convertedHow < 0 ? -1 : 1) * Math.floor(Math.abs(convertedHow));
// "Let int16bit be posInt modulo 2^16; that is, a finite
// integer value k of Number type with positive sign and
// less than 2^16 in magnitude such that the mathematical
// difference of posInt and k is mathematically an integer
// multiple of 2^16."
//
// "Return int16bit."
convertedHow = posInt % 65536;
if (convertedHow < 0) {
convertedHow += 65536;
}
}
// Now to the actual algorithm.
// "If how is not one of
// START_TO_START,
// START_TO_END,
// END_TO_END, and
// END_TO_START,
// throw a "NotSupportedError" exception and terminate these
// steps."
if (convertedHow != Range.START_TO_START
&& convertedHow != Range.START_TO_END
&& convertedHow != Range.END_TO_END
&& convertedHow != Range.END_TO_START) {
assert_throws("NOT_SUPPORTED_ERR", function() {
range1.compareBoundaryPoints(how, range2);
}, "NotSupportedError required if first parameter doesn't convert to 0-3 per WebIDL");
return;
}
// "If context object's root is not the same as sourceRange's
// root, throw a "WrongDocumentError" exception and terminate
// these steps."
if (furthestAncestor(range1.startContainer) != furthestAncestor(range2.startContainer)) {
assert_throws("WRONG_DOCUMENT_ERR", function() {
range1.compareBoundaryPoints(how, range2);
}, "WrongDocumentError required if the ranges don't share a root");
return;
}
// "If how is:
// START_TO_START:
// Let this point be the context object's start.
// Let other point be sourceRange's start.
// START_TO_END:
// Let this point be the context object's end.
// Let other point be sourceRange's start.
// END_TO_END:
// Let this point be the context object's end.
// Let other point be sourceRange's end.
// END_TO_START:
// Let this point be the context object's start.
// Let other point be sourceRange's end."
var thisPoint = convertedHow == Range.START_TO_START || convertedHow == Range.END_TO_START
? [range1.startContainer, range1.startOffset]
: [range1.endContainer, range1.endOffset];
var otherPoint = convertedHow == Range.START_TO_START || convertedHow == Range.START_TO_END
? [range2.startContainer, range2.startOffset]
: [range2.endContainer, range2.endOffset];
// "If the position of this point relative to other point is
// before
// Return 1.
// equal
// Return 0.
// after
// Return 1."
var position = getPosition(thisPoint[0], thisPoint[1], otherPoint[0], otherPoint[1]);
var expected;
if (position == "before") {
expected = -1;
} else if (position == "equal") {
expected = 0;
} else if (position == "after") {
expected = 1;
}
assert_equals(range1.compareBoundaryPoints(how, range2), expected,
"Wrong return value");
}, i + "," + j + "," + k + ": context range " + range1Desc + ", argument range " + range2Desc + ", how " + format_value(how));
}
}
}
testDiv.style.display = "none";
</script>

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<title>Range.comparePoint</title>
<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
<meta name=timeout content=long>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<div id=log></div>
<script>
test(function() {
var r = document.createRange();
r.detach()
assert_equals(r.comparePoint(document.body, 0), 1)
})
test(function() {
var r = document.createRange();
assert_throws(new TypeError(), function() { r.comparePoint(null, 0) })
})
test(function() {
var doc = document.implementation.createHTMLDocument("tralala")
var r = document.createRange();
assert_throws("WRONG_DOCUMENT_ERR", function() { r.comparePoint(doc.body, 0) })
})
</script>

View file

@ -0,0 +1,92 @@
<!doctype html>
<title>Range.comparePoint() 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";
// Will be filled in on the first run for that range
var testRangesCached = [];
for (var i = 0; i < testPoints.length; i++) {
var node = eval(testPoints[i])[0];
var offset = eval(testPoints[i])[1];
// comparePoint is an unsigned long, so per WebIDL, we need to treat it as
// though it wrapped to an unsigned 32-bit integer.
var normalizedOffset = offset % Math.pow(2, 32);
if (normalizedOffset < 0) {
normalizedOffset += Math.pow(2, 32);
}
for (var j = 0; j < testRanges.length; j++) {
test(function() {
if (testRangesCached[j] === undefined) {
try {
testRangesCached[j] = rangeFromEndpoints(eval(testRanges[i]));
} catch(e) {
testRangesCached[j] = null;
}
}
assert_not_equals(testRangesCached[j], null,
"Setting up the range failed");
var range = testRangesCached[j].cloneRange();
// "If node's root is different from the context object's root,
// throw a "WrongDocumentError" exception and terminate these
// steps."
if (furthestAncestor(node) !== furthestAncestor(range.startContainer)) {
assert_throws("WRONG_DOCUMENT_ERR", function() {
range.comparePoint(node, offset);
}, "Must throw WrongDocumentError if node and range have different roots");
return;
}
// "If node is a doctype, throw an "InvalidNodeTypeError" exception
// and terminate these steps."
if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
assert_throws("INVALID_NODE_TYPE_ERR", function() {
range.comparePoint(node, offset);
}, "Must throw InvalidNodeTypeError if node is a doctype");
return;
}
// "If offset is greater than node's length, throw an
// "IndexSizeError" exception and terminate these steps."
if (normalizedOffset > nodeLength(node)) {
assert_throws("INDEX_SIZE_ERR", function() {
range.comparePoint(node, offset);
}, "Must throw IndexSizeError if offset is greater than length");
return;
}
// "If (node, offset) is before start, return 1 and terminate
// these steps."
if (getPosition(node, normalizedOffset, range.startContainer, range.startOffset) === "before") {
assert_equals(range.comparePoint(node, offset), -1,
"Must return -1 if point is before start");
return;
}
// "If (node, offset) is after end, return 1 and terminate these
// steps."
if (getPosition(node, normalizedOffset, range.endContainer, range.endOffset) === "after") {
assert_equals(range.comparePoint(node, offset), 1,
"Must return 1 if point is after end");
return;
}
// "Return 0."
assert_equals(range.comparePoint(node, offset), 0,
"Must return 0 if point is neither before start nor after end");
}, "Point " + i + " " + testPoints[i] + ", range " + j + " " + testRanges[j]);
}
}
testDiv.style.display = "none";
</script>

View file

@ -0,0 +1,331 @@
<!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 or Comment node, call deleteData(original start offset, original
// end offset original start offset) on that node, and abort these
// steps."
if (originalStartNode == originalEndNode
&& (range.startContainer.nodeType == Node.TEXT_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 or Comment node, run deleteData() on
// it, with original start offset as the first argument and (length of
// original start node original start offset) as the second."
if (originalStartNode.nodeType == Node.TEXT_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 or Comment node, run deleteData(0,
// original end offset) on it."
if (originalEndNode.nodeType == Node.TEXT_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>

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<title>Range.detach</title>
<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
<meta name=timeout content=long>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<div id=log></div>
<script>
test(function() {
var r = document.createRange();
r.detach()
r.detach()
})
</script>

View file

@ -0,0 +1,248 @@
<!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>

View file

@ -0,0 +1,286 @@
<!doctype html>
<title>Range.insertNode() 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,16"). Only that test will be run. Then you can look at the resulting
iframes 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);
function restoreIframe(iframe, i, j) {
// 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 = testRangesShort[i];
iframe.contentWindow.testNodeInput = testNodesShort[j];
iframe.contentWindow.run();
}
function testInsertNode(i, j) {
var actualRange;
var expectedRange;
var actualNode;
var expectedNode;
var actualRoots = [];
var expectedRoots = [];
var detached = false;
domTests[i][j].step(function() {
restoreIframe(actualIframe, i, j);
restoreIframe(expectedIframe, i, j);
actualRange = actualIframe.contentWindow.testRange;
expectedRange = expectedIframe.contentWindow.testRange;
actualNode = actualIframe.contentWindow.testNode;
expectedNode = expectedIframe.contentWindow.testNode;
try {
actualRange.collapsed;
} catch (e) {
detached = true;
}
assert_equals(actualIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for actual insertNode()");
assert_equals(expectedIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for simulated insertNode()");
assert_equals(typeof actualRange, "object",
"typeof Range produced in actual iframe");
assert_false(actualRange === null,
"Range produced in actual iframe was null");
assert_equals(typeof expectedRange, "object",
"typeof Range produced in expected iframe");
assert_false(expectedRange === null,
"Range produced in expected iframe was null");
assert_equals(typeof actualNode, "object",
"typeof Node produced in actual iframe");
assert_false(actualNode === null,
"Node produced in actual iframe was null");
assert_equals(typeof expectedNode, "object",
"typeof Node produced in expected iframe");
assert_false(expectedNode === null,
"Node produced in expected iframe was null");
// We want to test that the trees containing the ranges are equal, and
// also the trees containing the moved nodes. These might not be the
// same, if we're inserting a node from a detached tree or a different
// document.
//
// Detached ranges are always in the contentDocument.
if (detached) {
actualRoots.push(actualIframe.contentDocument);
expectedRoots.push(expectedIframe.contentDocument);
} else {
actualRoots.push(furthestAncestor(actualRange.startContainer));
expectedRoots.push(furthestAncestor(expectedRange.startContainer));
}
if (furthestAncestor(actualNode) != actualRoots[0]) {
actualRoots.push(furthestAncestor(actualNode));
}
if (furthestAncestor(expectedNode) != expectedRoots[0]) {
expectedRoots.push(furthestAncestor(expectedNode));
}
assert_equals(actualRoots.length, expectedRoots.length,
"Either the actual node and actual range are in the same tree but the expected are in different trees, or vice versa");
// This doctype stuff is to work around the fact that Opera 11.00 will
// move around doctypes within a document, even to totally invalid
// positions, but it won't allow a new doctype to be added to a
// document in any way I can figure out. So if we try moving a doctype
// to some invalid place, in Opera it will actually succeed, and then
// restoreIframe() will remove the doctype along with the root element,
// and then nothing can re-add the doctype. So instead, we catch it
// during the test itself and move it back to the right place while we
// still can.
//
// I spent *way* too much time debugging and working around this bug.
var actualDoctype = actualIframe.contentDocument.doctype;
var expectedDoctype = expectedIframe.contentDocument.doctype;
var result;
try {
result = myInsertNode(expectedRange, expectedNode);
} catch (e) {
if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
}
throw e;
}
if (typeof result == "string") {
assert_throws(result, function() {
try {
actualRange.insertNode(actualNode);
} catch (e) {
if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
}
if (actualDoctype != actualIframe.contentDocument.firstChild) {
actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
}
throw e;
}
}, "A " + result + " DOMException must be thrown in this case");
// Don't return, we still need to test DOM equality
} else {
try {
actualRange.insertNode(actualNode);
} catch (e) {
if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
}
if (actualDoctype != actualIframe.contentDocument.firstChild) {
actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
}
throw e;
}
}
for (var k = 0; k < actualRoots.length; k++) {
assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
}
});
domTests[i][j].done();
positionTests[i][j].step(function() {
assert_equals(actualIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for actual insertNode()");
assert_equals(expectedIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for simulated insertNode()");
assert_equals(typeof actualRange, "object",
"typeof Range produced in actual iframe");
assert_false(actualRange === null,
"Range produced in actual iframe was null");
assert_equals(typeof expectedRange, "object",
"typeof Range produced in expected iframe");
assert_false(expectedRange === null,
"Range produced in expected iframe was null");
assert_equals(typeof actualNode, "object",
"typeof Node produced in actual iframe");
assert_false(actualNode === null,
"Node produced in actual iframe was null");
assert_equals(typeof expectedNode, "object",
"typeof Node produced in expected iframe");
assert_false(expectedNode === null,
"Node produced in expected iframe was null");
for (var k = 0; k < actualRoots.length; k++) {
assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
}
if (detached) {
// No further tests we can do
return;
}
assert_equals(actualRange.startOffset, expectedRange.startOffset,
"Unexpected startOffset after insertNode()");
assert_equals(actualRange.endOffset, expectedRange.endOffset,
"Unexpected endOffset after insertNode()");
// 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 insertNode(), 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][j].done();
}
testRanges.unshift('"detached"');
var iStart = 0;
var iStop = testRangesShort.length;
var jStart = 0;
var jStop = testNodesShort.length;
if (/subtest=[0-9]+,[0-9]+/.test(location.search)) {
var matches = /subtest=([0-9]+),([0-9]+)/.exec(location.search);
iStart = Number(matches[1]);
iStop = Number(matches[1]) + 1;
jStart = Number(matches[2]) + 0;
jStop = Number(matches[2]) + 1;
}
var domTests = [];
var positionTests = [];
for (var i = iStart; i < iStop; i++) {
domTests[i] = [];
positionTests[i] = [];
for (var j = jStart; j < jStop; j++) {
domTests[i][j] = async_test(i + "," + j + ": resulting DOM for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
positionTests[i][j] = async_test(i + "," + j + ": resulting range position for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
}
}
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);
var referenceDoc = document.implementation.createHTMLDocument("");
referenceDoc.removeChild(referenceDoc.documentElement);
actualIframe.onload = function() {
expectedIframe.onload = function() {
for (var i = iStart; i < iStop; i++) {
for (var j = jStart; j < jStop; j++) {
testInsertNode(i, j);
}
}
}
expectedIframe.src = "Range-test-iframe.html";
referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
}
actualIframe.src = "Range-test-iframe.html";
</script>

View file

@ -0,0 +1,25 @@
<!doctype html>
<title>Range.intersectsNode</title>
<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com">
<meta name=timeout content=long>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<div id=log></div>
<script>
test(function() {
var r = document.createRange();
assert_throws(new TypeError(), function() { r.intersectsNode(); });
assert_throws(new TypeError(), function() { r.intersectsNode(null); });
assert_throws(new TypeError(), function() { r.intersectsNode(undefined); });
assert_throws(new TypeError(), function() { r.intersectsNode(42); });
assert_throws(new TypeError(), function() { r.intersectsNode("foo"); });
assert_throws(new TypeError(), function() { r.intersectsNode({}); });
r.detach();
assert_throws(new TypeError(), function() { r.intersectsNode(); });
assert_throws(new TypeError(), function() { r.intersectsNode(null); });
assert_throws(new TypeError(), function() { r.intersectsNode(undefined); });
assert_throws(new TypeError(), function() { r.intersectsNode(42); });
assert_throws(new TypeError(), function() { r.intersectsNode("foo"); });
assert_throws(new TypeError(), function() { r.intersectsNode({}); });
}, "Calling intersectsNode without an argument or with an invalid argument should throw a TypeError.")
</script>

View file

@ -0,0 +1,78 @@
<!doctype html>
<title>Range.intersectsNode() 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";
// Will be filled in on the first run for that range
var testRangesCached = [];
for (var i = 0; i < testNodes.length; i++) {
var node = eval(testNodes[i]);
// "If the detached flag is set, throw an "InvalidStateError" exception and
// terminate these steps."
test(function() {
var range = document.createRange();
range.detach();
assert_equals(range.intersectsNode(node), node === document);
}, "Node " + i + " " + testNodes[i] + ", detached range");
for (var j = 0; j < testRanges.length; j++) {
test(function() {
if (testRangesCached[j] === undefined) {
try {
testRangesCached[j] = rangeFromEndpoints(eval(testRanges[i]));
} catch(e) {
testRangesCached[j] = null;
}
}
assert_not_equals(testRangesCached[j], null,
"Setting up the range failed");
var range = testRangesCached[j].cloneRange();
// "If node's root is different from the context object's root,
// return false and terminate these steps."
if (furthestAncestor(node) !== furthestAncestor(range.startContainer)) {
assert_equals(range.intersectsNode(node), false,
"Must return false if node and range have different roots");
return;
}
// "Let parent be node's parent."
var parent_ = node.parentNode;
// "If parent is null, return true and terminate these steps."
if (!parent_) {
assert_equals(range.intersectsNode(node), true,
"Must return true if node's parent is null");
return;
}
// "Let offset be node's index."
var offset = indexOf(node);
// "If (parent, offset) is before end and (parent, offset + 1) is
// after start, return true and terminate these steps."
if (getPosition(parent_, offset, range.endContainer, range.endOffset) === "before"
&& getPosition(parent_, offset + 1, range.startContainer, range.startOffset) === "after") {
assert_equals(range.intersectsNode(node), true,
"Must return true if (parent, offset) is before range end and (parent, offset + 1) is after range start");
return;
}
// "Return false."
assert_equals(range.intersectsNode(node), false,
"Must return false if (parent, offset) is not before range end or (parent, offset + 1) is not after range start");
}, "Node " + i + " " + testNodes[i] + ", range " + j + " " + testRanges[j]);
}
}
testDiv.style.display = "none";
</script>

View file

@ -0,0 +1,83 @@
<!doctype html>
<title>Range.isPointInRange() 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";
var testRangesCached = [];
test(function() {
for (var j = 0; j < testRanges.length; j++) {
test(function() {
testRangesCached[j] = rangeFromEndpoints(eval(testRanges[j]));
}, "Set up for range " + j + " " + testRanges[j]);
}
var detachedRange = document.createRange();
detachedRange.detach();
testRanges.push("detached");
testRangesCached.push(detachedRange);
}, "Setup");
for (var i = 0; i < testPoints.length; i++) {
var node = eval(testPoints[i])[0];
var offset = eval(testPoints[i])[1];
// isPointInRange is an unsigned long, so per WebIDL, we need to treat it
// as though it wrapped to an unsigned 32-bit integer.
var normalizedOffset = offset % Math.pow(2, 32);
if (normalizedOffset < 0) {
normalizedOffset += Math.pow(2, 32);
}
for (var j = 0; j < testRanges.length; j++) {
test(function() {
var range = testRangesCached[j].cloneRange();
// "If node's root is different from the context object's root,
// return false and terminate these steps."
if (furthestAncestor(node) !== furthestAncestor(range.startContainer)) {
assert_false(range.isPointInRange(node, offset),
"Must return false if node has a different root from the context object");
return;
}
// "If node is a doctype, throw an "InvalidNodeTypeError" exception
// and terminate these steps."
if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
assert_throws("INVALID_NODE_TYPE_ERR", function() {
range.isPointInRange(node, offset);
}, "Must throw InvalidNodeTypeError if node is a doctype");
return;
}
// "If offset is greater than node's length, throw an
// "IndexSizeError" exception and terminate these steps."
if (normalizedOffset > nodeLength(node)) {
assert_throws("INDEX_SIZE_ERR", function() {
range.isPointInRange(node, offset);
}, "Must throw IndexSizeError if offset is greater than length");
return;
}
// "If (node, offset) is before start or after end, return false
// and terminate these steps."
if (getPosition(node, normalizedOffset, range.startContainer, range.startOffset) === "before"
|| getPosition(node, normalizedOffset, range.endContainer, range.endOffset) === "after") {
assert_false(range.isPointInRange(node, offset),
"Must return false if point is before start or after end");
return;
}
// "Return true."
assert_true(range.isPointInRange(node, offset),
"Must return true if point is not before start, after end, or in different tree");
}, "Point " + i + " " + testPoints[i] + ", range " + j + " " + testRanges[j]);
}
}
testDiv.style.display = "none";
</script>

View file

@ -0,0 +1,959 @@
<!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:
//
// * replaceWholeText() tests
// * 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() {
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),
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);
assert_equals(getSelection().rangeCount, 1,
"Sanity check: selection must have exactly one range after adding the range");
assert_equals(getSelection().getRangeAt(0), range,
"Sanity check: selection's range must initially be the same as the range we added");
assert_equals(range.startContainer, startContainer,
"Sanity check: range's startContainer must initially be the one we set");
assert_equals(range.endContainer, endContainer,
"Sanity check: range's endContainer must initially be the one we set");
assert_equals(range.startOffset, startOffset,
"Sanity check: range's startOffset must initially be the one we set");
assert_equals(range.endOffset, endOffset,
"Sanity check: range's endOffset must initially be the one we set");
}
var expected = callback();
if (useSelection) {
assert_equals(getSelection().getRangeAt(0), range,
"The range we added must not be removed from the selection");
}
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>

View file

@ -0,0 +1,99 @@
<!doctype html>
<title>Range.selectNode() and .selectNodeContents() 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";
function testSelectNode(range, node) {
try {
range.collapsed;
} catch (e) {
// Range is detached
assert_throws("INVALID_STATE_ERR", function () {
range.selectNode(node);
}, "selectNode() on a detached node must throw INVALID_STATE_ERR");
assert_throws("INVALID_STATE_ERR", function () {
range.selectNodeContents(node);
}, "selectNodeContents() on a detached node must throw INVALID_STATE_ERR");
return;
}
if (!node.parentNode) {
assert_throws("INVALID_NODE_TYPE_ERR", function() {
range.selectNode(node);
}, "selectNode() on a node with no parent must throw INVALID_NODE_TYPE_ERR");
} else {
var index = 0;
while (node.parentNode.childNodes[index] != node) {
index++;
}
range.selectNode(node);
assert_equals(range.startContainer, node.parentNode,
"After selectNode(), startContainer must equal parent node");
assert_equals(range.endContainer, node.parentNode,
"After selectNode(), endContainer must equal parent node");
assert_equals(range.startOffset, index,
"After selectNode(), startOffset must be index of node in parent (" + index + ")");
assert_equals(range.endOffset, index + 1,
"After selectNode(), endOffset must be one plus index of node in parent (" + (index + 1) + ")");
}
if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
assert_throws("INVALID_NODE_TYPE_ERR", function () {
range.selectNodeContents(node);
}, "selectNodeContents() on a doctype must throw INVALID_NODE_TYPE_ERR");
} else {
range.selectNodeContents(node);
assert_equals(range.startContainer, node,
"After selectNodeContents(), startContainer must equal node");
assert_equals(range.endContainer, node,
"After selectNodeContents(), endContainer must equal node");
assert_equals(range.startOffset, 0,
"After selectNodeContents(), startOffset must equal 0");
var len = nodeLength(node);
assert_equals(range.endOffset, len,
"After selectNodeContents(), endOffset must equal node length (" + len + ")");
}
}
var range = document.createRange();
var foreignRange = foreignDoc.createRange();
var xmlRange = xmlDoc.createRange();
var detachedRange = document.createRange();
detachedRange.detach();
var tests = [];
function testTree(root, marker) {
if (root.nodeType == Node.ELEMENT_NODE && root.id == "log") {
// This is being modified during the tests, so let's not test it.
return;
}
tests.push([marker + root.nodeName.toLowerCase() + " node, current doc's range, type " + root.nodeType, range, root]);
tests.push([marker + root.nodeName.toLowerCase() + " node, foreign doc's range, type " + root.nodeType, foreignRange, root]);
tests.push([marker + root.nodeName.toLowerCase() + " node, XML doc's range, type " + root.nodeType, xmlRange, root]);
tests.push([marker + root.nodeName.toLowerCase() + " node, detached range, type " + root.nodeType, detachedRange, root]);
for (var i = 0; i < root.childNodes.length; i++) {
testTree(root.childNodes[i], "**" + marker);
}
}
testTree(document, " current doc: ");
testTree(foreignDoc, " foreign doc: ");
testTree(detachedDiv, " detached div in current doc: ");
var otherTests = [xmlDoc, xmlElement, detachedTextNode, foreignTextNode,
xmlTextNode, processingInstruction, comment, foreignComment, xmlComment,
docfrag, foreignDocfrag, xmlDocfrag];
for (var i = 0; i < otherTests.length; i++) {
testTree(otherTests[i], " ");
}
generate_tests(testSelectNode, tests);
testDiv.style.display = "none";
</script>

View file

@ -0,0 +1,221 @@
<!doctype html>
<title>Range setting 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";
function testSetStart(range, node, offset) {
if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
assert_throws("INVALID_NODE_TYPE_ERR", function() {
range.setStart(node, offset);
}, "setStart() to a doctype must throw INVALID_NODE_TYPE_ERR");
return;
}
if (offset < 0 || offset > nodeLength(node)) {
assert_throws("INDEX_SIZE_ERR", function() {
range.setStart(node, offset);
}, "setStart() to a too-large offset must throw INDEX_SIZE_ERR");
return;
}
var newRange = range.cloneRange();
newRange.setStart(node, offset);
assert_equals(newRange.startContainer, node,
"setStart() must change startContainer to the new node");
assert_equals(newRange.startOffset, offset,
"setStart() must change startOffset to the new offset");
// FIXME: I'm assuming comparePoint() is correct, but the tests for that
// will depend on setStart()/setEnd().
if (furthestAncestor(node) != furthestAncestor(range.startContainer)
|| range.comparePoint(node, offset) > 0) {
assert_equals(newRange.endContainer, node,
"setStart(node, offset) where node is after current end or in different document must set the end node to node too");
assert_equals(newRange.endOffset, offset,
"setStart(node, offset) where node is after current end or in different document must set the end offset to offset too");
} else {
assert_equals(newRange.endContainer, range.endContainer,
"setStart() must not change the end node if the new start is before the old end");
assert_equals(newRange.endOffset, range.endOffset,
"setStart() must not change the end offset if the new start is before the old end");
}
}
function testSetEnd(range, node, offset) {
if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
assert_throws("INVALID_NODE_TYPE_ERR", function() {
range.setEnd(node, offset);
}, "setEnd() to a doctype must throw INVALID_NODE_TYPE_ERR");
return;
}
if (offset < 0 || offset > nodeLength(node)) {
assert_throws("INDEX_SIZE_ERR", function() {
range.setEnd(node, offset);
}, "setEnd() to a too-large offset must throw INDEX_SIZE_ERR");
return;
}
var newRange = range.cloneRange();
newRange.setEnd(node, offset);
// FIXME: I'm assuming comparePoint() is correct, but the tests for that
// will depend on setStart()/setEnd().
if (furthestAncestor(node) != furthestAncestor(range.startContainer)
|| range.comparePoint(node, offset) < 0) {
assert_equals(newRange.startContainer, node,
"setEnd(node, offset) where node is before current start or in different document must set the end node to node too");
assert_equals(newRange.startOffset, offset,
"setEnd(node, offset) where node is before current start or in different document must set the end offset to offset too");
} else {
assert_equals(newRange.startContainer, range.startContainer,
"setEnd() must not change the start node if the new end is after the old start");
assert_equals(newRange.startOffset, range.startOffset,
"setEnd() must not change the start offset if the new end is after the old start");
}
assert_equals(newRange.endContainer, node,
"setEnd() must change endContainer to the new node");
assert_equals(newRange.endOffset, offset,
"setEnd() must change endOffset to the new offset");
}
function testSetStartBefore(range, node) {
var parent = node.parentNode;
if (parent === null) {
assert_throws("INVALID_NODE_TYPE_ERR", function () {
range.setStartBefore(node);
}, "setStartBefore() to a node with null parent must throw INVALID_NODE_TYPE_ERR");
return;
}
var idx = 0;
while (node.parentNode.childNodes[idx] != node) {
idx++;
}
testSetStart(range, node.parentNode, idx);
}
function testSetStartAfter(range, node) {
var parent = node.parentNode;
if (parent === null) {
assert_throws("INVALID_NODE_TYPE_ERR", function () {
range.setStartAfter(node);
}, "setStartAfter() to a node with null parent must throw INVALID_NODE_TYPE_ERR");
return;
}
var idx = 0;
while (node.parentNode.childNodes[idx] != node) {
idx++;
}
testSetStart(range, node.parentNode, idx + 1);
}
function testSetEndBefore(range, node) {
var parent = node.parentNode;
if (parent === null) {
assert_throws("INVALID_NODE_TYPE_ERR", function () {
range.setEndBefore(node);
}, "setEndBefore() to a node with null parent must throw INVALID_NODE_TYPE_ERR");
return;
}
var idx = 0;
while (node.parentNode.childNodes[idx] != node) {
idx++;
}
testSetEnd(range, node.parentNode, idx);
}
function testSetEndAfter(range, node) {
var parent = node.parentNode;
if (parent === null) {
assert_throws("INVALID_NODE_TYPE_ERR", function () {
range.setEndAfter(node);
}, "setEndAfter() to a node with null parent must throw INVALID_NODE_TYPE_ERR");
return;
}
var idx = 0;
while (node.parentNode.childNodes[idx] != node) {
idx++;
}
testSetEnd(range, node.parentNode, idx + 1);
}
var startTests = [];
var endTests = [];
var startBeforeTests = [];
var startAfterTests = [];
var endBeforeTests = [];
var endAfterTests = [];
// Don't want to eval() each point a bazillion times
var testPointsCached = testPoints.map(eval);
var testNodesCached = testNodesShort.map(eval);
for (var i = 0; i < testRangesShort.length; i++) {
var endpoints = eval(testRangesShort[i]);
var range;
test(function() {
range = ownerDocument(endpoints[0]).createRange();
range.setStart(endpoints[0], endpoints[1]);
range.setEnd(endpoints[2], endpoints[3]);
}, "Set up range " + i + " " + testRangesShort[i]);
for (var j = 0; j < testPoints.length; j++) {
startTests.push(["setStart() with range " + i + " " + testRangesShort[i] + ", point " + j + " " + testPoints[j],
range,
testPointsCached[j][0],
testPointsCached[j][1]
]);
endTests.push(["setEnd() with range " + i + " " + testRangesShort[i] + ", point " + j + " " + testPoints[j],
range,
testPointsCached[j][0],
testPointsCached[j][1]
]);
}
for (var j = 0; j < testNodesShort.length; j++) {
startBeforeTests.push(["setStartBefore() with range " + i + " " + testRangesShort[i] + ", node " + j + " " + testNodesShort[j],
range,
testNodesCached[j]
]);
startAfterTests.push(["setStartAfter() with range " + i + " " + testRangesShort[i] + ", node " + j + " " + testNodesShort[j],
range,
testNodesCached[j]
]);
endBeforeTests.push(["setEndBefore() with range " + i + " " + testRangesShort[i] + ", node " + j + " " + testNodesShort[j],
range,
testNodesCached[j]
]);
endAfterTests.push(["setEndAfter() with range " + i + " " + testRangesShort[i] + ", node " + j + " " + testNodesShort[j],
range,
testNodesCached[j]
]);
}
}
generate_tests(testSetStart, startTests);
generate_tests(testSetEnd, endTests);
generate_tests(testSetStartBefore, startBeforeTests);
generate_tests(testSetStartAfter, startAfterTests);
generate_tests(testSetEndBefore, endBeforeTests);
generate_tests(testSetEndAfter, endAfterTests);
testDiv.style.display = "none";
</script>

View file

@ -0,0 +1,354 @@
<!doctype html>
<meta charset=utf-8>
<title>Range.surroundContents() 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,16"). Only that test will be run. Then you can look at the resulting
iframes 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);
function mySurroundContents(range, newParent) {
try {
// "If the detached flag is set, throw an "InvalidStateError" exception
// and terminate these steps."
try {
range.collapsed;
} catch (e) {
return "INVALID_STATE_ERR";
}
// "If a non-Text node is partially contained in the context object,
// throw a "InvalidStateError" exception and terminate these steps."
var node = range.commonAncestorContainer;
var stop = nextNodeDescendants(node);
for (; node != stop; node = nextNode(node)) {
if (node.nodeType != Node.TEXT_NODE
&& isPartiallyContained(node, range)) {
return "INVALID_STATE_ERR";
}
}
// "If newParent is a Document, DocumentType, or DocumentFragment node,
// throw an "InvalidNodeTypeError" exception and terminate these
// steps."
if (newParent.nodeType == Node.DOCUMENT_NODE
|| newParent.nodeType == Node.DOCUMENT_TYPE_NODE
|| newParent.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
return "INVALID_NODE_TYPE_ERR";
}
// "Call extractContents() on the context object, and let fragment be
// the result."
var fragment = myExtractContents(range);
if (typeof fragment == "string") {
return fragment;
}
// "While newParent has children, remove its first child."
while (newParent.childNodes.length) {
newParent.removeChild(newParent.firstChild);
}
// "Call insertNode(newParent) on the context object."
var ret = myInsertNode(range, newParent);
if (typeof ret == "string") {
return ret;
}
// "Call appendChild(fragment) on newParent."
newParent.appendChild(fragment);
// "Call selectNode(newParent) on the context object."
//
// We just reimplement this in-place.
if (!newParent.parentNode) {
return "INVALID_NODE_TYPE_ERR";
}
var index = indexOf(newParent);
range.setStart(newParent.parentNode, index);
range.setEnd(newParent.parentNode, index + 1);
} catch (e) {
return getDomExceptionName(e);
}
}
function restoreIframe(iframe, i, j) {
// 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 = testRangesShort[i];
iframe.contentWindow.testNodeInput = testNodesShort[j];
iframe.contentWindow.run();
}
function testSurroundContents(i, j) {
var actualRange;
var expectedRange;
var actualNode;
var expectedNode;
var actualRoots = [];
var expectedRoots = [];
var detached = false;
domTests[i][j].step(function() {
restoreIframe(actualIframe, i, j);
restoreIframe(expectedIframe, i, j);
actualRange = actualIframe.contentWindow.testRange;
expectedRange = expectedIframe.contentWindow.testRange;
actualNode = actualIframe.contentWindow.testNode;
expectedNode = expectedIframe.contentWindow.testNode;
try {
actualRange.collapsed;
} catch (e) {
detached = true;
}
assert_equals(actualIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for actual surroundContents()");
assert_equals(expectedIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for simulated surroundContents()");
assert_equals(typeof actualRange, "object",
"typeof Range produced in actual iframe");
assert_false(actualRange === null,
"Range produced in actual iframe was null");
assert_equals(typeof expectedRange, "object",
"typeof Range produced in expected iframe");
assert_false(expectedRange === null,
"Range produced in expected iframe was null");
assert_equals(typeof actualNode, "object",
"typeof Node produced in actual iframe");
assert_false(actualNode === null,
"Node produced in actual iframe was null");
assert_equals(typeof expectedNode, "object",
"typeof Node produced in expected iframe");
assert_false(expectedNode === null,
"Node produced in expected iframe was null");
// We want to test that the trees containing the ranges are equal, and
// also the trees containing the moved nodes. These might not be the
// same, if we're inserting a node from a detached tree or a different
// document.
//
// Detached ranges are always in the contentDocument.
if (detached) {
actualRoots.push(actualIframe.contentDocument);
expectedRoots.push(expectedIframe.contentDocument);
} else {
actualRoots.push(furthestAncestor(actualRange.startContainer));
expectedRoots.push(furthestAncestor(expectedRange.startContainer));
}
if (furthestAncestor(actualNode) != actualRoots[0]) {
actualRoots.push(furthestAncestor(actualNode));
}
if (furthestAncestor(expectedNode) != expectedRoots[0]) {
expectedRoots.push(furthestAncestor(expectedNode));
}
assert_equals(actualRoots.length, expectedRoots.length,
"Either the actual node and actual range are in the same tree but the expected are in different trees, or vice versa");
// This doctype stuff is to work around the fact that Opera 11.00 will
// move around doctypes within a document, even to totally invalid
// positions, but it won't allow a new doctype to be added to a
// document in any way I can figure out. So if we try moving a doctype
// to some invalid place, in Opera it will actually succeed, and then
// restoreIframe() will remove the doctype along with the root element,
// and then nothing can re-add the doctype. So instead, we catch it
// during the test itself and move it back to the right place while we
// still can.
//
// I spent *way* too much time debugging and working around this bug.
var actualDoctype = actualIframe.contentDocument.doctype;
var expectedDoctype = expectedIframe.contentDocument.doctype;
var result;
try {
result = mySurroundContents(expectedRange, expectedNode);
} catch (e) {
if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
}
throw e;
}
if (typeof result == "string") {
assert_throws(result, function() {
try {
actualRange.surroundContents(actualNode);
} catch (e) {
if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
}
if (actualDoctype != actualIframe.contentDocument.firstChild) {
actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
}
throw e;
}
}, "A " + result + " must be thrown in this case");
// Don't return, we still need to test DOM equality
} else {
try {
actualRange.surroundContents(actualNode);
} catch (e) {
if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
}
if (actualDoctype != actualIframe.contentDocument.firstChild) {
actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
}
throw e;
}
}
for (var k = 0; k < actualRoots.length; k++) {
assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
}
});
domTests[i][j].done();
positionTests[i][j].step(function() {
assert_equals(actualIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for actual surroundContents()");
assert_equals(expectedIframe.contentWindow.unexpectedException, null,
"Unexpected exception thrown when setting up Range for simulated surroundContents()");
assert_equals(typeof actualRange, "object",
"typeof Range produced in actual iframe");
assert_false(actualRange === null,
"Range produced in actual iframe was null");
assert_equals(typeof expectedRange, "object",
"typeof Range produced in expected iframe");
assert_false(expectedRange === null,
"Range produced in expected iframe was null");
assert_equals(typeof actualNode, "object",
"typeof Node produced in actual iframe");
assert_false(actualNode === null,
"Node produced in actual iframe was null");
assert_equals(typeof expectedNode, "object",
"typeof Node produced in expected iframe");
assert_false(expectedNode === null,
"Node produced in expected iframe was null");
for (var k = 0; k < actualRoots.length; k++) {
assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
}
if (detached) {
// No further tests we can do
return;
}
assert_equals(actualRange.startOffset, expectedRange.startOffset,
"Unexpected startOffset after surroundContents()");
assert_equals(actualRange.endOffset, expectedRange.endOffset,
"Unexpected endOffset after surroundContents()");
// 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 surroundContents(), 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][j].done();
}
testRanges.unshift('"detached"');
var iStart = 0;
var iStop = testRangesShort.length;
var jStart = 0;
var jStop = testNodesShort.length;
if (/subtest=[0-9]+,[0-9]+/.test(location.search)) {
var matches = /subtest=([0-9]+),([0-9]+)/.exec(location.search);
iStart = Number(matches[1]);
iStop = Number(matches[1]) + 1;
jStart = Number(matches[2]) + 0;
jStop = Number(matches[2]) + 1;
}
var domTests = [];
var positionTests = [];
for (var i = iStart; i < iStop; i++) {
domTests[i] = [];
positionTests[i] = [];
for (var j = jStart; j < jStop; j++) {
domTests[i][j] = async_test(i + "," + j + ": resulting DOM for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
positionTests[i][j] = async_test(i + "," + j + ": resulting range position for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
}
}
var actualIframe = document.createElement("iframe");
actualIframe.style.display = "none";
actualIframe.id = "actual";
document.body.appendChild(actualIframe);
var expectedIframe = document.createElement("iframe");
expectedIframe.style.display = "none";
expectedIframe.id = "expected";
document.body.appendChild(expectedIframe);
var referenceDoc = document.implementation.createHTMLDocument("");
referenceDoc.removeChild(referenceDoc.documentElement);
actualIframe.onload = function() {
expectedIframe.onload = function() {
for (var i = iStart; i < iStop; i++) {
for (var j = jStart; j < jStop; j++) {
testSurroundContents(i, j);
}
}
}
expectedIframe.src = "Range-test-iframe.html";
referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
}
actualIframe.src = "Range-test-iframe.html";
</script>

View file

@ -0,0 +1,56 @@
<!doctype html>
<title>Range test iframe</title>
<link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
<meta name=timeout content=long>
<body onload=run()>
<script src=../common.js></script>
<script>
"use strict";
// This script only exists because we want to evaluate the range endpoints
// in each iframe using that iframe's local variables set up by common.js. It
// just creates the range and does nothing else. The data is returned via
// window.testRange, and if an exception is thrown, it's put in
// window.unexpectedException.
window.unexpectedException = null;
function run() {
try {
window.unexpectedException = null;
if (typeof window.testNodeInput != "undefined") {
window.testNode = eval(window.testNodeInput);
}
var rangeEndpoints;
if (typeof window.testRangeInput == "undefined") {
// Use the hash (old way of doing things, bad because it requires
// navigation)
if (location.hash == "") {
return;
}
rangeEndpoints = eval(location.hash.substr(1));
} else {
// Get the variable directly off the window, faster and can be done
// synchronously
rangeEndpoints = eval(window.testRangeInput);
}
var range;
if (rangeEndpoints == "detached") {
range = document.createRange();
range.detach();
} else {
range = ownerDocument(rangeEndpoints[0]).createRange();
range.setStart(rangeEndpoints[0], rangeEndpoints[1]);
range.setEnd(rangeEndpoints[2], rangeEndpoints[3]);
}
window.testRange = range;
} catch(e) {
window.unexpectedException = e;
}
}
testDiv.style.display = "none";
</script>