Skip to content

Commit 62a19a9

Browse files
takenspcWilcoFiersstraker
authored
feat(aria-prohibited-attr): add support for fallback roles (#4325)
This PR adds fallback role support to `aria-prohibited-attr`. For example, `<div role="foo img" aria-label="...">` is legal. At this time, I do not plan to add fallback roles support for other checks. Related: * #3768 Closes: --------- Co-authored-by: Wilco Fiers <[email protected]> Co-authored-by: Steven Lambert <[email protected]>
1 parent ecbee24 commit 62a19a9

File tree

5 files changed

+96
-5
lines changed

5 files changed

+96
-5
lines changed

lib/checks/aria/aria-prohibited-attr-evaluate.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ export default function ariaProhibitedAttrEvaluate(
3434
) {
3535
const elementsAllowedAriaLabel = options?.elementsAllowedAriaLabel || [];
3636
const { nodeName } = virtualNode.props;
37-
const role = getRole(virtualNode, { chromium: true });
37+
const role = getRole(virtualNode, {
38+
chromium: true,
39+
// this check allows fallback roles. For example, `<div role="foo img" aria-label="...">` is legal.
40+
fallback: true
41+
});
3842

3943
const prohibitedList = listProhibitedAttrs(
4044
virtualNode,
@@ -53,7 +57,7 @@ export default function ariaProhibitedAttrEvaluate(
5357
return false;
5458
}
5559

56-
let messageKey = virtualNode.hasAttr('role') ? 'hasRole' : 'noRole';
60+
let messageKey = role !== null ? 'hasRole' : 'noRole';
5761
messageKey += prohibited.length > 1 ? 'Plural' : 'Singular';
5862
this.data({ role, nodeName, messageKey, prohibited });
5963

test/checks/aria/aria-prohibited-attr.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,39 @@ describe('aria-prohibited-attr', () => {
143143
assert.isFalse(checkEvaluate.apply(checkContext, params));
144144
});
145145

146+
it('should not allow aria-label on divs that have an invalid role', function () {
147+
const params = checkSetup(
148+
'<div id="target" role="foo" aria-label="foo"></div>'
149+
);
150+
assert.isTrue(checkEvaluate.apply(checkContext, params));
151+
assert.deepEqual(checkContext._data, {
152+
nodeName: 'div',
153+
role: null,
154+
messageKey: 'noRoleSingular',
155+
prohibited: ['aria-label']
156+
});
157+
});
158+
159+
it('should allow aria-label on divs with a valid fallback role', function () {
160+
const params = checkSetup(
161+
'<div id="target" role="foo dialog" aria-label="foo"></div>'
162+
);
163+
assert.isFalse(checkEvaluate.apply(checkContext, params));
164+
});
165+
166+
it('should not allow aria-label on divs with no valid fallback roles', function () {
167+
const params = checkSetup(
168+
'<div id="target" role="foo bar" aria-label="foo"></div>'
169+
);
170+
assert.isTrue(checkEvaluate.apply(checkContext, params));
171+
assert.deepEqual(checkContext._data, {
172+
nodeName: 'div',
173+
role: null,
174+
messageKey: 'noRoleSingular',
175+
prohibited: ['aria-label']
176+
});
177+
});
178+
146179
describe('widget ancestor', () => {
147180
it('should allow aria-label', () => {
148181
const params = checkSetup(`

test/integration/rules/aria-prohibited-attr/aria-prohibited-attr.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<div role="row" aria-colcount="value" id="pass5"></div>
66
<div role="button"><span id="pass6" aria-label="value"></span></div>
77
<div role="button"><span id="pass7" aria-labelledby="value"></span></div>
8+
<div role="foo dialog" aria-label="foo" id="pass8"></div>
89

910
<div role="caption" aria-label="value" id="fail1"></div>
1011
<div role="caption" aria-labelledby="value" id="fail2"></div>
@@ -26,7 +27,7 @@
2627
<div role="superscript" aria-labelledby="value" id="fail18"></div>
2728
<div aria-label="value" id="fail19"></div>
2829
<div aria-labelledby="value" id="fail20"></div>
29-
<!- aria-label(ledby) is prohibited on none / presentation. Axe-core considers this to trigger presentation role
30+
<!- aria-label(ledby) is prohibited on none / presentation. Axe-core considers this to trigger presentation role
3031
conflict, which was true in ARIA 1.1. This changed in ARIA 1.2 but so far has only been implemented in Chomium. -->
3132
<span aria-label="value" id="fail21"></span>
3233
<strong aria-label="value" id="fail22"></strong>
@@ -39,6 +40,8 @@
3940
<div role="suggestion" aria-labelledby="value" id="fail29"></div>
4041
<div role="grid"><span id="fail30" aria-label="value"></span></div>
4142
<div role="grid"><span id="fail31" aria-labelledby="value"></span></div>
43+
<div role="foo" aria-label="foo" id="fail32"></div>
44+
<div role="foo bar" aria-label="foo" id="fail33"></div>
4245

4346
<div id="incomplete1" aria-label="foo">Foo</div>
4447
<div id="incomplete2" aria-labelledby="missing">Foo</div>

test/integration/rules/aria-prohibited-attr/aria-prohibited-attr.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
["#pass4"],
99
["#pass5"],
1010
["#pass6"],
11-
["#pass7"]
11+
["#pass7"],
12+
["#pass8"]
1213
],
1314
"incomplete": [["#incomplete1"], ["#incomplete2"], ["#incomplete3"]],
1415
"violations": [
@@ -42,6 +43,8 @@
4243
["#fail28"],
4344
["#fail29"],
4445
["#fail30"],
45-
["#fail31"]
46+
["#fail31"],
47+
["#fail32"],
48+
["#fail33"]
4649
]
4750
}

test/integration/virtual-rules/aria-prohibited-attr.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,52 @@ describe('aria-prohibited-attr virtual-rule', () => {
5858
assert.lengthOf(results.violations, 1);
5959
assert.lengthOf(results.incomplete, 0);
6060
});
61+
62+
it('should fail for invalid role', () => {
63+
const vNode = new axe.SerialVirtualNode({
64+
nodeName: 'div',
65+
attributes: {
66+
role: 'foo',
67+
'aria-label': 'foo'
68+
}
69+
});
70+
vNode.children = [];
71+
72+
const results = axe.runVirtualRule('aria-prohibited-attr', vNode);
73+
74+
assert.lengthOf(results.passes, 0);
75+
assert.lengthOf(results.violations, 1);
76+
assert.lengthOf(results.incomplete, 0);
77+
});
78+
79+
it('should pass for fallback roles', () => {
80+
const results = axe.runVirtualRule('aria-prohibited-attr', {
81+
nodeName: 'div',
82+
attributes: {
83+
role: 'foo dialog',
84+
'aria-label': 'foo'
85+
}
86+
});
87+
88+
assert.lengthOf(results.passes, 1);
89+
assert.lengthOf(results.violations, 0);
90+
assert.lengthOf(results.incomplete, 0);
91+
});
92+
93+
it('should fail for multiple invalid roles', () => {
94+
const vNode = new axe.SerialVirtualNode({
95+
nodeName: 'div',
96+
attributes: {
97+
role: 'foo bar',
98+
'aria-label': 'foo'
99+
}
100+
});
101+
vNode.children = [];
102+
103+
const results = axe.runVirtualRule('aria-prohibited-attr', vNode);
104+
105+
assert.lengthOf(results.passes, 0);
106+
assert.lengthOf(results.violations, 1);
107+
assert.lengthOf(results.incomplete, 0);
108+
});
61109
});

0 commit comments

Comments
 (0)