From f0190685722495d00be644cabb1c9741d74acdea Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Thu, 11 Jul 2024 08:12:21 -0600 Subject: [PATCH] fix(aria-allowed-attr): allow aria-multiline=false for element with contenteditable (#4537) Closes: https://github.com/dequelabs/axe-core/issues/4463 --- lib/checks/aria/aria-allowed-attr-evaluate.js | 20 ++++++-- test/checks/aria/aria-allowed-attr.js | 50 +++++++++++++++++++ .../rules/aria-allowed-attr/failures.html | 4 ++ .../rules/aria-allowed-attr/failures.json | 11 +++- .../rules/aria-allowed-attr/passes.html | 1 + .../rules/aria-allowed-attr/passes.json | 3 +- 6 files changed, 84 insertions(+), 5 deletions(-) diff --git a/lib/checks/aria/aria-allowed-attr-evaluate.js b/lib/checks/aria/aria-allowed-attr-evaluate.js index ab1002eb37..59d09e7e6e 100644 --- a/lib/checks/aria/aria-allowed-attr-evaluate.js +++ b/lib/checks/aria/aria-allowed-attr-evaluate.js @@ -42,7 +42,7 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) { if ( validateAttr(attrName) && !allowed.includes(attrName) && - !ignoredAttrs(attrName, virtualNode.attr(attrName)) + !ignoredAttrs(attrName, virtualNode.attr(attrName), virtualNode) ) { invalid.push(attrName); } @@ -62,8 +62,22 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) { return false; } -function ignoredAttrs(attrName, attrValue) { +function ignoredAttrs(attrName, attrValue, vNode) { // 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'; + if (attrName === 'aria-required' && attrValue === 'false') { + return true; + } + + // allow aria-multiline=false when contenteditable is set + // @see https://github.com/dequelabs/axe-core/issues/4463 + if ( + attrName === 'aria-multiline' && + attrValue === 'false' && + vNode.hasAttr('contenteditable') + ) { + return true; + } + + return false; } diff --git a/test/checks/aria/aria-allowed-attr.js b/test/checks/aria/aria-allowed-attr.js index 578dd2771b..b25b9df4a1 100644 --- a/test/checks/aria/aria-allowed-attr.js +++ b/test/checks/aria/aria-allowed-attr.js @@ -124,6 +124,56 @@ describe('aria-allowed-attr', () => { assert.deepEqual(checkContext._data, ['aria-required="true"']); }); + it('should not report on aria-multiline=false with contenteditable', () => { + 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-multiline=true and contenteditable', () => { + const vNode = queryFixture( + '
' + ); + + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, ['aria-multiline="true"']); + }); + + it('should return false for unallowed aria-multiline=false', () => { + const vNode = queryFixture( + '
' + ); + + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, ['aria-multiline="false"']); + }); + + it('should return false for unallowed aria-multiline=true', () => { + const vNode = queryFixture('
'); + + assert.isFalse( + axe.testUtils + .getCheckEvaluate('aria-allowed-attr') + .call(checkContext, null, null, vNode) + ); + assert.deepEqual(checkContext._data, ['aria-multiline="true"']); + }); + it('should return undefined for custom element that has no role and is not focusable', () => { const vNode = queryFixture( '' diff --git a/test/integration/rules/aria-allowed-attr/failures.html b/test/integration/rules/aria-allowed-attr/failures.html index cdb251a248..21c84b0e50 100644 --- a/test/integration/rules/aria-allowed-attr/failures.html +++ b/test/integration/rules/aria-allowed-attr/failures.html @@ -9,3 +9,7 @@ aria-orientation="horizontal" id="fail5" > + +
+
+
diff --git a/test/integration/rules/aria-allowed-attr/failures.json b/test/integration/rules/aria-allowed-attr/failures.json index cedecf0810..02d9579b81 100644 --- a/test/integration/rules/aria-allowed-attr/failures.json +++ b/test/integration/rules/aria-allowed-attr/failures.json @@ -1,5 +1,14 @@ { "description": "aria-allowed-attr failing tests", "rule": "aria-allowed-attr", - "violations": [["#fail1"], ["#fail2"], ["#fail3"], ["#fail4"], ["#fail5"]] + "violations": [ + ["#fail1"], + ["#fail2"], + ["#fail3"], + ["#fail4"], + ["#fail5"], + ["#fail6"], + ["#fail7"], + ["#fail8"] + ] } diff --git a/test/integration/rules/aria-allowed-attr/passes.html b/test/integration/rules/aria-allowed-attr/passes.html index 788e362f5f..a21c68c3b1 100644 --- a/test/integration/rules/aria-allowed-attr/passes.html +++ b/test/integration/rules/aria-allowed-attr/passes.html @@ -2172,3 +2172,4 @@ +
diff --git a/test/integration/rules/aria-allowed-attr/passes.json b/test/integration/rules/aria-allowed-attr/passes.json index 5eda613aba..0974e07250 100644 --- a/test/integration/rules/aria-allowed-attr/passes.json +++ b/test/integration/rules/aria-allowed-attr/passes.json @@ -103,6 +103,7 @@ ["#pass98"], ["#pass99"], ["#pass100"], - ["#pass101"] + ["#pass101"], + ["#pass102"] ] }