Skip to content

Commit

Permalink
fix(color-contrast): account for text client rects that start outside…
Browse files Browse the repository at this point in the history
… the parent container (#2682)

* fix(color-contrast): account for text client rects that start otuside the parent container

* fix?

* Update lib/commons/dom/get-text-element-stack.js

Co-authored-by: Wilco Fiers <[email protected]>

Co-authored-by: Wilco Fiers <[email protected]>
  • Loading branch information
straker and WilcoFiers authored Dec 18, 2020
1 parent 13a7cf1 commit a4e4a34
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 63 deletions.
27 changes: 21 additions & 6 deletions lib/commons/dom/get-text-element-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,29 @@ function getTextElementStack(node) {
range.selectNodeContents(elm);
const rects = range.getClientRects();

// if the elements bounding rect is smaller than the text rects
// (for example, it's using text truncation or overflow) we
// need to get the rect of the element rather than look at the
// text node rects as they will return the full width of the
// text node (which can go off the screen)
// if any text rect is larger than the bounds of the parent,
// or goes outside of the bounds of the parent, we need to use
// the parent rect so we stay within the bounds of the element.
//
// since we use the midpoint of the element when determining
// the rect stack we will also use the midpoint of the text rect
// to determine out of bounds.
//
// @see https://github.com/dequelabs/axe-core/issues/2178
// @see https://github.com/dequelabs/axe-core/issues/2483
if (Array.from(rects).some(rect => rect.width > nodeRect.width)) {
// @see https://github.com/dequelabs/axe-core/issues/2681
const outsideRectBounds = Array.from(rects).some(rect => {
const horizontalMidpoint = rect.left + rect.width / 2;
const verticalMidpoint = rect.top + rect.height / 2;

return (
horizontalMidpoint < nodeRect.left ||
horizontalMidpoint > nodeRect.right ||
verticalMidpoint < nodeRect.top ||
verticalMidpoint > nodeRect.bottom
);
});
if (outsideRectBounds) {
return;
}

Expand Down
57 changes: 0 additions & 57 deletions test/commons/dom/get-element-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ describe('dom.getElementStack', function() {

var fixture = document.getElementById('fixture');
var getElementStack = axe.commons.dom.getElementStack;
var getTextElementStack = axe.commons.dom.getTextElementStack;
var isIE11 = axe.testUtils.isIE11;
var shadowSupported = axe.testUtils.shadowSupport.v1;

Expand Down Expand Up @@ -534,60 +533,4 @@ describe('dom.getElementStack', function() {
assert.deepEqual(stack, ['target', '3', '2', '1', 'fixture']);
});
});

describe('dom.getTextElementStack', function() {
it('should return array of client text rects', function() {
fixture.innerHTML =
'<main id="1">' +
'<div id="target">' +
'<span id="2">Hello</span><br/>World' +
'</div>' +
'</main>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var stacks = getTextElementStack(target).map(mapToIDs);
assert.deepEqual(stacks, [['target', '1', 'fixture']]);
});

it('should ignore newline characters', function() {
fixture.innerHTML =
'<main id="1">' +
'<div id="target">' +
'<span id="2">Hello</span><br/>\n' +
'World' +
'</div>' +
'</main>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var stacks = getTextElementStack(target).map(mapToIDs);
assert.deepEqual(stacks, [['target', '1', 'fixture']]);
});

it('should handle truncated text', function() {
fixture.innerHTML =
'<main id="1">' +
'<div id="target" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100px;">' +
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fusce mi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla, sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero. Donec dapibus, diam sit amet posuere commodo, magna orci hendrerit ipsum, eu egestas mauris nulla ut ipsum. Sed luctus, orci in fringilla finibus, odio leo porta dolor, eu dignissim risus eros eget erat.' +
'World' +
'</div>' +
'</main>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var stacks = getTextElementStack(target).map(mapToIDs);
assert.deepEqual(stacks, [['target', '1', 'fixture']]);
});

it('should handle text that is too large for the container', function() {
fixture.innerHTML =
'<pre id="1" style="width: 400px; overflow: auto;">' +
'<span id="target" style="display: flex; with: 400px;">\n\n' +
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fusce mi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla, sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero. Donec dapibus, diam sit amet posuere commodo, magna orci hendrerit ipsum, eu egestas mauris nulla ut ipsum. Sed luctus, orci in fringilla finibus, odio leo porta dolor, eu dignissim risus eros eget erat.' +
'</span>' +
'</pre>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var stacks = getTextElementStack(target).map(mapToIDs);
assert.deepEqual(stacks, [['target', '1', 'fixture']]);
});
});
});
85 changes: 85 additions & 0 deletions test/commons/dom/get-text-element-stack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
describe('dom.getTextElementStack', function() {
'use strict';

var fixture = document.getElementById('fixture');
var getTextElementStack = axe.commons.dom.getTextElementStack;

function mapToIDs(stack) {
return stack
.map(function(node) {
return node.id;
})
.filter(function(id) {
return !!id;
});
}

afterEach(function() {
fixture.innerHTML = '';
});

it('should return array of client text rects', function() {
fixture.innerHTML =
'<main id="1">' +
'<div id="target">' +
'<span id="2">Hello</span><br/>World' +
'</div>' +
'</main>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var stacks = getTextElementStack(target).map(mapToIDs);
assert.deepEqual(stacks, [['target', '1', 'fixture']]);
});

it('should ignore newline characters', function() {
fixture.innerHTML =
'<main id="1">' +
'<div id="target">' +
'<span id="2">Hello</span><br/>\n' +
'World' +
'</div>' +
'</main>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var stacks = getTextElementStack(target).map(mapToIDs);
assert.deepEqual(stacks, [['target', '1', 'fixture']]);
});

it('should handle truncated text', function() {
fixture.innerHTML =
'<main id="1">' +
'<div id="target" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100px;">' +
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fusce mi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla, sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero. Donec dapibus, diam sit amet posuere commodo, magna orci hendrerit ipsum, eu egestas mauris nulla ut ipsum. Sed luctus, orci in fringilla finibus, odio leo porta dolor, eu dignissim risus eros eget erat.' +
'World' +
'</div>' +
'</main>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var stacks = getTextElementStack(target).map(mapToIDs);
assert.deepEqual(stacks, [['target', '1', 'fixture']]);
});

it('should handle text that is too large for the container', function() {
fixture.innerHTML =
'<pre id="1" style="width: 400px; overflow: auto;">' +
'<span id="target" style="display: flex; width: 400px;">\n\n' +
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed et sollicitudin quam. Fusce mi odio, egestas pulvinar erat eget, vehicula tempus est. Proin vitae ullamcorper velit. Donec sagittis est justo, mattis iaculis arcu facilisis id. Proin pulvinar ornare arcu a fermentum. Quisque et dignissim nulla, sit amet consectetur ipsum. Donec in libero porttitor, dapibus neque imperdiet, aliquam est. Vivamus blandit volutpat fringilla. In mi magna, mollis sit amet imperdiet eu, rutrum ut tellus. Mauris vel condimentum nibh, quis ultricies nisi. Vivamus accumsan quam mauris, id iaculis quam fringilla ac. Curabitur pulvinar dolor ac magna vehicula, non auctor ligula dignissim. Nam ac nibh porttitor, malesuada tortor varius, feugiat turpis. Mauris dapibus, tellus ut viverra porta, ipsum turpis bibendum ligula, at tempor felis ante non libero. Donec dapibus, diam sit amet posuere commodo, magna orci hendrerit ipsum, eu egestas mauris nulla ut ipsum. Sed luctus, orci in fringilla finibus, odio leo porta dolor, eu dignissim risus eros eget erat.' +
'</span>' +
'</pre>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var stacks = getTextElementStack(target).map(mapToIDs);
assert.deepEqual(stacks, [['target', '1', 'fixture']]);
});

it('should handle text that overflows outside the parent', function() {
fixture.innerHTML =
'<div id="1" style="width: 250px; overflow: hidden">' +
'<p id="target" style="max-height: 80px; overflow: hidden; line-height: 20px; font-size: 13px;">The Chandni Chowk (Moonlight Square) is one of the oldest and busiest markets in Old Delhi, India. Chandni Chowk is located close to Old Delhi Railway Station. The Red Fort monument is located within the market. It was built in the 17th century by Mughal Emperor of India Shah Jahan and designed by his daughter Jahanara. The market was once divided by canals (now closed) to reflect moonlight and remains one of India\'s largest wholesale markets.</p>' +
'</div>';
axe.testUtils.flatTreeSetup(fixture);
var target = fixture.querySelector('#target');
var stacks = getTextElementStack(target).map(mapToIDs);
assert.deepEqual(stacks, [['target', '1', 'fixture']]);
});
});

0 comments on commit a4e4a34

Please sign in to comment.