Allow running multiple commands per test
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Thu, 21 Jul 2011 14:56:14 -0600
changeset 435 866d41db1de6
parent 434 91d75263f14b
child 436 60444f81966a
Allow running multiple commands per test

This is necessary to properly test the insertion-affecting state/value
stuff.
autoimplementation.html
tests.js
--- a/autoimplementation.html	Thu Jul 21 14:49:40 2011 -0600
+++ b/autoimplementation.html	Thu Jul 21 14:56:14 2011 -0600
@@ -37,13 +37,15 @@
 
 		var div = document.createElement("div");
 		div.id = command;
-		var supported;
-		try {
-			supported = document.queryCommandSupported(command)
-				? "<span style=color:green>Supported</span>"
-				: "<span style=color:red>Not supported</span>";
-		} catch (e) {
-			supported = "<strong style=color:red>Exception for queryCommandSupported</strong>";
+		var supported = "";
+		if (command != "multitest") {
+			try {
+				supported = document.queryCommandSupported(command)
+					? "<span style=color:green>Supported</span>"
+					: "<span style=color:red>Not supported</span>";
+			} catch (e) {
+				supported = "<strong style=color:red>Exception for queryCommandSupported</strong>";
+			}
 		}
 		div.innerHTML = "<h1>" + command + "</h1>"
 			+ supported
@@ -76,7 +78,10 @@
 		// In case the input contains something unpleasant like newlines, we
 		// smuggle in the string as a data attribute, not just the value.  This
 		// is probably unnecessarily magic.
-		if (typeof tests[command][i] == "string") {
+		if (command == "multitest") {
+			inputs[0].value = JSON.stringify(tests[command][i]);
+			inputs[0].setAttribute("data-input", JSON.stringify(tests[command][i]));
+		} else if (typeof tests[command][i] == "string") {
 			inputs[0].value = tests[command][i];
 			inputs[0].setAttribute("data-input", tests[command][i]);
 			if (inputs.length == 2) {
@@ -131,36 +136,31 @@
 	}
 	inputs[0].removeAttribute("data-input");
 
-	doInputCell(tr, test);
-	doSpecCell(tr, test, command, false);
-	doBrowserCell(tr, test, command, false);
+	// Firefox refuses to do anything for hiliteColor unless styleWithCSS is
+	// true.  In other cases where there's supposed to be no effect, we make
+	// styleWithCss false.
+	var normalizedTest = normalizeTest(command, test, command == "hilitecolor");
+
+	doInputCell(tr, normalizedTest, command);
+	doSpecCell(tr, normalizedTest, command);
+	doBrowserCell(tr, normalizedTest, command);
 	doSameCell(tr);
 
 	if (doubleTesting) {
 		var tr = doSetup("#" + command + " > table", 1);
 
-		doInputCell(tr, test);
-		doSpecCell(tr, test, command, true);
-		doBrowserCell(tr, test, command, true);
+		normalizedTest = normalizeTest(command, test, true);
+
+		doInputCell(tr, normalizedTest, command);
+		doSpecCell(tr, normalizedTest, command);
+		doBrowserCell(tr, normalizedTest, command);
 		doSameCell(tr);
 	}
 
 	doTearDown(command);
 }
 
-function doBrowserCell(tr, test, command, styleWithCss) {
-	var value;
-
-	if (typeof test != "string") {
-		value = test[0];
-		test = test[1];
-	}
-
-	if (command == "hilitecolor") {
-		// Firefox refuses to do anything unless styleWithCSS is true.
-		styleWithCss = true;
-	}
-
+function doBrowserCell(tr, test, command) {
 	var browserCell = document.createElement("td");
 	tr.appendChild(browserCell);
 	if (!document.querySelector("#browser-checkbox").checked) {
@@ -169,7 +169,7 @@
 	}
 
 	try {
-		var points = setupCell(browserCell, test);
+		var points = setupCell(browserCell, test[0]);
 
 		var testDiv = browserCell.firstChild;
 		// Work around weird Firefox bug:
@@ -179,25 +179,32 @@
 		testDiv.contentEditable = "true";
 		testDiv.spellcheck = false;
 
-		try {
-			document.execCommand("styleWithCSS", false, styleWithCss);
-		} catch (e) {}
+		if (command != "multitest") {
+			try { var beforeIndeterm = document.queryCommandIndeterm(command) }
+			catch(e) { beforeIndeterm = "Exception" }
+			try { var beforeState = document.queryCommandState(command) }
+			catch(e) { beforeState = "Exception" }
+			try { var beforeValue = document.queryCommandValue(command) }
+			catch(e) { beforeValue = "Exception" }
+		}
 
-		try { var beforeIndeterm = document.queryCommandIndeterm(command) }
-		catch(e) { beforeIndeterm = "Exception" }
-		try { var beforeState = document.queryCommandState(command) }
-		catch(e) { beforeState = "Exception" }
-		try { var beforeValue = document.queryCommandValue(command) }
-		catch(e) { beforeValue = "Exception" }
+		for (var i = 1; i < test.length; i++) {
+			if (test[i][0] === "stylewithcss") {
+				// try/catch to avoid an exception in IE
+				try { document.execCommand(test[i][0], false, test[i][1]) } catch (e) {}
+			} else {
+				document.execCommand(test[i][0], false, test[i][1]);
+			}
+		}
 
-		document.execCommand(command, false, value);
-
-		try { var afterIndeterm = document.queryCommandIndeterm(command) }
-		catch(e) { afterIndeterm = "Exception" }
-		try { var afterState = document.queryCommandState(command) }
-		catch(e) { afterState = "Exception" }
-		try { var afterValue = document.queryCommandValue(command) }
-		catch(e) { afterValue = "Exception" }
+		if (command != "multitest") {
+			try { var afterIndeterm = document.queryCommandIndeterm(command) }
+			catch(e) { afterIndeterm = "Exception" }
+			try { var afterState = document.queryCommandState(command) }
+			catch(e) { afterState = "Exception" }
+			try { var afterValue = document.queryCommandValue(command) }
+			catch(e) { afterValue = "Exception" }
+		}
 
 		testDiv.contentEditable = "inherit";
 		testDiv.removeAttribute("spellcheck");
@@ -247,10 +254,12 @@
 		}
 
 		browserCell.lastChild.textContent = browserCell.firstChild.innerHTML;
-		browserCell.lastChild.appendChild(queryOutputHelper(
-			beforeIndeterm, beforeState, beforeValue,
-			afterIndeterm, afterState, afterValue,
-			command, value));
+		if (command != "multitest") {
+			browserCell.lastChild.appendChild(queryOutputHelper(
+				beforeIndeterm, beforeState, beforeValue,
+				afterIndeterm, afterState, afterValue,
+				command, test[2][1]));
+		}
 	} catch (e) {
 		browserCellException(e, testDiv, browserCell);
 	}
--- a/tests.js	Thu Jul 21 14:49:40 2011 -0600
+++ b/tests.js	Thu Jul 21 14:56:14 2011 -0600
@@ -2956,8 +2956,8 @@
 		'foo<span class=foo>b[a]r</span>baz',
 		'[foo<b style="font-weight: normal">bar</b>baz]',
 		'foo<b style="font-weight: normal">b[a]r</b>baz',
-		'<p style="background-color: blue">foo[bar]baz</p>',
-		'<p><span style="background-color: blue">foo[bar]baz</span></p>',
+		'<p style="background-color: aqua">foo[bar]baz</p>',
+		'<p><span style="background-color: aqua">foo[bar]baz</span></p>',
 		'<p style="font-weight: bold">foo[bar]baz</p>',
 		'<b><p style="font-weight: bold">foo[bar]baz</p></b>',
 		'<p style="font-variant: small-caps">foo[bar]baz</p>',
@@ -3290,6 +3290,29 @@
 	stylewithcss: ['foo[bar]baz'],
 	usecss: ['foo[bar]baz'],
 	quasit: ['foo[bar]baz'],
+	multitest: [
+	//@{
+		// Insertion-affecting state
+		['foo[]bar', 'bold', ['inserttext', 'a']],
+		['foo[]bar', 'italic', ['inserttext', 'a']],
+		['foo[]bar', 'strikethrough', ['inserttext', 'a']],
+		['foo[]bar', 'subscript', ['inserttext', 'a']],
+		['foo[]bar', 'superscript', ['inserttext', 'a']],
+		['foo[]bar', 'underline', ['inserttext', 'a']],
+
+		// Insertion-affecting value
+		['foo[]bar', 'createlink', ['inserttext', 'a']],
+		['foo[]bar', 'fontname', ['inserttext', 'a']],
+		['foo[]bar', 'fontsize', ['inserttext', 'a']],
+		['foo[]bar', 'forecolor', ['inserttext', 'a']],
+		['foo[]bar', 'hilitecolor', ['inserttext', 'a']],
+
+		// Lots and lots of stuff in a row
+		['foo[]bar', 'bold', 'italic', 'strikethrough', 'subscript',
+		'superscript', 'underline', 'createlink', 'fontname', 'fontsize',
+		'forecolor', 'hilitecolor', ['inserttext', 'a']],
+	],
+	//@}
 };
 tests.backcolor = tests.hilitecolor;
 tests.insertlinebreak = tests.insertparagraph;
@@ -3337,7 +3360,11 @@
 
 function prettyPrint(value) {
 //@{
-	// Stolen from testharness.js
+	// Partly stolen from testharness.js
+	if (typeof value != "string") {
+		return String(value);
+	}
+
 	for (var i = 0; i < 32; i++) {
 		var replace = "\\";
 		switch (i) {
@@ -3376,7 +3403,7 @@
 		}
 		value = value.replace(String.fromCharCode(i), replace);
 	}
-	return value;
+	return '"' + value.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
 }
 //@}
 
@@ -3392,13 +3419,6 @@
 }
 //@}
 
-function trivialPrettyPrint(value) {
-	if (typeof value == "string") {
-		return '"' + value.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
-	}
-	return value;
-}
-
 function queryOutputHelper(beforeIndeterm, beforeState, beforeValue, afterIndeterm, afterState, afterValue, command, value) {
 //@{
 	var frag = document.createDocumentFragment();
@@ -3437,8 +3457,8 @@
 				? "good-result"
 				: "bad-result";
 	}
-	beforeDiv.lastChild.textContent = "indeterm " + trivialPrettyPrint(beforeIndeterm);
-	afterDiv.lastChild.textContent = "indeterm " + trivialPrettyPrint(afterIndeterm);
+	beforeDiv.lastChild.textContent = "indeterm " + prettyPrint(beforeIndeterm);
+	afterDiv.lastChild.textContent = "indeterm " + prettyPrint(afterIndeterm);
 
 	beforeDiv.appendChild(document.createTextNode(", "));
 	afterDiv.appendChild(document.createTextNode(", "));
@@ -3478,8 +3498,8 @@
 				? "good-result"
 				: "bad-result";
 	}
-	beforeDiv.lastChild.textContent = "state " + trivialPrettyPrint(beforeState);
-	afterDiv.lastChild.textContent = "state " + trivialPrettyPrint(afterState);
+	beforeDiv.lastChild.textContent = "state " + prettyPrint(beforeState);
+	afterDiv.lastChild.textContent = "state " + prettyPrint(afterState);
 
 	beforeDiv.appendChild(document.createTextNode(", "));
 	afterDiv.appendChild(document.createTextNode(", "));
@@ -3537,45 +3557,94 @@
 				? "good-result"
 				: "bad-result";
 	}
-	beforeDiv.lastChild.textContent = "value " + trivialPrettyPrint(beforeValue);
-	afterDiv.lastChild.textContent = "value " + trivialPrettyPrint(afterValue);
+	beforeDiv.lastChild.textContent = "value " + prettyPrint(beforeValue);
+	afterDiv.lastChild.textContent = "value " + prettyPrint(afterValue);
 
 	return frag;
 }
 //@}
 
-function doInputCell(tr, test) {
+function normalizeTest(command, test, styleWithCss) {
 //@{
-	var value = null;
-	if (typeof test != "string") {
-		value = test[0];
-		test = test[1];
+	// Our standard format for test processing is:
+	//   [input HTML, [command1, value1], [command2, value2], ...]
+	// But this is verbose, so we actually use three different formats in the
+	// tests and multiTests arrays:
+	//
+	// 1) Plain string giving the input HTML.  The command is implicit from the
+	// key of the tests array.  If the command takes values, the value is given
+	// by defaultValues, otherwise it's "".  Has to be converted to
+	// [input HTML, [command, value].
+	//
+	// 2) Two-element array [value, input HTML].  Has to be converted to
+	// [input HTML, [command, value]].
+	//
+	// 3) An element of multiTests.  This just has to have values filled in.
+	//
+	// Optionally, a styleWithCss argument can be passed, either true or false.
+	// If it is, we'll prepend a styleWithCss invocation.
+	if (command == "multitest") {
+		// Magic
+		test = JSON.parse(test);
+	}
+
+	if (typeof test == "string") {
+		if (command in defaultValues) {
+			test = [test, [command, defaultValues[command]]];
+		} else {
+			test = [test, [command, ""]];
+		}
+	} else if (test.length == 2) {
+		test = [test[1], [command, String(test[0])]];
+	} else for (var i = 1; i < test.length; i++) {
+		if (typeof test[i] == "string"
+		&& test[i] in defaultValues) {
+			test[i] = [test[i], defaultValues[test[i]]];
+		} else if (typeof test[i] == "string") {
+			test[i] = [test[i], ""];
+		}
+	}
+
+	if (command != "multitest") {
+		test.splice(1, 0, ["stylewithcss", String(styleWithCss)]);
+	}
+
+	return test;
+}
+//@}
+
+function doInputCell(tr, test, command) {
+//@{
+	var testHtml = test[0];
+
+	var msg = null;
+	if (command in defaultValues) {
+		// Single command with a value, possibly with a styleWithCss stuck
+		// before.  We don't need to specify the command itself, since this
+		// presumably isn't in multiTests, so the command is already given by
+		// the section header.
+		msg = 'value: ' + prettyPrint(test[test.length - 1][1]);
+	} else if (command == "multitest") {
+		// Uses a different input format
+		msg = JSON.stringify(test);
 	}
 	var inputCell = document.createElement("td");
 	inputCell.innerHTML = "<div></div><div></div>";
-	inputCell.firstChild.innerHTML = test;
+	inputCell.firstChild.innerHTML = testHtml;
 	inputCell.lastChild.textContent = inputCell.firstChild.innerHTML;
-	if (value !== null) {
-		value = prettyPrint(value);
-		inputCell.lastChild.textContent += ' (value: "' + value + '")';
+	if (msg !== null) {
+		inputCell.lastChild.textContent += " (" + msg + ")";
 	}
 	tr.appendChild(inputCell);
 }
 //@}
 
-function doSpecCell(tr, test, command, styleWithCss) {
+function doSpecCell(tr, test, command) {
 //@{
-	var value;
-
-	if (typeof test != "string") {
-		value = test[0];
-		test = test[1];
-	}
-
 	var specCell = document.createElement("td");
 	tr.appendChild(specCell);
 	try {
-		var points = setupCell(specCell, test);
+		var points = setupCell(specCell, test[0]);
 		var range = document.createRange();
 		range.setStart(points[0], points[1]);
 		range.setEnd(points[2], points[3]);
@@ -3585,23 +3654,28 @@
 		}
 		specCell.firstChild.contentEditable = "true";
 		specCell.firstChild.spellcheck = false;
-		myExecCommand("styleWithCSS", false, styleWithCss, range);
-
-		try { var beforeIndeterm = myQueryCommandIndeterm(command, range) }
-		catch(e) { beforeIndeterm = "Exception" }
-		try { var beforeState = myQueryCommandState(command, range) }
-		catch(e) { beforeState = "Exception" }
-		try { var beforeValue = myQueryCommandValue(command, range) }
-		catch(e) { beforeValue = "Exception" }
-
-		myExecCommand(command, false, value, range);
-
-		try { var afterIndeterm = myQueryCommandIndeterm(command, range) }
-		catch(e) { afterIndeterm = "Exception" }
-		try { var afterState = myQueryCommandState(command, range) }
-		catch(e) { afterState = "Exception" }
-		try { var afterValue = myQueryCommandValue(command, range) }
-		catch(e) { afterValue = "Exception" }
+
+		if (command != "multitest") {
+			try { var beforeIndeterm = myQueryCommandIndeterm(command, range) }
+			catch(e) { beforeIndeterm = "Exception" }
+			try { var beforeState = myQueryCommandState(command, range) }
+			catch(e) { beforeState = "Exception" }
+			try { var beforeValue = myQueryCommandValue(command, range) }
+			catch(e) { beforeValue = "Exception" }
+		}
+
+		for (var i = 1; i < test.length; i++) {
+			myExecCommand(test[i][0], false, test[i][1], range);
+		}
+
+		if (command != "multitest") {
+			try { var afterIndeterm = myQueryCommandIndeterm(command, range) }
+			catch(e) { afterIndeterm = "Exception" }
+			try { var afterState = myQueryCommandState(command, range) }
+			catch(e) { afterState = "Exception" }
+			try { var afterValue = myQueryCommandValue(command, range) }
+			catch(e) { afterValue = "Exception" }
+		}
 
 		specCell.firstChild.contentEditable = "inherit";
 		specCell.firstChild.removeAttribute("spellcheck");
@@ -3634,10 +3708,12 @@
 		}
 
 		specCell.lastChild.textContent = specCell.firstChild.innerHTML;
-		specCell.lastChild.appendChild(queryOutputHelper(
-			beforeIndeterm, beforeState, beforeValue,
-			afterIndeterm, afterState, afterValue,
-			command, value));
+		if (command != "multitest") {
+			specCell.lastChild.appendChild(queryOutputHelper(
+				beforeIndeterm, beforeState, beforeValue,
+				afterIndeterm, afterState, afterValue,
+				command, test[2][1]));
+		}
 	} catch (e) {
 		specCell.firstChild.contentEditable = "inherit";
 		specCell.firstChild.removeAttribute("spellcheck");
@@ -3658,9 +3734,14 @@
 		}
 	}
 
-	var key = "execcommand-" + command
-		+ "-" + Number(styleWithCss)
-		+ "-" + tr.firstChild.lastChild.textContent;
+	if (command != "multitest") {
+		// Old storage format
+		var key = "execcommand-" + command
+			+ "-" + (test[1][1] == "false" ? "0" : "1")
+			+ "-" + tr.firstChild.lastChild.textContent;
+	} else {
+		var key = "execcommand-" + JSON.stringify(test);
+	}
 
 	// Use getItem() instead of direct property access to work around Firefox
 	// bug: https://bugzilla.mozilla.org/show_bug.cgi?id=532062
@@ -3815,7 +3896,7 @@
 }
 //@}
 
-function setupCell(cell, test) {
+function setupCell(cell, html) {
 //@{
 	cell.innerHTML = "<div></div><div></div>";
 
@@ -3823,7 +3904,7 @@
 	var re = /\{|\[|data-start/g;
 	var markers = [];
 	var marker;
-	while (marker = re.exec(test)) {
+	while (marker = re.exec(html)) {
 		markers.push(marker);
 	}
 	if (markers.length != 1) {
@@ -3833,7 +3914,7 @@
 	var re = /\}|\]|data-end/g;
 	var markers = [];
 	var marker;
-	while (marker = re.exec(test)) {
+	while (marker = re.exec(html)) {
 		markers.push(marker);
 	}
 	if (markers.length != 1) {
@@ -3841,7 +3922,7 @@
 	}
 
 	var node = cell.firstChild;
-	node.innerHTML = test;
+	node.innerHTML = html;
 
 	var startNode, startOffset, endNode, endOffset;