diff --git a/lib/checks/aria/abstractrole.js b/lib/checks/aria/abstractrole.js index e59b744034..1ac0f3c1c5 100644 --- a/lib/checks/aria/abstractrole.js +++ b/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; diff --git a/lib/checks/aria/abstractrole.json b/lib/checks/aria/abstractrole.json index 58d0cb81ab..9030d21b7a 100644 --- a/lib/checks/aria/abstractrole.json +++ b/lib/checks/aria/abstractrole.json @@ -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}" + } } } } diff --git a/lib/checks/aria/fallbackrole.js b/lib/checks/aria/fallbackrole.js new file mode 100644 index 0000000000..b464458e0e --- /dev/null +++ b/lib/checks/aria/fallbackrole.js @@ -0,0 +1 @@ +return axe.utils.tokenList(virtualNode.attr('role')).length > 1; diff --git a/lib/checks/aria/fallbackrole.json b/lib/checks/aria/fallbackrole.json new file mode 100644 index 0000000000..f30109955f --- /dev/null +++ b/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" + } + } +} diff --git a/lib/checks/aria/invalidrole.js b/lib/checks/aria/invalidrole.js index 86a649e87f..c44ce05ce9 100644 --- a/lib/checks/aria/invalidrole.js +++ b/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; diff --git a/lib/checks/aria/invalidrole.json b/lib/checks/aria/invalidrole.json index 8f159d678d..4cf26216cd 100644 --- a/lib/checks/aria/invalidrole.json +++ b/lib/checks/aria/invalidrole.json @@ -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}" + } } } } diff --git a/lib/rules/aria-roles.json b/lib/rules/aria-roles.json index 3cf92ef149..a16410a29a 100644 --- a/lib/rules/aria-roles.json +++ b/lib/rules/aria-roles.json @@ -8,5 +8,5 @@ }, "all": [], "any": [], - "none": ["invalidrole", "abstractrole", "unsupportedrole"] + "none": ["fallbackrole", "invalidrole", "abstractrole", "unsupportedrole"] } diff --git a/test/checks/aria/fallbackrole.js b/test/checks/aria/fallbackrole.js new file mode 100644 index 0000000000..b25820f8fe --- /dev/null +++ b/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( + '
Foo
' + ); + assert.isTrue( + checks.fallbackrole.evaluate(virtualNode.actualNode, 'radio', virtualNode) + ); + }); + + it('should return false if fallback role is not used', function() { + var virtualNode = queryFixture('
Foo
'); + assert.isFalse( + checks.fallbackrole.evaluate(virtualNode.actualNode, 'radio', virtualNode) + ); + }); + + it('should return false if applied to an invalid role', function() { + var virtualNode = queryFixture('
Foo
'); + assert.isFalse( + checks.fallbackrole.evaluate(virtualNode.actualNode, 'radio', virtualNode) + ); + }); + + it('should return false if applied to an invalid role', function() { + var virtualNode = queryFixture('
Foo
'); + assert.isFalse( + checks.fallbackrole.evaluate(virtualNode.actualNode, 'radio', virtualNode) + ); + }); +}); diff --git a/test/checks/shared/abstractrole.js b/test/checks/shared/abstractrole.js index bc9aa7e348..ff249ae00e 100644 --- a/test/checks/shared/abstractrole.js +++ b/test/checks/shared/abstractrole.js @@ -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 = ''; - var node = fixture.querySelector('#target'); - assert.isFalse(checks.abstractrole.evaluate(node, 'radio')); + var virtualNode = queryFixture( + '' + ); + 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 = '
Contents
'; - var node = fixture.querySelector('#target'); - assert.isFalse(checks.abstractrole.evaluate(node, 'radio')); + var virtualNode = queryFixture( + '
Contents
' + ); + 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 = '
Contents
'; - var node = fixture.querySelector('#target'); - assert.isTrue(checks.abstractrole.evaluate(node, 'radio')); + var virtualNode = queryFixture( + '
Contents
' + ); + 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( + '
Contents
' + ); + 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( + '
Contents
' + ); + assert.isTrue( + checks.abstractrole.evaluate.call( + checkContext, + virtualNode.actualNode, + 'radio', + virtualNode + ) + ); + assert.deepEqual(checkContext._data, ['widget', 'structure']); }); }); diff --git a/test/checks/shared/invalidrole.js b/test/checks/shared/invalidrole.js index 19c28b32fc..e396dd1677 100644 --- a/test/checks/shared/invalidrole.js +++ b/test/checks/shared/invalidrole.js @@ -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 = '
Contents
'; - var node = fixture.querySelector('#target'); - assert.isTrue(checks.invalidrole.evaluate(node, 'radio')); + var virtualNode = queryFixture('
Contents
'); + 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 = '
Contents
'; - var node = fixture.querySelector('#target'); - assert.isTrue(checks.invalidrole.evaluate(node, 'radio')); + var virtualNode = queryFixture( + '
Contents
' + ); + 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 = ''; - var node = fixture.querySelector('#target'); - assert.isFalse(checks.invalidrole.evaluate(node, 'radio')); + var virtualNode = queryFixture( + '' + ); + 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 = '
Contents
'; - var node = fixture.querySelector('#target'); - assert.isFalse(checks.invalidrole.evaluate(node, 'radio')); + var virtualNode = queryFixture( + '
Contents
' + ); + 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( + '
Contents
' + ); + 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( + '
Contents
' + ); + assert.isTrue( + checks.invalidrole.evaluate.call( + checkContext, + virtualNode.actualNode, + null, + virtualNode + ) + ); + assert.deepEqual(checkContext._data, ['foo', 'bar']); }); }); diff --git a/test/integration/rules/aria-roles/aria-roles.html b/test/integration/rules/aria-roles/aria-roles.html index 95380ac162..a9212a665f 100644 --- a/test/integration/rules/aria-roles/aria-roles.html +++ b/test/integration/rules/aria-roles/aria-roles.html @@ -126,4 +126,6 @@
fail
ok
+ +
fail
diff --git a/test/integration/rules/aria-roles/aria-roles.json b/test/integration/rules/aria-roles/aria-roles.json index 39460d1c93..e049d9e880 100644 --- a/test/integration/rules/aria-roles/aria-roles.json +++ b/test/integration/rules/aria-roles/aria-roles.json @@ -15,7 +15,8 @@ ["#fail11"], ["#fail12"], ["#fail13"], - ["#fail14"] + ["#fail14"], + ["#fail15"] ], "passes": [ ["#pass1"],