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

feat(presentation-role-conflict): create rule to flag elements with role conflict resolution #2284 #2440

Merged
merged 11 commits into from
Sep 28, 2020
57 changes: 29 additions & 28 deletions doc/rule-descriptions.md

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions lib/checks/aria/has-global-aria-attribute-evaluate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import getGlobalAriaAttrs from '../../commons/standards/get-global-aria-attrs';

function hasGlobalAriaAttributeEvaluate(node, options, virtualNode) {
const globalAttrs = getGlobalAriaAttrs().filter(attr =>
virtualNode.hasAttr(attr)
);
this.data(globalAttrs);
return globalAttrs.length > 0;
}

export default hasGlobalAriaAttributeEvaluate;
14 changes: 14 additions & 0 deletions lib/checks/aria/has-global-aria-attribute.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "has-global-aria-attribute",
"evaluate": "has-global-aria-attribute-evaluate",
"metadata": {
"impact": "minor",
"messages": {
"pass": {
"singular": "Element has global ARIA attribute: ${data.values}",
"plural": "Element has global ARIA attributes: ${data.values}"
},
"fail": "Element does not have global ARIA attribute"
}
}
}
7 changes: 7 additions & 0 deletions lib/checks/aria/is-element-focusable-evaluate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { isFocusable } from '../../commons/dom';

function isElementFocusableEvaluate(node, options, virtualNode) {
return isFocusable(virtualNode);
}

export default isElementFocusableEvaluate;
11 changes: 11 additions & 0 deletions lib/checks/aria/is-element-focusable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "is-element-focusable",
"evaluate": "is-element-focusable-evaluate",
"metadata": {
"impact": "minor",
"messages": {
"pass": "Element is focusable.",
"fail": "Element is not focusable."
}
}
}
4 changes: 4 additions & 0 deletions lib/core/base/metadata-function-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import ariaUnsupportedAttrEvaluate from '../../checks/aria/aria-unsupported-attr
import ariaValidAttrEvaluate from '../../checks/aria/aria-valid-attr-evaluate';
import ariaValidAttrValueEvaluate from '../../checks/aria/aria-valid-attr-value-evaluate';
import fallbackroleEvaluate from '../../checks/aria/fallbackrole-evaluate';
import hasGlobalAriaAttributeEvaluate from '../../checks/aria/has-global-aria-attribute-evaluate';
import hasWidgetRoleEvaluate from '../../checks/aria/has-widget-role-evaluate';
import invalidroleEvaluate from '../../checks/aria/invalidrole-evaluate';
import isElementFocusableEvaluate from '../../checks/aria/is-element-focusable-evaluate';
import noImplicitExplicitLabelEvaluate from '../../checks/aria/no-implicit-explicit-label-evaluate';
import unsupportedroleEvaluate from '../../checks/aria/unsupportedrole-evaluate';
import validScrollableSemanticsEvaluate from '../../checks/aria/valid-scrollable-semantics-evaluate';
Expand Down Expand Up @@ -175,8 +177,10 @@ const metadataFunctionMap = {
'aria-valid-attr-evaluate': ariaValidAttrEvaluate,
'aria-valid-attr-value-evaluate': ariaValidAttrValueEvaluate,
'fallbackrole-evaluate': fallbackroleEvaluate,
'has-global-aria-attribute-evaluate': hasGlobalAriaAttributeEvaluate,
'has-widget-role-evaluate': hasWidgetRoleEvaluate,
'invalidrole-evaluate': invalidroleEvaluate,
'is-element-focusable-evaluate': isElementFocusableEvaluate,
'no-implicit-explicit-label-evaluate': noImplicitExplicitLabelEvaluate,
'unsupportedrole-evaluate': unsupportedroleEvaluate,
'valid-scrollable-semantics-evaluate': validScrollableSemanticsEvaluate,
Expand Down
12 changes: 12 additions & 0 deletions lib/rules/presentation-role-conflict.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "presentation-role-conflict",
"selector": "[role=\"none\"], [role=\"presentation\"]",
"tags": ["cat.aria", "best-practice"],
"metadata": {
"description": "Flags elements whose role is none or presentation and which cause the role conflict resolution to trigger.",
"help": "Elements of role none or presentation should be flagged"
},
"all": [],
"any": [],
"none": ["is-element-focusable", "has-global-aria-attribute"]
}
Empty file modified package-lock.json
100644 → 100755
Empty file.
31 changes: 31 additions & 0 deletions test/checks/aria/has-global-aria-attribute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
describe('has-global-aria-attribute', function() {
'use strict';

var fixture = document.getElementById('fixture');
var checkSetup = axe.testUtils.checkSetup;

var checkContext = axe.testUtils.MockCheckContext();
var evaluate = axe.testUtils.getCheckEvaluate('has-global-aria-attribute');

afterEach(function() {
fixture.innerHTML = '';
axe._tree = undefined;
checkContext.reset();
});

it('should return true if any global ARIA attributes are found', function() {
var node = document.createElement('div');
node.id = 'test';
node.setAttribute('aria-label', 'hello');
var params = checkSetup(node);
assert.isTrue(evaluate.apply(checkContext, params));
});

it('should return false if no valid ARIA attributes are found', function() {
var node = document.createElement('div');
node.id = 'test';
node.setAttribute('aria-random', 'hello');
var params = checkSetup(node);
assert.isFalse(evaluate.apply(checkContext, params));
});
});
41 changes: 41 additions & 0 deletions test/checks/aria/is-element-focusable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
describe('is-element-focusable', function() {
'use strict';

var fixture = document.getElementById('fixture');
var checkContext = axe.testUtils.MockCheckContext();

afterEach(function() {
fixture.innerHTML = '';
checkContext.reset();
});

it('should return true for div with a tabindex', function() {
var node = document.createElement('div');
node.id = 'target';
node.tabIndex = 1;
fixture.appendChild(node);
axe._tree = axe.utils.getFlattenedTree(fixture);
var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node);

assert.isTrue(
axe.testUtils
.getCheckEvaluate('is-element-focusable')
.call(checkContext, node, {}, virtualNode)
);
});

it('should return false for natively unfocusable element', function() {
var node = document.createElement('span');
node.id = 'target';
node.role = 'link';
node.href = '#';
fixture.appendChild(node);
axe._tree = axe.utils.getFlattenedTree(fixture);
var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node);
assert.isFalse(
axe.testUtils
.getCheckEvaluate('is-element-focusable')
.call(checkContext, node, {}, virtualNode)
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div id="pass1" role="presentation">Test</div>
<div id="pass2" role="presentation">Test Button</div>
<li id="violation1" role="presentation" aria-label="My Heading">Hello</li>
<h1 id="violation2" role="none" aria-label="My Heading">Hello</h1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rule": "presentation-role-conflict",
"description": "presentation-role-conflict tests",
"violations": [["#violation1"], ["#violation2"]],
"passes": [["#pass1"], ["#pass2"]]
}