Skip to content

Commit 30f0e01

Browse files
committed
fix(rule): allow "tabindex=-1" for rules "aria-text" and "nested-interactive"
Closes issue #2934
1 parent 739f035 commit 30f0e01

7 files changed

Lines changed: 62 additions & 12 deletions

File tree

lib/checks/keyboard/no-focusable-content-evaluate.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import isFocusable from '../../commons/dom/is-focusable';
22

33
function focusableDescendants(vNode) {
4-
if (isFocusable(vNode)) {
4+
const isNodeFocusable = isFocusable(vNode);
5+
let tabIndex = parseInt(vNode.attr('tabindex'), 10);
6+
tabIndex = !isNaN(tabIndex) ? tabIndex : null;
7+
8+
if(tabIndex ? isNodeFocusable && tabIndex >= 0 : isNodeFocusable) {
59
return true;
610
}
711

test/checks/keyboard/no-focusable-content.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,26 @@ describe('no-focusable-content tests', function() {
3737
);
3838
assert.isFalse(noFocusableContent(null, null, vNode));
3939
});
40+
41+
it('should return true on span with tabindex=-1', function() {
42+
var vNode = queryFixture('<span id="target" role="text"> some text '
43+
+'<span tabIndex="-1">JavaScript is able to focus this</span> '
44+
+'</span>');
45+
assert.isTrue(noFocusableContent(null, null, vNode));
46+
});
47+
48+
it('should return true on aria-hidden span with tabindex=-1', function() {
49+
var vNode = queryFixture('<span id="target" role="text"> some text '
50+
+'<span tabIndex="-1" aria-hidden="true">JavaScript is able to focus this</span> '
51+
+'</span>');
52+
assert.isTrue(noFocusableContent(null, null, vNode));
53+
});
54+
55+
it('should return false on span with tabindex=0', function() {
56+
var vNode = queryFixture('<span id="target" role="text"> some text '
57+
+'<span tabIndex="0">anyone is able to focus this</span> '
58+
+'</span>');
59+
assert.isFalse(noFocusableContent(null, null, vNode));
60+
});
61+
4062
});

test/integration/rules/aria-text/aria-text.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ <h1>
1515
<div role="text" id="fail2">
1616
<a href="#" role="none">Flattened text</a> because of the explicit role.
1717
</div>
18-
<div role="text" id="fail3">
18+
<div role="text" id="pass4">
1919
<a href="#" tabindex="-1" role="none">Flattened text</a> because of the
20-
explicit role.
20+
explicit role. Considered non-focusable because of tabindex=-1...
2121
</div>
22-
<p role="text" id="fail4"><button>Hello</button></p>
22+
<p role="text" id="fail3"><button>Hello</button></p>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"description": "aria-text tests",
33
"rule": "aria-text",
4-
"violations": [["#fail1"], ["#fail2"], ["#fail3"], ["#fail4"]],
5-
"passes": [["#pass1"], ["#pass2"], ["#pass3"]]
4+
"violations": [["#fail1"], ["#fail2"], ["#fail3"]],
5+
"passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"]]
66
}

test/integration/rules/nested-interactive/nested-interactive.html

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<button id="pass1">pass</button>
2-
<div role="button" id="pass2">pass</div>
3-
<div role="tab" id="pass3">pass</div>
4-
<div role="checkbox" id="pass4">pass</div>
5-
<div role="radio" id="pass5"><span>pass</span></div>
2+
<button id="pass2"><span tabindex="-1">pass</span></button>
3+
<div role="button" id="pass3">pass</div>
4+
<div role="tab" id="pass4">pass</div>
5+
<div role="checkbox" id="pass5">pass</div>
6+
<div role="radio" id="pass6"><span>pass</span></div>
7+
<div role="radio" id="pass7"><span tabindex="-1">pass</span></div>
68

79
<button id="fail1"><span tabindex="0">fail</span></button>
810
<div role="button" id="fail2"><input /></div>
9-
<div role="tab" id="fail3"><button id="pass6">fail</button></div>
11+
<div role="tab" id="fail3"><button id="pass8">fail</button></div>
1012
<div role="checkbox" id="fail4"><a href="foo.html">fail</a></div>
1113
<div role="radio" id="fail5"><span tabindex="0">fail</span></div>
1214

test/integration/rules/nested-interactive/nested-interactive.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
["#pass3"],
99
["#pass4"],
1010
["#pass5"],
11-
["#pass6"]
11+
["#pass6"],
12+
["#pass7"],
13+
["#pass8"]
1214
]
1315
}

test/integration/virtual-rules/nested-interactive.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,26 @@ describe('nested-interactive virtual-rule', function() {
3838
assert.lengthOf(results.incomplete, 0);
3939
});
4040

41+
it('should pass for element with content with tabindex=-1', function() {
42+
var node = new axe.SerialVirtualNode({
43+
nodeName: 'button'
44+
});
45+
var child = new axe.SerialVirtualNode({
46+
nodeName: 'span',
47+
attributes: {
48+
tabindex: -1
49+
}
50+
});
51+
child.children = [];
52+
node.children = [child];
53+
54+
var results = axe.runVirtualRule('nested-interactive', node);
55+
56+
assert.lengthOf(results.passes, 1);
57+
assert.lengthOf(results.violations, 0);
58+
assert.lengthOf(results.incomplete, 0);
59+
});
60+
4161
it('should pass for empty element without', function() {
4262
var node = new axe.SerialVirtualNode({
4363
nodeName: 'div',

0 commit comments

Comments
 (0)