From 0f98481346f4032a3eefa175dc0af80e64d87bf8 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Tue, 18 Jul 2017 18:44:56 +0200 Subject: [PATCH] fix: Let findUp work on shadow root children (#447) * fix: Let findUp work on shadow root children * fix: Solve a few tests # Conflicts: # test/testutils.js * Fix: Properly disable of shadow DOM checks * test: Use findUp on host element --- lib/checks/label/explicit.js | 3 +- lib/commons/dom/find-up.js | 25 +++------ test/checks/lists/dlitem.js | 4 +- test/checks/lists/has-listitem.js | 4 +- test/checks/lists/listitem.js | 4 +- test/checks/lists/only-dlitems.js | 4 +- test/checks/lists/only-listitems.js | 4 +- test/checks/lists/structured-dlitems.js | 4 +- test/checks/navigation/landmark.js | 6 +-- test/checks/shared/button-has-visible-text.js | 22 ++++---- test/commons/dom/find-up.js | 52 +++++++++++++------ 11 files changed, 70 insertions(+), 62 deletions(-) diff --git a/lib/checks/label/explicit.js b/lib/checks/label/explicit.js index bff93e1482..eee064f700 100644 --- a/lib/checks/label/explicit.js +++ b/lib/checks/label/explicit.js @@ -1,6 +1,7 @@ if (node.getAttribute('id')) { + const root = axe.commons.dom.getRootNode(node); const id = axe.commons.utils.escapeSelector(node.getAttribute('id')); - const label = document.querySelector(`label[for="${id}"]`); + const label = root.querySelector(`label[for="${id}"]`); if (label) { return !!axe.commons.text.accessibleText(label); diff --git a/lib/commons/dom/find-up.js b/lib/commons/dom/find-up.js index 06e20ffe05..8f2db7f8f9 100644 --- a/lib/commons/dom/find-up.js +++ b/lib/commons/dom/find-up.js @@ -9,25 +9,16 @@ * @return {HTMLElement|null} Either the matching HTMLElement or `null` if there was no match */ dom.findUp = function (element, target) { - 'use strict'; - /*jslint browser:true*/ + let doc, matches, + parent = element; - var parent, - doc = axe.commons.dom.getRootNode(element), - matches; - - matches = doc.querySelectorAll(target); - matches = axe.utils.toArray(matches); - if (doc === document && !matches.length) { - return null; - } - - // recursively walk up the DOM, checking each parent node - parent = dom.getComposedParent(element); - while (parent && matches.indexOf(parent) === -1) { - parent = (parent.assignedSlot) ? parent.assignedSlot : parent.parentNode; + do {// recursively walk up the DOM, checking each parent node + parent = (parent.assignedSlot ? parent.assignedSlot : parent.parentNode); if (parent && parent.nodeType === 11) { + matches = null; parent = parent.host; + } + if (!matches) { doc = axe.commons.dom.getRootNode(parent); matches = doc.querySelectorAll(target); matches = axe.utils.toArray(matches); @@ -35,7 +26,7 @@ dom.findUp = function (element, target) { return null; } } - } + } while (parent && !matches.includes(parent)); return parent; }; diff --git a/test/checks/lists/dlitem.js b/test/checks/lists/dlitem.js index 827e6a6405..56f7596b36 100644 --- a/test/checks/lists/dlitem.js +++ b/test/checks/lists/dlitem.js @@ -21,7 +21,7 @@ describe('dlitem', function () { assert.isFalse(checks.dlitem.evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should return true in a shadow DOM pass', function () { + (shadowSupport.v1 ? it : xit)('should return true in a shadow DOM pass', function () { var node = document.createElement('div'); node.innerHTML = '
My list item
'; var shadow = node.attachShadow({ mode: 'open' }); @@ -31,7 +31,7 @@ describe('dlitem', function () { assert.isTrue(checks.dlitem.evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should return false in a shadow DOM fail', function () { + (shadowSupport.v1 ? it : xit)('should return false in a shadow DOM fail', function () { var node = document.createElement('div'); node.innerHTML = '
My list item
'; var shadow = node.attachShadow({ mode: 'open' }); diff --git a/test/checks/lists/has-listitem.js b/test/checks/lists/has-listitem.js index 87ca1f5865..b3b2087a60 100644 --- a/test/checks/lists/has-listitem.js +++ b/test/checks/lists/has-listitem.js @@ -33,7 +33,7 @@ describe('has-listitem', function () { assert.isFalse(checks['has-listitem'].evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should return true in a shadow DOM pass', function () { + (shadowSupport.v1 ? it : xit)('should return true in a shadow DOM pass', function () { var node = document.createElement('div'); node.innerHTML = '
  • My list item
  • '; var shadow = node.attachShadow({ mode: 'open' }); @@ -43,7 +43,7 @@ describe('has-listitem', function () { assert.isFalse(checks['has-listitem'].evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should return false in a shadow DOM fail', function () { + (shadowSupport.v1 ? it : xit)('should return false in a shadow DOM fail', function () { var node = document.createElement('div'); node.innerHTML = '

    Not a list

    '; var shadow = node.attachShadow({ mode: 'open' }); diff --git a/test/checks/lists/listitem.js b/test/checks/lists/listitem.js index 179a631cd6..e543634883 100644 --- a/test/checks/lists/listitem.js +++ b/test/checks/lists/listitem.js @@ -33,7 +33,7 @@ describe('listitem', function () { assert.isFalse(checks.listitem.evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should return true in a shadow DOM pass', function () { + (shadowSupport.v1 ? it : xit)('should return true in a shadow DOM pass', function () { var node = document.createElement('div'); node.innerHTML = '
  • My list item
  • '; var shadow = node.attachShadow({ mode: 'open' }); @@ -43,7 +43,7 @@ describe('listitem', function () { assert.isTrue(checks.listitem.evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should return false in a shadow DOM fail', function () { + (shadowSupport.v1 ? it : xit)('should return false in a shadow DOM fail', function () { var node = document.createElement('div'); node.innerHTML = '
  • My list item
  • '; var shadow = node.attachShadow({ mode: 'open' }); diff --git a/test/checks/lists/only-dlitems.js b/test/checks/lists/only-dlitems.js index efca12bc64..e0b890f514 100644 --- a/test/checks/lists/only-dlitems.js +++ b/test/checks/lists/only-dlitems.js @@ -104,7 +104,7 @@ describe('only-dlitems', function () { assert.isFalse(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); }); - (shadowSupport ? it : xit)('should return false in a shadow DOM pass', function () { + (shadowSupport.v1 ? it : xit)('should return false in a shadow DOM pass', function () { var node = document.createElement('div'); node.innerHTML = '
    My list item
    '; var shadow = node.attachShadow({ mode: 'open' }); @@ -114,7 +114,7 @@ describe('only-dlitems', function () { assert.isFalse(checks['only-dlitems'].evaluate.apply(checkContext, checkArgs)); }); - (shadowSupport ? it : xit)('should return true in a shadow DOM fail', function () { + (shadowSupport.v1 ? it : xit)('should return true in a shadow DOM fail', function () { var node = document.createElement('div'); node.innerHTML = '

    Not a list

    '; var shadow = node.attachShadow({ mode: 'open' }); diff --git a/test/checks/lists/only-listitems.js b/test/checks/lists/only-listitems.js index ad283c26b6..9150cad922 100644 --- a/test/checks/lists/only-listitems.js +++ b/test/checks/lists/only-listitems.js @@ -97,7 +97,7 @@ describe('only-listitems', function () { assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); }); - (shadowSupport ? it : xit)('should return false in a shadow DOM pass', function () { + (shadowSupport.v1 ? it : xit)('should return false in a shadow DOM pass', function () { var node = document.createElement('div'); node.innerHTML = '
  • My list item
  • '; var shadow = node.attachShadow({ mode: 'open' }); @@ -107,7 +107,7 @@ describe('only-listitems', function () { assert.isFalse(checks['only-listitems'].evaluate.apply(checkContext, checkArgs)); }); - (shadowSupport ? it : xit)('should return true in a shadow DOM fail', function () { + (shadowSupport.v1 ? it : xit)('should return true in a shadow DOM fail', function () { var node = document.createElement('div'); node.innerHTML = '

    Not a list item

    '; var shadow = node.attachShadow({ mode: 'open' }); diff --git a/test/checks/lists/structured-dlitems.js b/test/checks/lists/structured-dlitems.js index 953b47acd6..09d3898504 100644 --- a/test/checks/lists/structured-dlitems.js +++ b/test/checks/lists/structured-dlitems.js @@ -49,7 +49,7 @@ describe('structured-dlitems', function () { assert.isFalse(checks['structured-dlitems'].evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should return false in a shadow DOM pass', function () { + (shadowSupport.v1 ? it : xit)('should return false in a shadow DOM pass', function () { var node = document.createElement('div'); node.innerHTML = '
    Grayhound bus
    at dawn
    '; var shadow = node.attachShadow({ mode: 'open' }); @@ -59,7 +59,7 @@ describe('structured-dlitems', function () { assert.isFalse(checks['structured-dlitems'].evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should return true in a shadow DOM fail', function () { + (shadowSupport.v1 ? it : xit)('should return true in a shadow DOM fail', function () { var node = document.createElement('div'); node.innerHTML = '
    Galileo
    Figaro
    '; var shadow = node.attachShadow({ mode: 'open' }); diff --git a/test/checks/navigation/landmark.js b/test/checks/navigation/landmark.js index 0d679d8913..81747751f0 100644 --- a/test/checks/navigation/landmark.js +++ b/test/checks/navigation/landmark.js @@ -24,7 +24,7 @@ describe('landmark', function () { assert.isFalse(checks.landmark.evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should not automatically pass if there is a shadow tree', function () { + (shadowSupport.v1 ? it : xit)('should not automatically pass if there is a shadow tree', function () { var node = document.createElement('div'); var shadow = node.attachShadow({ mode: 'open' }); shadow.innerHTML = '
    '; @@ -33,7 +33,7 @@ describe('landmark', function () { assert.isFalse(checks.landmark.evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should find elements inside shadow trees', function () { + (shadowSupport.v1 ? it : xit)('should find elements inside shadow trees', function () { var node = document.createElement('div'); var shadow = node.attachShadow({ mode: 'open' }); shadow.innerHTML = '
    '; @@ -42,7 +42,7 @@ describe('landmark', function () { assert.isTrue(checks.landmark.evaluate.apply(null, checkArgs)); }); - (shadowSupport ? it : xit)('should find elements slotted in shadow trees', function () { + (shadowSupport.v1 ? it : xit)('should find elements slotted in shadow trees', function () { var node = document.createElement('div'); node.innerHTML = '
    '; var shadow = node.attachShadow({ mode: 'open' }); diff --git a/test/checks/shared/button-has-visible-text.js b/test/checks/shared/button-has-visible-text.js index fb3d0fa565..3526aea46b 100644 --- a/test/checks/shared/button-has-visible-text.js +++ b/test/checks/shared/button-has-visible-text.js @@ -2,7 +2,7 @@ describe('button-has-visible-text', function () { 'use strict'; var fixture = document.getElementById('fixture'); - + var checkSetup = axe.testUtils.checkSetup; var checkContext = { _data: null, data: function (d) { @@ -16,32 +16,28 @@ describe('button-has-visible-text', function () { }); it('should return false if button element is empty', function () { - fixture.innerHTML = ''; + var checkArgs = checkSetup('', 'button'); - var node = fixture.querySelector('button'); - assert.isFalse(checks['button-has-visible-text'].evaluate.call(checkContext, node)); + assert.isFalse(checks['button-has-visible-text'].evaluate.apply(checkContext, checkArgs)); }); it('should return true if a button element has text', function () { - fixture.innerHTML = ''; + var checkArgs = checkSetup('', 'button'); - var node = fixture.querySelector('button'); - assert.isTrue(checks['button-has-visible-text'].evaluate.call(checkContext, node)); + assert.isTrue(checks['button-has-visible-text'].evaluate.apply(checkContext, checkArgs)); assert.deepEqual(checkContext._data, 'Name'); }); it('should return true if ARIA button has text', function () { - fixture.innerHTML = '
    Text
    '; + var checkArgs = checkSetup('
    Text
    >', '[role=button]'); - var node = fixture.querySelector('div'); - assert.isTrue(checks['button-has-visible-text'].evaluate.call(checkContext, node)); + assert.isTrue(checks['button-has-visible-text'].evaluate.apply(checkContext, checkArgs)); assert.deepEqual(checkContext._data, 'Text'); }); it('should return false if ARIA button has no text', function () { - fixture.innerHTML = '
    '; + var checkArgs = checkSetup('
    >', '[role=button]'); - var node = fixture.querySelector('div'); - assert.isFalse(checks['button-has-visible-text'].evaluate.call(checkContext, node)); + assert.isFalse(checks['button-has-visible-text'].evaluate.apply(checkContext, checkArgs)); }); }); \ No newline at end of file diff --git a/test/commons/dom/find-up.js b/test/commons/dom/find-up.js index e96f4983ed..2463d30537 100644 --- a/test/commons/dom/find-up.js +++ b/test/commons/dom/find-up.js @@ -74,7 +74,18 @@ describe('dom.findUp', function () { assert.equal(axe.commons.dom.findUp(el.actualNode, 'label'), fixture.firstChild); }); - it('should walk up the assigned slot', function () { + (shadowSupport.v0 ? it : xit)('should work on shadow root children', function () { + fixture.innerHTML = '
    '; + var shadow = fixture.querySelector('#something').createShadowRoot(); + + shadow.innerHTML = '
    item 1
    '; + var listItem = shadow.querySelector('[role=listitem]'); + + assert.equal(axe.commons.dom.findUp(listItem, '[role=list]'), + fixture.firstChild); + }); + + (shadowSupport.v1 ? it : xit)('should walk up the assigned slot', function () { function createContentSlotted() { var group = document.createElement('div'); group.innerHTML = ''; @@ -86,16 +97,15 @@ describe('dom.findUp', function () { root.appendChild(div); div.appendChild(createContentSlotted()); } - if (shadowSupport.v1) { - fixture.innerHTML = ''; - makeShadowTree(fixture.querySelector('div')); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; - assert.equal(axe.commons.dom.findUp(el.actualNode, 'label'), fixture.firstChild); - } + + fixture.innerHTML = ''; + makeShadowTree(fixture.querySelector('div')); + var tree = axe.utils.getFlattenedTree(fixture.firstChild); + var el = axe.utils.querySelectorAll(tree, 'a')[0]; + assert.equal(axe.commons.dom.findUp(el.actualNode, 'label'), fixture.firstChild); }); - it('should walk up the shadow DOM', function () { + (shadowSupport.v1 ? it : xit)('should walk up the shadow DOM', function () { function createContent() { var group = document.createElement('div'); group.innerHTML = 'thing'; @@ -107,12 +117,22 @@ describe('dom.findUp', function () { root.appendChild(div); div.appendChild(createContent()); } - if (shadowSupport.v1) { - fixture.innerHTML = ''; - makeShadowTree(fixture.querySelector('div')); - var tree = axe.utils.getFlattenedTree(fixture.firstChild); - var el = axe.utils.querySelectorAll(tree, 'a')[0]; - assert.equal(axe.commons.dom.findUp(el.actualNode, 'label'), fixture.firstChild); - } + + fixture.innerHTML = ''; + makeShadowTree(fixture.querySelector('div')); + var tree = axe.utils.getFlattenedTree(fixture.firstChild); + var el = axe.utils.querySelectorAll(tree, 'a')[0]; + assert.equal(axe.commons.dom.findUp(el.actualNode, 'label'), fixture.firstChild); + }); + + (shadowSupport.v1 ? it : xit)('should work on shadow root children', function () { + fixture.innerHTML = '
    '; + var shadow = fixture.querySelector('#something').attachShadow({ mode: 'open' }); + + shadow.innerHTML = '
    item 1
    '; + var listItem = shadow.querySelector('[role=listitem]'); + + assert.equal(axe.commons.dom.findUp(listItem, '[role=list]'), + fixture.firstChild); }); }); \ No newline at end of file