Skip to content

Commit

Permalink
feat(rule): aria-allowed-role (#945)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeeyyy committed Aug 16, 2018
1 parent 7452a51 commit c270a46
Show file tree
Hide file tree
Showing 17 changed files with 2,280 additions and 96 deletions.
1 change: 1 addition & 0 deletions doc/rule-descriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
| accesskeys | Ensures every accesskey attribute value is unique | Serious | best-practice, cat.keyboard | true |
| area-alt | Ensures <area> elements of image maps have alternate text | Critical | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a | true |
| aria-allowed-attr | Ensures ARIA attributes are allowed for an element's role | Critical | cat.aria, wcag2a, wcag412 | true |
| aria-allowed-role | Ensures role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | true |
| aria-dpub-role-fallback | Ensures unsupported DPUB roles are only used on elements with implicit fallback roles | Moderate | cat.aria, wcag2a, wcag131 | true |
| aria-hidden-body | Ensures aria-hidden='true' is not present on the document body. | Critical | cat.aria, wcag2a, wcag412 | true |
| aria-required-attr | Ensures elements with ARIA roles have all required ARIA attributes | Critical | cat.aria, wcag2a, wcag412 | true |
Expand Down
23 changes: 23 additions & 0 deletions lib/checks/aria/aria-allowed-role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Implements allowed roles defined at:
* https://www.w3.org/TR/html-aria/#docconformance
* https://www.w3.org/TR/SVG2/struct.html#implicit-aria-semantics
*/
const { allowImplicit = true, ignoredTags = [] } = options || {};
const tagName = node.nodeName.toUpperCase();

// check if the element should be ignored, by an user setting
if (ignoredTags.map(t => t.toUpperCase()).includes(tagName)) {
return true;
}

const unallowedRoles = axe.commons.aria.getElementUnallowedRoles(
node,
allowImplicit
);

if (unallowedRoles.length) {
this.data(unallowedRoles);
return false;
}
return true;
15 changes: 15 additions & 0 deletions lib/checks/aria/aria-allowed-role.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "aria-allowed-role",
"evaluate": "aria-allowed-role.js",
"options": {
"allowImplicit": true,
"ignoredTags": []
},
"metadata": {
"impact": "minor",
"messages": {
"pass": "ARIA role is allowed for given element",
"fail": "role{{=it.data && it.data.length > 1 ? 's' : ''}} {{=it.data.join(', ')}} {{=it.data && it.data.length > 1 ? 'are' : ' is'}} not allowed for given element"
}
}
}
80 changes: 80 additions & 0 deletions lib/commons/aria/get-element-unallowed-roles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* global aria */
/**
* gets all unallowed roles for a given node
* @method getElementUnallowedRoles
* @param {Object} node HTMLElement to validate
* @param {String} tagName tag name of a node
* @param {String} allowImplicit option to allow implicit roles, defaults to true
* @return {Array<String>} retruns an array of roles that are not allowed on the given node
*/
aria.getElementUnallowedRoles = function getElementUnallowedRoles(
node,
allowImplicit
) {
/**
* Get roles applied to a given node
* @param {HTMLElement} node HTMLElement
* @return {Array<String>} return an array of roles applied to the node, if no roles, return an empty array.
*/
// TODO: not moving this to outer namespace yet, work with wilco to see overlap with his PR(WIP) - aria.getRole
function getRoleSegments(node) {
let roles = [];
if (!node) {
return roles;
}
if (node.hasAttribute('role')) {
const nodeRoles = axe.utils.tokenList(
node.getAttribute('role').toLowerCase()
);
roles = roles.concat(nodeRoles);
}
if (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) {
const epubRoles = axe.utils
.tokenList(
node
.getAttributeNS('http://www.idpf.org/2007/ops', 'type')
.toLowerCase()
)
.map(role => `doc-${role}`);
roles = roles.concat(epubRoles);
}
return roles;
}

const tagName = node.nodeName.toUpperCase();

// by pass custom elements
if (!axe.utils.isHtmlElement(node)) {
return [];
}

const roleSegments = getRoleSegments(node);
const implicitRole = axe.commons.aria.implicitRole(node);

// stores all roles that are not allowed for a specific element most often an element only has one explicit role
const unallowedRoles = roleSegments.filter(role => {
if (!axe.commons.aria.isValidRole(role)) {
// do not check made-up/ fake roles
return false;
}

// check if an implicit role may be set explicit following a setting
if (!allowImplicit && role === implicitRole) {
// edge case: setting implicit role row on tr element is allowed when child of table[role='grid']
if (
!(
role === 'row' &&
tagName === 'TR' &&
axe.utils.matchesSelector(node, 'table[role="grid"] > tr')
)
) {
return true;
}
}
if (!aria.isAriaRoleAllowedOnElement(node, role)) {
return true;
}
});

return unallowedRoles;
};
Loading

1 comment on commit c270a46

@jeankaplansky
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://dequeuniversity.com/rules/axe/3.1/aria-allowed-role page created. Content requires review.

Please sign in to comment.