Skip to content

Commit

Permalink
feat(rule): Require unique aria labels in checkboxgroup & radiogroup (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
WilcoFiers authored and jeeyyy committed Jan 23, 2019
1 parent 6f649d6 commit c9b310d
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 76 deletions.
3 changes: 3 additions & 0 deletions lib/checks/forms/group-labelledby-after.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
var seen = {};

// Filter out nodes that have the same type and name.
// If you have two `<input type="radio" name="foo" />` elements
// only the first one can pass / fail the rule.
return results.filter(function(result) {
var data = result.data;
if (data) {
Expand Down
81 changes: 54 additions & 27 deletions lib/checks/forms/group-labelledby.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,61 @@
this.data({
name: node.getAttribute('name'),
type: node.getAttribute('type')
});
const { dom, text, utils } = axe.commons;

const type = utils.escapeSelector(node.type);
const name = utils.escapeSelector(node.name);
const doc = dom.getRootNode(node);
const data = {
name: node.name,
type: node.type
};

var doc = axe.commons.dom.getRootNode(node);
var matchingNodes = doc.querySelectorAll(
'input[type="' +
axe.commons.utils.escapeSelector(node.type) +
'"][name="' +
axe.commons.utils.escapeSelector(node.name) +
'"]'
const matchingNodes = Array.from(
doc.querySelectorAll(`input[type="${type}"][name="${name}"]`)
);
// There is only one node with this name & type, so there's no need for a group
if (matchingNodes.length <= 1) {
this.data(data);
return true;
}

// Check to see if there's an aria-labelledby value that all nodes have in common
return (
[].map
.call(matchingNodes, function(m) {
var l = m.getAttribute('aria-labelledby');
return l ? l.split(/\s+/) : [];
})
.reduce(function(prev, curr) {
return prev.filter(function(n) {
return curr.includes(n);
});
})
.filter(function(n) {
var labelNode = doc.getElementById(n);
return labelNode && axe.commons.text.accessibleText(labelNode, true);
}).length !== 0
let sharedLabels = dom.idrefs(node, 'aria-labelledby').filter(label => !!label); // Filter for "null" labels
let uniqueLabels = sharedLabels.slice();

// Figure out which labels are unique, which are shared by all items, or neither
matchingNodes.forEach(groupItem => {
if (groupItem === node) {
return;
}
// Find new labels associated with current groupItem
const labels = dom
.idrefs(groupItem, 'aria-labelledby')
.filter(newLabel => newLabel);

sharedLabels = sharedLabels.filter(sharedLabel =>
labels.includes(sharedLabel)
);
uniqueLabels = uniqueLabels.filter(
uniqueLabel => !labels.includes(uniqueLabel)
);
});

// filter items with no accessible name, do this last for performance reasons
uniqueLabels = uniqueLabels.filter(labelNode =>
text.accessibleText(labelNode, true)
);
sharedLabels = sharedLabels.filter(labelNode =>
text.accessibleText(labelNode, true)
);

if (uniqueLabels.length > 0 && sharedLabels.length > 0) {
this.data(data);
return true;
}

if (uniqueLabels.length > 0 && sharedLabels.length === 0) {
data.failureCode = 'no-shared-label';
} else if (uniqueLabels.length === 0 && sharedLabels.length > 0) {
data.failureCode = 'no-unique-label';
}

this.data(data);
return false;
4 changes: 2 additions & 2 deletions lib/checks/forms/group-labelledby.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"metadata": {
"impact": "critical",
"messages": {
"pass": "All elements with the name \"{{=it.data.name}}\" reference the same element with aria-labelledby",
"fail": "All elements with the name \"{{=it.data.name}}\" do not reference the same element with aria-labelledby"
"pass": "Elements with the name \"{{=it.data.name}}\" have both a shared label, and a unique label, referenced through aria-labelledby",
"fail": "{{var code = it.data && it.data.failureCode;}}Elements with the name \"{{=it.data.name}}\" do not all have {{? code === 'no-shared-label' }}a shared label{{?? code === 'no-unique-label' }}a unique label{{??}}both a shared label, and a unique label{{?}}, referenced through aria-labelledby"
}
}
}
Loading

0 comments on commit c9b310d

Please sign in to comment.