forked from dequelabs/axe-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
aria-valid-attr-value-evaluate.js
152 lines (137 loc) · 4.49 KB
/
aria-valid-attr-value-evaluate.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import { validateAttrValue } from '../../commons/aria';
import standards from '../../standards';
/**
* Check that each ARIA attribute on an element has a valid value.
*
* Valid ARIA attribute values are taken from the `ariaAttrs` standards object from an attributes `type` property.
*
* ##### Data:
* <table class="props">
* <thead>
* <tr>
* <th>Type</th>
* <th>Description</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><code>Mixed</code></td>
* <td>Object with Strings `messageKey` and `needsReview` if `aria-current` or `aria-describedby` are invalid. Otherwise a list of all invalid ARIA attributes and their value</td>
* </tr>
* </tbody>
* </table>
*
* @memberof checks
* @return {Mixed} True if all ARIA attributes have a valid value. Undefined for invalid `aria-current` or `aria-describedby` values. False otherwise.
*/
export default function ariaValidAttrValueEvaluate(node, options, virtualNode) {
options = Array.isArray(options.value) ? options.value : [];
let needsReview = '';
let messageKey = '';
const invalid = [];
const aria = /^aria-/;
const skipAttrs = ['aria-errormessage'];
const preChecks = {
// aria-controls should only check if element exists if the element
// doesn't have aria-expanded=false, aria-selected=false (tabs),
// or aria-haspopup (may load later)
// @see https://github.com/dequelabs/axe-core/issues/1463
// @see https://github.com/dequelabs/axe-core/issues/4363
'aria-controls': () => {
const hasPopup =
['false', null].includes(virtualNode.attr('aria-haspopup')) === false;
if (hasPopup) {
needsReview = `aria-controls="${virtualNode.attr('aria-controls')}"`;
messageKey = 'controlsWithinPopup';
}
return (
virtualNode.attr('aria-expanded') !== 'false' &&
virtualNode.attr('aria-selected') !== 'false' &&
hasPopup === false
);
},
// aria-current should mark as needs review if any value is used that is
// not one of the valid values (since any value is treated as "true")
'aria-current': validValue => {
if (!validValue) {
needsReview = `aria-current="${virtualNode.attr('aria-current')}"`;
messageKey = 'ariaCurrent';
}
return;
},
// aria-owns should only check if element exists if the element
// doesn't have aria-expanded=false (combobox)
// @see https://github.com/dequelabs/axe-core/issues/1524
'aria-owns': () => {
return virtualNode.attr('aria-expanded') !== 'false';
},
// aria-describedby should not mark missing element as violation but
// instead as needs review
// @see https://github.com/dequelabs/axe-core/issues/1151
'aria-describedby': validValue => {
if (!validValue) {
needsReview = `aria-describedby="${virtualNode.attr(
'aria-describedby'
)}"`;
// TODO: es-modules_tree
messageKey =
axe._tree && axe._tree[0]._hasShadowRoot ? 'noIdShadow' : 'noId';
}
return;
},
// aria-labelledby should not mark missing element as violation but
// instead as needs review
// @see https://github.com/dequelabs/axe-core/issues/2621
'aria-labelledby': validValue => {
if (!validValue) {
needsReview = `aria-labelledby="${virtualNode.attr(
'aria-labelledby'
)}"`;
// TODO: es-modules_tree
messageKey =
axe._tree && axe._tree[0]._hasShadowRoot ? 'noIdShadow' : 'noId';
}
}
};
virtualNode.attrNames.forEach(attrName => {
if (
skipAttrs.includes(attrName) ||
options.includes(attrName) ||
!aria.test(attrName)
) {
return;
}
let validValue;
const attrValue = virtualNode.attr(attrName);
try {
validValue = validateAttrValue(virtualNode, attrName);
} catch {
needsReview = `${attrName}="${attrValue}"`;
messageKey = 'idrefs';
return;
}
if (
(preChecks[attrName] ? preChecks[attrName](validValue) : true) &&
!validValue
) {
if (attrValue === '' && !isStringType(attrName)) {
needsReview = attrName;
messageKey = 'empty';
} else {
invalid.push(`${attrName}="${attrValue}"`);
}
}
});
if (invalid.length) {
this.data(invalid);
return false;
}
if (needsReview) {
this.data({ messageKey, needsReview });
return undefined;
}
return true;
}
function isStringType(attrName) {
return standards.ariaAttrs[attrName]?.type === 'string';
}