From 1012dfec29a7a69b70357211233bb350159fa83e Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Wed, 1 Apr 2020 09:18:42 -0600 Subject: [PATCH] fix(scrollable-region-focusable): pass for elements with contenteditable (#2133) * fix(scrollable-region-focusable): pass for elements with contenteditable * change order * full test cases * fix ie11 --- lib/checks/keyboard/focusable-element.js | 30 +++++++++++- test/checks/keyboard/focusable-element.js | 48 +++++++++++++++++++ .../scrollable-region-focusable.html | 26 ++++++++++ .../scrollable-region-focusable.json | 11 ++++- 4 files changed, 111 insertions(+), 4 deletions(-) diff --git a/lib/checks/keyboard/focusable-element.js b/lib/checks/keyboard/focusable-element.js index eacb898168..dc5d750697 100644 --- a/lib/checks/keyboard/focusable-element.js +++ b/lib/checks/keyboard/focusable-element.js @@ -4,9 +4,35 @@ * - if element is focusable * - if element is in focus order via `tabindex` */ -const isFocusable = virtualNode.isFocusable; +if (virtualNode.hasAttr('contenteditable') && isContenteditable(virtualNode)) { + return true; +} -let tabIndex = parseInt(virtualNode.actualNode.getAttribute('tabindex'), 10); +const isFocusable = virtualNode.isFocusable; +let tabIndex = parseInt(virtualNode.attr('tabindex'), 10); tabIndex = !isNaN(tabIndex) ? tabIndex : null; return tabIndex ? isFocusable && tabIndex >= 0 : isFocusable; + +// contenteditable is focusable when it is an empty string (whitespace +// is not considered empty) or "true". if the value is "false" +// you can't edit it, but if it's anything else it inherits the value +// from the first valid ancestor +// @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable +function isContenteditable(vNode) { + const contenteditable = vNode.attr('contenteditable'); + if (contenteditable === 'true' || contenteditable === '') { + return true; + } + + if (contenteditable === 'false') { + return false; + } + + const ancestor = axe.utils.closest(virtualNode.parent, '[contenteditable]'); + if (!ancestor) { + return false; + } + + return isContenteditable(ancestor); +} diff --git a/test/checks/keyboard/focusable-element.js b/test/checks/keyboard/focusable-element.js index 179936c6dc..a989f2e8b7 100644 --- a/test/checks/keyboard/focusable-element.js +++ b/test/checks/keyboard/focusable-element.js @@ -43,4 +43,52 @@ describe('focusable-element tests', function() { var actual = check.evaluate.apply(checkContext, params); assert.isTrue(actual); }); + + it('returns true when element made focusable by contenteditable', function() { + var params = checkSetup( + '

I hold some text

' + ); + var actual = check.evaluate.apply(checkContext, params); + assert.isTrue(actual); + }); + + it('returns true when element made focusable by contenteditable="true"', function() { + var params = checkSetup( + '

I hold some text

' + ); + var actual = check.evaluate.apply(checkContext, params); + assert.isTrue(actual); + }); + + it('returns false when element made focusable by contenteditable="false"', function() { + var params = checkSetup( + '

I hold some text

' + ); + var actual = check.evaluate.apply(checkContext, params); + assert.isFalse(actual); + }); + + it('returns true when element made focusable by contenteditable="invalid" and parent is contenteditable', function() { + var params = checkSetup( + '

I hold some text

' + ); + var actual = check.evaluate.apply(checkContext, params); + assert.isTrue(actual); + }); + + it('returns false when element made focusable by contenteditable="invalid" and parent is not contenteditable', function() { + var params = checkSetup( + '

I hold some text

' + ); + var actual = check.evaluate.apply(checkContext, params); + assert.isFalse(actual); + }); + + it('returns false when element made focusable by contenteditable="invalid" and parent is contenteditable="false"', function() { + var params = checkSetup( + '

I hold some text

' + ); + var actual = check.evaluate.apply(checkContext, params); + assert.isFalse(actual); + }); }); diff --git a/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.html b/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.html index 3ee4ed78f7..94ad8f2a3a 100644 --- a/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.html +++ b/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.html @@ -16,6 +16,26 @@

+
+
+

Content

+
+
+ +
+
+
+
+

Content

+
+
+
+
+
@@ -27,6 +47,12 @@
+
+
+

Content

+
+
+
This element is not scrollable
diff --git a/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.json b/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.json index 44d2f4d059..65efdf74d2 100644 --- a/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.json +++ b/test/integration/rules/scrollable-region-focusable/scrollable-region-focusable.json @@ -1,6 +1,13 @@ { "description": "scrollable-region-focusable tests", "rule": "scrollable-region-focusable", - "violations": [["#fail1"], ["#fail2"]], - "passes": [["#pass1"], ["#pass2"], ["#pass3"]] + "violations": [["#fail1"], ["#fail2"], ["#fail3"]], + "passes": [ + ["#pass1"], + ["#pass2"], + ["#pass3"], + ["#pass4"], + ["#pass5"], + ["#pass6"] + ] }