diff --git a/lib/checks/aria/aria-valid-attr-value-evaluate.js b/lib/checks/aria/aria-valid-attr-value-evaluate.js index 469f29186f..0b6007ff42 100644 --- a/lib/checks/aria/aria-valid-attr-value-evaluate.js +++ b/lib/checks/aria/aria-valid-attr-value-evaluate.js @@ -67,7 +67,9 @@ function ariaValidAttrValueEvaluate(node, options, virtualNode) { needsReview = `aria-describedby="${virtualNode.attr( 'aria-describedby' )}"`; - messageKey = 'noId'; + // TODO: es-modules_tree + messageKey = + axe._tree && axe._tree[0]._hasShadowRoot ? 'noIdShadow' : 'noId'; } return; @@ -80,7 +82,9 @@ function ariaValidAttrValueEvaluate(node, options, virtualNode) { needsReview = `aria-labelledby="${virtualNode.attr( 'aria-labelledby' )}"`; - messageKey = 'noId'; + // TODO: es-modules_tree + messageKey = + axe._tree && axe._tree[0]._hasShadowRoot ? 'noIdShadow' : 'noId'; } } }; diff --git a/lib/checks/aria/aria-valid-attr-value.json b/lib/checks/aria/aria-valid-attr-value.json index 7c14f3d59f..12658c4149 100644 --- a/lib/checks/aria/aria-valid-attr-value.json +++ b/lib/checks/aria/aria-valid-attr-value.json @@ -12,6 +12,7 @@ }, "incomplete": { "noId": "ARIA attribute element ID does not exist on the page: ${data.needsReview}", + "noIdShadow": "ARIA attribute element ID does not exist on the page or is a descendant of a different shadow DOM tree: ${data.needsReview}", "ariaCurrent": "ARIA attribute value is invalid and will be treated as \"aria-current=true\": ${data.needsReview}", "idrefs": "Unable to determine if ARIA attribute element ID exists on the page: ${data.needsReview}" } diff --git a/lib/core/utils/get-flattened-tree.js b/lib/core/utils/get-flattened-tree.js index af50400157..6bddc10ab3 100644 --- a/lib/core/utils/get-flattened-tree.js +++ b/lib/core/utils/get-flattened-tree.js @@ -20,6 +20,8 @@ import cache from '../base/cache'; * the spec for this) */ +let hasShadowRoot; + /** * find all the fallback content for a and return these as an array * this array will also include any #text nodes @@ -65,6 +67,8 @@ function flattenTree(node, shadowId, parent) { nodeName = node.nodeName.toLowerCase(); if (isShadowRoot(node)) { + hasShadowRoot = true; + // generate an ID for this shadow root and overwrite the current // closure shadowId with this value so that it cascades down the tree retVal = new VirtualNode(node, parent, shadowId); @@ -140,12 +144,19 @@ function flattenTree(node, shadowId, parent) { * ancestor of the node */ function getFlattenedTree(node = document.documentElement, shadowId) { + hasShadowRoot = false; cache.set('nodeMap', new WeakMap()); // specifically pass `null` to the parent to designate the top // node of the tree. if parent === undefined then we know // we are in a disconnected tree - return flattenTree(node, shadowId, null); + const tree = flattenTree(node, shadowId, null); + + // allow rules and checks to know if there is a shadow root attached + // to the current tree + tree[0]._hasShadowRoot = hasShadowRoot; + + return tree; } export default getFlattenedTree; diff --git a/test/checks/aria/valid-attr-value.js b/test/checks/aria/valid-attr-value.js index 41a49b9e1f..8ad93a95eb 100644 --- a/test/checks/aria/valid-attr-value.js +++ b/test/checks/aria/valid-attr-value.js @@ -5,6 +5,8 @@ describe('aria-valid-attr-value', function() { var queryFixture = axe.testUtils.queryFixture; var checkContext = axe.testUtils.MockCheckContext(); var fixtureSetup = axe.testUtils.fixtureSetup; + var shadowCheckSetup = axe.testUtils.shadowCheckSetup; + var shadowSupported = axe.testUtils.shadowSupport.v1; afterEach(function() { fixture.innerHTML = ''; @@ -210,8 +212,31 @@ describe('aria-valid-attr-value', function() { .getCheckEvaluate('aria-valid-attr-value') .call(checkContext, null, null, vNode) ); + assert.deepEqual(checkContext._data, { + messageKey: 'noId', + needsReview: 'aria-describedby="test"' + }); }); + (shadowSupported ? it : xit)( + 'should return undefined on aria-describedby when the element is in a different shadow tree', + function() { + var params = shadowCheckSetup( + '
', + '' + ); + assert.isUndefined( + axe.testUtils + .getCheckEvaluate('aria-valid-attr-value') + .apply(checkContext, params) + ); + assert.deepEqual(checkContext._data, { + messageKey: 'noIdShadow', + needsReview: 'aria-describedby="test"' + }); + } + ); + it('should return undefined on aria-labelledby when the element is not in the DOM', function() { var vNode = queryFixture( '' @@ -221,8 +246,31 @@ describe('aria-valid-attr-value', function() { .getCheckEvaluate('aria-valid-attr-value') .call(checkContext, null, null, vNode) ); + assert.deepEqual(checkContext._data, { + messageKey: 'noId', + needsReview: 'aria-labelledby="test"' + }); }); + (shadowSupported ? it : xit)( + 'should return undefined on aria-labelledby when the element is in a different shadow tree', + function() { + var params = shadowCheckSetup( + '
', + '' + ); + assert.isUndefined( + axe.testUtils + .getCheckEvaluate('aria-valid-attr-value') + .apply(checkContext, params) + ); + assert.deepEqual(checkContext._data, { + messageKey: 'noIdShadow', + needsReview: 'aria-labelledby="test"' + }); + } + ); + it('should return undefined on aria-current with invalid value', function() { var vNode = queryFixture( ''