diff --git a/lib/checks/aria/aria-allowed-attr-evaluate.js b/lib/checks/aria/aria-allowed-attr-evaluate.js index a1049ef38d..ab1002eb37 100644 --- a/lib/checks/aria/aria-allowed-attr-evaluate.js +++ b/lib/checks/aria/aria-allowed-attr-evaluate.js @@ -39,7 +39,11 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) { // Unknown ARIA attributes are tested in aria-valid-attr for (const attrName of virtualNode.attrNames) { - if (validateAttr(attrName) && !allowed.includes(attrName)) { + if ( + validateAttr(attrName) && + !allowed.includes(attrName) && + !ignoredAttrs(attrName, virtualNode.attr(attrName)) + ) { invalid.push(attrName); } } @@ -57,3 +61,9 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) { } return false; } + +function ignoredAttrs(attrName, attrValue) { + // allow aria-required=false as screen readers consistently ignore it + // @see https://github.com/dequelabs/axe-core/issues/3756 + return attrName === 'aria-required' && attrValue === 'false'; +} diff --git a/test/checks/aria/aria-allowed-attr.js b/test/checks/aria/aria-allowed-attr.js index b1a0d5933f..578dd2771b 100644 --- a/test/checks/aria/aria-allowed-attr.js +++ b/test/checks/aria/aria-allowed-attr.js @@ -1,15 +1,15 @@ -describe('aria-allowed-attr', function () { +describe('aria-allowed-attr', () => { 'use strict'; - var queryFixture = axe.testUtils.queryFixture; - var checkContext = axe.testUtils.MockCheckContext(); + const queryFixture = axe.testUtils.queryFixture; + const checkContext = axe.testUtils.MockCheckContext(); - afterEach(function () { + afterEach(() => { checkContext.reset(); }); - it('should detect incorrectly used attributes', function () { - var vNode = queryFixture( + it('should detect incorrectly used attributes', () => { + const vNode = queryFixture( '
' ); @@ -21,8 +21,8 @@ describe('aria-allowed-attr', function () { assert.deepEqual(checkContext._data, ['aria-selected="true"']); }); - it('should not report on required attributes', function () { - var vNode = queryFixture( + it('should not report on required attributes', () => { + const vNode = queryFixture( '' ); @@ -33,8 +33,8 @@ describe('aria-allowed-attr', function () { ); }); - it('should detect incorrectly used attributes - implicit role', function () { - var vNode = queryFixture( + it('should detect incorrectly used attributes - implicit role', () => { + const vNode = queryFixture( '' ); @@ -46,8 +46,8 @@ describe('aria-allowed-attr', function () { assert.deepEqual(checkContext._data, ['aria-selected="true"']); }); - it('should return true for global attributes if there is no role', function () { - var vNode = queryFixture( + it('should return true for global attributes if there is no role', () => { + const vNode = queryFixture( '
' ); @@ -59,8 +59,8 @@ describe('aria-allowed-attr', function () { assert.isNull(checkContext._data); }); - it('should return false for non-global attributes if there is no role', function () { - var vNode = queryFixture( + it('should return false for non-global attributes if there is no role', () => { + const vNode = queryFixture( '
' ); @@ -72,8 +72,8 @@ describe('aria-allowed-attr', function () { assert.deepEqual(checkContext._data, ['aria-selected="true"']); }); - it('should not report on invalid attributes', function () { - var vNode = queryFixture( + it('should not report on invalid attributes', () => { + const vNode = queryFixture( '' ); @@ -85,8 +85,8 @@ describe('aria-allowed-attr', function () { assert.isNull(checkContext._data); }); - it('should not report on allowed attributes', function () { - var vNode = queryFixture( + it('should not report on allowed attributes', () => { + const vNode = queryFixture( '' ); @@ -98,8 +98,34 @@ describe('aria-allowed-attr', function () { assert.isNull(checkContext._data); }); - it('should return undefined for custom element that has no role and is not focusable', function () { - var vNode = queryFixture( + it('should not report on aria-required=false', () => { + const vNode = queryFixture( + '' + ); + + assert.isTrue( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr') + .call(checkContext, null, null, vNode) + ); + assert.isNull(checkContext._data); + }); + + it('should return false for unallowed aria-required=true', () => { + const vNode = queryFixture( + '' + ); + + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, ['aria-required="true"']); + }); + + it('should return undefined for custom element that has no role and is not focusable', () => { + const vNode = queryFixture( '' ); @@ -111,8 +137,8 @@ describe('aria-allowed-attr', function () { assert.isNotNull(checkContext._data); }); - it("should return false for custom element that has a role which doesn't allow the attribute", function () { - var vNode = queryFixture( + it("should return false for custom element that has a role which doesn't allow the attribute", () => { + const vNode = queryFixture( '' ); @@ -124,8 +150,8 @@ describe('aria-allowed-attr', function () { assert.isNotNull(checkContext._data); }); - it('should return false for custom element that is focusable', function () { - var vNode = queryFixture( + it('should return false for custom element that is focusable', () => { + const vNode = queryFixture( '' ); @@ -137,8 +163,8 @@ describe('aria-allowed-attr', function () { assert.isNotNull(checkContext._data); }); - describe('options', function () { - it('should allow provided attribute names for a role', function () { + describe('options', () => { + it('should allow provided attribute names for a role', () => { axe.configure({ standards: { ariaRoles: { @@ -149,7 +175,7 @@ describe('aria-allowed-attr', function () { } }); - var vNode = queryFixture( + const vNode = queryFixture( '
' ); @@ -171,7 +197,7 @@ describe('aria-allowed-attr', function () { ); }); - it('should handle multiple roles provided in options', function () { + it('should handle multiple roles provided in options', () => { axe.configure({ standards: { ariaRoles: { @@ -185,10 +211,10 @@ describe('aria-allowed-attr', function () { } }); - var vNode = queryFixture( + const vNode = queryFixture( '
' ); - var options = { + const options = { mccheddarton: ['aria-selected'], bagley: ['aria-selected'] }; diff --git a/test/integration/rules/aria-allowed-attr/passes.html b/test/integration/rules/aria-allowed-attr/passes.html index c1ebea91bf..788e362f5f 100644 --- a/test/integration/rules/aria-allowed-attr/passes.html +++ b/test/integration/rules/aria-allowed-attr/passes.html @@ -2169,3 +2169,6 @@ + + + diff --git a/test/integration/rules/aria-allowed-attr/passes.json b/test/integration/rules/aria-allowed-attr/passes.json index aa13ff6f71..5eda613aba 100644 --- a/test/integration/rules/aria-allowed-attr/passes.json +++ b/test/integration/rules/aria-allowed-attr/passes.json @@ -102,6 +102,7 @@ ["#pass97"], ["#pass98"], ["#pass99"], - ["#pass100"] + ["#pass100"], + ["#pass101"] ] }