From 439bc71674e8211e590c116c4c8ba15af4f255d2 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Wed, 19 Jul 2017 12:18:55 +0200 Subject: [PATCH 1/3] feat(duplicate-id): Add shadow DOM support --- lib/checks/shared/duplicate-id.js | 24 ++++++------- test/checks/shared/duplicate-id.js | 54 ++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/lib/checks/shared/duplicate-id.js b/lib/checks/shared/duplicate-id.js index 70b5ae79b3..b7ef630c90 100644 --- a/lib/checks/shared/duplicate-id.js +++ b/lib/checks/shared/duplicate-id.js @@ -1,21 +1,17 @@ +const id = node.getAttribute('id').trim(); // Since empty ID's are not meaningful and are ignored by Edge, we'll // let those pass. -if (!node.getAttribute('id').trim()) { +if (!id) { return true; } +const root = axe.commons.dom.getRootNode(node); +const matchingNodes = Array.from(root.querySelectorAll( + `[id="${ axe.commons.utils.escapeSelector(id) }"]` + )).filter(foundNode => foundNode !== node); -const id = axe.commons.utils.escapeSelector(node.getAttribute('id')); -var matchingNodes = document.querySelectorAll(`[id="${id}"]`); -var related = []; - -for (var i = 0; i < matchingNodes.length; i++) { - if (matchingNodes[i] !== node) { - related.push(matchingNodes[i]); - } -} -if (related.length) { - this.relatedNodes(related); +if (matchingNodes.length) { + this.relatedNodes(matchingNodes); } -this.data(node.getAttribute('id')); +this.data(id); -return (matchingNodes.length <= 1); +return (matchingNodes.length === 0); diff --git a/test/checks/shared/duplicate-id.js b/test/checks/shared/duplicate-id.js index 93d8d41991..bb31ca4d54 100644 --- a/test/checks/shared/duplicate-id.js +++ b/test/checks/shared/duplicate-id.js @@ -2,7 +2,7 @@ describe('duplicate-id', function () { 'use strict'; var fixture = document.getElementById('fixture'); - + var shadowSupport = axe.testUtils.shadowSupport; var checkContext = { _relatedNodes: [], @@ -27,7 +27,6 @@ describe('duplicate-id', function () { assert.isTrue(checks['duplicate-id'].evaluate.call(checkContext, node)); assert.equal(checkContext._data, node.id); assert.deepEqual(checkContext._relatedNodes, []); - }); it('should return false if there are multiple elements with an ID', function () { @@ -39,7 +38,8 @@ describe('duplicate-id', function () { }); it('should return remove duplicates', function () { - assert.deepEqual(checks['duplicate-id'].after([{data: 'a'}, {data: 'b'}, {data: 'b'}]), [{data: 'a'}, {data: 'b'}]); + assert.deepEqual(checks['duplicate-id'].after([ + {data: 'a'}, {data: 'b'}, {data: 'b'}]), [{data: 'a'}, {data: 'b'}]); }); it('should ignore empty ids', function () { @@ -58,4 +58,52 @@ describe('duplicate-id', function () { assert.isTrue(checks['duplicate-id'].evaluate.call(checkContext, node)); }); + (shadowSupport.v1 ? it : xit)('should find duplicate IDs in shadow trees', function () { + var div = document.createElement('div'); + div.id = 'target'; + var shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = '

text

'; + var node = shadow.querySelector('span'); + fixture.appendChild(div); + + assert.isFalse(checks['duplicate-id'].evaluate.call(checkContext, node)); + assert.lengthOf(checkContext._relatedNodes, 1); + assert.deepEqual(checkContext._relatedNodes, [shadow.querySelector('p')]); + }); + + (shadowSupport.v1 ? it : xit)('should ignore same IDs in shadow trees', function () { + var node = document.createElement('div'); + node.id = 'target'; + var shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + fixture.appendChild(node); + + assert.isTrue(checks['duplicate-id'].evaluate.call(checkContext, node)); + assert.lengthOf(checkContext._relatedNodes, 0); + }); + + (shadowSupport.v1 ? it : xit)('should ignore same IDs outside shadow trees', function () { + var div = document.createElement('div'); + div.id = 'target'; + var shadow = div.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + var node = shadow.querySelector('#target'); + fixture.appendChild(div); + + assert.isTrue(checks['duplicate-id'].evaluate.call(checkContext, node)); + assert.lengthOf(checkContext._relatedNodes, 0); + }); + + (shadowSupport.v1 ? it : xit)('should not ignore slotted elements', function () { + var node = document.createElement('div'); + node.id = 'target'; + node.innerHTML = '

text

'; + var shadow = node.attachShadow({ mode: 'open' }); + shadow.innerHTML = ''; + fixture.appendChild(node); + + assert.isFalse(checks['duplicate-id'].evaluate.call(checkContext, node)); + assert.lengthOf(checkContext._relatedNodes, 1); + assert.deepEqual(checkContext._relatedNodes, [node.querySelector('p')]); + }); }); From 797f9c28b164ab5a49c7b211ca8efb99d9a1e827 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Mon, 24 Jul 2017 15:24:50 +0200 Subject: [PATCH 2/3] test(duplicate-id): clarify test statement --- test/checks/shared/duplicate-id.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/checks/shared/duplicate-id.js b/test/checks/shared/duplicate-id.js index bb31ca4d54..2fd4c949a0 100644 --- a/test/checks/shared/duplicate-id.js +++ b/test/checks/shared/duplicate-id.js @@ -94,7 +94,7 @@ describe('duplicate-id', function () { assert.lengthOf(checkContext._relatedNodes, 0); }); - (shadowSupport.v1 ? it : xit)('should not ignore slotted elements', function () { + (shadowSupport.v1 ? it : xit)('should compare slotted content with the outer tree', function () { var node = document.createElement('div'); node.id = 'target'; node.innerHTML = '

text

'; From 5b1cc7a0bec7d751fb55a7e386c10e1222c161e1 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Tue, 1 Aug 2017 12:47:57 +0200 Subject: [PATCH 3/3] test(duplicate-id): Update test text --- test/checks/shared/duplicate-id.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/checks/shared/duplicate-id.js b/test/checks/shared/duplicate-id.js index 2fd4c949a0..95e6627150 100644 --- a/test/checks/shared/duplicate-id.js +++ b/test/checks/shared/duplicate-id.js @@ -58,7 +58,7 @@ describe('duplicate-id', function () { assert.isTrue(checks['duplicate-id'].evaluate.call(checkContext, node)); }); - (shadowSupport.v1 ? it : xit)('should find duplicate IDs in shadow trees', function () { + (shadowSupport.v1 ? it : xit)('should find duplicate IDs in the same shadow DOM', function () { var div = document.createElement('div'); div.id = 'target'; var shadow = div.attachShadow({ mode: 'open' }); @@ -71,7 +71,7 @@ describe('duplicate-id', function () { assert.deepEqual(checkContext._relatedNodes, [shadow.querySelector('p')]); }); - (shadowSupport.v1 ? it : xit)('should ignore same IDs in shadow trees', function () { + (shadowSupport.v1 ? it : xit)('should ignore duplicate IDs if they are in different document roots', function () { var node = document.createElement('div'); node.id = 'target'; var shadow = node.attachShadow({ mode: 'open' }); @@ -94,7 +94,7 @@ describe('duplicate-id', function () { assert.lengthOf(checkContext._relatedNodes, 0); }); - (shadowSupport.v1 ? it : xit)('should compare slotted content with the outer tree', function () { + (shadowSupport.v1 ? it : xit)('should compare slotted content with the light DOM', function () { var node = document.createElement('div'); node.id = 'target'; node.innerHTML = '

text

';