Skip to content

Commit

Permalink
fix(nested-interactive): add focusable descendants as related nodes (#…
Browse files Browse the repository at this point in the history
…3261)

* fix(nested-interactive): add focusable descendants as related nodes

* fix tests

* fix
  • Loading branch information
straker authored Nov 12, 2021
1 parent 7ee6c1e commit 3b2fdda
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 28 deletions.
52 changes: 30 additions & 22 deletions lib/checks/keyboard/no-focusable-content-evaluate.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
import isFocusable from '../../commons/dom/is-focusable';

export default function noFocusableContentEvaluate(node, options, virtualNode) {
if (!virtualNode.children) {
return undefined;
}

try {
const focusableDescendants = getFocusableDescendants(virtualNode);

if (!focusableDescendants.length) {
return true;
}

const notHiddenElements = focusableDescendants.filter(
usesUnreliableHidingStrategy
);

if (notHiddenElements.length > 0) {
this.data({ messageKey: 'notHidden' });
this.relatedNodes(notHiddenElements);
} else {
this.relatedNodes(focusableDescendants);
}

return false;
} catch (e) {
return undefined;
}
}

function getFocusableDescendants(vNode) {
if (!vNode.children) {
if (vNode.props.nodeType === 1) {
Expand All @@ -11,7 +40,7 @@ function getFocusableDescendants(vNode) {

const retVal = [];
vNode.children.forEach(child => {
if(isFocusable(child)) {
if (isFocusable(child)) {
retVal.push(child);
} else {
retVal.push(...getFocusableDescendants(child));
Expand All @@ -24,24 +53,3 @@ function usesUnreliableHidingStrategy(vNode) {
const tabIndex = parseInt(vNode.attr('tabindex'), 10);
return !isNaN(tabIndex) && tabIndex < 0;
}

function noFocusableContentEvaluate(node, options, virtualNode) {
if (!virtualNode.children) {
return undefined;
}
try {
const focusableDescendants = getFocusableDescendants(virtualNode);
if(focusableDescendants.length > 0) {
const notHiddenElements = focusableDescendants.filter(usesUnreliableHidingStrategy);
if(notHiddenElements.length > 0) {
this.data({ messageKey: 'notHidden' });
this.relatedNodes(notHiddenElements);
}
}
return focusableDescendants.length === 0;
} catch (e) {
return undefined;
}
}

export default noFocusableContentEvaluate;
11 changes: 11 additions & 0 deletions lib/core/utils/check-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,18 @@ function checkHelper(checkResult, options, resolve, reject) {
checkResult.data = data;
},
relatedNodes(nodes) {
if (!window.Node) {
return;
}

nodes = nodes instanceof window.Node ? [nodes] : toArray(nodes);

if (
!nodes.every(node => node instanceof window.Node || node.actualNode)
) {
return;
}

checkResult.relatedNodes = nodes.map(element => {
return new DqElement(element, options);
});
Expand Down
31 changes: 25 additions & 6 deletions test/checks/keyboard/no-focusable-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,45 @@ describe('no-focusable-content tests', function() {
});

it('should return false if element has focusable content', function() {
var vNode = queryFixture(
var params = checkSetup(
'<button id="target"><span tabindex="0">Hello</span></button>'
);
assert.isFalse(noFocusableContent(null, null, vNode));

assert.isFalse(noFocusableContent.apply(checkContext, params));
assert.deepEqual(checkContext._data, null);
assert.deepEqual(checkContext._relatedNodes, [params[2].children[0]]);
});

it('should return false if element has natively focusable content', function() {
var vNode = queryFixture(
var params = checkSetup(
'<button id="target"><a href="foo.html">Hello</a></button>'
);
assert.isFalse(noFocusableContent(null, null, vNode));

assert.isFalse(noFocusableContent.apply(checkContext, params));
assert.deepEqual(checkContext._data, null);
assert.deepEqual(checkContext._relatedNodes, [params[2].children[0]]);
});


it('should add each focusable child as related nodes', function() {
var params = checkSetup(
'<button id="target"><span tabindex="0">Hello</span><a href="foo.html">Hello</a></button>'
);

assert.isFalse(noFocusableContent.apply(checkContext, params));
assert.deepEqual(checkContext._data, null);
assert.deepEqual(checkContext._relatedNodes, [
params[2].children[0],
params[2].children[1]
]);
});

it('should return false if element has natively focusable content with negative tabindex', function() {
var params = checkSetup(
'<button id="target"><a href="foo.html" tabindex="-1">Hello</a></button>'
);
axe.utils.getFlattenedTree(document.documentElement);
assert.isFalse(check.evaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, { messageKey: 'notHidden' });
assert.deepEqual(checkContext._relatedNodes, [params[2].children[0]]);
});

});
11 changes: 11 additions & 0 deletions test/core/utils/check-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ describe('axe.utils.checkHelper', function() {
assert.equal(target.relatedNodes[0].element, fixture.children[0]);
assert.equal(target.relatedNodes[1].element, fixture.children[1]);
});
it('should noop for non-node-like objects', function() {
var target = {},
helper = axe.utils.checkHelper(target, noop);
var nodes = new axe.SerialVirtualNode({
nodeName: 'div'
});
assert.doesNotThrow(function() {
helper.relatedNodes(nodes);
});
assert.lengthOf(target.relatedNodes, 0);
});
});
});
});

0 comments on commit 3b2fdda

Please sign in to comment.