Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(aria-allowed-attr): check for invalid aria-attributes for role="row" #3160

Merged
merged 16 commits into from
Oct 1, 2021
57 changes: 54 additions & 3 deletions lib/checks/aria/aria-allowed-attr-evaluate.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { uniqueArray } from '../../core/utils';
import { uniqueArray, closest } from '../../core/utils';
import { getRole, allowedAttr, validateAttr } from '../../commons/aria';
import cache from '../../core/base/cache';

/**
* Check if each ARIA attribute on an element is allowed for its semantic role.
Expand Down Expand Up @@ -31,13 +32,42 @@ function ariaAllowedAttrEvaluate(node, options, virtualNode) {
const role = getRole(virtualNode);
const attrs = virtualNode.attrNames;
let allowed = allowedAttr(role);

// @deprecated: allowed attr options to pass more attrs.
// configure the standards spec instead
if (Array.isArray(options[role])) {
allowed = uniqueArray(options[role].concat(allowed));
}

// TODO: look into memoizing getRole and removing this local cache
straker marked this conversation as resolved.
Show resolved Hide resolved
let tableMap = cache.get('aria-allowed-attr-table');
if (!tableMap) {
tableMap = new WeakMap();
cache.set('aria-allowed-attr-table', tableMap);
}

// check if the parent exists otherwise a TypeError will occur (virtual-nodes specifically)
if (virtualNode.parent) {
Zidious marked this conversation as resolved.
Show resolved Hide resolved
const table = closest(
virtualNode,
'table, [role="treegrid"], [role="table"], [role="grid"]'
);
let tableRole = tableMap.get(table);
if (table && !tableRole) {
tableRole = getRole(table);
tableMap.set(table, tableRole);
}

if (['table', 'grid'].includes(tableRole) && role === 'row') {
validateDescendantAttrs(attrs, virtualNode, invalid);
this.data({
messageKey: 'table',
values: invalid,
tableRole
});
return false;
}
}

if (allowed) {
for (let i = 0; i < attrs.length; i++) {
const attrName = attrs[i];
Expand All @@ -51,8 +81,29 @@ function ariaAllowedAttrEvaluate(node, options, virtualNode) {
this.data(invalid);
return false;
}

return true;
}

/**
* https://www.w3.org/TR/wai-aria-1.2/#row
* When row=role is a desecendant of table or grid if said row
* contains 'aria-posinset', 'aria-setsize', 'aria-expanded', 'aria-level'
* We want to push the attrName to invalid
* @attrs - row to evaluate
* @invalid - array to push element
**/
function validateDescendantAttrs(attrs, virtualNode, invalid) {
for (let i = 0; i < attrs.length; i++) {
const attrName = attrs[i];
if (
validateAttr(attrName) &&
['aria-posinset', 'aria-setsize', 'aria-expanded', 'aria-level'].includes(
attrName
)
) {
invalid.push(attrName + '="' + virtualNode.attr(attrName) + '"');
}
}
}

export default ariaAllowedAttrEvaluate;
3 changes: 2 additions & 1 deletion lib/checks/aria/aria-allowed-attr.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"pass": "ARIA attributes are used correctly for the defined role",
"fail": {
"singular": "ARIA attribute is not allowed: ${data.values}",
"plural": "ARIA attributes are not allowed: ${data.values}"
"plural": "ARIA attributes are not allowed: ${data.values}",
"table": "ARIA attributes are not allowed: ${data.values} on role=row when a descendant of ${data.roleTable}"
}
}
}
Expand Down
1 change: 0 additions & 1 deletion lib/checks/aria/aria-allowed-role-evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ function ariaAllowedRoleEvaluate(node, options = {}, virtualNode) {
}

const unallowedRoles = getElementUnallowedRoles(virtualNode, allowImplicit);

if (unallowedRoles.length) {
this.data(unallowedRoles);
if (!isVisible(virtualNode, true)) {
Expand Down
66 changes: 66 additions & 0 deletions test/checks/aria/allowed-attr.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,72 @@ describe('aria-allowed-attr', function() {
assert.isNull(checkContext._data);
});

[
'aria-posinset="1"',
'aria-setsize="1"',
'aria-expanded="true"',
'aria-level="1"'
].forEach(function(attrName) {
it(
'should return false when ' +
attrName +
' is used on role=row thats parent is a table',
function() {
var vNode = queryFixture(
' <div role="table">' +
'<div id="target" role="row" ' +
attrName +
'></div>' +
'</div>'
);

assert.isFalse(
axe.testUtils
.getCheckEvaluate('aria-allowed-attr')
.call(checkContext, null, null, vNode)
);
assert.deepEqual(checkContext._data, {
messageKey: 'table',
values: [attrName],
tableRole: 'table'
});
}
);
});

[
'aria-posinset="1"',
'aria-setsize="1"',
'aria-expanded="true"',
'aria-level="1"'
].forEach(function(attrName) {
it(
'should return false when ' +
attrName +
' is used on role=row thats parent is a grid',
function() {
var vNode = queryFixture(
' <div role="grid">' +
'<div id="target" role="row" ' +
attrName +
'></div>' +
'</div>'
);

assert.isFalse(
axe.testUtils
.getCheckEvaluate('aria-allowed-attr')
.call(checkContext, null, null, vNode)
);
assert.deepEqual(checkContext._data, {
messageKey: 'table',
values: [attrName],
tableRole: 'grid'
});
}
);
});

describe('options', function() {
it('should allow provided attribute names for a role', function() {
axe.configure({
Expand Down
14 changes: 14 additions & 0 deletions test/integration/rules/aria-allowed-attr/failures.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,17 @@
<div role="mark" aria-labelledby="value" id="fail32">fail</div>
<div role="suggestion" aria-label="value" id="fail33">fail</div>
<div role="suggestion" aria-labelledby="value" id="fail34">fail</div>

<div role="table">
<div role="row" aria-expanded="false" id="fail35"></div>
<div role="row" aria-posinset="1" id="fail36"></div>
<div role="row" aria-setsize="10" id="fail37"></div>
<div role="row" aria-level="1" id="fail38"></div>
</div>

<div role="grid">
<div role="row" aria-expanded="false" id="fail39"></div>
<div role="row" aria-posinset="1" id="fail40"></div>
<div role="row" aria-setsize="10" id="fail41"></div>
<div role="row" aria-level="1" id="fail42"></div>
</div>
10 changes: 9 additions & 1 deletion test/integration/rules/aria-allowed-attr/failures.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@
["#fail31"],
["#fail32"],
["#fail33"],
["#fail34"]
["#fail34"],
["#fail35"],
["#fail36"],
["#fail37"],
["#fail38"],
["#fail39"],
["#fail40"],
["#fail41"],
["#fail42"]
]
}