Test collapseToStart/End properly
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Tue, 11 Oct 2011 15:30:30 -0600
changeset 643 8e67144eadbd
parent 642 c8002489da61
child 644 2a39ece41cdb
Test collapseToStart/End properly

Results unsurprising: Gecko is perfect, WebKit is almost perfect except
that it doesn't support addRange() correctly, Opera is totally broken,
IE is basically okay and is correct per previous vague specs but has a
bug or two in light of the precise wording of the current spec.
editing.html
selecttest/Selection-collapseToEnd.html
selecttest/Selection-collapseToStart.html
selecttest/Selection-collapseToStartEnd.html
source.html
--- a/editing.html	Tue Oct 11 13:20:52 2011 -0600
+++ b/editing.html	Tue Oct 11 15:30:30 2011 -0600
@@ -925,21 +925,22 @@
   <var title="">offset</var>).
 </ol>
 
+<p class=comments>For collapseToStart/End, IE9 mutates the existing range,
+while Firefox 9.0a2 and Chrome 15 dev replace it with a new one.  The spec
+follows the majority and replaces it with a new one, leaving the old Range
+object unchanged.
+
 <p>The <dfn id=dom-selection-collapsetostart title=dom-Selection-collapseToStart><code>collapseToStart()</code></dfn> method
 must <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-throw title=concept-throw>throw</a> an <code class=external data-anolis-spec=dom><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#invalidstateerror>InvalidStateError</a></code> exception if the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#context-object>context object</a>'s
-<a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> is null. Otherwise, it must invoke the <code title=dom-Selection-collapse><a href=#dom-selection-collapse>collapse()</a></code> method with
-the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start-node title=concept-range-start-node>start node</a> and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start-offset title=concept-range-start-offset>start offset</a> of the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#context-object>context object</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> as
-the arguments.
+<a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> is null.  Otherwise, it must set the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#context-object>context object</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> to
+a new <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> object with <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start title=concept-range-start>start</a> and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end title=concept-range-end>end</a> both equal to the
+<a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#context-object>context object</a>'s old <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start title=concept-range-start>start</a>.
 
 <p>The <dfn id=dom-selection-collapsetoend title=dom-Selection-collapseToEnd><code>collapseToEnd()</code></dfn> method
 must <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-throw title=concept-throw>throw</a> an <code class=external data-anolis-spec=dom><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#invalidstateerror>InvalidStateError</a></code> exception if the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#context-object>context object</a>'s
-<a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> is null. Otherwise, it must invoke the <code title=dom-Selection-collapse><a href=#dom-selection-collapse>collapse()</a></code> method with
-the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end-node title=concept-range-end-node>end node</a> and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end-offset title=concept-range-end-offset>end offset</a> values of the last <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> object in the
-list as the arguments.
-
-<p class=XXX>This implies that they'll replace the range object instead of
-changing it.  Will they?  Needs testing.  It seems like they shouldn't.  Same
-for collapse() itself.
+<a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> is null.  Otherwise, it must set the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#context-object>context object</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> to
+a new <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a> object with <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start title=concept-range-start>start</a> and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end title=concept-range-end>end</a> both equal to the
+<a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#context-object>context object</a>'s old <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range title=concept-range>range</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end title=concept-range-end>end</a>.
 
 <p class=comments>Reverse-engineered circa January 2011.  IE doesn't support it, so I'm
 relying on Firefox (implemented extend() sometime before 2000) and WebKit
--- a/selecttest/Selection-collapseToEnd.html	Tue Oct 11 13:20:52 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<title>The collapseToEnd method</title>
-<div id=log></div>
-<script src=http://w3c-test.org/resources/testharness.js></script>
-<script>
-test(function() {
-  var sel = getSelection();
-  sel.removeAllRanges();
-  assert_throws("INVALID_STATE_ERR", function() { sel.collapseToEnd(); })
-});
-</script>
--- a/selecttest/Selection-collapseToStart.html	Tue Oct 11 13:20:52 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<title>The collapseToStart method</title>
-<div id=log></div>
-<script src=http://w3c-test.org/resources/testharness.js></script>
-<script>
-test(function() {
-  var sel = getSelection();
-  sel.removeAllRanges();
-  assert_throws("INVALID_STATE_ERR", function() { sel.collapseToStart(); })
-});
-</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/selecttest/Selection-collapseToStartEnd.html	Tue Oct 11 15:30:30 2011 -0600
@@ -0,0 +1,123 @@
+<!doctype html>
+<title>Selection.collapseTo(Start|End)() tests</title>
+<div id=log></div>
+<script src=http://w3c-test.org/resources/testharness.js></script>
+<script src=common.js></script>
+<script>
+"use strict";
+
+function testCollapseToStartEnd(range) {
+}
+
+// Also test a selection with no ranges
+testRanges.unshift("[]");
+
+for (var i = 0; i < testRanges.length; i++) {
+	test(function() {
+		selection.removeAllRanges();
+		var endpoints = eval(testRanges[i]);
+		if (!endpoints.length) {
+			assert_throws("INVALID_STATE_ERR", function() {
+				selection.collapseToStart();
+			}, "Must throw InvalidStateErr if the selection's range is null");
+			return;
+		}
+
+		var addedRange = ownerDocument(endpoints[0]).createRange();
+		addedRange.setStart(endpoints[0], endpoints[1]);
+		addedRange.setEnd(endpoints[2], endpoints[3]);
+		selection.addRange(addedRange);
+
+		// We don't penalize browsers here for mishandling addRange() and
+		// adding a different range than we specified.  They fail addRange()
+		// tests for that, and don't have to fail collapseToStart/End() tests
+		// too.  They do fail if they throw unexpectedly, though.  I also fail
+		// them if there's no range at all, because otherwise they could pass
+		// all tests if addRange() always does nothing and collapseToStart()
+		// always throws.
+		assert_equals(selection.rangeCount, 1,
+			"Sanity check: rangeCount must equal 1 after addRange()");
+
+		var expectedEndpoint = [
+			selection.getRangeAt(0).startContainer,
+			selection.getRangeAt(0).startOffset
+		];
+
+		selection.collapseToStart();
+
+		assert_equals(selection.rangeCount, 1,
+			"selection.rangeCount must equal 1");
+		assert_equals(selection.focusNode, expectedEndpoint[0],
+			"focusNode must equal the original start node");
+		assert_equals(selection.focusOffset, expectedEndpoint[1],
+			"focusOffset must equal the original start offset");
+		assert_equals(selection.anchorNode, expectedEndpoint[0],
+			"anchorNode must equal the original start node");
+		assert_equals(selection.anchorOffset, expectedEndpoint[1],
+			"anchorOffset must equal the original start offset");
+		assert_equals(addedRange.startContainer, endpoints[0],
+			"collapseToStart() must not change the startContainer of the selection's original range");
+		assert_equals(addedRange.startOffset, endpoints[1],
+			"collapseToStart() must not change the startOffset of the selection's original range");
+		assert_equals(addedRange.endContainer, endpoints[2],
+			"collapseToStart() must not change the endContainer of the selection's original range");
+		assert_equals(addedRange.endOffset, endpoints[3],
+			"collapseToStart() must not change the endOffset of the selection's original range");
+	}, "Range " + i + " " + testRanges[i] + " collapseToStart()");
+
+	// Copy-paste of above
+	test(function() {
+		selection.removeAllRanges();
+		var endpoints = eval(testRanges[i]);
+		if (!endpoints.length) {
+			assert_throws("INVALID_STATE_ERR", function() {
+				selection.collapseToEnd();
+			}, "Must throw InvalidStateErr if the selection's range is null");
+			return;
+		}
+
+		var addedRange = ownerDocument(endpoints[0]).createRange();
+		addedRange.setStart(endpoints[0], endpoints[1]);
+		addedRange.setEnd(endpoints[2], endpoints[3]);
+		selection.addRange(addedRange);
+
+		// We don't penalize browsers here for mishandling addRange() and
+		// adding a different range than we specified.  They fail addRange()
+		// tests for that, and don't have to fail collapseToStart/End() tests
+		// too.  They do fail if they throw unexpectedly, though.  I also fail
+		// them if there's no range at all, because otherwise they could pass
+		// all tests if addRange() always does nothing and collapseToStart()
+		// always throws.
+		assert_equals(selection.rangeCount, 1,
+			"Sanity check: rangeCount must equal 1 after addRange()");
+
+		var expectedEndpoint = [
+			selection.getRangeAt(0).endContainer,
+			selection.getRangeAt(0).endOffset
+		];
+
+		selection.collapseToEnd();
+
+		assert_equals(selection.rangeCount, 1,
+			"selection.rangeCount must equal 1");
+		assert_equals(selection.focusNode, expectedEndpoint[0],
+			"focusNode must equal the original end node");
+		assert_equals(selection.focusOffset, expectedEndpoint[1],
+			"focusOffset must equal the original end offset");
+		assert_equals(selection.anchorNode, expectedEndpoint[0],
+			"anchorNode must equal the original end node");
+		assert_equals(selection.anchorOffset, expectedEndpoint[1],
+			"anchorOffset must equal the original end offset");
+		assert_equals(addedRange.startContainer, endpoints[0],
+			"collapseToEnd() must not change the startContainer of the selection's original range");
+		assert_equals(addedRange.startOffset, endpoints[1],
+			"collapseToEnd() must not change the startOffset of the selection's original range");
+		assert_equals(addedRange.endContainer, endpoints[2],
+			"collapseToEnd() must not change the endContainer of the selection's original range");
+		assert_equals(addedRange.endOffset, endpoints[3],
+			"collapseToEnd() must not change the endOffset of the selection's original range");
+	}, "Range " + i + " " + testRanges[i] + " collapseToEnd()");
+}
+
+testDiv.style.display = "none";
+</script>
--- a/source.html	Tue Oct 11 13:20:52 2011 -0600
+++ b/source.html	Tue Oct 11 15:30:30 2011 -0600
@@ -868,23 +868,24 @@
   <var>offset</var>).
 </ol>
 
+<p class=comments>For collapseToStart/End, IE9 mutates the existing range,
+while Firefox 9.0a2 and Chrome 15 dev replace it with a new one.  The spec
+follows the majority and replaces it with a new one, leaving the old Range
+object unchanged.
+
 <p>The <dfn
 title=dom-Selection-collapseToStart><code>collapseToStart()</code></dfn> method
 must [[throw]] an [[InvalidStateError]] exception if the [[contextobject]]'s
-[[range]] is null. Otherwise, it must invoke the [[selcollapse|]] method with
-the [[startnode]] and [[startoffset]] of the [[contextobject]]'s [[range]] as
-the arguments.
+[[range]] is null.  Otherwise, it must set the [[contextobject]]'s [[range]] to
+a new [[range]] object with [[rangestart]] and [[rangeend]] both equal to the
+[[contextobject]]'s old [[range]]'s [[rangestart]].
 
 <p>The <dfn
 title=dom-Selection-collapseToEnd><code>collapseToEnd()</code></dfn> method
 must [[throw]] an [[InvalidStateError]] exception if the [[contextobject]]'s
-[[range]] is null. Otherwise, it must invoke the [[selcollapse|]] method with
-the [[endnode]] and [[endoffset]] values of the last [[range]] object in the
-list as the arguments.
-
-<p class=XXX>This implies that they'll replace the range object instead of
-changing it.  Will they?  Needs testing.  It seems like they shouldn't.  Same
-for collapse() itself.
+[[range]] is null.  Otherwise, it must set the [[contextobject]]'s [[range]] to
+a new [[range]] object with [[rangestart]] and [[rangeend]] both equal to the
+[[contextobject]]'s old [[range]]'s [[rangeend]].
 
 <p class=comments>Reverse-engineered circa January 2011.  IE doesn't support it, so I'm
 relying on Firefox (implemented extend() sometime before 2000) and WebKit