Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(is-in-tab-order): add isInTabOrder to commons #3619

Merged
merged 13 commits into from
Aug 30, 2022
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));
dbowling marked this conversation as resolved.
Show resolved Hide resolved
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 () {
dbowling marked this conversation as resolved.
Show resolved Hide resolved
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));
});
});
dbowling marked this conversation as resolved.
Show resolved Hide resolved