From fb6438b4451ad3795755c8a3abbd0cf8564b60ee Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Wed, 22 Aug 2018 00:12:57 +0200 Subject: [PATCH] fix: Allow live-region and dialog in region rule (#1073) --- lib/checks/navigation/region.js | 45 ++++++++++++++++++-------------- test/checks/navigation/region.js | 29 ++++++++++++++++++++ 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/lib/checks/navigation/region.js b/lib/checks/navigation/region.js index 8af2c17ebc..e273ba4116 100644 --- a/lib/checks/navigation/region.js +++ b/lib/checks/navigation/region.js @@ -25,27 +25,32 @@ function isSkipLink(vNode) { } // Check if the current element is a landmark -function isLandmark(virtualNode) { - var node = virtualNode.actualNode; - var explictRole = (node.getAttribute('role') || '').trim().toLowerCase(); +function isRegion(virtualNode) { + const node = virtualNode.actualNode; + const explicitRole = axe.commons.aria.getRole(node, { noImplicit: true }); + const ariaLive = (node.getAttribute('aria-live') || '').toLowerCase().trim(); - if (explictRole) { - return landmarkRoles.includes(explictRole); - } else { - // Check if the node matches any of the CSS selectors of implicit landmarks - return implicitLandmarks.some(implicitSelector => { - let matches = axe.utils.matchesSelector(node, implicitSelector); - if (node.tagName.toLowerCase() === 'form') { - let titleAttr = node.getAttribute('title'); - let title = - titleAttr && titleAttr.trim() !== '' - ? axe.commons.text.sanitize(titleAttr) - : null; - return matches && (!!aria.labelVirtual(virtualNode) || !!title); - } - return matches; - }); + if (explicitRole) { + return explicitRole === 'dialog' || landmarkRoles.includes(explicitRole); } + // Ignore content inside of aria-live + if (['assertive', 'polite'].includes(ariaLive)) { + return true; + } + + // Check if the node matches any of the CSS selectors of implicit landmarks + return implicitLandmarks.some(implicitSelector => { + let matches = axe.utils.matchesSelector(node, implicitSelector); + if (node.tagName.toLowerCase() === 'form') { + let titleAttr = node.getAttribute('title'); + let title = + titleAttr && titleAttr.trim() !== '' + ? axe.commons.text.sanitize(titleAttr) + : null; + return matches && (!!aria.labelVirtual(virtualNode) || !!title); + } + return matches; + }); } /** @@ -55,7 +60,7 @@ function findRegionlessElms(virtualNode) { const node = virtualNode.actualNode; // End recursion if the element is a landmark, skiplink, or hidden content if ( - isLandmark(virtualNode) || + isRegion(virtualNode) || isSkipLink(virtualNode) || !dom.isVisible(node, true) ) { diff --git a/test/checks/navigation/region.js b/test/checks/navigation/region.js index ee0af39281..8162ff8a3c 100644 --- a/test/checks/navigation/region.js +++ b/test/checks/navigation/region.js @@ -211,6 +211,35 @@ describe('region', function() { assert.isTrue(checks.region.evaluate.apply(checkContext, checkArgs)); }); + it('allows content in aria-live=assertive', function() { + var checkArgs = checkSetup( + '

This is random content.

' + ); + assert.isTrue(checks.region.evaluate.apply(checkContext, checkArgs)); + }); + + it('allows content in aria-live=polite', function() { + var checkArgs = checkSetup( + '

This is random content.

' + ); + assert.isTrue(checks.region.evaluate.apply(checkContext, checkArgs)); + }); + + it('does not allow content in aria-live=off', function() { + var checkArgs = checkSetup( + '

This is random content.

' + ); + assert.isFalse(checks.region.evaluate.apply(checkContext, checkArgs)); + }); + + it('treats role=dialog elements as regions', function() { + var checkArgs = checkSetup( + '' + ); + + assert.isTrue(checks.region.evaluate.apply(checkContext, checkArgs)); + }); + (shadowSupport.v1 ? it : xit)('should test Shadow tree content', function() { var div = document.createElement('div'); var shadow = div.attachShadow({ mode: 'open' });