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

Commit

Permalink
Merge pull request #138 from RickSBrown/master
Browse files Browse the repository at this point in the history
Updates to axs.utils.getRoles and related changes to some Audits.
All issues raised by @alice are addressed - pulling this PR.
  • Loading branch information
ricksbrown committed May 1, 2015
2 parents f61fd04 + 581e575 commit a33b34f
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 34 deletions.
21 changes: 8 additions & 13 deletions src/audits/AriaRoleNotScoped.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,20 @@ axs.AuditRules.addRule({
* Checks that this element is in the required scope for its role.
*/
var elementRole = axs.utils.getRoles(element);
if (!elementRole || !elementRole.roles.length)
if (!elementRole || !elementRole.applied)
return false;
elementRole = elementRole.roles[0];
if (!elementRole || !elementRole.valid)
return false;
var ariaRole = elementRole.details;
var appliedRole = elementRole.applied;
var ariaRole = appliedRole.details;
var requiredScope = ariaRole['scope'];
if (!requiredScope || requiredScope.length === 0) {
return false;
}
var parent = element;
while ((parent = parent.parentNode)) {
var parentRole = axs.utils.getRoles(parent, true);
if (parentRole && parentRole.roles.length) {
parentRole = parentRole.roles[0];
if (requiredScope.indexOf(parentRole.name) >= 0) // if this ancestor role is one of the required roles
if (parentRole && parentRole.applied) {
var appliedParentRole = parentRole.applied;
if (requiredScope.indexOf(appliedParentRole.name) >= 0) // if this ancestor role is one of the required roles
return false;
}
}
Expand All @@ -60,11 +58,8 @@ axs.AuditRules.addRule({
if (owners) {
for (var i = 0; i < owners.length; i++) {
var ownerRole = axs.utils.getRoles(owners[i], true);
if (ownerRole && ownerRole.roles.length)
ownerRole = ownerRole.roles[0];
if (ownerRole && requiredScope.indexOf(ownerRole.name) >= 0) { // if the owner role is one of the required roles
return false;
}
if (ownerRole && ownerRole.applied && requiredScope.indexOf(ownerRole.applied.name) >= 0)
return false; // the owner role is one of the required roles
}
}
return true;
Expand Down
14 changes: 7 additions & 7 deletions src/audits/RequiredOwnedAriaRoleMissing.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ goog.require('axs.utils');
for (var i = ownedElements.length - 1; i >= 0; i--) {
var ownedElement = ownedElements[i];
var ownedElementRole = axs.utils.getRoles(ownedElement, true);
if (ownedElementRole && ownedElementRole.roles.length) {
ownedElementRole = ownedElementRole.roles[0];
if (ownedElementRole && ownedElementRole.applied) {
var appliedRole = ownedElementRole.applied;
for (var j = required.length - 1; j >= 0; j--) {
if (ownedElementRole.name === required[j]) { // if this explicitly owned element has a required role
if (appliedRole.name === required[j]) { // if this explicitly owned element has a required role
return false;
}
}
Expand All @@ -74,12 +74,12 @@ goog.require('axs.utils');
*/
function getRequired(element) {
var elementRole = axs.utils.getRoles(element);
if (!elementRole || !elementRole.roles.length)
if (!elementRole || !elementRole.applied)
return [];
elementRole = elementRole.roles[0];
if (!elementRole.valid)
var appliedRole = elementRole.applied;
if (!appliedRole.valid)
return [];
return elementRole.details['mustcontain'] || [];
return appliedRole.details['mustcontain'] || [];
}
axs.AuditRules.addRule(spec);
})();
4 changes: 2 additions & 2 deletions src/audits/UnsupportedAriaAttribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ goog.require('axs.utils');
// Even though we may not need to look up role, supported etc it's better performance to do it here than in loop
var role = axs.utils.getRoles(element, true);
var supported;
if (role && role.roles.length) {
supported = role.roles[0].details.propertiesSet;
if (role && role.applied) {
supported = role.applied.details.propertiesSet;
}
else {
// This test ignores the fact that some HTML elements should not take even global attributes.
Expand Down
19 changes: 11 additions & 8 deletions src/js/AccessibilityUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1051,22 +1051,25 @@ axs.utils.getRoles = function(element, implicit) {
if (!roleValue) // role='' or implicit role came up empty
return null;
var roleNames = roleValue.split(' ');
var roles = [];
var valid = true;
var result = { roles: [], valid: false };
for (var i = 0; i < roleNames.length; i++) {
var role = roleNames[i];
var ariaRole = axs.constants.ARIA_ROLES[role];
var roleObject = { 'name': role };
if (ariaRole && !ariaRole.abstract) {
var roleObject = {'name': role, 'details': axs.constants.ARIA_ROLES[role], 'valid': true};
roles.push(roleObject);
roleObject.details = ariaRole;
if (!result.applied) {
result.applied = roleObject;
}
roleObject.valid = result.valid = true;
} else {
var roleObject = {'name': role, 'valid': false};
valid = false;
roles.push(roleObject);
roleObject.valid = false;

}
result.roles.push(roleObject);
}

return { 'roles': roles, 'valid': valid };
return result;
};

/**
Expand Down
59 changes: 55 additions & 4 deletions test/js/utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ test("returns the aria owners for a given element", function() {
var actual = axs.utils.getIdReferrers("aria-owns", owned);
equal(expected.length, ownerCount); // sanity check the test itself
equal(actual.length, ownerCount);
var allFound = Array.prototype.every.call(expected, function(element){
var allFound = Array.prototype.every.call(expected, function(element) {
return (Array.prototype.indexOf.call(actual, element) >= 0);
});
equal(allFound, true);
Expand All @@ -193,7 +193,7 @@ test("returns the elements this element labels", function() {
var actual = axs.utils.getIdReferrers("aria-labelledby", label);
equal(expected.length, labelledCount); // sanity check the test itself
equal(actual.length, labelledCount);
var allFound = Array.prototype.every.call(expected, function(element){
var allFound = Array.prototype.every.call(expected, function(element) {
return (Array.prototype.indexOf.call(actual, element) >= 0);
});
equal(allFound, true);
Expand Down Expand Up @@ -246,9 +246,11 @@ module("getRoles", {
test("getRoles on element with valid role.", function() {
for (var role in axs.constants.ARIA_ROLES) {
if (axs.constants.ARIA_ROLES.hasOwnProperty(role) && !axs.constants.ARIA_ROLES[role].abstract) {
var appliedRole = { name: role, valid: true, details: axs.constants.ARIA_ROLES[role] };
var expected = {
valid: true,
roles: [{ name: role, valid: true, details: axs.constants.ARIA_ROLES[role] }]
applied: appliedRole,
roles: [appliedRole]
};
var element = document.createElement('div');
element.setAttribute('role', role);
Expand All @@ -275,9 +277,11 @@ test("getRoles on element with empty role.", function() {
});

test("getRoles on element with implicit role and options.implicit.", function() {
var appliedRole = { name: 'checkbox', valid: true, details: axs.constants.ARIA_ROLES['checkbox'] };
var expected = {
valid: true,
roles: [{ name: 'checkbox', valid: true, details: axs.constants.ARIA_ROLES['checkbox'] }]
applied: appliedRole,
roles: [appliedRole]
};
var element = document.createElement('input');
element.setAttribute('type', 'checkbox');
Expand Down Expand Up @@ -307,3 +311,50 @@ test("getRoles on element with abstract role.", function() {
}
}
});
(function() {
/**
* Creates a 'role detail' object which can be used for comparison in the assertions below.
* @param {!string} role A potential ARIA role.
* @return The 'role detail' object.
*/
function createExpectedRoleObject(role) {
var valid = (axs.constants.ARIA_ROLES.hasOwnProperty(role) && !axs.constants.ARIA_ROLES[role].abstract);
var result = { name: role, valid: valid };
if (valid) {
result.details = axs.constants.ARIA_ROLES[role];
}
return result;
}

/**
* Helper for multiple role tests.
* @param {!Array<string>} roles Strings to set in the 'role' attribute of the element under test.
* @param {!number} validIdx The index of the expected applied (valid) ARIA role in the array above
* or a negative number if there are no valid roles.
* @return {Function} A test function for qunit.
*/
function multipleRoleTestHelper(roles, validIdx) {
return function() {
var expectedRoles = roles.map(createExpectedRoleObject);
var expected = {
roles: expectedRoles
};
if (validIdx >= 0) {
expected.valid = true;
expected.applied = expectedRoles[validIdx];
}
else {
expected.valid = false;
}
var element = document.createElement('div');
element.setAttribute('role', roles.join(' '));
var actual = axs.utils.getRoles(element);
deepEqual(actual, expected);
};
}

test("getRoles on element with multiple valid roles.", multipleRoleTestHelper(['checkbox', 'button', 'radio'], 0));
test("getRoles on element with invalid and valid roles.", multipleRoleTestHelper(['foo', 'button', 'bar'], 1));
test("getRoles on element with multiple invalid roles.", multipleRoleTestHelper(['foo', 'fubar', 'bar'], -1));

}());

0 comments on commit a33b34f

Please sign in to comment.