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
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
8 changes: 2 additions & 6 deletions lib/commons/dom/get-tabbable-elements.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isInTabOrder } from '.';
import { querySelectorAll } from '../../core/utils';

/**
Expand All @@ -12,12 +13,7 @@ function getTabbableElements(virtualNode) {
const nodeAndDescendents = querySelectorAll(virtualNode, '*');

const tabbableElements = nodeAndDescendents.filter(vNode => {
const isFocusable = vNode.isFocusable;
dbowling marked this conversation as resolved.
Show resolved Hide resolved
let tabIndex = vNode.actualNode.getAttribute('tabindex');
tabIndex =
tabIndex && !isNaN(parseInt(tabIndex, 10)) ? parseInt(tabIndex) : null;

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

return tabbableElements;
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
10 changes: 2 additions & 8 deletions lib/commons/dom/inserted-into-focus-order.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import isFocusable from './is-focusable';
import isInTabOrder from './is-in-tab-order';
import isNativelyFocusable from './is-natively-focusable';

/**
Expand All @@ -12,13 +12,7 @@ import isNativelyFocusable from './is-natively-focusable';
* if its tabindex were removed. Else, false.
*/
function insertedIntoFocusOrder(el) {
const tabIndex = parseInt(el.getAttribute('tabindex'), 10);

// an element that has an invalid tabindex will return 0 or -1 based on
// if it is natively focusable or not, which will always be false for this
// check as NaN is not > 1
// @see https://www.w3.org/TR/html51/editing.html#the-tabindex-attribute
return tabIndex > -1 && isFocusable(el) && !isNativelyFocusable(el);
return isInTabOrder(el) && !isNativelyFocusable(el);
dbowling marked this conversation as resolved.
Show resolved Hide resolved
}

export default insertedIntoFocusOrder;
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));
});

it('should return false natively focusable hidden element', function () {
dbowling marked this conversation as resolved.
Show resolved Hide resolved
var target = queryFixture('<button id="target" hidden></button>');
assert.isFalse(isInTabOrder(target));
});

it('should return false hidden element with tabindex 1', function () {
dbowling marked this conversation as resolved.
Show resolved Hide resolved
var target = queryFixture('<div id="target" tabindex="1" hidden></div>');
assert.isFalse(isInTabOrder(target));
});
});
dbowling marked this conversation as resolved.
Show resolved Hide resolved