mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 17:23:59 +00:00
Bug 468034 - make automated tests for name calculation rules, r=marcoz, davidb
This commit is contained in:
parent
f7a7d7a499
commit
3dae18d659
@ -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) \
|
||||
|
@ -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
|
||||
|
||||
|
168
accessible/tests/mochitest/namerules.xml
Normal file
168
accessible/tests/mochitest/namerules.xml
Normal 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>
|
@ -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;
|
||||
}
|
||||
|
44
accessible/tests/mochitest/test_name_markup.html
Normal file
44
accessible/tests/mochitest/test_name_markup.html
Normal 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>
|
Loading…
Reference in New Issue
Block a user