Skip to content

Commit 2e242e1

Browse files
authored
fix(aria-allowed-attr): allow aria-required=false when normally not allowed (#4532)
Closes: #3756
1 parent 2f5b7c3 commit 2e242e1

File tree

4 files changed

+72
-32
lines changed

4 files changed

+72
-32
lines changed

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) {
3939

4040
// Unknown ARIA attributes are tested in aria-valid-attr
4141
for (const attrName of virtualNode.attrNames) {
42-
if (validateAttr(attrName) && !allowed.includes(attrName)) {
42+
if (
43+
validateAttr(attrName) &&
44+
!allowed.includes(attrName) &&
45+
!ignoredAttrs(attrName, virtualNode.attr(attrName))
46+
) {
4347
invalid.push(attrName);
4448
}
4549
}
@@ -57,3 +61,9 @@ export default function ariaAllowedAttrEvaluate(node, options, virtualNode) {
5761
}
5862
return false;
5963
}
64+
65+
function ignoredAttrs(attrName, attrValue) {
66+
// allow aria-required=false as screen readers consistently ignore it
67+
// @see https://github.com/dequelabs/axe-core/issues/3756
68+
return attrName === 'aria-required' && attrValue === 'false';
69+
}

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

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
describe('aria-allowed-attr', function () {
1+
describe('aria-allowed-attr', () => {
22
'use strict';
33

4-
var queryFixture = axe.testUtils.queryFixture;
5-
var checkContext = axe.testUtils.MockCheckContext();
4+
const queryFixture = axe.testUtils.queryFixture;
5+
const checkContext = axe.testUtils.MockCheckContext();
66

7-
afterEach(function () {
7+
afterEach(() => {
88
checkContext.reset();
99
});
1010

11-
it('should detect incorrectly used attributes', function () {
12-
var vNode = queryFixture(
11+
it('should detect incorrectly used attributes', () => {
12+
const vNode = queryFixture(
1313
'<div role="link" id="target" tabindex="1" aria-selected="true"></div>'
1414
);
1515

@@ -21,8 +21,8 @@ describe('aria-allowed-attr', function () {
2121
assert.deepEqual(checkContext._data, ['aria-selected="true"']);
2222
});
2323

24-
it('should not report on required attributes', function () {
25-
var vNode = queryFixture(
24+
it('should not report on required attributes', () => {
25+
const vNode = queryFixture(
2626
'<div role="checkbox" id="target" tabindex="1" aria-checked="true"></div>'
2727
);
2828

@@ -33,8 +33,8 @@ describe('aria-allowed-attr', function () {
3333
);
3434
});
3535

36-
it('should detect incorrectly used attributes - implicit role', function () {
37-
var vNode = queryFixture(
36+
it('should detect incorrectly used attributes - implicit role', () => {
37+
const vNode = queryFixture(
3838
'<a href="#" id="target" tabindex="1" aria-selected="true"></a>'
3939
);
4040

@@ -46,8 +46,8 @@ describe('aria-allowed-attr', function () {
4646
assert.deepEqual(checkContext._data, ['aria-selected="true"']);
4747
});
4848

49-
it('should return true for global attributes if there is no role', function () {
50-
var vNode = queryFixture(
49+
it('should return true for global attributes if there is no role', () => {
50+
const vNode = queryFixture(
5151
'<div id="target" tabindex="1" aria-busy="true" aria-owns="foo"></div>'
5252
);
5353

@@ -59,8 +59,8 @@ describe('aria-allowed-attr', function () {
5959
assert.isNull(checkContext._data);
6060
});
6161

62-
it('should return false for non-global attributes if there is no role', function () {
63-
var vNode = queryFixture(
62+
it('should return false for non-global attributes if there is no role', () => {
63+
const vNode = queryFixture(
6464
'<div id="target" tabindex="1" aria-selected="true" aria-owns="foo"></div>'
6565
);
6666

@@ -72,8 +72,8 @@ describe('aria-allowed-attr', function () {
7272
assert.deepEqual(checkContext._data, ['aria-selected="true"']);
7373
});
7474

75-
it('should not report on invalid attributes', function () {
76-
var vNode = queryFixture(
75+
it('should not report on invalid attributes', () => {
76+
const vNode = queryFixture(
7777
'<div role="dialog" id="target" tabindex="1" aria-cats="true"></div>'
7878
);
7979

@@ -85,8 +85,8 @@ describe('aria-allowed-attr', function () {
8585
assert.isNull(checkContext._data);
8686
});
8787

88-
it('should not report on allowed attributes', function () {
89-
var vNode = queryFixture(
88+
it('should not report on allowed attributes', () => {
89+
const vNode = queryFixture(
9090
'<div role="radio" id="target" tabindex="1" aria-required="true" aria-checked="true"></div>'
9191
);
9292

@@ -98,8 +98,34 @@ describe('aria-allowed-attr', function () {
9898
assert.isNull(checkContext._data);
9999
});
100100

101-
it('should return undefined for custom element that has no role and is not focusable', function () {
102-
var vNode = queryFixture(
101+
it('should not report on aria-required=false', () => {
102+
const vNode = queryFixture(
103+
'<button id="target" aria-required="false"></button>'
104+
);
105+
106+
assert.isTrue(
107+
axe.testUtils
108+
.getCheckEvaluate('aria-allowed-attr')
109+
.call(checkContext, null, null, vNode)
110+
);
111+
assert.isNull(checkContext._data);
112+
});
113+
114+
it('should return false for unallowed aria-required=true', () => {
115+
const vNode = queryFixture(
116+
'<button id="target" aria-required="true"></button>'
117+
);
118+
119+
assert.isFalse(
120+
axe.testUtils
121+
.getCheckEvaluate('aria-allowed-attr')
122+
.call(checkContext, null, null, vNode)
123+
);
124+
assert.deepEqual(checkContext._data, ['aria-required="true"']);
125+
});
126+
127+
it('should return undefined for custom element that has no role and is not focusable', () => {
128+
const vNode = queryFixture(
103129
'<my-custom-element id="target" aria-expanded="true"></my-custom-element>'
104130
);
105131

@@ -111,8 +137,8 @@ describe('aria-allowed-attr', function () {
111137
assert.isNotNull(checkContext._data);
112138
});
113139

114-
it("should return false for custom element that has a role which doesn't allow the attribute", function () {
115-
var vNode = queryFixture(
140+
it("should return false for custom element that has a role which doesn't allow the attribute", () => {
141+
const vNode = queryFixture(
116142
'<my-custom-element role="insertion" id="target" aria-expanded="true"></my-custom-element>'
117143
);
118144

@@ -124,8 +150,8 @@ describe('aria-allowed-attr', function () {
124150
assert.isNotNull(checkContext._data);
125151
});
126152

127-
it('should return false for custom element that is focusable', function () {
128-
var vNode = queryFixture(
153+
it('should return false for custom element that is focusable', () => {
154+
const vNode = queryFixture(
129155
'<my-custom-element tabindex="1" id="target" aria-expanded="true"></my-custom-element>'
130156
);
131157

@@ -137,8 +163,8 @@ describe('aria-allowed-attr', function () {
137163
assert.isNotNull(checkContext._data);
138164
});
139165

140-
describe('options', function () {
141-
it('should allow provided attribute names for a role', function () {
166+
describe('options', () => {
167+
it('should allow provided attribute names for a role', () => {
142168
axe.configure({
143169
standards: {
144170
ariaRoles: {
@@ -149,7 +175,7 @@ describe('aria-allowed-attr', function () {
149175
}
150176
});
151177

152-
var vNode = queryFixture(
178+
const vNode = queryFixture(
153179
'<div role="mccheddarton" id="target" aria-checked="true" aria-selected="true"></div>'
154180
);
155181

@@ -171,7 +197,7 @@ describe('aria-allowed-attr', function () {
171197
);
172198
});
173199

174-
it('should handle multiple roles provided in options', function () {
200+
it('should handle multiple roles provided in options', () => {
175201
axe.configure({
176202
standards: {
177203
ariaRoles: {
@@ -185,10 +211,10 @@ describe('aria-allowed-attr', function () {
185211
}
186212
});
187213

188-
var vNode = queryFixture(
214+
const vNode = queryFixture(
189215
'<div role="bagley" id="target" aria-selected="true"></div>'
190216
);
191-
var options = {
217+
const options = {
192218
mccheddarton: ['aria-selected'],
193219
bagley: ['aria-selected']
194220
};

test/integration/rules/aria-allowed-attr/passes.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2169,3 +2169,6 @@
21692169
<input type="checkbox" aria-checked="mixed" id="pass99" />
21702170

21712171
<search aria-expanded="true" id="pass100"></search>
2172+
2173+
<!-- Ignored ARIA attributes -->
2174+
<button id="pass101" aria-required="false"></button>

test/integration/rules/aria-allowed-attr/passes.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
["#pass97"],
103103
["#pass98"],
104104
["#pass99"],
105-
["#pass100"]
105+
["#pass100"],
106+
["#pass101"]
106107
]
107108
}

0 commit comments

Comments
 (0)