diff --git a/lib/checks/aria/aria-prohibited-attr-evaluate.js b/lib/checks/aria/aria-prohibited-attr-evaluate.js index ff843065fd..c4e7085761 100644 --- a/lib/checks/aria/aria-prohibited-attr-evaluate.js +++ b/lib/checks/aria/aria-prohibited-attr-evaluate.js @@ -26,14 +26,12 @@ import standards from '../../standards'; * @memberof checks * @return {Boolean} True if the element uses any prohibited ARIA attributes. False otherwise. */ -function ariaProhibitedAttrEvaluate(node, options = {}, virtualNode) { - const extraElementsAllowedAriaLabel = options.elementsAllowedAriaLabel || []; - - const prohibitedList = listProhibitedAttrs( - virtualNode, - extraElementsAllowedAriaLabel - ); +export default function ariaProhibitedAttrEvaluate(node, options = {}, virtualNode) { + const elementsAllowedAriaLabel = options?.elementsAllowedAriaLabel || []; + const { nodeName } = virtualNode.props; + const role = getRole(virtualNode, { chromium: true }); + const prohibitedList = listProhibitedAttrs(role, nodeName, elementsAllowedAriaLabel); const prohibited = prohibitedList.filter(attrName => { if (!virtualNode.attrNames.includes(attrName)) { return false; @@ -45,24 +43,26 @@ function ariaProhibitedAttrEvaluate(node, options = {}, virtualNode) { return false; } - this.data(prohibited); - const hasTextContent = sanitize(subtreeText(virtualNode)) !== ''; - // Don't fail if there is text content to announce - return hasTextContent ? undefined : true; + let messageKey = virtualNode.hasAttr('role') ? 'hasRole' : 'noRole'; + messageKey += prohibited.length > 1 ? 'Plural' : 'Singular'; + this.data({ role, nodeName, messageKey, prohibited }); + + // `subtreeDescendant` to override namedFromContents + const textContent = subtreeText(virtualNode, { subtreeDescendant: true }); + if (sanitize(textContent) !== '') { + // Don't fail if there is text content to announce + return undefined; + } + return true; } -function listProhibitedAttrs(virtualNode, elementsAllowedAriaLabel) { - const role = getRole(virtualNode, { chromium: true }); +function listProhibitedAttrs(role, nodeName, elementsAllowedAriaLabel) { const roleSpec = standards.ariaRoles[role]; if (roleSpec) { return roleSpec.prohibitedAttrs || []; } - - const { nodeName } = virtualNode.props; if (!!role || elementsAllowedAriaLabel.includes(nodeName)) { return []; } return ['aria-label', 'aria-labelledby']; } - -export default ariaProhibitedAttrEvaluate; diff --git a/lib/checks/aria/aria-prohibited-attr.json b/lib/checks/aria/aria-prohibited-attr.json index fd2f3e72bc..21e8a007f9 100644 --- a/lib/checks/aria/aria-prohibited-attr.json +++ b/lib/checks/aria/aria-prohibited-attr.json @@ -8,8 +8,18 @@ "impact": "serious", "messages": { "pass": "ARIA attribute is allowed", - "fail": "ARIA attribute: ${data.values} is not allowed. Use a different role attribute or element.", - "incomplete": "ARIA attribute: ${data.values} is not well supported. Use a different role attribute or element." + "fail": { + "hasRolePlural": "${data.prohibited} attributes cannot be used with role \"${data.role}\".", + "hasRoleSingular": "${data.prohibited} attribute cannot be used with role \"${data.role}\".", + "noRolePlural": "${data.prohibited} attributes cannot be used on a ${data.nodeName} with no valid role attribute.", + "noRoleSingular": "${data.prohibited} attribute cannot be used on a ${data.nodeName} with no valid role attribute." + }, + "incomplete": { + "hasRoleSingular": "${data.prohibited} attribute is not well supported with role \"${data.role}\".", + "hasRolePlural": "${data.prohibited} attributes are not well supported with role \"${data.role}\".", + "noRoleSingular": "${data.prohibited} attribute is not well supported on a ${data.nodeName} with no valid role attribute.", + "noRolePlural": "${data.prohibited} attributes are not well supported on a ${data.nodeName} with no valid role attribute." + } } } } diff --git a/test/checks/aria/aria-prohibited-attr.js b/test/checks/aria/aria-prohibited-attr.js index 4900b6e3f5..6e7a492b74 100644 --- a/test/checks/aria/aria-prohibited-attr.js +++ b/test/checks/aria/aria-prohibited-attr.js @@ -9,25 +9,47 @@ describe('aria-prohibited-attr', function() { checkContext.reset(); }); - it('should return true for prohibited attributes', function() { + it('should return true for prohibited attributes and no content', function() { var params = checkSetup( - '
Contents
' + '
' ); assert.isTrue(checkEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._data, ['aria-label']); + assert.deepEqual(checkContext._data, { + nodeName: 'div', + role: 'code', + messageKey: 'hasRoleSingular', + prohibited: ['aria-label'] + }); + }); + + it('should return undefined for prohibited attributes and content', function() { + var params = checkSetup( + '
Contents
' + ); + assert.isUndefined(checkEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._data, { + nodeName: 'div', + role: 'code', + messageKey: 'hasRoleSingular', + prohibited: ['aria-label'] + }); }); it('should return true for multiple prohibited attributes', function() { var params = checkSetup( - '
Contents
' + '
' ); assert.isTrue(checkEvaluate.apply(checkContext, params)); - - // attribute order not important - assert.sameDeepMembers(checkContext._data, [ - 'aria-label', - 'aria-labelledby' - ]); + assert.deepEqual(checkContext._data, { + nodeName: 'div', + role: 'code', + messageKey: 'hasRolePlural', + // attribute order not important + prohibited: [ + 'aria-label', + 'aria-labelledby' + ] + }); }); it('should return undefined if element has no role and has text content', function() { @@ -35,6 +57,15 @@ describe('aria-prohibited-attr', function() { '
Contents
' ); assert.isUndefined(checkEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._data, { + nodeName: 'div', + role: null, + messageKey: 'noRolePlural', + prohibited: [ + 'aria-label', + 'aria-labelledby' + ] + }); }); it('should return true if element has no role and no text content', function() { @@ -42,6 +73,15 @@ describe('aria-prohibited-attr', function() { '
' ); assert.isTrue(checkEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._data, { + nodeName: 'div', + role: null, + messageKey: 'noRolePlural', + prohibited: [ + 'aria-label', + 'aria-labelledby' + ] + }); }); it('should return false if all attributes are allowed', function() { diff --git a/test/integration/rules/aria-allowed-attr/failures.html b/test/integration/rules/aria-allowed-attr/failures.html index ab576ae3ff..ff18ac4af8 100644 --- a/test/integration/rules/aria-allowed-attr/failures.html +++ b/test/integration/rules/aria-allowed-attr/failures.html @@ -1,25 +1,25 @@ - -
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
-
fail
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -34,10 +34,10 @@ aria-orientation="horizontal" id="fail30" > --> -
fail
-
fail
-
fail
-
fail
+
+
+
+
diff --git a/test/integration/rules/aria-allowed-attr/incomplete.html b/test/integration/rules/aria-allowed-attr/incomplete.html index 35793fb4a2..20c2fedb6c 100644 --- a/test/integration/rules/aria-allowed-attr/incomplete.html +++ b/test/integration/rules/aria-allowed-attr/incomplete.html @@ -1,2 +1,3 @@
Foo
Foo
+
Foo
diff --git a/test/integration/rules/aria-allowed-attr/incomplete.json b/test/integration/rules/aria-allowed-attr/incomplete.json index 475f884bdd..1d8c7e3fd7 100644 --- a/test/integration/rules/aria-allowed-attr/incomplete.json +++ b/test/integration/rules/aria-allowed-attr/incomplete.json @@ -1,5 +1,5 @@ { "description": "aria-allowed-attr incomplete tests", "rule": "aria-allowed-attr", - "incomplete": [["#incomplete0"], ["#incomplete1"]] + "incomplete": [["#incomplete0"], ["#incomplete1"], ["#incomplete2"]] }