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));
+ });
});