summaryrefslogtreecommitdiffstats
path: root/accessible/tests/mochitest/attributes.js
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests/mochitest/attributes.js')
-rw-r--r--accessible/tests/mochitest/attributes.js516
1 files changed, 516 insertions, 0 deletions
diff --git a/accessible/tests/mochitest/attributes.js b/accessible/tests/mochitest/attributes.js
new file mode 100644
index 0000000000..2621e24076
--- /dev/null
+++ b/accessible/tests/mochitest/attributes.js
@@ -0,0 +1,516 @@
+/* import-globals-from common.js */
+
+// //////////////////////////////////////////////////////////////////////////////
+// Object attributes.
+
+/**
+ * Test object attributes.
+ *
+ * @param aAccOrElmOrID [in] the accessible identifier
+ * @param aAttrs [in] the map of expected object attributes
+ * (name/value pairs)
+ * @param aSkipUnexpectedAttrs [in] points this function doesn't fail if
+ * unexpected attribute is encountered
+ * @param aTodo [in] true if this is a 'todo'
+ */
+function testAttrs(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, aTodo) {
+ testAttrsInternal(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, null, aTodo);
+}
+
+/**
+ * Test object attributes that must not be present.
+ *
+ * @param aAccOrElmOrID [in] the accessible identifier
+ * @param aAbsentAttrs [in] map of attributes that should not be
+ * present (name/value pairs)
+ * @param aTodo [in] true if this is a 'todo'
+ */
+function testAbsentAttrs(aAccOrElmOrID, aAbsentAttrs, aTodo) {
+ testAttrsInternal(aAccOrElmOrID, {}, true, aAbsentAttrs, aTodo);
+}
+
+/**
+ * Test object attributes that aren't right, but should be (todo)
+ *
+ * @param aAccOrElmOrID [in] the accessible identifier
+ * @param aKey [in] attribute name
+ * @param aExpectedValue [in] expected attribute value
+ */
+function todoAttr(aAccOrElmOrID, aKey, aExpectedValue) {
+ testAttrs(
+ aAccOrElmOrID,
+ Object.fromEntries([[aKey, aExpectedValue]]),
+ true,
+ true
+ );
+}
+
+/**
+ * Test CSS based object attributes.
+ */
+function testCSSAttrs(aID) {
+ var node = document.getElementById(aID);
+ var computedStyle = document.defaultView.getComputedStyle(node);
+
+ var attrs = {
+ display: computedStyle.display,
+ "text-align": computedStyle.textAlign,
+ "text-indent": computedStyle.textIndent,
+ "margin-left": computedStyle.marginLeft,
+ "margin-right": computedStyle.marginRight,
+ "margin-top": computedStyle.marginTop,
+ "margin-bottom": computedStyle.marginBottom,
+ };
+ testAttrs(aID, attrs, true);
+}
+
+/**
+ * Test the accessible that it doesn't have CSS-based object attributes.
+ */
+function testAbsentCSSAttrs(aID) {
+ var attrs = {
+ display: "",
+ "text-align": "",
+ "text-indent": "",
+ "margin-left": "",
+ "margin-right": "",
+ "margin-top": "",
+ "margin-bottom": "",
+ };
+ testAbsentAttrs(aID, attrs);
+}
+
+/**
+ * Test group object attributes (posinset, setsize and level) and
+ * nsIAccessible::groupPosition() method.
+ *
+ * @param aAccOrElmOrID [in] the ID, DOM node or accessible
+ * @param aPosInSet [in] the value of 'posinset' attribute
+ * @param aSetSize [in] the value of 'setsize' attribute
+ * @param aLevel [in, optional] the value of 'level' attribute
+ */
+function testGroupAttrs(aAccOrElmOrID, aPosInSet, aSetSize, aLevel, aTodo) {
+ var acc = getAccessible(aAccOrElmOrID);
+ var levelObj = {},
+ posInSetObj = {},
+ setSizeObj = {};
+ acc.groupPosition(levelObj, setSizeObj, posInSetObj);
+
+ let groupPos = {},
+ expectedGroupPos = {};
+
+ if (aPosInSet && aSetSize) {
+ groupPos.setsize = String(setSizeObj.value);
+ groupPos.posinset = String(posInSetObj.value);
+
+ expectedGroupPos.setsize = String(aSetSize);
+ expectedGroupPos.posinset = String(aPosInSet);
+ }
+
+ if (aLevel) {
+ groupPos.level = String(levelObj.value);
+
+ expectedGroupPos.level = String(aLevel);
+ }
+
+ compareSimpleObjects(
+ groupPos,
+ expectedGroupPos,
+ false,
+ "wrong groupPos",
+ aTodo
+ );
+
+ testAttrs(aAccOrElmOrID, expectedGroupPos, true, aTodo);
+}
+
+function testGroupParentAttrs(
+ aAccOrElmOrID,
+ aChildItemCount,
+ aIsHierarchical,
+ aTodo
+) {
+ testAttrs(
+ aAccOrElmOrID,
+ { "child-item-count": String(aChildItemCount) },
+ true,
+ aTodo
+ );
+
+ if (aIsHierarchical) {
+ testAttrs(aAccOrElmOrID, { tree: "true" }, true, aTodo);
+ } else {
+ testAbsentAttrs(aAccOrElmOrID, { tree: "true" });
+ }
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Text attributes.
+
+/**
+ * Test text attributes.
+ *
+ * @param aID [in] the ID of DOM element having text
+ * accessible
+ * @param aOffset [in] the offset inside text accessible to fetch
+ * text attributes
+ * @param aAttrs [in] the map of expected text attributes
+ * (name/value pairs) exposed at the offset
+ * @param aDefAttrs [in] the map of expected text attributes
+ * (name/value pairs) exposed on hyper text
+ * accessible
+ * @param aStartOffset [in] expected start offset where text attributes
+ * are applied
+ * @param aEndOffset [in] expected end offset where text attribute
+ * are applied
+ * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if
+ * unexpected attribute is encountered
+ */
+function testTextAttrs(
+ aID,
+ aOffset,
+ aAttrs,
+ aDefAttrs,
+ aStartOffset,
+ aEndOffset,
+ aSkipUnexpectedAttrs
+) {
+ var accessible = getAccessible(aID, [nsIAccessibleText]);
+ if (!accessible) {
+ return;
+ }
+
+ var startOffset = { value: -1 };
+ var endOffset = { value: -1 };
+
+ // do not include attributes exposed on hyper text accessible
+ var attrs = getTextAttributes(
+ aID,
+ accessible,
+ false,
+ aOffset,
+ startOffset,
+ endOffset
+ );
+
+ if (!attrs) {
+ return;
+ }
+
+ var errorMsg = " for " + aID + " at offset " + aOffset;
+
+ is(startOffset.value, aStartOffset, "Wrong start offset" + errorMsg);
+ is(endOffset.value, aEndOffset, "Wrong end offset" + errorMsg);
+
+ compareAttrs(errorMsg, attrs, aAttrs, aSkipUnexpectedAttrs);
+
+ // include attributes exposed on hyper text accessible
+ var expectedAttrs = {};
+ for (let name in aAttrs) {
+ expectedAttrs[name] = aAttrs[name];
+ }
+
+ for (let name in aDefAttrs) {
+ if (!(name in expectedAttrs)) {
+ expectedAttrs[name] = aDefAttrs[name];
+ }
+ }
+
+ attrs = getTextAttributes(
+ aID,
+ accessible,
+ true,
+ aOffset,
+ startOffset,
+ endOffset
+ );
+
+ if (!attrs) {
+ return;
+ }
+
+ compareAttrs(errorMsg, attrs, expectedAttrs, aSkipUnexpectedAttrs);
+}
+
+/**
+ * Test default text attributes.
+ *
+ * @param aID [in] the ID of DOM element having text
+ * accessible
+ * @param aDefAttrs [in] the map of expected text attributes
+ * (name/value pairs)
+ * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if
+ * unexpected attribute is encountered
+ */
+function testDefaultTextAttrs(aID, aDefAttrs, aSkipUnexpectedAttrs) {
+ var accessible = getAccessible(aID, [nsIAccessibleText]);
+ if (!accessible) {
+ return;
+ }
+
+ var defAttrs = null;
+ try {
+ defAttrs = accessible.defaultTextAttributes;
+ } catch (e) {}
+
+ if (!defAttrs) {
+ ok(false, "Can't get default text attributes for " + aID);
+ return;
+ }
+
+ var errorMsg = ". Getting default text attributes for " + aID;
+ compareAttrs(errorMsg, defAttrs, aDefAttrs, aSkipUnexpectedAttrs);
+}
+
+/**
+ * Test text attributes for wrong offset.
+ */
+function testTextAttrsWrongOffset(aID, aOffset) {
+ var res = false;
+ try {
+ var s = {},
+ e = {};
+ // Bug 1602031
+ // eslint-disable-next-line no-undef
+ var acc = getAccessible(ID, [nsIAccessibleText]);
+ acc.getTextAttributes(false, 157, s, e);
+ } catch (ex) {
+ res = true;
+ }
+
+ ok(
+ res,
+ "text attributes are calculated successfully at wrong offset " +
+ aOffset +
+ " for " +
+ prettyName(aID)
+ );
+}
+
+const kNormalFontWeight = function equalsToNormal(aWeight) {
+ return aWeight <= 400;
+};
+
+const kBoldFontWeight = function equalsToBold(aWeight) {
+ return aWeight > 400;
+};
+
+let isNNT = SpecialPowers.getBoolPref("widget.non-native-theme.enabled");
+// The pt font size of the input element can vary by Linux distro.
+const kInputFontSize =
+ WIN || (MAC && isNNT)
+ ? "10pt"
+ : MAC
+ ? "8pt"
+ : function() {
+ return true;
+ };
+
+const kAbsentFontFamily = function(aFontFamily) {
+ return aFontFamily != "sans-serif";
+};
+const kInputFontFamily = function(aFontFamily) {
+ return aFontFamily != "sans-serif";
+};
+
+const kMonospaceFontFamily = function(aFontFamily) {
+ return aFontFamily != "monospace";
+};
+const kSansSerifFontFamily = function(aFontFamily) {
+ return aFontFamily != "sans-serif";
+};
+const kSerifFontFamily = function(aFontFamily) {
+ return aFontFamily != "serif";
+};
+
+const kCursiveFontFamily = LINUX ? "DejaVu Serif" : "Comic Sans MS";
+
+/**
+ * Return used font from the given computed style.
+ */
+function fontFamily(aComputedStyle) {
+ var name = aComputedStyle.fontFamily;
+ switch (name) {
+ case "monospace":
+ return kMonospaceFontFamily;
+ case "sans-serif":
+ return kSansSerifFontFamily;
+ case "serif":
+ return kSerifFontFamily;
+ default:
+ return name;
+ }
+}
+
+/**
+ * Returns a computed system color for this document.
+ */
+function getSystemColor(aColor) {
+ let { r, g, b, a } = InspectorUtils.colorToRGBA(aColor, document);
+ return a == 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a})`;
+}
+
+/**
+ * Build an object of default text attributes expected for the given accessible.
+ *
+ * @param aID [in] identifier of accessible
+ * @param aFontSize [in] font size
+ * @param aFontWeight [in, optional] kBoldFontWeight or kNormalFontWeight,
+ * default value is kNormalFontWeight
+ */
+function buildDefaultTextAttrs(aID, aFontSize, aFontWeight, aFontFamily) {
+ var elm = getNode(aID);
+ var computedStyle = document.defaultView.getComputedStyle(elm);
+ var bgColor =
+ computedStyle.backgroundColor == "rgba(0, 0, 0, 0)"
+ ? getSystemColor("Canvas")
+ : computedStyle.backgroundColor;
+
+ var defAttrs = {
+ "font-style": computedStyle.fontStyle,
+ "font-size": aFontSize,
+ "background-color": bgColor,
+ "font-weight": aFontWeight ? aFontWeight : kNormalFontWeight,
+ color: computedStyle.color,
+ "font-family": aFontFamily ? aFontFamily : fontFamily(computedStyle),
+ "text-position": computedStyle.verticalAlign,
+ };
+
+ return defAttrs;
+}
+
+// //////////////////////////////////////////////////////////////////////////////
+// Private.
+
+function getTextAttributes(
+ aID,
+ aAccessible,
+ aIncludeDefAttrs,
+ aOffset,
+ aStartOffset,
+ aEndOffset
+) {
+ // This function expects the passed in accessible to already be queried for
+ // nsIAccessibleText.
+ var attrs = null;
+ try {
+ attrs = aAccessible.getTextAttributes(
+ aIncludeDefAttrs,
+ aOffset,
+ aStartOffset,
+ aEndOffset
+ );
+ } catch (e) {}
+
+ if (attrs) {
+ return attrs;
+ }
+
+ ok(false, "Can't get text attributes for " + aID);
+ return null;
+}
+
+function testAttrsInternal(
+ aAccOrElmOrID,
+ aAttrs,
+ aSkipUnexpectedAttrs,
+ aAbsentAttrs,
+ aTodo
+) {
+ var accessible = getAccessible(aAccOrElmOrID);
+ if (!accessible) {
+ return;
+ }
+
+ var attrs = null;
+ try {
+ attrs = accessible.attributes;
+ } catch (e) {}
+
+ if (!attrs) {
+ ok(false, "Can't get object attributes for " + prettyName(aAccOrElmOrID));
+ return;
+ }
+
+ var errorMsg = " for " + prettyName(aAccOrElmOrID);
+ compareAttrs(
+ errorMsg,
+ attrs,
+ aAttrs,
+ aSkipUnexpectedAttrs,
+ aAbsentAttrs,
+ aTodo
+ );
+}
+
+function compareAttrs(
+ aErrorMsg,
+ aAttrs,
+ aExpectedAttrs,
+ aSkipUnexpectedAttrs,
+ aAbsentAttrs,
+ aTodo
+) {
+ // Check if all obtained attributes are expected and have expected value.
+ let attrObject = {};
+ for (let prop of aAttrs.enumerate()) {
+ attrObject[prop.key] = prop.value;
+ }
+
+ // Create expected attributes set by using the return values from
+ // embedded functions to determine the entry's value.
+ let expectedObj = Object.fromEntries(
+ Object.entries(aExpectedAttrs).map(([k, v]) => {
+ if (v instanceof Function) {
+ // If value is a function that returns true given the received
+ // attribute value, assign the attribute value to the entry.
+ // If it is false, stringify the function for good error reporting.
+ let value = v(attrObject[k]) ? attrObject[k] : v.toString();
+ return [k, value];
+ }
+
+ return [k, v];
+ })
+ );
+
+ compareSimpleObjects(
+ attrObject,
+ expectedObj,
+ aSkipUnexpectedAttrs,
+ aErrorMsg,
+ aTodo
+ );
+
+ // Check if all unexpected attributes are absent.
+ if (aAbsentAttrs) {
+ let presentAttrs = Object.keys(attrObject).filter(
+ k => aAbsentAttrs[k] !== undefined
+ );
+ if (presentAttrs.length) {
+ (aTodo ? todo : ok)(
+ false,
+ `There were unexpected attributes: ${presentAttrs}`
+ );
+ }
+ }
+}
+
+function compareSimpleObjects(
+ aObj,
+ aExpectedObj,
+ aSkipUnexpectedAttrs,
+ aMessage,
+ aTodo
+) {
+ let keys = aSkipUnexpectedAttrs
+ ? Object.keys(aExpectedObj).sort()
+ : Object.keys(aObj).sort();
+ let o1 = JSON.stringify(aObj, keys);
+ let o2 = JSON.stringify(aExpectedObj, keys);
+
+ if (aTodo) {
+ todo_is(o1, o2, `${aMessage} - Got ${o1}, expected ${o2}`);
+ } else {
+ is(o1, o2, aMessage);
+ }
+}