Skip to content
This repository was archived by the owner on Dec 7, 2022. It is now read-only.

Commit a33b34f

Browse files
committed
Merge pull request #138 from RickSBrown/master
Updates to axs.utils.getRoles and related changes to some Audits. All issues raised by @alice are addressed - pulling this PR.
2 parents f61fd04 + 581e575 commit a33b34f

File tree

5 files changed

+83
-34
lines changed

5 files changed

+83
-34
lines changed

src/audits/AriaRoleNotScoped.js

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,20 @@ axs.AuditRules.addRule({
3535
* Checks that this element is in the required scope for its role.
3636
*/
3737
var elementRole = axs.utils.getRoles(element);
38-
if (!elementRole || !elementRole.roles.length)
38+
if (!elementRole || !elementRole.applied)
3939
return false;
40-
elementRole = elementRole.roles[0];
41-
if (!elementRole || !elementRole.valid)
42-
return false;
43-
var ariaRole = elementRole.details;
40+
var appliedRole = elementRole.applied;
41+
var ariaRole = appliedRole.details;
4442
var requiredScope = ariaRole['scope'];
4543
if (!requiredScope || requiredScope.length === 0) {
4644
return false;
4745
}
4846
var parent = element;
4947
while ((parent = parent.parentNode)) {
5048
var parentRole = axs.utils.getRoles(parent, true);
51-
if (parentRole && parentRole.roles.length) {
52-
parentRole = parentRole.roles[0];
53-
if (requiredScope.indexOf(parentRole.name) >= 0) // if this ancestor role is one of the required roles
49+
if (parentRole && parentRole.applied) {
50+
var appliedParentRole = parentRole.applied;
51+
if (requiredScope.indexOf(appliedParentRole.name) >= 0) // if this ancestor role is one of the required roles
5452
return false;
5553
}
5654
}
@@ -60,11 +58,8 @@ axs.AuditRules.addRule({
6058
if (owners) {
6159
for (var i = 0; i < owners.length; i++) {
6260
var ownerRole = axs.utils.getRoles(owners[i], true);
63-
if (ownerRole && ownerRole.roles.length)
64-
ownerRole = ownerRole.roles[0];
65-
if (ownerRole && requiredScope.indexOf(ownerRole.name) >= 0) { // if the owner role is one of the required roles
66-
return false;
67-
}
61+
if (ownerRole && ownerRole.applied && requiredScope.indexOf(ownerRole.applied.name) >= 0)
62+
return false; // the owner role is one of the required roles
6863
}
6964
}
7065
return true;

src/audits/RequiredOwnedAriaRoleMissing.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ goog.require('axs.utils');
5353
for (var i = ownedElements.length - 1; i >= 0; i--) {
5454
var ownedElement = ownedElements[i];
5555
var ownedElementRole = axs.utils.getRoles(ownedElement, true);
56-
if (ownedElementRole && ownedElementRole.roles.length) {
57-
ownedElementRole = ownedElementRole.roles[0];
56+
if (ownedElementRole && ownedElementRole.applied) {
57+
var appliedRole = ownedElementRole.applied;
5858
for (var j = required.length - 1; j >= 0; j--) {
59-
if (ownedElementRole.name === required[j]) { // if this explicitly owned element has a required role
59+
if (appliedRole.name === required[j]) { // if this explicitly owned element has a required role
6060
return false;
6161
}
6262
}
@@ -74,12 +74,12 @@ goog.require('axs.utils');
7474
*/
7575
function getRequired(element) {
7676
var elementRole = axs.utils.getRoles(element);
77-
if (!elementRole || !elementRole.roles.length)
77+
if (!elementRole || !elementRole.applied)
7878
return [];
79-
elementRole = elementRole.roles[0];
80-
if (!elementRole.valid)
79+
var appliedRole = elementRole.applied;
80+
if (!appliedRole.valid)
8181
return [];
82-
return elementRole.details['mustcontain'] || [];
82+
return appliedRole.details['mustcontain'] || [];
8383
}
8484
axs.AuditRules.addRule(spec);
8585
})();

src/audits/UnsupportedAriaAttribute.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ goog.require('axs.utils');
4444
// Even though we may not need to look up role, supported etc it's better performance to do it here than in loop
4545
var role = axs.utils.getRoles(element, true);
4646
var supported;
47-
if (role && role.roles.length) {
48-
supported = role.roles[0].details.propertiesSet;
47+
if (role && role.applied) {
48+
supported = role.applied.details.propertiesSet;
4949
}
5050
else {
5151
// This test ignores the fact that some HTML elements should not take even global attributes.

src/js/AccessibilityUtils.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,22 +1051,25 @@ axs.utils.getRoles = function(element, implicit) {
10511051
if (!roleValue) // role='' or implicit role came up empty
10521052
return null;
10531053
var roleNames = roleValue.split(' ');
1054-
var roles = [];
1055-
var valid = true;
1054+
var result = { roles: [], valid: false };
10561055
for (var i = 0; i < roleNames.length; i++) {
10571056
var role = roleNames[i];
10581057
var ariaRole = axs.constants.ARIA_ROLES[role];
1058+
var roleObject = { 'name': role };
10591059
if (ariaRole && !ariaRole.abstract) {
1060-
var roleObject = {'name': role, 'details': axs.constants.ARIA_ROLES[role], 'valid': true};
1061-
roles.push(roleObject);
1060+
roleObject.details = ariaRole;
1061+
if (!result.applied) {
1062+
result.applied = roleObject;
1063+
}
1064+
roleObject.valid = result.valid = true;
10621065
} else {
1063-
var roleObject = {'name': role, 'valid': false};
1064-
valid = false;
1065-
roles.push(roleObject);
1066+
roleObject.valid = false;
1067+
10661068
}
1069+
result.roles.push(roleObject);
10671070
}
10681071

1069-
return { 'roles': roles, 'valid': valid };
1072+
return result;
10701073
};
10711074

10721075
/**

test/js/utils-test.js

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ test("returns the aria owners for a given element", function() {
173173
var actual = axs.utils.getIdReferrers("aria-owns", owned);
174174
equal(expected.length, ownerCount); // sanity check the test itself
175175
equal(actual.length, ownerCount);
176-
var allFound = Array.prototype.every.call(expected, function(element){
176+
var allFound = Array.prototype.every.call(expected, function(element) {
177177
return (Array.prototype.indexOf.call(actual, element) >= 0);
178178
});
179179
equal(allFound, true);
@@ -193,7 +193,7 @@ test("returns the elements this element labels", function() {
193193
var actual = axs.utils.getIdReferrers("aria-labelledby", label);
194194
equal(expected.length, labelledCount); // sanity check the test itself
195195
equal(actual.length, labelledCount);
196-
var allFound = Array.prototype.every.call(expected, function(element){
196+
var allFound = Array.prototype.every.call(expected, function(element) {
197197
return (Array.prototype.indexOf.call(actual, element) >= 0);
198198
});
199199
equal(allFound, true);
@@ -246,9 +246,11 @@ module("getRoles", {
246246
test("getRoles on element with valid role.", function() {
247247
for (var role in axs.constants.ARIA_ROLES) {
248248
if (axs.constants.ARIA_ROLES.hasOwnProperty(role) && !axs.constants.ARIA_ROLES[role].abstract) {
249+
var appliedRole = { name: role, valid: true, details: axs.constants.ARIA_ROLES[role] };
249250
var expected = {
250251
valid: true,
251-
roles: [{ name: role, valid: true, details: axs.constants.ARIA_ROLES[role] }]
252+
applied: appliedRole,
253+
roles: [appliedRole]
252254
};
253255
var element = document.createElement('div');
254256
element.setAttribute('role', role);
@@ -275,9 +277,11 @@ test("getRoles on element with empty role.", function() {
275277
});
276278

277279
test("getRoles on element with implicit role and options.implicit.", function() {
280+
var appliedRole = { name: 'checkbox', valid: true, details: axs.constants.ARIA_ROLES['checkbox'] };
278281
var expected = {
279282
valid: true,
280-
roles: [{ name: 'checkbox', valid: true, details: axs.constants.ARIA_ROLES['checkbox'] }]
283+
applied: appliedRole,
284+
roles: [appliedRole]
281285
};
282286
var element = document.createElement('input');
283287
element.setAttribute('type', 'checkbox');
@@ -307,3 +311,50 @@ test("getRoles on element with abstract role.", function() {
307311
}
308312
}
309313
});
314+
(function() {
315+
/**
316+
* Creates a 'role detail' object which can be used for comparison in the assertions below.
317+
* @param {!string} role A potential ARIA role.
318+
* @return The 'role detail' object.
319+
*/
320+
function createExpectedRoleObject(role) {
321+
var valid = (axs.constants.ARIA_ROLES.hasOwnProperty(role) && !axs.constants.ARIA_ROLES[role].abstract);
322+
var result = { name: role, valid: valid };
323+
if (valid) {
324+
result.details = axs.constants.ARIA_ROLES[role];
325+
}
326+
return result;
327+
}
328+
329+
/**
330+
* Helper for multiple role tests.
331+
* @param {!Array<string>} roles Strings to set in the 'role' attribute of the element under test.
332+
* @param {!number} validIdx The index of the expected applied (valid) ARIA role in the array above
333+
* or a negative number if there are no valid roles.
334+
* @return {Function} A test function for qunit.
335+
*/
336+
function multipleRoleTestHelper(roles, validIdx) {
337+
return function() {
338+
var expectedRoles = roles.map(createExpectedRoleObject);
339+
var expected = {
340+
roles: expectedRoles
341+
};
342+
if (validIdx >= 0) {
343+
expected.valid = true;
344+
expected.applied = expectedRoles[validIdx];
345+
}
346+
else {
347+
expected.valid = false;
348+
}
349+
var element = document.createElement('div');
350+
element.setAttribute('role', roles.join(' '));
351+
var actual = axs.utils.getRoles(element);
352+
deepEqual(actual, expected);
353+
};
354+
}
355+
356+
test("getRoles on element with multiple valid roles.", multipleRoleTestHelper(['checkbox', 'button', 'radio'], 0));
357+
test("getRoles on element with invalid and valid roles.", multipleRoleTestHelper(['foo', 'button', 'bar'], 1));
358+
test("getRoles on element with multiple invalid roles.", multipleRoleTestHelper(['foo', 'fubar', 'bar'], -1));
359+
360+
}());

0 commit comments

Comments
 (0)