Skip to content

Commit

Permalink
feat(is-in-tab-order): add isInTabOrder to commons (#3619)
Browse files Browse the repository at this point in the history
* add commons.isInTabOrder()
* replace uses of isFocusable with isInTabOrder when isFocusable also checked for negative tabindex

references: #3500

Co-authored-by: Wilco Fiers <WilcoFiers@users.noreply.github.com>
Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 30, 2022
1 parent b16f553 commit 77afe90
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 8 deletions.
7 changes: 2 additions & 5 deletions lib/checks/keyboard/focusable-element-evaluate.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isInTabOrder } from '../../commons/dom';
import { closest } from '../../core/utils';

function focusableElementEvaluate(node, options, virtualNode) {
Expand All @@ -14,11 +15,7 @@ function focusableElementEvaluate(node, options, virtualNode) {
return true;
}

const isFocusable = virtualNode.isFocusable;
let tabIndex = parseInt(virtualNode.attr('tabindex'), 10);
tabIndex = !isNaN(tabIndex) ? tabIndex : null;

return tabIndex ? isFocusable && tabIndex >= 0 : isFocusable;
return isInTabOrder(virtualNode);

// contenteditable is focusable when it is an empty string (whitespace
// is not considered empty) or "true". if the value is "false"
Expand Down
5 changes: 2 additions & 3 deletions lib/checks/keyboard/frame-focusable-content-evaluate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import isFocusable from '../../commons/dom/is-focusable';
import { isInTabOrder } from '../../commons/dom';

export default function frameFocusableContentEvaluate(
node,
Expand All @@ -19,8 +19,7 @@ export default function frameFocusableContentEvaluate(
}

function focusableDescendants(vNode) {
const tabIndex = parseInt(vNode.attr('tabindex'), 10);
if ((isNaN(tabIndex) || tabIndex > -1) && isFocusable(vNode)) {
if (isInTabOrder(vNode)) {
return true;
}

Expand Down
1 change: 1 addition & 0 deletions lib/commons/dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export { default as isCurrentPageLink } from './is-current-page-link';
export { default as isFocusable } from './is-focusable';
export { default as isHiddenWithCSS } from './is-hidden-with-css';
export { default as isHTML5 } from './is-html5';
export { default as isInTabOrder } from './is-in-tab-order';
export { default as isInTextBlock } from './is-in-text-block';
export { default as isModalOpen } from './is-modal-open';
export { default as isMultiline } from './is-multiline';
Expand Down
26 changes: 26 additions & 0 deletions lib/commons/dom/is-in-tab-order.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
import { getNodeFromTree } from '../../core/utils';
import isFocusable from './is-focusable';

/**
* Determines if an element is focusable and able to be tabbed to.
* @method isInTabOrder
* @memberof axe.commons.dom
* @instance
* @param {HTMLElement} el The HTMLElement
* @return {Boolean} The element's tabindex status
*/
export default function isInTabOrder(el) {
const vNode = el instanceof AbstractVirtualNode ? el : getNodeFromTree(el);

if (vNode.props.nodeType !== 1) {
return false;
}

const tabindex = parseInt(vNode.attr('tabindex', 10));
if (tabindex <= -1) {
return false; // Elements with tabindex=-1 are never in the tab order
}

return isFocusable(vNode);
}
100 changes: 100 additions & 0 deletions test/commons/dom/is-in-tab-order.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
describe('dom.isInTabOrder', function () {
'use strict';

var queryFixture = axe.testUtils.queryFixture;
var isInTabOrder = axe.commons.dom.isInTabOrder;
var isIE11 = axe.testUtils.isIE11;

it('should return false for presentation element with negative tabindex', function () {
var target = queryFixture('<div id="target" tabindex="-1"></div>');
assert.isFalse(isInTabOrder(target));
});

it('should return true for presentation element with positive tabindex', function () {
var target = queryFixture('<div id="target" tabindex="1"></div>');
assert.isTrue(isInTabOrder(target));
});

it('should return false for presentation element with tabindex not set', function () {
var target = queryFixture('<div id="target"></div>');
assert.isFalse(isInTabOrder(target));
});

it('should return false for presentation element with tabindex set to non-parseable value', function () {
var target = queryFixture('<div id="target" tabindex="foobar"></div>');
assert.isFalse(isInTabOrder(target));
});

it('should return false for presentation element with tabindex not set and role of natively focusable element', function () {
var target = queryFixture('<div id="target" role="button"></div>');
assert.isFalse(isInTabOrder(target));
});

it('should return true for natively focusable element with tabindex 0', function () {
var target = queryFixture('<button id="target" tabindex="0"></button>');
assert.isTrue(isInTabOrder(target));
});

it('should return true for natively focusable element with tabindex 1', function () {
var target = queryFixture('<button id="target" tabindex="1"></button>');
assert.isTrue(isInTabOrder(target));
});

it('should return false for natively focusable element with tabindex -1', function () {
var target = queryFixture('<button id="target" tabindex="-1"></button>');
assert.isFalse(isInTabOrder(target));
});

it('should return true for natively focusable element with tabindex not set', function () {
var target = queryFixture('<button id="target"></button>');
assert.isTrue(isInTabOrder(target));
});

// IE11 returns a negative tabindex for elements with tabindex set to an empty string, rather than create false positives, skip it
(isIE11 ? xit : it)(
'should return true for natively focusable element with tabindex set to empty string',
function () {
var target = queryFixture('<button id="target" tabindex=""></button>');
assert.isTrue(isInTabOrder(target));
}
);

it('should return true for natively focusable element with tabindex set to non-parseable value', function () {
var target = queryFixture(
'<button id="target" tabindex="foobar"></button>'
);
assert.isTrue(isInTabOrder(target));
});

it('should return false for disabled', function () {
var target = queryFixture('<button id="target" disabled></button>');
assert.isFalse(isInTabOrder(target));
});

it('should return false for disabled natively focusable element with tabindex', function () {
var target = queryFixture(
'<button id="target" disabled tabindex="0"></button>'
);
assert.isFalse(isInTabOrder(target));
});

it('should return false for hidden inputs', function () {
var target = queryFixture('<input type="hidden" id="target"></input>');
assert.isFalse(isInTabOrder(target));
});

it('should return false for non-element nodes', function () {
var target = queryFixture('<span id="target">Hello World</span>');
assert.isFalse(isInTabOrder(target.children[0]));
});

it('should return false for natively focusable hidden element', function () {
var target = queryFixture('<button id="target" hidden></button>');
assert.isFalse(isInTabOrder(target));
});

it('should return for false hidden element with tabindex 1', function () {
var target = queryFixture('<div id="target" tabindex="1" hidden></div>');
assert.isFalse(isInTabOrder(target));
});
});

0 comments on commit 77afe90

Please sign in to comment.