diff --git a/lib/commons/dom/is-in-text-block.js b/lib/commons/dom/is-in-text-block.js index 97c53d0fbd..a6f867b508 100644 --- a/lib/commons/dom/is-in-text-block.js +++ b/lib/commons/dom/is-in-text-block.js @@ -1,48 +1,42 @@ /* global axe, dom, window */ - function walkDomNode(node, functor) { - 'use strict'; - var shouldWalk = functor(node); - node = node.firstChild; - while (node) { - if (shouldWalk !== false) { - walkDomNode(node, functor); - } - node = node.nextSibling; - } + if (functor(node.actualNode) !== false) { + node.children.forEach(child => walkDomNode(child, functor)); + } } var blockLike = ['block', 'list-item', 'table', 'flex', 'grid', 'inline-block']; function isBlock(elm) { - 'use strict'; var display = window.getComputedStyle(elm).getPropertyValue('display'); - return (blockLike.indexOf(display) !== -1 || - display.substr(0, 6) === 'table-'); + return (blockLike.includes(display) || display.substr(0, 6) === 'table-'); +} + +function getBlockParent (node) { + // Find the closest parent + let parentBlock = dom.getComposedParent(node); + while (parentBlock && !isBlock(parentBlock)) { + parentBlock = dom.getComposedParent(parentBlock); + } + return axe.utils.getNodeFromTree(axe._tree[0], parentBlock); } dom.isInTextBlock = function isInTextBlock(node) { // jshint maxcomplexity: 15 - 'use strict'; - // Ignore if the link is a block if (isBlock(node)) { + // Ignore if the link is a block return false; } - // Find the closest parent - var parentBlock = node.parentNode; - while (parentBlock.nodeType === 1 && !isBlock(parentBlock)) { - parentBlock = parentBlock.parentNode; - } - // Find all the text part of the parent block not in a link, and all the text in a link - var parentText = ''; - var linkText = ''; - var inBrBlock = 0; + const virtualParent = getBlockParent(node); + let parentText = ''; + let linkText = ''; + let inBrBlock = 0; // We want to ignore hidden text, and if br / hr is used, only use the section of the parent // that has the link we're looking at - walkDomNode(parentBlock, function (currNode) { + walkDomNode(virtualParent, function (currNode) { // We're already passed it, skip everything else if (inBrBlock === 2) { return false; @@ -59,7 +53,7 @@ dom.isInTextBlock = function isInTextBlock(node) { var nodeName = (currNode.nodeName || '').toUpperCase(); // BR and HR elements break the line - if (['BR', 'HR'].indexOf(nodeName) !== -1) { + if (['BR', 'HR'].includes(nodeName)) { if (inBrBlock === 0) { parentText = ''; linkText = ''; @@ -70,8 +64,8 @@ dom.isInTextBlock = function isInTextBlock(node) { // Don't walk nodes with content not displayed on screen. } else if (currNode.style.display === 'none' || currNode.style.overflow === 'hidden' || - ['', null, 'none'].indexOf(currNode.style.float) === -1 || - ['', null, 'relative'].indexOf(currNode.style.position) === -1) { + !['', null, 'none'].includes(currNode.style.float) || + !['', null, 'relative'].includes(currNode.style.position)) { return false; // Don't walk links, we're only interested in what's not in them. diff --git a/test/commons/dom/is-in-text-block.js b/test/commons/dom/is-in-text-block.js index 6c779b2e9d..f89519bb19 100644 --- a/test/commons/dom/is-in-text-block.js +++ b/test/commons/dom/is-in-text-block.js @@ -2,143 +2,179 @@ describe('dom.isInTextBlock', function () { 'use strict'; var fixture = document.getElementById('fixture'); + var shadowSupport = axe.testUtils.shadowSupport; + var fixtureSetup = axe.testUtils.fixtureSetup; afterEach(function () { fixture.innerHTML = ''; }); it('returns true if the element is a node in a block of text', function () { - fixture.innerHTML = + fixtureSetup( '

Some paragraph with text ' + ' link' + - '

'; + '

'); var link = document.getElementById('link'); assert.isTrue(axe.commons.dom.isInTextBlock(link)); }); it('returns false if the element is a block', function () { - fixture.innerHTML = + fixtureSetup( '

Some paragraph with text ' + ' link' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('returns false if the element has the only text in the block', function () { - fixture.innerHTML = + fixtureSetup( '

' + ' link'+ - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('returns false if there is more text in link(s) than in the rest of the block', function () { - fixture.innerHTML = + fixtureSetup( '

short text:' + ' on a link with a very long text' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('return false if there are links along side other links', function () { - fixture.innerHTML = + fixtureSetup( '

' + ' link' + ' other link' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('ignores hidden content', function () { - fixture.innerHTML = + fixtureSetup( '

' + ' link' + ' some hidden text' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('ignores floated content', function () { - fixture.innerHTML = + fixtureSetup( '

' + ' A floating text in the area' + ' link' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('ignores positioned content', function () { - fixture.innerHTML = + fixtureSetup( '

' + ' Some absolute potitioned text' + ' link' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('ignores none-text content', function () { - fixture.innerHTML = + fixtureSetup( '

' + ' Some graphical component' + ' link' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('ignore text in the block coming before a br', function () { - fixture.innerHTML = + fixtureSetup( '

Some paragraph with text
' + ' link' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('ignore text in the block coming after a br', function () { - fixture.innerHTML = + fixtureSetup( '

' + ' link
' + ' Some paragraph with text ' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('ignore text in the block coming before and after a br', function () { - fixture.innerHTML = + fixtureSetup( '

Some paragraph with text
' + ' link
' + ' Some paragraph with text ' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('treats hr elements the same as br elements', function () { - fixture.innerHTML = - '

Some paragraph with text


' + + fixtureSetup( + '
Some paragraph with text
' + ' link
' + ' Some paragraph with text ' + - '

'; + '
'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); it('ignore comments', function () { - fixture.innerHTML = + fixtureSetup( '

' + ' link' + - '

'; + '

'); var link = document.getElementById('link'); assert.isFalse(axe.commons.dom.isInTextBlock(link)); }); + (shadowSupport ? it : xit)('can reach outside a shadow tree', function () { + var div = document.createElement('div'); + div.innerHTML = 'Some paragraph with text '; + var shadow = div.querySelector('span').attachShadow({ mode: 'open' }); + shadow.innerHTML = 'link'; + fixtureSetup(div); + + var link = shadow.querySelector('#link'); + assert.isTrue(axe.commons.dom.isInTextBlock(link)); + }); + + (shadowSupport.v1 ? it : xit)('can reach into a shadow tree', function () { + var div = document.createElement('div'); + div.innerHTML = 'link'; + var shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

Some paragraph with text

'; + fixtureSetup(div); + + var link = fixture.querySelector('#link'); + assert.isTrue(axe.commons.dom.isInTextBlock(link)); + }); + + (shadowSupport.v1 ? it : xit)('treats shadow DOM slots as siblings', function () { + var div = document.createElement('div'); + div.innerHTML = '
'; + var shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

Some paragraph with text ' + + ' link

'; + fixtureSetup(div); + + var link = shadow.querySelector('#link'); + assert.isFalse(axe.commons.dom.isInTextBlock(link)); + }); + }); \ No newline at end of file