Skip to content

Commit

Permalink
fix(aria-roles): report error for fallback roles (#1970)
Browse files Browse the repository at this point in the history
* fix(aria-roles): report error for fallback roles

* message

* allow all checks to look at multiple roles

* fix

* revert playground

* add roles as data
  • Loading branch information
straker committed Jan 27, 2020
1 parent 22d8c3b commit a1b7e08
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 29 deletions.
11 changes: 10 additions & 1 deletion lib/checks/aria/abstractrole.js
@@ -1 +1,10 @@
return axe.commons.aria.getRoleType(node.getAttribute('role')) === 'abstract';
const abstractRoles = axe.utils
.tokenList(virtualNode.attr('role'))
.filter(role => axe.commons.aria.getRoleType(role) === 'abstract');

if (abstractRoles.length > 0) {
this.data(abstractRoles);
return true;
}

return false;
5 changes: 4 additions & 1 deletion lib/checks/aria/abstractrole.json
Expand Up @@ -5,7 +5,10 @@
"impact": "serious",
"messages": {
"pass": "Abstract roles are not used",
"fail": "Abstract roles cannot be directly used"
"fail": {
"singular": "Abstract role cannot be directly used: ${data.values}",
"plural": "Abstract roles cannot be directly used: ${data.values}"
}
}
}
}
1 change: 1 addition & 0 deletions lib/checks/aria/fallbackrole.js
@@ -0,0 +1 @@
return axe.utils.tokenList(virtualNode.attr('role')).length > 1;
11 changes: 11 additions & 0 deletions lib/checks/aria/fallbackrole.json
@@ -0,0 +1,11 @@
{
"id": "fallbackrole",
"evaluate": "fallbackrole.js",
"metadata": {
"impact": "serious",
"messages": {
"pass": "Only one role value used",
"fail": "Use only one role value, since fallback roles are not supported in older browsers"
}
}
}
17 changes: 14 additions & 3 deletions lib/checks/aria/invalidrole.js
@@ -1,3 +1,14 @@
return !axe.commons.aria.isValidRole(node.getAttribute('role'), {
allowAbstract: true
});
const invalidRoles = axe.utils
.tokenList(virtualNode.attr('role'))
.filter(role => {
return !axe.commons.aria.isValidRole(role, {
allowAbstract: true
});
});

if (invalidRoles.length > 0) {
this.data(invalidRoles);
return true;
}

return false;
5 changes: 4 additions & 1 deletion lib/checks/aria/invalidrole.json
Expand Up @@ -5,7 +5,10 @@
"impact": "critical",
"messages": {
"pass": "ARIA role is valid",
"fail": "Role must be one of the valid ARIA roles"
"fail": {
"singular": "Role must be one of the valid ARIA roles: ${data.values}",
"plural": "Roles must be one of the valid ARIA roles: ${data.values}"
}
}
}
}
2 changes: 1 addition & 1 deletion lib/rules/aria-roles.json
Expand Up @@ -8,5 +8,5 @@
},
"all": [],
"any": [],
"none": ["invalidrole", "abstractrole", "unsupportedrole"]
"none": ["fallbackrole", "invalidrole", "abstractrole", "unsupportedrole"]
}
40 changes: 40 additions & 0 deletions test/checks/aria/fallbackrole.js
@@ -0,0 +1,40 @@
describe('fallbackrole', function() {
'use strict';

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

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

it('should return true if fallback role is used', function() {
var virtualNode = queryFixture(
'<div id="target" role="button foobar">Foo</div>'
);
assert.isTrue(
checks.fallbackrole.evaluate(virtualNode.actualNode, 'radio', virtualNode)
);
});

it('should return false if fallback role is not used', function() {
var virtualNode = queryFixture('<div id="target" role="button">Foo</div>');
assert.isFalse(
checks.fallbackrole.evaluate(virtualNode.actualNode, 'radio', virtualNode)
);
});

it('should return false if applied to an invalid role', function() {
var virtualNode = queryFixture('<div id="target" role="foobar">Foo</div>');
assert.isFalse(
checks.fallbackrole.evaluate(virtualNode.actualNode, 'radio', virtualNode)
);
});

it('should return false if applied to an invalid role', function() {
var virtualNode = queryFixture('<div id="target" role="foobar">Foo</div>');
assert.isFalse(
checks.fallbackrole.evaluate(virtualNode.actualNode, 'radio', virtualNode)
);
});
});
78 changes: 69 additions & 9 deletions test/checks/shared/abstractrole.js
Expand Up @@ -2,26 +2,86 @@ describe('abstractrole', function() {
'use strict';

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

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

it('should return false if applied to a concrete role', function() {
fixture.innerHTML = '<div id="target" role="alert">Contents</div>';
var node = fixture.querySelector('#target');
assert.isFalse(checks.abstractrole.evaluate(node, 'radio'));
var virtualNode = queryFixture(
'<div id="target" role="alert">Contents</div>'
);
assert.isFalse(
checks.abstractrole.evaluate.call(
checkContext,
virtualNode.actualNode,
'radio',
virtualNode
)
);
assert.isNull(checkContext._data);
});

it('should return false if applied to a nonsensical role', function() {
fixture.innerHTML = '<div id="target" role="foo">Contents</div>';
var node = fixture.querySelector('#target');
assert.isFalse(checks.abstractrole.evaluate(node, 'radio'));
var virtualNode = queryFixture(
'<div id="target" role="foo">Contents</div>'
);
assert.isFalse(
checks.abstractrole.evaluate.call(
checkContext,
virtualNode.actualNode,
'radio',
virtualNode
)
);
assert.isNull(checkContext._data);
});

it('should return true if applied to an abstract role', function() {
fixture.innerHTML = '<div id="target" role="widget">Contents</div>';
var node = fixture.querySelector('#target');
assert.isTrue(checks.abstractrole.evaluate(node, 'radio'));
var virtualNode = queryFixture(
'<div id="target" role="widget">Contents</div>'
);
assert.isTrue(
checks.abstractrole.evaluate.call(
checkContext,
virtualNode.actualNode,
'radio',
virtualNode
)
);
assert.deepEqual(checkContext._data, ['widget']);
});

it('should return false if applied to multiple concrete roles', function() {
var virtualNode = queryFixture(
'<div id="target" role="alert button">Contents</div>'
);
assert.isFalse(
checks.abstractrole.evaluate.call(
checkContext,
virtualNode.actualNode,
'radio',
virtualNode
)
);
assert.isNull(checkContext._data);
});

it('should return true if applied to at least one abstract role', function() {
var virtualNode = queryFixture(
'<div id="target" role="alert widget structure">Contents</div>'
);
assert.isTrue(
checks.abstractrole.evaluate.call(
checkContext,
virtualNode.actualNode,
'radio',
virtualNode
)
);
assert.deepEqual(checkContext._data, ['widget', 'structure']);
});
});
91 changes: 79 additions & 12 deletions test/checks/shared/invalidrole.js
Expand Up @@ -2,32 +2,99 @@ describe('invalidrole', function() {
'use strict';

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

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

it('should return true if applied to an empty role', function() {
fixture.innerHTML = '<div id="target" role="">Contents</div>';
var node = fixture.querySelector('#target');
assert.isTrue(checks.invalidrole.evaluate(node, 'radio'));
var virtualNode = queryFixture('<div id="target" role="">Contents</div>');
assert.isTrue(
checks.invalidrole.evaluate.call(
checkContext,
virtualNode.actualNode,
null,
virtualNode
)
);
assert.deepEqual(checkContext._data, ['']);
});

it('should return true if applied to a nonsensical role', function() {
fixture.innerHTML = '<div id="target" role="foo">Contents</div>';
var node = fixture.querySelector('#target');
assert.isTrue(checks.invalidrole.evaluate(node, 'radio'));
var virtualNode = queryFixture(
'<div id="target" role="foo">Contents</div>'
);
assert.isTrue(
checks.invalidrole.evaluate.call(
checkContext,
virtualNode.actualNode,
null,
virtualNode
)
);
assert.deepEqual(checkContext._data, ['foo']);
});

it('should return false if applied to a concrete role', function() {
fixture.innerHTML = '<div id="target" role="alert">Contents</div>';
var node = fixture.querySelector('#target');
assert.isFalse(checks.invalidrole.evaluate(node, 'radio'));
var virtualNode = queryFixture(
'<div id="target" role="alert">Contents</div>'
);
assert.isFalse(
checks.invalidrole.evaluate.call(
checkContext,
virtualNode.actualNode,
null,
virtualNode
)
);
assert.isNull(checkContext._data);
});

it('should return false if applied to an abstract role', function() {
fixture.innerHTML = '<div id="target" role="widget">Contents</div>';
var node = fixture.querySelector('#target');
assert.isFalse(checks.invalidrole.evaluate(node, 'radio'));
var virtualNode = queryFixture(
'<div id="target" role="widget">Contents</div>'
);
assert.isFalse(
checks.invalidrole.evaluate.call(
checkContext,
virtualNode.actualNode,
null,
virtualNode
)
);
assert.isNull(checkContext._data);
});

it('should return false if applied to multiple valid roles', function() {
var virtualNode = queryFixture(
'<div id="target" role="alert button">Contents</div>'
);
assert.isFalse(
checks.invalidrole.evaluate.call(
checkContext,
virtualNode.actualNode,
null,
virtualNode
)
);
assert.isNull(checkContext._data);
});

it('should return true if applied to at least one nonsensical role', function() {
var virtualNode = queryFixture(
'<div id="target" role="alert button foo bar">Contents</div>'
);
assert.isTrue(
checks.invalidrole.evaluate.call(
checkContext,
virtualNode.actualNode,
null,
virtualNode
)
);
assert.deepEqual(checkContext._data, ['foo', 'bar']);
});
});
2 changes: 2 additions & 0 deletions test/integration/rules/aria-roles/aria-roles.html
Expand Up @@ -126,4 +126,6 @@
<div role="lol" id="fail13">fail</div>
<div role="text" id="fail14">ok</div>
<!-- unsupported roles -->
<!-- fallback roles -->
<div role="button alert" id="fail15">fail</div>
</div>
3 changes: 2 additions & 1 deletion test/integration/rules/aria-roles/aria-roles.json
Expand Up @@ -15,7 +15,8 @@
["#fail11"],
["#fail12"],
["#fail13"],
["#fail14"]
["#fail14"],
["#fail15"]
],
"passes": [
["#pass1"],
Expand Down

0 comments on commit a1b7e08

Please sign in to comment.