Skip to content

Commit 83fd9c4

Browse files
BillyLevinljharb
authored andcommitted
[New] label-has-associated-control: add additional error message
Fixes #1005 by introducing a second error message, used when the label doesn't have accessible text.
1 parent 75147aa commit 83fd9c4

File tree

2 files changed

+47
-35
lines changed

2 files changed

+47
-35
lines changed

__tests__/src/rules/label-has-associated-control-test.js

+15-10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ const expectedError = {
2626
type: 'JSXOpeningElement',
2727
};
2828

29+
const expectedErrorNoLabel = {
30+
message: 'A form label must have accessible text.',
31+
type: 'JSXOpeningElement',
32+
};
33+
2934
const componentsSettings = {
3035
'jsx-a11y': {
3136
components: {
@@ -132,12 +137,12 @@ const nestingInvalid = [
132137
];
133138

134139
const neverValid = [
135-
{ code: '<label htmlFor="js_id" />', errors: [expectedError] },
136-
{ code: '<label htmlFor="js_id"><input /></label>', errors: [expectedError] },
137-
{ code: '<label htmlFor="js_id"><textarea /></label>', errors: [expectedError] },
138-
{ code: '<label></label>', errors: [expectedError] },
140+
{ code: '<label htmlFor="js_id" />', errors: [expectedErrorNoLabel] },
141+
{ code: '<label htmlFor="js_id"><input /></label>', errors: [expectedErrorNoLabel] },
142+
{ code: '<label htmlFor="js_id"><textarea /></label>', errors: [expectedErrorNoLabel] },
143+
{ code: '<label></label>', errors: [expectedErrorNoLabel] },
139144
{ code: '<label>A label</label>', errors: [expectedError] },
140-
{ code: '<div><label /><input /></div>', errors: [expectedError] },
145+
{ code: '<div><label /><input /></div>', errors: [expectedErrorNoLabel] },
141146
{ code: '<div><label>A label</label><input /></div>', errors: [expectedError] },
142147
// Custom label component.
143148
{ code: '<CustomLabel aria-label="A label" />', options: [{ labelComponents: ['CustomLabel'] }], errors: [expectedError] },
@@ -146,11 +151,11 @@ const neverValid = [
146151
// Custom label attributes.
147152
{ code: '<label label="A label" />', options: [{ labelAttributes: ['label'] }], errors: [expectedError] },
148153
// Custom controlComponents.
149-
{ code: '<label><span><CustomInput /></span></label>', options: [{ controlComponents: ['CustomInput'] }], errors: [expectedError] },
150-
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
151-
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }], errors: [expectedError] },
152-
{ code: '<label><span><CustomInput /></span></label>', settings: componentsSettings, errors: [expectedError] },
153-
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', settings: componentsSettings, errors: [expectedError] },
154+
{ code: '<label><span><CustomInput /></span></label>', options: [{ controlComponents: ['CustomInput'] }], errors: [expectedErrorNoLabel] },
155+
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }], errors: [expectedErrorNoLabel] },
156+
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }], errors: [expectedErrorNoLabel] },
157+
{ code: '<label><span><CustomInput /></span></label>', settings: componentsSettings, errors: [expectedErrorNoLabel] },
158+
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', settings: componentsSettings, errors: [expectedErrorNoLabel] },
154159
];
155160
// htmlFor valid
156161
ruleTester.run(ruleName, rule, {

src/rules/label-has-associated-control.js

+32-25
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import mayContainChildComponent from '../util/mayContainChildComponent';
1818
import mayHaveAccessibleLabel from '../util/mayHaveAccessibleLabel';
1919

2020
const errorMessage = 'A form label must be associated with a control.';
21+
const errorMessageNoLabel = 'A form label must have accessible text.';
2122

2223
const schema = generateObjSchema({
2324
labelComponents: arraySchema,
@@ -91,31 +92,37 @@ export default ({
9192
controlComponents,
9293
);
9394

94-
if (hasAccessibleLabel) {
95-
switch (assertType) {
96-
case 'htmlFor':
97-
if (hasLabelId) {
98-
return;
99-
}
100-
break;
101-
case 'nesting':
102-
if (hasNestedControl) {
103-
return;
104-
}
105-
break;
106-
case 'both':
107-
if (hasLabelId && hasNestedControl) {
108-
return;
109-
}
110-
break;
111-
case 'either':
112-
if (hasLabelId || hasNestedControl) {
113-
return;
114-
}
115-
break;
116-
default:
117-
break;
118-
}
95+
if (!hasAccessibleLabel) {
96+
context.report({
97+
node: node.openingElement,
98+
message: errorMessageNoLabel,
99+
});
100+
return;
101+
}
102+
103+
switch (assertType) {
104+
case 'htmlFor':
105+
if (hasLabelId) {
106+
return;
107+
}
108+
break;
109+
case 'nesting':
110+
if (hasNestedControl) {
111+
return;
112+
}
113+
break;
114+
case 'both':
115+
if (hasLabelId && hasNestedControl) {
116+
return;
117+
}
118+
break;
119+
case 'either':
120+
if (hasLabelId || hasNestedControl) {
121+
return;
122+
}
123+
break;
124+
default:
125+
break;
119126
}
120127

121128
// htmlFor case

0 commit comments

Comments
 (0)