Skip to content

Commit

Permalink
fix(is-in-text-block): Add Shadow DOM support
Browse files Browse the repository at this point in the history
  • Loading branch information
WilcoFiers committed Jul 19, 2017
1 parent 46a2cca commit a125f79
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 57 deletions.
50 changes: 22 additions & 28 deletions 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;
Expand All @@ -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 = '';
Expand All @@ -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.
Expand Down
94 changes: 65 additions & 29 deletions test/commons/dom/is-in-text-block.js
Expand Up @@ -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(
'<p>Some paragraph with text ' +
' <a href="" id="link">link</a>' +
'</p>';
'</p>');
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(
'<p>Some paragraph with text ' +
' <a href="" id="link" style="display:block">link</a>' +
'</p>';
'</p>');
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(
'<p>' +
' <a href="" id="link">link</a>'+
'</p>';
'</p>');
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(
'<p> short text:' +
' <a href="" id="link">on a link with a very long text</a>' +
'</p>';
'</p>');
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(
'<p>' +
' <a href="" id="link">link</a>' +
' <a href="">other link</a>' +
'</p>';
'</p>');
var link = document.getElementById('link');
assert.isFalse(axe.commons.dom.isInTextBlock(link));
});

it('ignores hidden content', function () {
fixture.innerHTML =
fixtureSetup(
'<p>' +
' <a href="" id="link">link</a>' +
' <span style="display:none">some hidden text</span>' +
'</p>';
'</p>');
var link = document.getElementById('link');
assert.isFalse(axe.commons.dom.isInTextBlock(link));
});

it('ignores floated content', function () {
fixture.innerHTML =
fixtureSetup(
'<p>' +
' <span style="float: left">A floating text in the area</span>' +
' <a href="" id="link">link</a>' +
'</p>';
'</p>');
var link = document.getElementById('link');
assert.isFalse(axe.commons.dom.isInTextBlock(link));
});

it('ignores positioned content', function () {
fixture.innerHTML =
fixtureSetup(
'<p>' +
' <span style="position:absolute;">Some absolute potitioned text</span>' +
' <a href="" id="link">link</a>' +
'</p>';
'</p>');
var link = document.getElementById('link');
assert.isFalse(axe.commons.dom.isInTextBlock(link));
});

it('ignores none-text content', function () {
fixture.innerHTML =
fixtureSetup(
'<p>' +
' <img alt="Some graphical component" src="img.png" />' +
' <a href="" id="link">link</a>' +
'</p>';
'</p>');
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(
'<p>Some paragraph with text <br>' +
' <a href="" id="link">link</a>' +
'</p>';
'</p>');
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(
'<p>' +
' <a href="" id="link">link</a> <br>' +
' Some paragraph with text ' +
'</p>';
'</p>');
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(
'<p>Some paragraph with text <br>' +
' <a href="" id="link">link</a> <br>' +
' Some paragraph with text ' +
'</p>';
'</p>');
var link = document.getElementById('link');
assert.isFalse(axe.commons.dom.isInTextBlock(link));
});

it('treats hr elements the same as br elements', function () {
fixture.innerHTML =
'<p>Some paragraph with text <hr>' +
fixtureSetup(
'<div>Some paragraph with text <hr>' +
' <a href="" id="link">link</a> <hr>' +
' Some paragraph with text ' +
'</p>';
'</div>');
var link = document.getElementById('link');
assert.isFalse(axe.commons.dom.isInTextBlock(link));
});

it('ignore comments', function () {
fixture.innerHTML =
fixtureSetup(
'<p><!-- Some paragraph with text -->' +
' <a href="" id="link">link</a>' +
'</p>';
'</p>');
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 <span></span> ';
var shadow = div.querySelector('span').attachShadow({ mode: 'open' });
shadow.innerHTML = '<a href="" id="link">link</a>';
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 = '<a href="" id="link">link</a>';
var shadow = div.attachShadow({ mode: 'open' });
shadow.innerHTML = '<p>Some paragraph with text <slot></slot> </p>';
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 = '<br>';
var shadow = div.attachShadow({ mode: 'open' });
shadow.innerHTML = '<p>Some paragraph with text ' +
'<slot></slot> <a href="" id="link">link</a></p>';
fixtureSetup(div);

var link = shadow.querySelector('#link');
assert.isFalse(axe.commons.dom.isInTextBlock(link));
});

});

0 comments on commit a125f79

Please sign in to comment.