Bug 468034 - make automated tests for name calculation rules, r=marcoz, davidb

This commit is contained in:
Alexander Surkov 2009-01-20 23:08:38 +08:00
parent f7a7d7a499
commit 3dae18d659
5 changed files with 433 additions and 15 deletions

View File

@ -69,6 +69,7 @@ _TEST_FILES =\
test_events_mutation.html \
test_groupattrs.xul \
test_groupattrs.html \
test_name_markup.html \
$(warning test_table_indexes.html temporarily disabled) \
test_nsIAccessible_actions.html \
$(warning test_nsIAccessible_actions.xul temporarily disabled) \

View File

@ -148,17 +148,19 @@ function getNode(aNodeOrID)
}
/**
* Return accessible for the given ID attribute or DOM element or accessible.
* Return accessible for the given identifier (may be ID attribute or DOM
* element or accessible object).
*
* @param aAccOrElmOrID [in] DOM element or ID attribute to get an accessible
* for or an accessible to query additional interfaces.
* @param aInterfaces [in, optional] the accessible interface or the array of
* accessible interfaces to query it/them from obtained
* accessible
* @param aElmObj [out, optional] object to store DOM element which
* accessible is obtained for
* @param aAccOrElmOrID [in] identifier to get an accessible implementing
* the given interfaces
* @param aInterfaces [in, optional] the interface or an array interfaces
* to query it/them from obtained accessible
* @param aElmObj [out, optional] object to store DOM element which
* accessible is obtained for
* @param aDoNotFailIfNoAcc [in, optional] no error if the given identifier is
* not accessible
*/
function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj)
function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIfNoAcc)
{
var elm = null;
@ -188,7 +190,9 @@ function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj)
}
if (!acc) {
ok(false, "Can't get accessible for " + aAccOrElmOrID);
if (!aDoNotFailIfNoAcc)
ok(false, "Can't get accessible for " + aAccOrElmOrID);
return null;
}
}
@ -218,6 +222,14 @@ function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj)
return acc;
}
/**
* Return true if the given identifier has an accessible.
*/
function isAccessible(aAccOrElmOrID)
{
return getAccessible(aAccOrElmOrID, null, null, true) ? true : false;
}
////////////////////////////////////////////////////////////////////////////////
// Accessible Events

View File

@ -0,0 +1,168 @@
<?xml version="1.0"?>
<!--
This XML file is used to create sequence of accessible name tests. It consist of
two sections. The first section 'ruledfn' declares name computation rules.
The second section 'rulesample' defines markup samples we need to check name
computation rules for.
Section 'ruledfn' consist of 'ruleset' elements. Every 'ruleset' element is
presented by 'rule' elements so that sequence of 'rule' elements gives the
sequence of name computations rules. Every 'rule' element can be one of four
types.
* name is equal to the value of attribute presented on the element. Example,
'aria-label' attribute. In this case 'rule' element has 'attr' attribute
pointing to attribute name and 'type' attribute with 'string' value. For
example, <rule attr="aria-label" type="string"/>.
* name is calculated from elements that are pointed to by attribute value on
the element. Example is 'aria-labelledby'. In this case 'rule' element
has 'attr' attribute holding the sequence of IDs of elements used to
compute the name, in addition the 'rule' element has 'type' attribute with
'ref' value. For example, <rule attr="aria-labelledby" type="ref"/>.
* name is calculated from another element. Example, html:label@for element.
In this case 'rule' element has 'elm' and 'elmattr' attributes. These
attributes are used to find an element by tagname and attribute with value
equaled to ID of the element. For example, <rule elm="label" elmattr="for"/>.
* name is computed from subtree. Example, html:button. In this case 'rule'
element has 'fromsubtree' attribute with 'true' value. For example,
<rule fromsubtree="true"/>
Section 'rulesample' provides set of markup samples ('markup' elements). Every
'markup' element contains an element that accessible name will be computed for
(let's call it test element). In addition the 'markup' element contains some
other elements from native markup used in name calculation process for test
element. Test element is pointed to by 'ref' attribute on 'markup' element.
Also 'markup' element has 'ruleset' attribute to indicate ruleset for the test
element.
How does it work? Let's consider simple example:
<ruledfn>
<ruleset id="aria">
<rule attr="aria-label" type="string"/>
<rule attr="aria-labelledby" type="ref"/>
</ruleset>
</ruledfn>
<rulesample>
<markup ref="html:div" ruleset="aria">
<html:span id="label" a11yname="test2">test2</html:span>
<html:div aria-label="test1"
aria-labelledby="label">it's a div</html:div>
</markup>
</rulesample>
Initially 'markup' element holds markup for all rules specified by 'ruleset'
attribute. This allows us to check if the sequence of name computation rules
is correct. Here 'ruleset' element defines two rules. We get the first rule
which means accesible name is computed from value of 'aria-label' attribute.
Then we check accessible name for the test element and remove 'aria-label'
attribute. After we get the second rule which means we should get IDs from
'aria-labelledby' attribute and compose accessible name from values of
'a11yname' attributes (that are supposed to give the desired name for each
element that is being pointed to by aria-labelledby). Check accessible name
and finish test.
-->
<rules xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<ruledfn>
<!-- bricks -->
<ruleset id="aria">
<rule attr="aria-label" type="string"/>
<rule attr="aria-labelledby" type="ref"/>
</ruleset>
<ruleset id="htmlctrl_start">
<ruleset ref="aria"/>
<rule elm="label" elmattr="for"/>
<rule fromsubtree="true"/>
</ruleset>
<ruleset id="htmlctrl_end">
<rule attr="title" type="string"/>
</ruleset>
<ruleset id="htmlelm_start">
<ruleset ref="aria"/>
<rule elm="label" elmattr="for"/>
</ruleset>
<ruleset id="htmlelm_end">
<rule attr="title" type="string"/>
</ruleset>
<!-- general -->
<ruleset id="htmlctrl">
<ruleset ref="htmlctrl_start"/>
<ruleset ref="htmlctrl_end"/>
</ruleset>
<ruleset id="htmlelm">
<ruleset ref="htmlelm_start"/>
<ruleset ref="htmlelm_end"/>
</ruleset>
<!-- specific -->
<ruleset id="htmlinputbutton">
<ruleset ref="htmlelm_start"/>
<rule attr="value" type="string"/>
<rule attr="alt" type="string"/>
<rule attr="src" type="string"/>
<rule attr="data" type="string"/>
<ruleset ref="htmlelm_end"/>
</ruleset>
<ruleset id="htmloption">
<ruleset ref="aria"/>
<rule attr="label" type="string"/>
<rule fromsubtree="true"/>
<rule attr="title" type="string"/>
</ruleset>
</ruledfn>
<rulesample>
<markup ref="html:button" ruleset="htmlctrl">
<html:span id="l1" a11yname="test2">test2</html:span>
<html:span id="l2" a11yname="test3">test3</html:span>
<html:label for="btn" a11yname="test4">test4</html:label>
<html:button id="btn"
aria-label="test1"
aria-labelledby="l1 l2"
title="test5"
a11yname="press me">press me</html:button>
</markup>
<markup ref="html:input" ruleset="htmlinputbutton">
<html:span id="l1" a11yname="test2">test2</html:span>
<html:span id="l2" a11yname="test3">test3</html:span>
<html:label for="btn" a11yname="test4">test4</html:label>
<html:input id="btn"
type="button"
aria-label="test1"
aria-labelledby="l1 l2"
value="test5"
alt="test6"
src="test7"
data="test8"
title="test9"/>
</markup>
<markup ref="html:select/html:option[1]" ruleset="htmloption">
<html:span id="l1" a11yname="test2">test2</html:span>
<html:span id="l2" a11yname="test3">test3</html:span>
<html:select>
<html:option id="opt"
aria-label="test1"
aria-labelledby="l1 l2"
label="test4"
title="test5"
a11yname="option1">option1</html:option>
<html:option>option2</html:option>
</html:select>
</markup>
</rulesample>
</rules>

View File

@ -1,14 +1,207 @@
function testName(aID, aName)
function testName(aAccOrElmOrID, aName, aMsg)
{
var acc = getAccessible(aID);
var msg = aMsg ? aMsg : "";
var acc = getAccessible(aAccOrElmOrID);
if (!acc) {
ok(false, "No accessible for " + aID + "!");
ok(false, msg + "No accessible for " + aAccOrElmOrID + "!");
}
try {
is(acc.name, aName, "Wrong name of the accessible for " + aID);
is(acc.name, aName, msg + "Wrong name of the accessible for " + aAccOrElmOrID);
} catch (e) {
ok(false, "Can't get name of the accessible for " + aID);
ok(false, msg + "Can't get name of the accessible for " + aAccOrElmOrID);
}
return acc;
}
////////////////////////////////////////////////////////////////////////////////
// Name tests described by "namerules.xml" file.
var gNameRulesFileURL =
"chrome://mochikit/content/a11y/accessible/namerules.xml";
var gRuleDoc = null;
/**
* Start name tests. Run through markup elements and test names for test
* element (see namerules.xml for details).
*/
function testNames()
{
var request = new XMLHttpRequest();
request.open("get", gNameRulesFileURL, false);
request.send();
gRuleDoc = request.responseXML;
var markupElms = evaluateXPath(gRuleDoc, "//rules/rulesample/markup");
for (var idx = 0; idx < markupElms.length; idx++)
testNamesForMarkup(markupElms[idx]);
}
////////////////////////////////////////////////////////////////////////////////
// Private section.
/**
* Process every 'markup' element and test names for it. Used by testNames
* function.
*/
function testNamesForMarkup(aMarkupElm)
{
var div = document.createElement("div");
div.setAttribute("test", "test");
var child = aMarkupElm.firstChild;
while (child) {
var newChild = document.importNode(child, true);
div.appendChild(newChild);
child = child.nextSibling;
}
document.body.appendChild(div);
var serializer = new XMLSerializer();
var expr = "//html/body/div[@test='test']/" + aMarkupElm.getAttribute("ref");
var elms = evaluateXPath(document, expr, htmlDocResolver);
var ruleId = aMarkupElm.getAttribute("ruleset");
var ruleElms = getRuleElmsByRulesetId(ruleId);
for (var idx = 0; idx < ruleElms.length; idx++)
testNameForRule(elms[0], ruleElms[idx]);
document.body.removeChild(div);
}
/**
* Test name for current rule and current 'markup' element. Used by
* testNamesForMarkup function.
*/
function testNameForRule(aElm, aRule)
{
var attr = aRule.getAttribute("attr");
if (attr) {
var name = "";
var attrValue = aElm.getAttribute(attr);
var type = aRule.getAttribute("type");
if (type == "string") {
name = attrValue;
} else if (type == "ref") {
var ids = attrValue.split(/\s+/);///\,\s*/);
for (var idx = 0; idx < ids.length; idx++) {
var labelElm = getNode(ids[idx]);
if (name != "")
name += " ";
name += labelElm.getAttribute("a11yname");
}
}
var msg = "Attribute '" + attr + "' test. ";
testName(aElm, name, msg);
aElm.removeAttribute(attr);
return;
}
var elm = aRule.getAttribute("elm");
var elmattr = aRule.getAttribute("elmattr");
if (elm && elmattr) {
var filter = {
acceptNode: function filter_acceptNode(aNode)
{
if (aNode.localName == this.mLocalName &&
aNode.getAttribute(this.mAttrName) == this.mAttrValue)
return NodeFilter.FILTER_ACCEPT;
return NodeFilter.FILTER_SKIP;
},
mLocalName: elm,
mAttrName: elmattr,
mAttrValue: aElm.getAttribute("id")
};
var treeWalker = document.createTreeWalker(document.body,
NodeFilter.SHOW_ELEMENT,
filter, false);
var labelElm = treeWalker.nextNode();
var msg = "Element '" + elm + "' test.";
testName(aElm, labelElm.getAttribute("a11yname"), msg);
labelElm.parentNode.removeChild(labelElm);
return;
}
var fromSubtree = aRule.getAttribute("fromsubtree");
if (fromSubtree == "true") {
var msg = "From subtree test.";
testName(aElm, aElm.getAttribute("a11yname"), msg);
while (aElm.firstChild)
aElm.removeChild(aElm.firstChild);
return;
}
}
/**
* Return array of 'rule' elements. Used in conjunction with
* getRuleElmsFromRulesetElm() function.
*/
function getRuleElmsByRulesetId(aRulesetId)
{
var expr = "//rules/ruledfn/ruleset[@id='" + aRulesetId + "']";
var rulesetElm = evaluateXPath(gRuleDoc, expr);
return getRuleElmsFromRulesetElm(rulesetElm[0]);
}
function getRuleElmsFromRulesetElm(aRulesetElm)
{
var rulesetId = aRulesetElm.getAttribute("ref");
if (rulesetId)
return getRuleElmsByRulesetId(rulesetId);
var ruleElms = [];
var child = aRulesetElm.firstChild;
while (child) {
if (child.localName == "ruleset")
ruleElms = ruleElms.concat(getRuleElmsFromRulesetElm(child));
if (child.localName == "rule")
ruleElms.push(child);
child = child.nextSibling;
}
return ruleElms;
}
/**
* Helper method to evaluate xpath expression.
*/
function evaluateXPath(aNode, aExpr, aResolver)
{
var xpe = new XPathEvaluator();
var resolver = aResolver;
if (!resolver) {
var node = aNode.ownerDocument == null ?
aNode.documentElement : aNode.ownerDocument.documentElement;
resolver = xpe.createNSResolver(node);
}
var result = xpe.evaluate(aExpr, aNode, resolver, 0, null);
var found = [];
var res;
while (res = result.iterateNext())
found.push(res);
return found;
}
function htmlDocResolver(aPrefix) {
var ns = {
'html' : 'http://www.w3.org/1999/xhtml'
};
return ns[aPrefix] || null;
}

View File

@ -0,0 +1,44 @@
<html>
<head>
<title>nsIAccessible::name calculation for HTML buttons</title>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/common.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/nsIAccessible_name.js"></script>
<script type="application/javascript">
function doTest()
{
testNames();
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=459635"
title="nsIAccessible::name calculation for HTML buttons">
Mozilla Bug 459635
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
</body>
</html>