diff --git a/src/audits/AriaRoleNotScoped.js b/src/audits/AriaRoleNotScoped.js index 9a99a702..d820c69f 100644 --- a/src/audits/AriaRoleNotScoped.js +++ b/src/audits/AriaRoleNotScoped.js @@ -35,12 +35,10 @@ 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; @@ -48,9 +46,9 @@ axs.AuditRules.addRule({ 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; } } @@ -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; diff --git a/src/audits/RequiredOwnedAriaRoleMissing.js b/src/audits/RequiredOwnedAriaRoleMissing.js index 52cab4cb..09ee394c 100644 --- a/src/audits/RequiredOwnedAriaRoleMissing.js +++ b/src/audits/RequiredOwnedAriaRoleMissing.js @@ -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; } } @@ -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); })(); diff --git a/src/audits/UnsupportedAriaAttribute.js b/src/audits/UnsupportedAriaAttribute.js index 486662a5..acf938ac 100644 --- a/src/audits/UnsupportedAriaAttribute.js +++ b/src/audits/UnsupportedAriaAttribute.js @@ -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. diff --git a/src/js/AccessibilityUtils.js b/src/js/AccessibilityUtils.js index 36c488b0..8f457bad 100644 --- a/src/js/AccessibilityUtils.js +++ b/src/js/AccessibilityUtils.js @@ -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; }; /** diff --git a/test/js/utils-test.js b/test/js/utils-test.js index 5f4f274d..93c0a185 100644 --- a/test/js/utils-test.js +++ b/test/js/utils-test.js @@ -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); @@ -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); @@ -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); @@ -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'); @@ -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} 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)); + +}());