Skip to content

Commit dd49060

Browse files
committed
Added test cases for an empty or undefined value of aria-label and aria-labelledby in alt-text
1 parent e8414ef commit dd49060

File tree

2 files changed

+64
-16
lines changed

2 files changed

+64
-16
lines changed

__tests__/src/rules/alt-text-test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ Use alt="" for presentational images.`,
2929
type: 'JSXOpeningElement',
3030
});
3131

32+
const ariaLabelValueError = 'The aria-label attribute must have a value. The alt attribute is preferred over aria-label for images.';
33+
const ariaLabelledbyValueError = 'The aria-labelledby attribute must have a value. The alt attribute is preferred over aria-labelledby for images.';
34+
3235
const preferAltError = () => ({
3336
message: 'Prefer alt="" over a presentational role. First rule of aria is to not use aria if it can be achieved via native HTML.',
3437
type: 'JSXOpeningElement',
@@ -170,25 +173,41 @@ ruleTester.run('alt-text', rule, {
170173
{ code: '<img alt role="presentation" />;', errors: [altValueError('img')] },
171174
{ code: '<img role="presentation" />;', errors: [preferAltError()] },
172175
{ code: '<img role="none" />;', errors: [preferAltError()] },
176+
{ code: '<img aria-label={undefined} />', errors: [ariaLabelValueError] },
177+
{ code: '<img aria-labelledby={undefined} />', errors: [ariaLabelledbyValueError] },
178+
{ code: '<img aria-label="" />', errors: [ariaLabelValueError] },
179+
{ code: '<img aria-labelledby="" />', errors: [ariaLabelledbyValueError] },
173180

174181
// DEFAULT ELEMENT 'object' TESTS
175182
{ code: '<object />', errors: [objectError] },
176183
{ code: '<object><div aria-hidden /></object>', errors: [objectError] },
177184
{ code: '<object title={undefined} />', errors: [objectError] },
185+
{ code: '<object aria-label="" />', errors: [objectError] },
186+
{ code: '<object aria-labelledby="" />', errors: [objectError] },
187+
{ code: '<object aria-label={undefined} />', errors: [objectError] },
188+
{ code: '<object aria-labelledby={undefined} />', errors: [objectError] },
178189

179190
// DEFAULT ELEMENT 'area' TESTS
180191
{ code: '<area />', errors: [areaError] },
181192
{ code: '<area alt />', errors: [areaError] },
182193
{ code: '<area alt={undefined} />', errors: [areaError] },
183194
{ code: '<area src="xyz" />', errors: [areaError] },
184195
{ code: '<area {...this.props} />', errors: [areaError] },
196+
{ code: '<area aria-label="" />', errors: [areaError] },
197+
{ code: '<area aria-label={undefined} />', errors: [areaError] },
198+
{ code: '<area aria-labelledby="" />', errors: [areaError] },
199+
{ code: '<area aria-labelledby={undefined} />', errors: [areaError] },
185200

186201
// DEFAULT ELEMENT 'input type="image"' TESTS
187202
{ code: '<input type="image" />', errors: [inputImageError] },
188203
{ code: '<input type="image" alt />', errors: [inputImageError] },
189204
{ code: '<input type="image" alt={undefined} />', errors: [inputImageError] },
190205
{ code: '<input type="image">Foo</input>', errors: [inputImageError] },
191206
{ code: '<input type="image" {...this.props} />', errors: [inputImageError] },
207+
{ code: '<input type="image" aria-label="" />', errors: [inputImageError] },
208+
{ code: '<input type="image" aria-label={undefined} />', errors: [inputImageError] },
209+
{ code: '<input type="image" aria-labelledby="" />', errors: [inputImageError] },
210+
{ code: '<input type="image" aria-labelledby={undefined} />', errors: [inputImageError] },
192211

193212
// CUSTOM ELEMENT TESTS FOR ARRAY OPTION TESTS
194213
{

src/rules/alt-text.js

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,19 @@ const schema = generateObjSchema({
3232
'input[type="image"]': arraySchema,
3333
});
3434

35+
const ariaLabelHasValue = (prop) => {
36+
const value = getPropValue(prop);
37+
if (value === undefined) {
38+
return false;
39+
}
40+
if (typeof value === 'string' && value.length === 0) {
41+
return false;
42+
}
43+
return true;
44+
};
45+
3546
const ruleByElement = {
3647
img(context, node) {
37-
// Check for label props (`aria-label` and `aria-labelledby`) to provide text alternative
38-
const ariaLabelProp = getProp(node.attributes, 'aria-label');
39-
const arialLabelledByProp = getProp(node.attributes, 'aria-labelledby');
40-
const hasLabel = ariaLabelProp !== undefined || arialLabelledByProp !== undefined;
41-
42-
if (hasLabel) {
43-
return;
44-
}
45-
4648
const nodeType = elementType(node);
4749
const altProp = getProp(node.attributes, 'alt');
4850

@@ -55,6 +57,33 @@ const ruleByElement = {
5557
});
5658
return;
5759
}
60+
// Check for `aria-label` to provide text alternative
61+
// Don't create an error if the attribute is used correctly. But if it
62+
// isn't, suggest that the developer use `alt` instead.
63+
const ariaLabelProp = getProp(node.attributes, 'aria-label');
64+
if (ariaLabelProp !== undefined) {
65+
if (!ariaLabelHasValue(ariaLabelProp)) {
66+
context.report({
67+
node,
68+
message: 'The aria-label attribute must have a value. The alt attribute is preferred over aria-label for images.',
69+
});
70+
}
71+
return;
72+
}
73+
// Check for `aria-labelledby` to provide text alternative
74+
// Don't create an error if the attribute is used correctly. But if it
75+
// isn't, suggest that the developer use `alt` instead.
76+
const ariaLabelledbyProp = getProp(node.attributes, 'aria-labelledby');
77+
if (ariaLabelledbyProp !== undefined) {
78+
if (!ariaLabelHasValue(ariaLabelledbyProp)) {
79+
context.report({
80+
node,
81+
message: 'The aria-labelledby attribute must have a value. The alt attribute is preferred over aria-labelledby for images.',
82+
});
83+
}
84+
return;
85+
}
86+
5887
context.report({
5988
node,
6089
message: `${nodeType} elements must have an alt prop, either with meaningful text, or an empty string for decorative images.`,
@@ -80,7 +109,7 @@ const ruleByElement = {
80109
object(context, node) {
81110
const ariaLabelProp = getProp(node.attributes, 'aria-label');
82111
const arialLabelledByProp = getProp(node.attributes, 'aria-labelledby');
83-
const hasLabel = ariaLabelProp !== undefined || arialLabelledByProp !== undefined;
112+
const hasLabel = ariaLabelHasValue(ariaLabelProp) || ariaLabelHasValue(arialLabelledByProp);
84113
const titleProp = getLiteralPropValue(getProp(node.attributes, 'title'));
85114
const hasTitleAttr = !!titleProp;
86115

@@ -95,9 +124,9 @@ const ruleByElement = {
95124
},
96125

97126
area(context, node) {
98-
const ariaLabelPropValue = getPropValue(getProp(node.attributes, 'aria-label'));
99-
const arialLabelledByPropValue = getPropValue(getProp(node.attributes, 'aria-labelledby'));
100-
const hasLabel = ariaLabelPropValue !== undefined || arialLabelledByPropValue !== undefined;
127+
const ariaLabelProp = getProp(node.attributes, 'aria-label');
128+
const arialLabelledByProp = getProp(node.attributes, 'aria-labelledby');
129+
const hasLabel = ariaLabelHasValue(ariaLabelProp) || ariaLabelHasValue(arialLabelledByProp);
101130

102131
if (hasLabel) {
103132
return;
@@ -132,9 +161,9 @@ const ruleByElement = {
132161
const typePropValue = getPropValue(getProp(node.attributes, 'type'));
133162
if (typePropValue !== 'image') { return; }
134163
}
135-
const ariaLabelPropValue = getPropValue(getProp(node.attributes, 'aria-label'));
136-
const arialLabelledByPropValue = getPropValue(getProp(node.attributes, 'aria-labelledby'));
137-
const hasLabel = ariaLabelPropValue !== undefined || arialLabelledByPropValue !== undefined;
164+
const ariaLabelProp = getProp(node.attributes, 'aria-label');
165+
const arialLabelledByProp = getProp(node.attributes, 'aria-labelledby');
166+
const hasLabel = ariaLabelHasValue(ariaLabelProp) || ariaLabelHasValue(arialLabelledByProp);
138167

139168
if (hasLabel) {
140169
return;

0 commit comments

Comments
 (0)