diff --git a/lib/checks/keyboard/no-focusable-content-evaluate.js b/lib/checks/keyboard/no-focusable-content-evaluate.js index 907e32ee29..27824fc4f4 100644 --- a/lib/checks/keyboard/no-focusable-content-evaluate.js +++ b/lib/checks/keyboard/no-focusable-content-evaluate.js @@ -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) { @@ -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)); @@ -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; diff --git a/lib/core/utils/check-helper.js b/lib/core/utils/check-helper.js index 54bd539eb1..68e39f6a4b 100644 --- a/lib/core/utils/check-helper.js +++ b/lib/core/utils/check-helper.js @@ -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); }); diff --git a/test/checks/keyboard/no-focusable-content.js b/test/checks/keyboard/no-focusable-content.js index 8c84f7da81..1389e0241a 100644 --- a/test/checks/keyboard/no-focusable-content.js +++ b/test/checks/keyboard/no-focusable-content.js @@ -29,19 +29,38 @@ describe('no-focusable-content tests', function() { }); it('should return false if element has focusable content', function() { - var vNode = queryFixture( + var params = checkSetup( '' ); - 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( '' ); - 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( + '' + ); + + 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( '' @@ -49,6 +68,6 @@ describe('no-focusable-content tests', function() { 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]]); }); - }); diff --git a/test/core/utils/check-helper.js b/test/core/utils/check-helper.js index a198ed23bb..a5c37b83b2 100644 --- a/test/core/utils/check-helper.js +++ b/test/core/utils/check-helper.js @@ -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); + }); }); }); });