diff options
Diffstat (limited to 'editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/compare.js')
-rw-r--r-- | editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/compare.js | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/compare.js b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/compare.js new file mode 100644 index 0000000000..be059cfc86 --- /dev/null +++ b/editor/libeditor/tests/browserscope/lib/richtext2/richtext2/static/js/compare.js @@ -0,0 +1,489 @@ +/** + * @fileoverview + * Comparison functions used in the RTE test suite. + * + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the 'License') + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @version 0.1 + * @author rolandsteiner@google.com + */ + +/** + * constants used only in the compare functions. + */ +var RESULT_DIFF = 0; // actual result doesn't match expectation +var RESULT_SEL = 1; // actual result matches expectation in HTML only +var RESULT_EQUAL = 2; // actual result matches expectation in both HTML and selection + +/** + * Gets the test expectations as an array from the passed-in field. + * + * @param {Array|String} the test expectation(s) as string or array. + * @return {Array} test expectations as an array. + */ +function getExpectationArray(expected) { + if (expected === undefined) { + return []; + } + if (expected === null) { + return [null]; + } + switch (typeof expected) { + case 'string': + case 'boolean': + case 'number': + return [expected]; + } + // Assume it's already an array. + return expected; +} + +/** + * Compare a test result to a single expectation string. + * + * FIXME: add support for optional elements/attributes. + * + * @param expected {String} the already canonicalized (with the exception of selection marks) expectation string + * @param actual {String} the already canonicalized (with the exception of selection marks) actual result + * @return {Integer} one of the RESULT_... return values + * @see variables.js for return values + */ +function compareHTMLToSingleExpectation(expected, actual) { + // If the test checks the selection, then the actual string must match the + // expectation exactly. + if (expected == actual) { + return RESULT_EQUAL; + } + + // Remove selection markers and see if strings match then. + expected = expected.replace(/ [{}\|]>/g, '>'); // intra-tag + expected = expected.replace(/[\[\]\^{}\|]/g, ''); // outside tag + actual = actual.replace(/ [{}\|]>/g, '>'); // intra-tag + actual = actual.replace(/[\[\]\^{}\|]/g, ''); // outside tag + + return (expected == actual) ? RESULT_SEL : RESULT_DIFF; +} + +/** + * Compare the current HTMLtest result to the expectation string(s). + * + * @param actual {String/Boolean} actual value + * @param expected {String/Array} expectation(s) + * @param emitFlags {Object} flags to use for canonicalization + * @return {Integer} one of the RESULT_... return values + * @see variables.js for return values + */ +function compareHTMLToExpectation(actual, expected, emitFlags) { + // Find the most favorable result among the possible expectation strings. + var expectedArr = getExpectationArray(expected); + var count = expectedArr ? expectedArr.length : 0; + var best = RESULT_DIFF; + + for (var idx = 0; idx < count && best < RESULT_EQUAL; ++idx) { + var expected = expectedArr[idx]; + expected = canonicalizeSpaces(expected); + expected = canonicalizeElementsAndAttributes(expected, emitFlags); + + var singleResult = compareHTMLToSingleExpectation(expected, actual); + + best = Math.max(best, singleResult); + } + return best; +} + +/** + * Compare the current HTMLtest result to expected and acceptable results + * + * @param expected {String/Array} expected result(s) + * @param accepted {String/Array} accepted result(s) + * @param actual {String} actual result + * @param emitFlags {Object} how to canonicalize the HTML strings + * @param result {Object} [out] object recieving the result of the comparison. + */ +function compareHTMLTestResultTo(expected, accepted, actual, emitFlags, result) { + actual = actual.replace(/[\x60\xb4]/g, ''); + actual = canonicalizeElementsAndAttributes(actual, emitFlags); + + var bestExpected = compareHTMLToExpectation(actual, expected, emitFlags); + + if (bestExpected == RESULT_EQUAL) { + // Shortcut - it doesn't get any better + result.valresult = VALRESULT_EQUAL; + result.selresult = SELRESULT_EQUAL; + return; + } + + var bestAccepted = compareHTMLToExpectation(actual, accepted, emitFlags); + + switch (bestExpected) { + case RESULT_SEL: + switch (bestAccepted) { + case RESULT_EQUAL: + // The HTML was equal to the/an expected HTML result as well + // (just not the selection there), therefore the difference + // between expected and accepted can only lie in the selection. + result.valresult = VALRESULT_EQUAL; + result.selresult = SELRESULT_ACCEPT; + return; + + case RESULT_SEL: + case RESULT_DIFF: + // The acceptable expectations did not yield a better result + // -> stay with the original (i.e., comparison to 'expected') result. + result.valresult = VALRESULT_EQUAL; + result.selresult = SELRESULT_DIFF; + return; + } + break; + + case RESULT_DIFF: + switch (bestAccepted) { + case RESULT_EQUAL: + result.valresult = VALRESULT_ACCEPT; + result.selresult = SELRESULT_EQUAL; + return; + + case RESULT_SEL: + result.valresult = VALRESULT_ACCEPT; + result.selresult = SELRESULT_DIFF; + return; + + case RESULT_DIFF: + result.valresult = VALRESULT_DIFF; + result.selresult = SELRESULT_NA; + return; + } + break; + } + + throw INTERNAL_ERR + HTML_COMPARISON; +} + +/** + * Verify that the canaries are unviolated. + * + * @param container {Object} the test container descriptor as object reference + * @param result {Object} object reference that contains the result data + * @return {Boolean} whether the canaries' HTML is OK (selection flagged, but not fatal) + */ +function verifyCanaries(container, result) { + if (!container.canary) { + return true; + } + + var str = canonicalizeElementsAndAttributes(result.bodyInnerHTML, emitFlagsForCanary); + + if (str.length < 2 * container.canary.length) { + result.valresult = VALRESULT_CANARY; + result.selresult = SELRESULT_NA; + result.output = result.bodyOuterHTML; + return false; + } + + var strBefore = str.substr(0, container.canary.length); + var strAfter = str.substr(str.length - container.canary.length); + + // Verify that the canary stretch doesn't contain any selection markers + if (SELECTION_MARKERS.test(strBefore) || SELECTION_MARKERS.test(strAfter)) { + str = str.replace(SELECTION_MARKERS, ''); + if (str.length < 2 * container.canary.length) { + result.valresult = VALRESULT_CANARY; + result.selresult = SELRESULT_NA; + result.output = result.bodyOuterHTML; + return false; + } + + // Selection escaped contentEditable element, but HTML may still be ok. + result.selresult = SELRESULT_CANARY; + strBefore = str.substr(0, container.canary.length); + strAfter = str.substr(str.length - container.canary.length); + } + + if (strBefore !== container.canary || strAfter !== container.canary) { + result.valresult = VALRESULT_CANARY; + result.selresult = SELRESULT_NA; + result.output = result.bodyOuterHTML; + return false; + } + + return true; +} + +/** + * Compare the current HTMLtest result to the expectation string(s). + * Sets the global result variables. + * + * @param suite {Object} the test suite as object reference + * @param group {Object} group of tests within the suite the test belongs to + * @param test {Object} the test as object reference + * @param container {Object} the test container description + * @param result {Object} [in/out] the result description, incl. HTML strings + * @see variables.js for result values + */ +function compareHTMLTestResult(suite, group, test, container, result) { + if (!verifyCanaries(container, result)) { + return; + } + + var emitFlags = { + emitAttrs: getTestParameter(suite, group, test, PARAM_CHECK_ATTRIBUTES), + emitStyle: getTestParameter(suite, group, test, PARAM_CHECK_STYLE), + emitClass: getTestParameter(suite, group, test, PARAM_CHECK_CLASS), + emitID: getTestParameter(suite, group, test, PARAM_CHECK_ID), + lowercase: true, + canonicalizeUnits: true + }; + + // 2a.) Compare opening tag - + // decide whether to compare vs. outer or inner HTML based on this. + var openingTagEnd = result.outerHTML.indexOf('>') + 1; + var openingTag = result.outerHTML.substr(0, openingTagEnd); + + openingTag = canonicalizeElementsAndAttributes(openingTag, emitFlags); + var tagCmp = compareHTMLToExpectation(openingTag, container.tagOpen, emitFlags); + + if (tagCmp == RESULT_EQUAL) { + result.output = result.innerHTML; + compareHTMLTestResultTo( + getTestParameter(suite, group, test, PARAM_EXPECTED), + getTestParameter(suite, group, test, PARAM_ACCEPT), + result.innerHTML, + emitFlags, + result) + } else { + result.output = result.outerHTML; + compareHTMLTestResultTo( + getContainerParameter(suite, group, test, container, PARAM_EXPECTED_OUTER), + getContainerParameter(suite, group, test, container, PARAM_ACCEPT_OUTER), + result.outerHTML, + emitFlags, + result) + } +} + +/** + * Insert a selection position indicator. + * + * @param node {DOMNode} the node where to insert the selection indicator + * @param offs {Integer} the offset of the selection indicator + * @param textInd {String} the indicator to use if the node is a text node + * @param elemInd {String} the indicator to use if the node is an element node + */ +function insertSelectionIndicator(node, offs, textInd, elemInd) { + switch (node.nodeType) { + case DOM_NODE_TYPE_TEXT: + // Insert selection marker for text node into text content. + var text = node.data; + node.data = text.substring(0, offs) + textInd + text.substring(offs); + break; + + case DOM_NODE_TYPE_ELEMENT: + var child = node.firstChild; + try { + // node has other children: insert marker as comment node + var comment = document.createComment(elemInd); + while (child && offs) { + --offs; + child = child.nextSibling; + } + if (child) { + node.insertBefore(comment, child); + } else { + node.appendChild(comment); + } + } catch (ex) { + // can't append child comment -> insert as special attribute(s) + switch (elemInd) { + case '|': + node.setAttribute(ATTRNAME_SEL_START, '1'); + node.setAttribute(ATTRNAME_SEL_END, '1'); + break; + + case '{': + node.setAttribute(ATTRNAME_SEL_START, '1'); + break; + + case '}': + node.setAttribute(ATTRNAME_SEL_END, '1'); + break; + } + } + break; + } +} + +/** + * Adds quotes around all text nodes to show cases with non-normalized + * text nodes. Those are not a bug, but may still be usefil in helping to + * debug erroneous cases. + * + * @param node {DOMNode} root node from which to descend + */ +function encloseTextNodesWithQuotes(node) { + switch (node.nodeType) { + case DOM_NODE_TYPE_ELEMENT: + for (var i = 0; i < node.childNodes.length; ++i) { + encloseTextNodesWithQuotes(node.childNodes[i]); + } + break; + + case DOM_NODE_TYPE_TEXT: + node.data = '\x60' + node.data + '\xb4'; + break; + } +} + +/** + * Retrieve the result of a test run and do some preliminary canonicalization. + * + * @param container {Object} the container where to retrieve the result from as object reference + * @param result {Object} object reference that contains the result data + * @return {String} a preliminarily canonicalized innerHTML with selection markers + */ +function prepareHTMLTestResult(container, result) { + // Start with empty strings in case any of the below throws. + result.innerHTML = ''; + result.outerHTML = ''; + + // 1.) insert selection markers + var selRange = createFromWindow(container.win); + if (selRange) { + // save values, since range object gets auto-modified + var node1 = selRange.getAnchorNode(); + var offs1 = selRange.getAnchorOffset(); + var node2 = selRange.getFocusNode(); + var offs2 = selRange.getFocusOffset(); + + // add markers + if (node1 && node1 == node2 && offs1 == offs2) { + // collapsed selection + insertSelectionIndicator(node1, offs1, '^', '|'); + } else { + // Start point and end point are different + if (node1) { + insertSelectionIndicator(node1, offs1, '[', '{'); + } + + if (node2) { + if (node1 == node2 && offs1 < offs2) { + // Anchor indicator was inserted under the same node, so we need + // to shift the offset by 1 + ++offs2; + } + insertSelectionIndicator(node2, offs2, ']', '}'); + } + } + } + + // 2.) insert markers for text node boundaries; + encloseTextNodesWithQuotes(container.editor); + + // 3.) retrieve inner and outer HTML + result.innerHTML = initialCanonicalizationOf(container.editor.innerHTML); + result.bodyInnerHTML = initialCanonicalizationOf(container.body.innerHTML); + if (goog.userAgent.IE) { + result.outerHTML = initialCanonicalizationOf(container.editor.outerHTML); + result.bodyOuterHTML = initialCanonicalizationOf(container.body.outerHTML); + result.outerHTML = result.outerHTML.replace(/^\s+/, ''); + result.outerHTML = result.outerHTML.replace(/\s+$/, ''); + result.bodyOuterHTML = result.bodyOuterHTML.replace(/^\s+/, ''); + result.bodyOuterHTML = result.bodyOuterHTML.replace(/\s+$/, ''); + } else { + result.outerHTML = initialCanonicalizationOf(new XMLSerializer().serializeToString(container.editor)); + result.bodyOuterHTML = initialCanonicalizationOf(new XMLSerializer().serializeToString(container.body)); + } +} + +/** + * Compare a text test result to the expectation string(s). + * + * @param suite {Object} the test suite as object reference + * @param group {Object} group of tests within the suite the test belongs to + * @param test {Object} the test as object reference + * @param actual {String/Boolean} actual value + * @param expected {String/Array} expectation(s) + * @return {Boolean} whether we found a match + */ +function compareTextTestResultWith(suite, group, test, actual, expected) { + var expectedArr = getExpectationArray(expected); + // Find the most favorable result among the possible expectation strings. + var count = expectedArr.length; + + // If the value matches the expectation exactly, then we're fine. + for (var idx = 0; idx < count; ++idx) { + if (actual === expectedArr[idx]) + return true; + } + + // Otherwise see if we should canonicalize specific value types. + // + // We only need to look at font name, color and size units if the originating + // test was both a) queryCommandValue and b) querying a font name/color/size + // specific criterion. + // + // TODO(rolandsteiner): This is ugly! Refactor! + switch (getTestParameter(suite, group, test, PARAM_QUERYCOMMANDVALUE)) { + case 'backcolor': + case 'forecolor': + case 'hilitecolor': + for (var idx = 0; idx < count; ++idx) { + if (new Color(actual).compare(new Color(expectedArr[idx]))) + return true; + } + return false; + + case 'fontname': + for (var idx = 0; idx < count; ++idx) { + if (new FontName(actual).compare(new FontName(expectedArr[idx]))) + return true; + } + return false; + + case 'fontsize': + for (var idx = 0; idx < count; ++idx) { + if (new FontSize(actual).compare(new FontSize(expectedArr[idx]))) + return true; + } + return false; + } + + return false; +} + +/** + * Compare the passed-in text test result to the expectation string(s). + * Sets the global result variables. + * + * @param suite {Object} the test suite as object reference + * @param group {Object} group of tests within the suite the test belongs to + * @param test {Object} the test as object reference + * @param actual {String/Boolean} actual value + * @return {Integer} a RESUTLHTML... result value + * @see variables.js for result values + */ +function compareTextTestResult(suite, group, test, result) { + var expected = getTestParameter(suite, group, test, PARAM_EXPECTED); + if (compareTextTestResultWith(suite, group, test, result.output, expected)) { + result.valresult = VALRESULT_EQUAL; + return; + } + var accepted = getTestParameter(suite, group, test, PARAM_ACCEPT); + if (accepted && compareTextTestResultWith(suite, group, test, result.output, accepted)) { + result.valresult = VALRESULT_ACCEPT; + return; + } + result.valresult = VALRESULT_DIFF; +} + |