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 = '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 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 = '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 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"],