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 = '