Skip to content

Commit

Permalink
[New] label-has-associated-control: add additional error message
Browse files Browse the repository at this point in the history
Fixes #1005 by introducing a second error message, used when the label
doesn't have accessible text.
  • Loading branch information
BillyLevin authored and ljharb committed Aug 23, 2024
1 parent 75147aa commit 83fd9c4
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 35 deletions.
25 changes: 15 additions & 10 deletions __tests__/src/rules/label-has-associated-control-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const expectedError = {
type: 'JSXOpeningElement',
};

const expectedErrorNoLabel = {
message: 'A form label must have accessible text.',
type: 'JSXOpeningElement',
};

const componentsSettings = {
'jsx-a11y': {
components: {
Expand Down Expand Up @@ -132,12 +137,12 @@ const nestingInvalid = [
];

const neverValid = [
{ code: '<label htmlFor="js_id" />', errors: [expectedError] },
{ code: '<label htmlFor="js_id"><input /></label>', errors: [expectedError] },
{ code: '<label htmlFor="js_id"><textarea /></label>', errors: [expectedError] },
{ code: '<label></label>', errors: [expectedError] },
{ code: '<label htmlFor="js_id" />', errors: [expectedErrorNoLabel] },
{ code: '<label htmlFor="js_id"><input /></label>', errors: [expectedErrorNoLabel] },
{ code: '<label htmlFor="js_id"><textarea /></label>', errors: [expectedErrorNoLabel] },
{ code: '<label></label>', errors: [expectedErrorNoLabel] },
{ code: '<label>A label</label>', errors: [expectedError] },
{ code: '<div><label /><input /></div>', errors: [expectedError] },
{ code: '<div><label /><input /></div>', errors: [expectedErrorNoLabel] },
{ code: '<div><label>A label</label><input /></div>', errors: [expectedError] },
// Custom label component.
{ code: '<CustomLabel aria-label="A label" />', options: [{ labelComponents: ['CustomLabel'] }], errors: [expectedError] },
Expand All @@ -146,11 +151,11 @@ const neverValid = [
// Custom label attributes.
{ code: '<label label="A label" />', options: [{ labelAttributes: ['label'] }], errors: [expectedError] },
// Custom controlComponents.
{ code: '<label><span><CustomInput /></span></label>', options: [{ controlComponents: ['CustomInput'] }], errors: [expectedError] },
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }], errors: [expectedError] },
{ code: '<label><span><CustomInput /></span></label>', settings: componentsSettings, errors: [expectedError] },
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', settings: componentsSettings, errors: [expectedError] },
{ code: '<label><span><CustomInput /></span></label>', options: [{ controlComponents: ['CustomInput'] }], errors: [expectedErrorNoLabel] },
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }], errors: [expectedErrorNoLabel] },
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }], errors: [expectedErrorNoLabel] },
{ code: '<label><span><CustomInput /></span></label>', settings: componentsSettings, errors: [expectedErrorNoLabel] },
{ code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', settings: componentsSettings, errors: [expectedErrorNoLabel] },
];
// htmlFor valid
ruleTester.run(ruleName, rule, {
Expand Down
57 changes: 32 additions & 25 deletions src/rules/label-has-associated-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import mayContainChildComponent from '../util/mayContainChildComponent';
import mayHaveAccessibleLabel from '../util/mayHaveAccessibleLabel';

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

const schema = generateObjSchema({
labelComponents: arraySchema,
Expand Down Expand Up @@ -91,31 +92,37 @@ export default ({
controlComponents,
);

if (hasAccessibleLabel) {
switch (assertType) {
case 'htmlFor':
if (hasLabelId) {
return;
}
break;
case 'nesting':
if (hasNestedControl) {
return;
}
break;
case 'both':
if (hasLabelId && hasNestedControl) {
return;
}
break;
case 'either':
if (hasLabelId || hasNestedControl) {
return;
}
break;
default:
break;
}
if (!hasAccessibleLabel) {
context.report({
node: node.openingElement,
message: errorMessageNoLabel,
});
return;
}

switch (assertType) {
case 'htmlFor':
if (hasLabelId) {
return;
}
break;
case 'nesting':
if (hasNestedControl) {
return;
}
break;
case 'both':
if (hasLabelId && hasNestedControl) {
return;
}
break;
case 'either':
if (hasLabelId || hasNestedControl) {
return;
}
break;
default:
break;
}

// htmlFor case
Expand Down

0 comments on commit 83fd9c4

Please sign in to comment.