diff --git a/lib/checks/aria/aria-prohibited-attr-evaluate.js b/lib/checks/aria/aria-prohibited-attr-evaluate.js
index e6e2f9da7d..cc9cefd5f8 100644
--- a/lib/checks/aria/aria-prohibited-attr-evaluate.js
+++ b/lib/checks/aria/aria-prohibited-attr-evaluate.js
@@ -1,6 +1,7 @@
-import { getRole } from '../../commons/aria';
+import { getRole, getRoleType } from '../../commons/aria';
import { sanitize, subtreeText } from '../../commons/text';
import standards from '../../standards';
+import memoize from '../../core/utils/memoize';
/**
* Check that an element does not use any prohibited ARIA attributes.
@@ -36,6 +37,7 @@ export default function ariaProhibitedAttrEvaluate(
const role = getRole(virtualNode, { chromium: true });
const prohibitedList = listProhibitedAttrs(
+ virtualNode,
role,
nodeName,
elementsAllowedAriaLabel
@@ -64,13 +66,32 @@ export default function ariaProhibitedAttrEvaluate(
return true;
}
-function listProhibitedAttrs(role, nodeName, elementsAllowedAriaLabel) {
+function listProhibitedAttrs(vNode, role, nodeName, elementsAllowedAriaLabel) {
const roleSpec = standards.ariaRoles[role];
if (roleSpec) {
return roleSpec.prohibitedAttrs || [];
}
- if (!!role || elementsAllowedAriaLabel.includes(nodeName)) {
+ if (
+ !!role ||
+ elementsAllowedAriaLabel.includes(nodeName) ||
+ getClosestAncestorRoleType(vNode) === 'widget'
+ ) {
return [];
}
return ['aria-label', 'aria-labelledby'];
}
+
+const getClosestAncestorRoleType = memoize(
+ function getClosestAncestorRoleTypeMemoized(vNode) {
+ if (!vNode) {
+ return;
+ }
+
+ const role = getRole(vNode, { noPresentational: true, chromium: true });
+ if (role) {
+ return getRoleType(role);
+ }
+
+ return getClosestAncestorRoleType(vNode.parent);
+ }
+);
diff --git a/test/checks/aria/aria-prohibited-attr.js b/test/checks/aria/aria-prohibited-attr.js
index 4ae70f6bb0..ad893a5d64 100644
--- a/test/checks/aria/aria-prohibited-attr.js
+++ b/test/checks/aria/aria-prohibited-attr.js
@@ -1,16 +1,16 @@
-describe('aria-prohibited-attr', function () {
+describe('aria-prohibited-attr', () => {
'use strict';
- var checkContext = axe.testUtils.MockCheckContext();
- var checkSetup = axe.testUtils.checkSetup;
- var checkEvaluate = axe.testUtils.getCheckEvaluate('aria-prohibited-attr');
+ const checkContext = axe.testUtils.MockCheckContext();
+ const checkSetup = axe.testUtils.checkSetup;
+ const checkEvaluate = axe.testUtils.getCheckEvaluate('aria-prohibited-attr');
- afterEach(function () {
+ afterEach(() => {
checkContext.reset();
});
- it('should return true for prohibited attributes and no content', function () {
- var params = checkSetup(
+ it('should return true for prohibited attributes and no content', () => {
+ const params = checkSetup(
'
'
);
assert.isTrue(checkEvaluate.apply(checkContext, params));
@@ -22,8 +22,8 @@ describe('aria-prohibited-attr', function () {
});
});
- it('should return undefined for prohibited attributes and content', function () {
- var params = checkSetup(
+ it('should return undefined for prohibited attributes and content', () => {
+ const params = checkSetup(
'
Contents
'
);
assert.isUndefined(checkEvaluate.apply(checkContext, params));
@@ -35,8 +35,8 @@ describe('aria-prohibited-attr', function () {
});
});
- it('should return true for multiple prohibited attributes', function () {
- var params = checkSetup(
+ it('should return true for multiple prohibited attributes', () => {
+ const params = checkSetup(
''
);
assert.isTrue(checkEvaluate.apply(checkContext, params));
@@ -49,8 +49,10 @@ describe('aria-prohibited-attr', function () {
});
});
- it('should return undefined if element has no role and has text content (singular)', function () {
- var params = checkSetup('
Contents
');
+ it('should return undefined if element has no role and has text content (singular)', () => {
+ const params = checkSetup(
+ '
Contents
'
+ );
assert.isUndefined(checkEvaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, {
nodeName: 'div',
@@ -60,8 +62,8 @@ describe('aria-prohibited-attr', function () {
});
});
- it('should return undefined if element has no role and has text content (plural)', function () {
- var params = checkSetup(
+ it('should return undefined if element has no role and has text content (plural)', () => {
+ const params = checkSetup(
'
Contents
'
);
assert.isUndefined(checkEvaluate.apply(checkContext, params));
@@ -73,8 +75,8 @@ describe('aria-prohibited-attr', function () {
});
});
- it('should return true if element has no role and no text content (singular)', function () {
- var params = checkSetup('');
+ it('should return true if element has no role and no text content (singular)', () => {
+ const params = checkSetup('');
assert.isTrue(checkEvaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, {
nodeName: 'div',
@@ -84,8 +86,8 @@ describe('aria-prohibited-attr', function () {
});
});
- it('should return true if element has no role and no text content (plural)', function () {
- var params = checkSetup(
+ it('should return true if element has no role and no text content (plural)', () => {
+ const params = checkSetup(
''
);
assert.isTrue(checkEvaluate.apply(checkContext, params));
@@ -97,45 +99,139 @@ describe('aria-prohibited-attr', function () {
});
});
- it('should return false if all attributes are allowed', function () {
- var params = checkSetup(
+ it('should return false if all attributes are allowed', () => {
+ const params = checkSetup(
'
Contents
'
);
assert.isFalse(checkEvaluate.apply(checkContext, params));
});
- it('should return false if no prohibited attributes are used', function () {
- var params = checkSetup(
+ it('should return false if no prohibited attributes are used', () => {
+ const params = checkSetup(
'
Contents
'
);
assert.isFalse(checkEvaluate.apply(checkContext, params));
});
- it('should return false if prohibited attributes have no value', function () {
- var params = checkSetup(
+ it('should return false if prohibited attributes have no value', () => {
+ const params = checkSetup(
'
Contents
'
);
assert.isFalse(checkEvaluate.apply(checkContext, params));
});
- it('should allow `elementsAllowedAriaLabel` nodes to have aria-label', function () {
- var params = checkSetup(
+ it('should allow `elementsAllowedAriaLabel` nodes to have aria-label', () => {
+ const params = checkSetup(
'',
{ elementsAllowedAriaLabel: ['div'] }
);
assert.isFalse(checkEvaluate.apply(checkContext, params));
});
- it('should not allow `elementsAllowedAriaLabel` nodes with a prohibited role', function () {
- var params = checkSetup(
+ it('should not allow `elementsAllowedAriaLabel` nodes with a prohibited role', () => {
+ const params = checkSetup(
'',
{ elementsAllowedAriaLabel: ['div'] }
);
assert.isTrue(checkEvaluate.apply(checkContext, params));
});
- it('should allow elements that have an implicit role in chromium', function () {
- var params = checkSetup('');
+ it('should allow elements that have an implicit role in chromium', () => {
+ const params = checkSetup(
+ ''
+ );
assert.isFalse(checkEvaluate.apply(checkContext, params));
});
+
+ describe('widget ancestor', () => {
+ it('should allow aria-label', () => {
+ const params = checkSetup(`
+
+ `);
+ assert.isFalse(checkEvaluate.apply(checkContext, params));
+ });
+
+ it('should allow aria-labelledby', () => {
+ const params = checkSetup(`
+