Skip to content

Commit

Permalink
feat: Add shadow DOM support to list checks (#439)
Browse files Browse the repository at this point in the history
* feat: add check testUtils

* fix: getComposedParent should not return slot nodes

* feat: Add shadow DOM support to list checks

* test: Add shadow DOM list check fails
  • Loading branch information
WilcoFiers authored Jul 16, 2017
1 parent b7008e1 commit d92c1a1
Show file tree
Hide file tree
Showing 14 changed files with 258 additions and 233 deletions.
4 changes: 2 additions & 2 deletions lib/checks/lists/dlitem.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
return node.parentNode.tagName === 'DL';

var parent = axe.commons.dom.getComposedParent(node);
return parent.nodeName.toUpperCase() === 'DL';
11 changes: 2 additions & 9 deletions lib/checks/lists/has-listitem.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,2 @@
var children = node.children;
if (children.length === 0) { return true; }

for (var i = 0; i < children.length; i++) {
if (children[i].nodeName.toUpperCase() === 'LI') { return false; }
}

return true;

return virtualNode.children.every(({ actualNode }) =>
actualNode.nodeName.toUpperCase() !== 'LI');
10 changes: 4 additions & 6 deletions lib/checks/lists/listitem.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@

if (['UL', 'OL'].indexOf(node.parentNode.nodeName.toUpperCase()) !== -1) {
return true;
}

return node.parentNode.getAttribute('role') === 'list';
var parent = axe.commons.dom.getComposedParent(node);
return (['UL', 'OL'].includes(parent.nodeName.toUpperCase()) ||
(parent.getAttribute('role') || '').toLowerCase() === 'list');

19 changes: 8 additions & 11 deletions lib/checks/lists/only-dlitems.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
var child,
nodeName,
bad = [],
children = node.childNodes,
var bad = [],
permitted = ['STYLE', 'META', 'LINK', 'MAP', 'AREA', 'SCRIPT', 'DATALIST', 'TEMPLATE'],
hasNonEmptyTextNode = false;

for (var i = 0; i < children.length; i++) {
child = children[i];
var nodeName = child.nodeName.toUpperCase();
if (child.nodeType === 1 && nodeName !== 'DT' && nodeName !== 'DD' && permitted.indexOf(nodeName) === -1) {
bad.push(child);
} else if (child.nodeType === 3 && child.nodeValue.trim() !== '') {
virtualNode.children.forEach(({ actualNode }) => {
var nodeName = actualNode.nodeName.toUpperCase();
if (actualNode.nodeType === 1 && nodeName !== 'DT' && nodeName !== 'DD' && permitted.indexOf(nodeName) === -1) {
bad.push(actualNode);
} else if (actualNode.nodeType === 3 && actualNode.nodeValue.trim() !== '') {
hasNonEmptyTextNode = true;
}
}
});

if (bad.length) {
this.relatedNodes(bad);
}
Expand Down
19 changes: 8 additions & 11 deletions lib/checks/lists/only-listitems.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
var child,
nodeName,
bad = [],
children = node.childNodes,
var bad = [],
permitted = ['STYLE', 'META', 'LINK', 'MAP', 'AREA', 'SCRIPT', 'DATALIST', 'TEMPLATE'],
hasNonEmptyTextNode = false;

for (var i = 0; i < children.length; i++) {
child = children[i];
nodeName = child.nodeName.toUpperCase();
if (child.nodeType === 1 && nodeName !== 'LI' && permitted.indexOf(nodeName) === -1) {
bad.push(child);
} else if (child.nodeType === 3 && child.nodeValue.trim() !== '') {
virtualNode.children.forEach(({ actualNode }) => {
var nodeName = actualNode.nodeName.toUpperCase();
if (actualNode.nodeType === 1 && nodeName !== 'LI' && permitted.indexOf(nodeName) === -1) {
bad.push(actualNode);
} else if (actualNode.nodeType === 3 && actualNode.nodeValue.trim() !== '') {
hasNonEmptyTextNode = true;
}
}
});

if (bad.length) {
this.relatedNodes(bad);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/checks/lists/structured-dlitems.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
var children = node.children;
var children = virtualNode.children;
if ( !children || !children.length) { return false; }

var hasDt = false, hasDd = false, nodeName;
for (var i = 0; i < children.length; i++) {
nodeName = children[i].nodeName.toUpperCase();
nodeName = children[i].actualNode.nodeName.toUpperCase();
if (nodeName === 'DT') { hasDt = true; }
if (hasDt && nodeName === 'DD') { return false; }
if (nodeName === 'DD') { hasDd = true; }
Expand Down
5 changes: 4 additions & 1 deletion lib/commons/dom/get-composed-parent.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
*/
dom.getComposedParent = function getComposedParent (element) {
if (element.assignedSlot) {
return element.assignedSlot; // content of a shadow DOM slot
// NOTE: If the display of a slot element isn't 'contents',
// the slot shouldn't be ignored. Chrome does not support this (yet) so,
// we'll skip this part for now.
return getComposedParent(element.assignedSlot); // content of a shadow DOM slot
} else if (element.parentNode) {
var parentNode = element.parentNode;
if (parentNode.nodeType === 1) {
Expand Down
30 changes: 23 additions & 7 deletions test/checks/lists/dlitem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,42 @@ describe('dlitem', function () {
'use strict';

var fixture = document.getElementById('fixture');
var checkSetup = axe.testUtils.checkSetup;
var shadowSupport = axe.testUtils.shadowSupport;

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

it('should pass if the dlitem has a parent <dl>', function () {
fixture.innerHTML = '<dl><dt id="target">My list item</dl>';
var node = fixture.querySelector('#target');
var checkArgs = checkSetup('<dl><dt id="target">My list item</dl>');

assert.isTrue(checks.dlitem.evaluate(node));
assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs));
});

it('should fail if the dlitem has an incorrect parent', function () {
var checkArgs = checkSetup('<video><dt id="target">My list item</video>');

assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs));
});

it('should fail if the dlitem has an incorrect parent', function () {
fixture.innerHTML = '<video><dt id="target">My list item</video>';
var node = fixture.querySelector('#target');
(shadowSupport ? it : xit)('should return true in a shadow DOM pass', function () {
var node = document.createElement('div');
node.innerHTML = '<dt>My list item </dt>';
var shadow = node.attachShadow({ mode: 'open' });
shadow.innerHTML = '<dl><slot></slot></dl>';

assert.isFalse(checks.dlitem.evaluate(node));
var checkArgs = checkSetup(node, 'dt');
assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs));
});

(shadowSupport ? it : xit)('should return false in a shadow DOM fail', function () {
var node = document.createElement('div');
node.innerHTML = '<dt>My list item </dt>';
var shadow = node.attachShadow({ mode: 'open' });
shadow.innerHTML = '<div><slot></slot></div>';

var checkArgs = checkSetup(node, 'dt');
assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs));
});
});
44 changes: 27 additions & 17 deletions test/checks/lists/has-listitem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,55 @@ describe('has-listitem', function () {
'use strict';

var fixture = document.getElementById('fixture');
var checkSetup = axe.testUtils.checkSetup;
var shadowSupport = axe.testUtils.shadowSupport;

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

it('should return true if the list has no contents', function () {
fixture.innerHTML = '<ol id="target"></ol>';
var node = fixture.querySelector('#target');

assert.isTrue(checks['has-listitem'].evaluate(node));

var checkArgs = checkSetup('<ol id="target"></ol>');

assert.isTrue(checks['has-listitem'].evaluate.apply(null, checkArgs));
});

it('should return true if the list has non-li contents with li children', function () {
fixture.innerHTML = '<ol id="target"><p>Not a list <ul><li>item</li></ul></p></ol>';
var node = fixture.querySelector('#target');

assert.isTrue(checks['has-listitem'].evaluate(node));

var checkArgs = checkSetup('<ol id="target"><p>Not a list <ul><li>item</li></ul></p></ol>');

assert.isTrue(checks['has-listitem'].evaluate.apply(null, checkArgs));
});

it('should return true if the list has non-li contents', function () {
fixture.innerHTML = '<ol id="target"><p>Not a list</p></ol>';
var node = fixture.querySelector('#target');
var checkArgs = checkSetup('<ol id="target"><p>Not a list</p></ol>');

assert.isTrue(checks['has-listitem'].evaluate(node));
assert.isTrue(checks['has-listitem'].evaluate.apply(null, checkArgs));
});

it('should return false if the list has at least one li', function () {
var checkArgs = checkSetup('<ol id="target"><li>A list</li><p>Not a list</p></ol>');

assert.isFalse(checks['has-listitem'].evaluate.apply(null, checkArgs));
});

it('should return false if the list has at least one li', function () {
fixture.innerHTML = '<ol id="target"><li>A list</li><p>Not a list</p></ol>';
var node = fixture.querySelector('#target');
(shadowSupport ? it : xit)('should return true in a shadow DOM pass', function () {
var node = document.createElement('div');
node.innerHTML = '<li>My list item </li>';
var shadow = node.attachShadow({ mode: 'open' });
shadow.innerHTML = '<ul><slot></slot></ul>';

assert.isFalse(checks['has-listitem'].evaluate(node));
var checkArgs = checkSetup(node, 'ul');
assert.isFalse(checks['has-listitem'].evaluate.apply(null, checkArgs));
});

(shadowSupport ? it : xit)('should return false in a shadow DOM fail', function () {
var node = document.createElement('div');
node.innerHTML = '<p>Not a list</p>';
var shadow = node.attachShadow({ mode: 'open' });
shadow.innerHTML = '<ul><slot></slot></ul>';

var checkArgs = checkSetup(node, 'ul');
assert.isTrue(checks['has-listitem'].evaluate.apply(null, checkArgs));
});

});
44 changes: 29 additions & 15 deletions test/checks/lists/listitem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,54 @@ describe('listitem', function () {
'use strict';

var fixture = document.getElementById('fixture');
var checkSetup = axe.testUtils.checkSetup;
var shadowSupport = axe.testUtils.shadowSupport;

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

it('should pass if the listitem has a parent <ol>', function () {
fixture.innerHTML = '<ol><li id="target">My list item</li></ol>';
var node = fixture.querySelector('#target');

assert.isTrue(checks.listitem.evaluate(node));
var checkArgs = checkSetup('<ol><li id="target">My list item</li></ol>');

assert.isTrue(checks.listitem.evaluate.apply(null, checkArgs));
});

it('should pass if the listitem has a parent <ul>', function () {
fixture.innerHTML = '<ul><li id="target">My list item</li></ul>';
var node = fixture.querySelector('#target');

assert.isTrue(checks.listitem.evaluate(node));
var checkArgs = checkSetup('<ul><li id="target">My list item</li></ul>');

assert.isTrue(checks.listitem.evaluate.apply(null, checkArgs));
});

it('should pass if the listitem has a parent role=list', function () {
fixture.innerHTML = '<div role="list"><li id="target">My list item</li></div>';
var node = fixture.querySelector('#target');

assert.isTrue(checks.listitem.evaluate(node));
var checkArgs = checkSetup('<div role="list"><li id="target">My list item</li></div>');

assert.isTrue(checks.listitem.evaluate.apply(null, checkArgs));
});

it('should fail if the listitem has an incorrect parent', function () {
fixture.innerHTML = '<div><li id="target">My list item</li></div>';
var node = fixture.querySelector('#target');
var checkArgs = checkSetup('<div><li id="target">My list item</li></div>');

assert.isFalse(checks.listitem.evaluate.apply(null, checkArgs));
});

(shadowSupport ? it : xit)('should return true in a shadow DOM pass', function () {
var node = document.createElement('div');
node.innerHTML = '<li>My list item </li>';
var shadow = node.attachShadow({ mode: 'open' });
shadow.innerHTML = '<ul><slot></slot></ul>';

var checkArgs = checkSetup(node, 'li');
assert.isTrue(checks.listitem.evaluate.apply(null, checkArgs));
});

assert.isFalse(checks.listitem.evaluate(node));
(shadowSupport ? it : xit)('should return false in a shadow DOM fail', function () {
var node = document.createElement('div');
node.innerHTML = '<li>My list item </li>';
var shadow = node.attachShadow({ mode: 'open' });
shadow.innerHTML = '<div><slot></slot></div>';

var checkArgs = checkSetup(node, 'li');
assert.isFalse(checks.listitem.evaluate.apply(null, checkArgs));
});
});
Loading

0 comments on commit d92c1a1

Please sign in to comment.