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
9 changes: 5 additions & 4 deletions doc/rule-descriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@

## WCAG 2.1 Level A & AA Rules

| Rule ID | Description | Impact | Tags | Issue Type |
| :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :------ | :--------------------------- | :--------- |
| [autocomplete-valid](https://dequeuniversity.com/rules/axe/3.5/autocomplete-valid?application=RuleDescription) | Ensure the autocomplete attribute is correct and suitable for the form field | Serious | cat.forms, wcag21aa, wcag135 | failure |
| [avoid-inline-spacing](https://dequeuniversity.com/rules/axe/3.5/avoid-inline-spacing?application=RuleDescription) | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | wcag21aa, wcag1412 | failure |
| Rule ID | Description | Impact | Tags | Issue Type |
| :----------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------- | :------ | :--------------------------- | :--------- |
| [autocomplete-valid](https://dequeuniversity.com/rules/axe/3.5/autocomplete-valid?application=RuleDescription) | Ensure the autocomplete attribute is correct and suitable for the form field | Serious | cat.forms, wcag21aa, wcag135 | failure |
| [avoid-inline-spacing](https://dequeuniversity.com/rules/axe/3.5/avoid-inline-spacing?application=RuleDescription) | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | wcag21aa, wcag1412 | failure |
| [flag-role](https://dequeuniversity.com/rules/axe/3.5/flag-role?application=RuleDescription) | Flags elements whose role is none or presentation and which cause the role conflict resolution to trigger. | Minor | cat.aria | failure |
jlin95 marked this conversation as resolved.
Show resolved Hide resolved

## Best Practices Rules

Expand Down
7 changes: 7 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,7 @@
import lookupTable from '../../commons/aria/lookup-table';
jlin95 marked this conversation as resolved.
Show resolved Hide resolved

function hasGlobalAriaAttributeEvaluate(node, options, virtualNode) {
return lookupTable.globalAttributes.some(attr => virtualNode.hasAttr(attr));
}

export default hasGlobalAriaAttributeEvaluate;
11 changes: 11 additions & 0 deletions lib/checks/aria/has-global-aria-attribute.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "has-global-aria-attribute",
"evaluate": "has-global-aria-attribute-evaluate",
"metadata": {
"impact": "minor",
"messages": {
"pass": "Element has global ARIA attribute",
jlin95 marked this conversation as resolved.
Show resolved Hide resolved
"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) {
return isFocusable(node);
jlin95 marked this conversation as resolved.
Show resolved Hide resolved
}

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 @@ -174,8 +176,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
13 changes: 13 additions & 0 deletions lib/rules/flag-role.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"id": "flag-role",
jlin95 marked this conversation as resolved.
Show resolved Hide resolved
"selector": "[role=\"none\"], [role=\"presentation\"]",
"tags": ["cat.aria"],
jlin95 marked this conversation as resolved.
Show resolved Hide resolved
"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"
},
"preload": false,
"all": ["is-element-focusable", "has-global-aria-attribute"],
Copy link
Contributor

Choose a reason for hiding this comment

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

So this will pass the rule if the element is both focusable and has a global aria attribute. Instead it should fail the rule, so I believe these should be in the none array.

"any": [],
"none": []
}
Empty file modified package-lock.json
100644 → 100755
Empty file.
37 changes: 37 additions & 0 deletions test/checks/aria/has-global-aria-attribute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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();
});

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

jlin95 marked this conversation as resolved.
Show resolved Hide resolved
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));
});
});
37 changes: 37 additions & 0 deletions test/checks/aria/is-element-focusable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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);

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

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);
assert.isFalse(
axe.testUtils
.getCheckEvaluate('is-element-focusable')
.call(checkContext, node)
);
});
});
8 changes: 8 additions & 0 deletions test/integration/rules/flag-role/flag-role.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!-- HTML Snippets-->
<div role="presentation" id="pass1" tabindex="1">
ok
</div>
<h1 role="none" id="pass2" aria-label="My Heading"></h1>

<div role="scrollbar" id="violation1">fail</div>
jlin95 marked this conversation as resolved.
Show resolved Hide resolved
<div role="slider" id="violation2">fail</div>
6 changes: 6 additions & 0 deletions test/integration/rules/flag-role/flag-role.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"description": "flag-role tests",
"rule": "flag-role",
"violations": [["#violation1"], ["#violation2"]],
"passes": [["#pass1"], ["#pass2"]]
}