From fbae36b0f8d92596d2493963620dbfc0439aace9 Mon Sep 17 00:00:00 2001 From: Paul Grenier Date: Fri, 17 May 2019 11:17:51 -0400 Subject: [PATCH] fix(multiple-label): no longer raises issue when aria-labelledby overrides how AT views multiple labels (#1538) --- lib/checks/label/multiple-label.js | 30 ++++++---- test/checks/label/multiple-label.js | 90 ++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/lib/checks/label/multiple-label.js b/lib/checks/label/multiple-label.js index 67dd91ae7c..b8c156cf79 100644 --- a/lib/checks/label/multiple-label.js +++ b/lib/checks/label/multiple-label.js @@ -3,16 +3,8 @@ let labels = Array.from(document.querySelectorAll(`label[for="${id}"]`)); let parent = node.parentNode; if (labels.length) { - // filter out hidden labels because they're fine - // except: fail first label if hidden because of VO - labels = labels.filter(function(label, index) { - if ( - (index === 0 && !axe.commons.dom.isVisible(label, true)) || - axe.commons.dom.isVisible(label, true) - ) { - return label; - } - }); + // filter out CSS hidden labels because they're fine + labels = labels.filter(label => axe.commons.dom.isVisible(label)); } while (parent) { @@ -26,4 +18,20 @@ while (parent) { } this.relatedNodes(labels); -return labels.length > 1; + +// more than 1 CSS visible label +if (labels.length > 1) { + const ATVisibleLabels = labels.filter(label => + axe.commons.dom.isVisible(label, true) + ); + // more than 1 AT visible label will fail IOS/Safari/VO even with aria-labelledby + if (ATVisibleLabels.length > 1) { + return true; + } + // make sure the ONE AT visible label is in the list of idRefs of aria-labelledby + const labelledby = axe.commons.dom.idrefs(node, 'aria-labelledby'); + return !labelledby.includes(ATVisibleLabels[0]); +} + +// only 1 CSS visible label +return false; diff --git a/test/checks/label/multiple-label.js b/test/checks/label/multiple-label.js index 971a5137b7..98bc10330f 100644 --- a/test/checks/label/multiple-label.js +++ b/test/checks/label/multiple-label.js @@ -69,19 +69,7 @@ describe('multiple-label', function() { assert.deepEqual(checkContext._relatedNodes, [l1]); }); - it('should return true if there are multiple explicit labels but the first one is hidden', function() { - fixture.innerHTML = - '' + - '' + - ''; - var target = fixture.querySelector('#test-input2'); - var l1 = fixture.querySelector('#l1'); - var l2 = fixture.querySelector('#l2'); - assert.isTrue(checks['multiple-label'].evaluate.call(checkContext, target)); - assert.deepEqual(checkContext._relatedNodes, [l1, l2]); - }); - - it('should return true if there are multiple explicit labels but the first one is hidden', function() { + it('should return true if there are multiple explicit labels but some are hidden', function() { fixture.innerHTML = '' + '' + @@ -112,4 +100,80 @@ describe('multiple-label', function() { checks['multiple-label'].evaluate.call(checkContext, target) ); }); + + it('should return true given multiple labels and no aria-labelledby', function() { + fixture.innerHTML = ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + var target = fixture.querySelector('#A'); + assert.isTrue(checks['multiple-label'].evaluate.call(checkContext, target)); + }); + + it('should return true given multiple labels, one label AT visible, and no aria-labelledby', function() { + fixture.innerHTML = ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + var target = fixture.querySelector('#B'); + assert.isTrue(checks['multiple-label'].evaluate.call(checkContext, target)); + }); + + it('should return false given multiple labels, one label AT visible, and aria-labelledby for AT visible', function() { + fixture.innerHTML = ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + var target = fixture.querySelector('#D'); + assert.isFalse( + checks['multiple-label'].evaluate.call(checkContext, target) + ); + }); + + it('should return false given multiple labels, one label AT visible, and aria-labelledby for all', function() { + fixture.innerHTML = ''; + fixture.innerHTML += + ''; + fixture.innerHTML += ''; + var target = fixture.querySelector('#F'); + assert.isFalse( + checks['multiple-label'].evaluate.call(checkContext, target) + ); + }); + + it('should return false given multiple labels, one label visible, and no aria-labelledby', function() { + fixture.innerHTML = ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + var target = fixture.querySelector('#I'); + assert.isFalse( + checks['multiple-label'].evaluate.call(checkContext, target) + ); + }); + + it('should return true given multiple labels, all visible, aria-labelledby for all', function() { + fixture.innerHTML = + ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + var target = fixture.querySelector('#J'); + assert.isTrue(checks['multiple-label'].evaluate.call(checkContext, target)); + }); + + it('should return true given multiple labels, one AT visible, no aria-labelledby', function() { + fixture.innerHTML = ''; + fixture.innerHTML += ''; + fixture.innerHTML += ''; + var target = fixture.querySelector('#Q'); + assert.isTrue(checks['multiple-label'].evaluate.call(checkContext, target)); + }); });