From 6f5327918ca40a4eebfd45533d51129ae72f8d0f Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Fri, 14 Jul 2017 13:52:33 +0200 Subject: [PATCH 1/4] feat: add check testUtils --- test/.jshintrc | 1 + test/testutils.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/test/.jshintrc b/test/.jshintrc index dce6c75bef..d8372a2fbc 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -3,6 +3,7 @@ "globals": { "describe": true, "it": true, + "xit": true, "before": true, "beforeEach": true, "after": true, diff --git a/test/testutils.js b/test/testutils.js index d7cab92b85..07508a5b33 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -16,4 +16,35 @@ testUtils.shadowSupport = (function(document) { })(document); +testUtils.fixtureSetup = function (content) { + 'use strict'; + var fixture = document.querySelector('#fixture'); + if (typeof content === 'string') { + fixture.innerHTML = content; + } else if (content instanceof Node) { + fixture.appendChild(content); + } + axe._tree = axe.utils.getFlattenedTree(fixture); + return fixture; +}; + +/** + * Create check arguments + * + * @param Node|String Stuff to go into the fixture (html or node) + * @param Object Options argument for the check (optional, default: {}) + * @param String Target for the check, CSS selector (default: '#target') + */ +testUtils.checkSetup = function (content, options, target) { + 'use strict'; + // Normalize the params + if (typeof options !== 'object') { + target = options; + options = {}; + } + testUtils.fixtureSetup(content); + var node = axe.utils.querySelectorAll(axe._tree[0], target || '#target')[0]; + return [node.actualNode, options, node]; +}; + axe.testUtils = testUtils; \ No newline at end of file From c2ca9c77b86ef2247be3f21dc9d188037b5d0ac7 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Fri, 14 Jul 2017 17:59:11 +0200 Subject: [PATCH 2/4] fix: getComposedParent should not return slot nodes --- lib/commons/dom/get-composed-parent.js | 5 ++++- test/commons/dom/get-composed-parent.js | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/commons/dom/get-composed-parent.js b/lib/commons/dom/get-composed-parent.js index c5cfc74339..fdec0a4b18 100644 --- a/lib/commons/dom/get-composed-parent.js +++ b/lib/commons/dom/get-composed-parent.js @@ -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) { diff --git a/test/commons/dom/get-composed-parent.js b/test/commons/dom/get-composed-parent.js index f7c5138b4a..60923a1fe0 100644 --- a/test/commons/dom/get-composed-parent.js +++ b/test/commons/dom/get-composed-parent.js @@ -23,7 +23,7 @@ describe('dom.getComposedParent', function () { ); }); - (shadowSupport ? it : xit)('returns the slot node for slotted content', function () { + (shadowSupport ? it : xit)('skip the slot node for slotted content', function () { fixture.innerHTML = '
'; var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '
' + @@ -32,15 +32,15 @@ describe('dom.getComposedParent', function () { var actual = getComposedParent(fixture.querySelector('#target')); assert.instanceOf(actual, Node); - assert.equal(actual, shadowRoot.querySelector('#parent')); + assert.equal(actual, shadowRoot.querySelector('#grand-parent')); }); - (shadowSupport ? it : xit)('returns explicitly slotted nodes', function () { + (shadowSupport ? it : xit)('understands explicitly slotted nodes', function () { fixture.innerHTML = '
'; var shadowRoot = document.getElementById('shadow').attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '
' + '' + - '' + + '
' + '
'; var actual = getComposedParent(fixture.querySelector('#target')); From 856414800056f49ab8bda9ccbf3da104a49dd67d Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Fri, 14 Jul 2017 21:43:07 +0200 Subject: [PATCH 3/4] feat: Add shadow DOM support to list checks --- lib/checks/lists/dlitem.js | 4 +- lib/checks/lists/has-listitem.js | 11 +-- lib/checks/lists/listitem.js | 10 +-- lib/checks/lists/only-dlitems.js | 19 ++--- lib/checks/lists/only-listitems.js | 19 ++--- lib/checks/lists/structured-dlitems.js | 4 +- test/checks/lists/dlitem.js | 22 +++-- test/checks/lists/has-listitem.js | 36 ++++---- test/checks/lists/listitem.js | 34 ++++---- test/checks/lists/only-dlitems.js | 108 +++++++++++------------- test/checks/lists/only-listitems.js | 98 ++++++++++----------- test/checks/lists/structured-dlitems.js | 59 ++++++------- 12 files changed, 193 insertions(+), 231 deletions(-) diff --git a/lib/checks/lists/dlitem.js b/lib/checks/lists/dlitem.js index 96ec9e65b1..80a6608fc7 100644 --- a/lib/checks/lists/dlitem.js +++ b/lib/checks/lists/dlitem.js @@ -1,2 +1,2 @@ -return node.parentNode.tagName === 'DL'; - +var parent = axe.commons.dom.getComposedParent(node); +return parent.nodeName.toUpperCase() === 'DL'; diff --git a/lib/checks/lists/has-listitem.js b/lib/checks/lists/has-listitem.js index d38f7bd805..05aff66304 100644 --- a/lib/checks/lists/has-listitem.js +++ b/lib/checks/lists/has-listitem.js @@ -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'); diff --git a/lib/checks/lists/listitem.js b/lib/checks/lists/listitem.js index 16e396b878..1a7abcb40d 100644 --- a/lib/checks/lists/listitem.js +++ b/lib/checks/lists/listitem.js @@ -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'); + \ No newline at end of file diff --git a/lib/checks/lists/only-dlitems.js b/lib/checks/lists/only-dlitems.js index a003f8dbee..ff3a20d62d 100644 --- a/lib/checks/lists/only-dlitems.js +++ b/lib/checks/lists/only-dlitems.js @@ -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); } diff --git a/lib/checks/lists/only-listitems.js b/lib/checks/lists/only-listitems.js index 547fe79652..bdb53064e5 100644 --- a/lib/checks/lists/only-listitems.js +++ b/lib/checks/lists/only-listitems.js @@ -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); } diff --git a/lib/checks/lists/structured-dlitems.js b/lib/checks/lists/structured-dlitems.js index b8578dffb1..7654da061d 100644 --- a/lib/checks/lists/structured-dlitems.js +++ b/lib/checks/lists/structured-dlitems.js @@ -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; } diff --git a/test/checks/lists/dlitem.js b/test/checks/lists/dlitem.js index f8d50778c8..807a7ad4c9 100644 --- a/test/checks/lists/dlitem.js +++ b/test/checks/lists/dlitem.js @@ -2,26 +2,32 @@ 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
', function () { - fixture.innerHTML = '
My list item
'; - var node = fixture.querySelector('#target'); - - assert.isTrue(checks.dlitem.evaluate(node)); - + var checkArgs = checkSetup('
My list item
'); + assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); it('should fail if the dlitem has an incorrect parent', function () { - fixture.innerHTML = '
'; - var node = fixture.querySelector('#target'); - assert.isFalse(checks['only-dlitems'].evaluate.call(checkContext, node)); + var checkArgs = checkSetup('
A list
'); + + assert.isFalse(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); }); it('should return false if
A list
'; - var node = fixture.querySelector('#target'); - assert.isFalse(checks['only-dlitems'].evaluate.call(checkContext, node)); + var checkArgs = checkSetup('
A list
'); + + assert.isFalse(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); }); it('should return false if