Skip to content

Commit

Permalink
fix: complete shadow support for color matches
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcy Sutton committed Jul 13, 2017
1 parent 6a5f3f1 commit f0fe551
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 43 deletions.
12 changes: 6 additions & 6 deletions lib/rules/color-contrast-matches.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

var nodeName = node.nodeName.toUpperCase(),
nodeType = node.type,
doc = document;
doc = axe.commons.dom.getRootNode(node);

if (node.getAttribute('aria-disabled') === 'true' || axe.commons.dom.findUp(node, '[aria-disabled="true"]')) {
return false;
Expand Down Expand Up @@ -45,9 +45,9 @@ if (nodeName === 'LABEL' || nodeParentLabel) {
return false;
}

var candidate = node.querySelector('input:not([type="hidden"]):not([type="image"])' +
var candidate = axe.utils.querySelectorAll(virtualNode, 'input:not([type="hidden"]):not([type="image"])' +
':not([type="button"]):not([type="submit"]):not([type="reset"]), select, textarea');
if (candidate && candidate.disabled) {
if (candidate.length && candidate[0].actualNode.disabled) {
return false;
}

Expand All @@ -66,15 +66,15 @@ if (axe.commons.text.visible(virtualNode, false, true) === '') {
}

var range = document.createRange(),
childNodes = node.childNodes,
childNodes = virtualNode.children,
length = childNodes.length,
child, index;

for (index = 0; index < length; index++) {
child = childNodes[index];

if (child.nodeType === 3 && axe.commons.text.sanitize(child.nodeValue) !== '') {
range.selectNodeContents(child);
if (child.actualNode.nodeType === 3 && axe.commons.text.sanitize(child.actualNode.nodeValue) !== '') {
range.selectNodeContents(child.actualNode);
}
}

Expand Down
163 changes: 126 additions & 37 deletions test/rule-matches/color-contrast-matches.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* global xit */
describe('color-contrast-matches', function () {
'use strict';

Expand Down Expand Up @@ -72,86 +71,99 @@ describe('color-contrast-matches', function () {
it('should match <input type="text">', function () {
fixture.innerHTML = '<input type="text">';
var target = fixture.querySelector('input');
assert.isTrue(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isTrue(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match <input type="hidden">', function () {
fixture.innerHTML = '<input type="hidden">';
var target = fixture.querySelector('input');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match <input type="checkbox">', function () {
fixture.innerHTML = '<input type="checkbox">';
var target = fixture.querySelector('input');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match <input type="radio">', function () {
fixture.innerHTML = '<input type="radio">';
var target = fixture.querySelector('input');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match <input type="color">', function () {
fixture.innerHTML = '<input type="color">';
var target = fixture.querySelector('input');
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
// Some browsers will fallback to type=text for unknown input types (looking at you IE)
if (target.type === 'color') {
assert.isFalse(rule.matches(target));
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
}
});

it('should not match <input type="range">', function () {
fixture.innerHTML = '<input type="range">';
var target = fixture.querySelector('input');
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
// Some browsers will fallback to type=text for unknown input types (looking at you IE)
if (target.type === 'range') {
assert.isFalse(rule.matches(target));
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
}
});

it('should match <select> with options', function () {
fixture.innerHTML = '<select><option>Hello</option></select>';
var target = fixture.querySelector('select');
assert.isTrue(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isTrue(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match <select> with no options', function () {
fixture.innerHTML = '<select></select>';
var target = fixture.querySelector('select');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should match <textarea>', function () {
fixture.innerHTML = '<textarea></textarea>';
var target = fixture.querySelector('textarea');
assert.isTrue(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isTrue(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match <option>', function () {
fixture.innerHTML = '<select><option>hi</option></select>';
var target = fixture.querySelector('option');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match inputs that are disabled', function () {
fixture.innerHTML = '<input type="text" disabled>';
var target = fixture.querySelector('input');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));

});

it('should not match <textarea disabled>', function () {
fixture.innerHTML = '<textarea disabled></textarea>';
var target = fixture.querySelector('textarea');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match <select> with options', function () {
fixture.innerHTML = '<select disabled><option>Hello</option></select>';
var target = fixture.querySelector('select');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should match <button>', function () {
Expand All @@ -164,97 +176,174 @@ describe('color-contrast-matches', function () {
it('should not match <button disabled>', function () {
fixture.innerHTML = '<button disabled>hi</button>';
var target = fixture.querySelector('button');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match <button disabled><span></span></button>', function () {
fixture.innerHTML = '<button disabled><span>Hi</span></button>';
var target = fixture.querySelector('button');
assert.isFalse(rule.matches(target.querySelector('span')));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target.querySelector('span'), axe.utils.getNodeFromTree(tree[0], target.querySelector('span'))));
});

it('should not match <button disabled><span><i></i></span></button>', function () {
fixture.innerHTML = '<button disabled><span><i>Hi</i></span></button>';
var target = fixture.querySelector('button');
assert.isFalse(rule.matches(target.querySelector('i')));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target.querySelector('i'), axe.utils.getNodeFromTree(tree[0], target.querySelector('i'))));
});

it('should not match <input type=image>', function () {
fixture.innerHTML = '<input type="image">';
var target = fixture.querySelector('input');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match a disabled input\'s label - explicit label', function () {
fixture.innerHTML = '<label for="t1">Test</label><input type="text" id="t1" disabled>';
var target = fixture.querySelector('label');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match a disabled input\'s label - implicit label (input)', function () {
fixture.innerHTML = '<label>Test<input type="text" disabled></label>';
var target = fixture.querySelector('label');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match a disabled input\'s label - implicit label (textarea)', function () {
fixture.innerHTML = '<label>Test<textarea disabled>Hi</textarea></label>';
var target = fixture.querySelector('label');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match a disabled input\'s label - implicit label (select)', function () {
fixture.innerHTML = '<label>Test<select disabled><option>Test</option></select></label>';
var target = fixture.querySelector('label');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match a disabled input\'s label - aria-labelledby', function () {
fixture.innerHTML = '<div id="t1">Test</div><input type="text" aria-labelledby="bob t1 fred" disabled>';
var target = fixture.querySelector('div');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match aria-disabled=true', function () {
fixture.innerHTML = '<div aria-disabled="true">hi</div>';
var target = fixture.querySelector('div');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match a descendant of aria-disabled=true', function () {
fixture.innerHTML = '<div aria-disabled="true"><span>hi</span></div>';
var target = fixture.querySelector('span');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match a descendant of a disabled fieldset', function () {
fixture.innerHTML = '<fieldset disabled><label>hi <input type="checkbox"></label></fieldset>';
var target = fixture.querySelector('label');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match a descendant of an explicit label for a disabled input', function () {
fixture.innerHTML = '<input id="input" type="checkbox" disabled><label for="input"><span>hi</span></label>';
var target = fixture.querySelector('span');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

it('should not match a descendant of an implicit label for a disabled input', function () {
fixture.innerHTML = '<label for="input"><span>hi</span><input id="input" type="checkbox" disabled></label>';
var target = fixture.querySelector('span');
assert.isFalse(rule.matches(target));
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(tree[0], target)));
});

(shadowSupport ? it : xit)
('should match a descendant of an element across a shadow boundary', function () {
fixture.innerHTML = '<div id="parent" style="background-color: #000;">' +
if (shadowSupport) {
it('should match a descendant of an element across a shadow boundary', function () {
fixture.innerHTML = '<div id="parent" style="background-color: #000;">' +
'</div>';

var shadowRoot = document.getElementById('parent').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="shadowTarget" style="color: #333">Text</div>';

var shadowTarget = fixture.firstChild.shadowRoot.querySelector('#shadowTarget');
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isTrue(rule.matches(shadowTarget, axe.utils.getNodeFromTree(tree[0], shadowTarget)));
});

it('should look at the correct root node when looking up an explicit label and disabled input', function () {
fixture.innerHTML = '<div id="parent">'+
'<input id="input">' +
'</div>';

var shadowRoot = document.getElementById('parent').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="shadowTarget" style="color: #333">Text</div>';
var shadowRoot = document.getElementById('parent').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="shadowParent">' +
'<label for="input" id="shadowLabel">Label</label>' +
'<input id="input" disabled>' +
'</div>';

var shadowTarget = fixture.firstChild.shadowRoot.querySelector('#shadowTarget');
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isTrue(rule.matches(shadowTarget, axe.utils.getNodeFromTree(tree[0], shadowTarget)));
});
var shadowLabel = fixture.firstChild.shadowRoot.querySelector('#shadowLabel');
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(shadowLabel, axe.utils.getNodeFromTree(tree[0], shadowLabel)));
});

it('should look at the correct root node when looking up implicit label and disabled input', function () {
fixture.innerHTML = '<div id="parent">'+
'<input id="input">' +
'</div>';

var shadowRoot = document.getElementById('parent').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="shadowParent">' +
'<label for="input" id="shadowLabel">Label' +
'<input id="input" disabled></label>' +
'</div>';

var shadowLabel = fixture.firstChild.shadowRoot.querySelector('#shadowLabel');
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(shadowLabel, axe.utils.getNodeFromTree(tree[0], shadowLabel)));
});

it('should look at the correct root node for a disabled control\'s label associated w/ aria-labelledby', function () {
fixture.innerHTML = '<div id="parent">'+
'<input id="input">' +
'</div>';

var shadowRoot = document.getElementById('parent').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div id="shadowParent">' +
'<label id="shadowLabel">Label</label>' +
'<input aria-labelledby="shadowLabel" disabled>' +
'</div>';

var shadowLabel = fixture.firstChild.shadowRoot.querySelector('#shadowLabel');
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isFalse(rule.matches(shadowLabel, axe.utils.getNodeFromTree(tree[0], shadowLabel)));
});

it('should look at the children of a virtual node for overlap', function () {
fixture.innerHTML = '<div id="parent">'+
'<div id="firstChild" style="background-color: #ccc; color: #fff;"></div>' +
'</div>';

var shadowRoot = document.getElementById('firstChild').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = 'Some text' +
'<p style="color: #fff;" id="shadowTarget">Other text</p>';

var firstChild = fixture.querySelector('#firstChild');
var tree = axe._tree = axe.utils.getFlattenedTree(fixture);
assert.isTrue(rule.matches(firstChild, axe.utils.getNodeFromTree(tree[0], firstChild)));
});
}
});

0 comments on commit f0fe551

Please sign in to comment.