@@ -21,15 +21,20 @@ const ruleTester = new RuleTester();
21
21
22
22
const ruleName = 'label-has-associated-control' ;
23
23
24
- const expectedError = {
25
- message : 'A form label must be associated with a control.' ,
26
- type : 'JSXOpeningElement' ,
27
- } ;
28
-
29
- const expectedErrorNoLabel = {
30
- message : 'A form label must have accessible text.' ,
31
- type : 'JSXOpeningElement' ,
24
+ const errorMessages = {
25
+ accessibleLabel : 'A form label must have accessible text.' ,
26
+ htmlFor : 'A form label must have a valid htmlFor attribute.' ,
27
+ nesting : 'A form label must have an associated control as a descendant.' ,
28
+ either : 'A form label must either have a valid htmlFor attribute or a control as a descendant.' ,
29
+ both : 'A form label must have a valid htmlFor attribute and a control as a descendant.' ,
32
30
} ;
31
+ const expectedErrors = { } ;
32
+ Object . keys ( errorMessages ) . forEach ( ( key ) => {
33
+ expectedErrors [ key ] = {
34
+ message : errorMessages [ key ] ,
35
+ type : 'JSXOpeningElement' ,
36
+ } ;
37
+ } ) ;
33
38
34
39
const componentsSettings = {
35
40
'jsx-a11y' : {
@@ -123,58 +128,58 @@ const alwaysValid = [
123
128
{ code : '<input type="hidden" />' } ,
124
129
] ;
125
130
126
- const htmlForInvalid = [
127
- { code : '<label htmlFor="js_id"><span><span><span>A label</span></span></span></label>' , options : [ { depth : 4 } ] , errors : [ expectedError ] } ,
128
- { code : '<label htmlFor="js_id" aria-label="A label" />' , errors : [ expectedError ] } ,
129
- { code : '<label htmlFor="js_id" aria-labelledby="A label" />' , errors : [ expectedError ] } ,
131
+ const htmlForInvalid = ( assertType ) => [
132
+ { code : '<label htmlFor="js_id"><span><span><span>A label</span></span></span></label>' , options : [ { depth : 4 } ] , errors : [ expectedErrors [ assertType ] ] } ,
133
+ { code : '<label htmlFor="js_id" aria-label="A label" />' , errors : [ expectedErrors [ assertType ] ] } ,
134
+ { code : '<label htmlFor="js_id" aria-labelledby="A label" />' , errors : [ expectedErrors [ assertType ] ] } ,
130
135
// Custom label component.
131
- { code : '<CustomLabel htmlFor="js_id" aria-label="A label" />' , options : [ { labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
132
- { code : '<CustomLabel htmlFor="js_id" label="A label" />' , options : [ { labelAttributes : [ 'label' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
133
- { code : '<CustomLabel htmlFor="js_id" aria-label="A label" />' , settings : componentsSettings , errors : [ expectedError ] } ,
136
+ { code : '<CustomLabel htmlFor="js_id" aria-label="A label" />' , options : [ { labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
137
+ { code : '<CustomLabel htmlFor="js_id" label="A label" />' , options : [ { labelAttributes : [ 'label' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
138
+ { code : '<CustomLabel htmlFor="js_id" aria-label="A label" />' , settings : componentsSettings , errors : [ expectedErrors [ assertType ] ] } ,
134
139
// Custom label attributes.
135
- { code : '<label htmlFor="js_id" label="A label" />' , options : [ { labelAttributes : [ 'label' ] } ] , errors : [ expectedError ] } ,
140
+ { code : '<label htmlFor="js_id" label="A label" />' , options : [ { labelAttributes : [ 'label' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
136
141
] ;
137
- const nestingInvalid = [
138
- { code : '<label>A label<input /></label>' , errors : [ expectedError ] } ,
139
- { code : '<label>A label<textarea /></label>' , errors : [ expectedError ] } ,
140
- { code : '<label><img alt="A label" /><input /></label>' , errors : [ expectedError ] } ,
141
- { code : '<label><img aria-label="A label" /><input /></label>' , errors : [ expectedError ] } ,
142
- { code : '<label><span>A label<input /></span></label>' , errors : [ expectedError ] } ,
143
- { code : '<label><span><span>A label<input /></span></span></label>' , options : [ { depth : 3 } ] , errors : [ expectedError ] } ,
144
- { code : '<label><span><span><span>A label<input /></span></span></span></label>' , options : [ { depth : 4 } ] , errors : [ expectedError ] } ,
145
- { code : '<label><span><span><span><span>A label</span><input /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedError ] } ,
146
- { code : '<label><span><span><span><span aria-label="A label" /><input /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedError ] } ,
147
- { code : '<label><span><span><span><input aria-label="A label" /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedError ] } ,
142
+ const nestingInvalid = ( assertType ) => [
143
+ { code : '<label>A label<input /></label>' , errors : [ expectedErrors [ assertType ] ] } ,
144
+ { code : '<label>A label<textarea /></label>' , errors : [ expectedErrors [ assertType ] ] } ,
145
+ { code : '<label><img alt="A label" /><input /></label>' , errors : [ expectedErrors [ assertType ] ] } ,
146
+ { code : '<label><img aria-label="A label" /><input /></label>' , errors : [ expectedErrors [ assertType ] ] } ,
147
+ { code : '<label><span>A label<input /></span></label>' , errors : [ expectedErrors [ assertType ] ] } ,
148
+ { code : '<label><span><span>A label<input /></span></span></label>' , options : [ { depth : 3 } ] , errors : [ expectedErrors [ assertType ] ] } ,
149
+ { code : '<label><span><span><span>A label<input /></span></span></span></label>' , options : [ { depth : 4 } ] , errors : [ expectedErrors [ assertType ] ] } ,
150
+ { code : '<label><span><span><span><span>A label</span><input /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedErrors [ assertType ] ] } ,
151
+ { code : '<label><span><span><span><span aria-label="A label" /><input /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedErrors [ assertType ] ] } ,
152
+ { code : '<label><span><span><span><input aria-label="A label" /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedErrors [ assertType ] ] } ,
148
153
// Custom controlComponents.
149
- { code : '<label>A label<OtherCustomInput /></label>' , options : [ { controlComponents : [ 'CustomInput' ] } ] , errors : [ expectedError ] } ,
150
- { code : '<label><span>A label<CustomInput /></span></label>' , options : [ { controlComponents : [ 'CustomInput' ] } ] , errors : [ expectedError ] } ,
151
- { code : '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
152
- { code : '<CustomLabel><span label="A label"><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] , labelAttributes : [ 'label' ] } ] , errors : [ expectedError ] } ,
153
- { code : '<label><span>A label<CustomInput /></span></label>' , settings : componentsSettings , errors : [ expectedError ] } ,
154
- { code : '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>' , settings : componentsSettings , errors : [ expectedError ] } ,
154
+ { code : '<label>A label<OtherCustomInput /></label>' , options : [ { controlComponents : [ 'CustomInput' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
155
+ { code : '<label><span>A label<CustomInput /></span></label>' , options : [ { controlComponents : [ 'CustomInput' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
156
+ { code : '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
157
+ { code : '<CustomLabel><span label="A label"><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] , labelAttributes : [ 'label' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
158
+ { code : '<label><span>A label<CustomInput /></span></label>' , settings : componentsSettings , errors : [ expectedErrors [ assertType ] ] } ,
159
+ { code : '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>' , settings : componentsSettings , errors : [ expectedErrors [ assertType ] ] } ,
155
160
] ;
156
161
157
- const neverValid = [
158
- { code : '<label htmlFor="js_id" />' , errors : [ expectedErrorNoLabel ] } ,
159
- { code : '<label htmlFor="js_id"><input /></label>' , errors : [ expectedErrorNoLabel ] } ,
160
- { code : '<label htmlFor="js_id"><textarea /></label>' , errors : [ expectedErrorNoLabel ] } ,
161
- { code : '<label></label>' , errors : [ expectedErrorNoLabel ] } ,
162
- { code : '<label>A label</label>' , errors : [ expectedError ] } ,
163
- { code : '<div><label /><input /></div>' , errors : [ expectedErrorNoLabel ] } ,
164
- { code : '<div><label>A label</label><input /></div>' , errors : [ expectedError ] } ,
162
+ const neverValid = ( assertType ) => [
163
+ { code : '<label htmlFor="js_id" />' , errors : [ expectedErrors . accessibleLabel ] } ,
164
+ { code : '<label htmlFor="js_id"><input /></label>' , errors : [ expectedErrors . accessibleLabel ] } ,
165
+ { code : '<label htmlFor="js_id"><textarea /></label>' , errors : [ expectedErrors . accessibleLabel ] } ,
166
+ { code : '<label></label>' , errors : [ expectedErrors . accessibleLabel ] } ,
167
+ { code : '<label>A label</label>' , errors : [ expectedErrors [ assertType ] ] } ,
168
+ { code : '<div><label /><input /></div>' , errors : [ expectedErrors . accessibleLabel ] } ,
169
+ { code : '<div><label>A label</label><input /></div>' , errors : [ expectedErrors [ assertType ] ] } ,
165
170
// Custom label component.
166
- { code : '<CustomLabel aria-label="A label" />' , options : [ { labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
167
- { code : '<MUILabel aria-label="A label" />' , options : [ { labelComponents : [ '???Label' ] } ] , errors : [ expectedError ] } ,
168
- { code : '<CustomLabel label="A label" />' , options : [ { labelAttributes : [ 'label' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
169
- { code : '<CustomLabel aria-label="A label" />' , settings : componentsSettings , errors : [ expectedError ] } ,
171
+ { code : '<CustomLabel aria-label="A label" />' , options : [ { labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
172
+ { code : '<MUILabel aria-label="A label" />' , options : [ { labelComponents : [ '???Label' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
173
+ { code : '<CustomLabel label="A label" />' , options : [ { labelAttributes : [ 'label' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
174
+ { code : '<CustomLabel aria-label="A label" />' , settings : componentsSettings , errors : [ expectedErrors [ assertType ] ] } ,
170
175
// Custom label attributes.
171
- { code : '<label label="A label" />' , options : [ { labelAttributes : [ 'label' ] } ] , errors : [ expectedError ] } ,
176
+ { code : '<label label="A label" />' , options : [ { labelAttributes : [ 'label' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
172
177
// Custom controlComponents.
173
- { code : '<label><span><CustomInput /></span></label>' , options : [ { controlComponents : [ 'CustomInput' ] } ] , errors : [ expectedErrorNoLabel ] } ,
174
- { code : '<CustomLabel><span><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrorNoLabel ] } ,
175
- { code : '<CustomLabel><span><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] , labelAttributes : [ 'label' ] } ] , errors : [ expectedErrorNoLabel ] } ,
176
- { code : '<label><span><CustomInput /></span></label>' , settings : componentsSettings , errors : [ expectedErrorNoLabel ] } ,
177
- { code : '<CustomLabel><span><CustomInput /></span></CustomLabel>' , settings : componentsSettings , errors : [ expectedErrorNoLabel ] } ,
178
+ { code : '<label><span><CustomInput /></span></label>' , options : [ { controlComponents : [ 'CustomInput' ] } ] , errors : [ expectedErrors . accessibleLabel ] } ,
179
+ { code : '<CustomLabel><span><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors . accessibleLabel ] } ,
180
+ { code : '<CustomLabel><span><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] , labelAttributes : [ 'label' ] } ] , errors : [ expectedErrors . accessibleLabel ] } ,
181
+ { code : '<label><span><CustomInput /></span></label>' , settings : componentsSettings , errors : [ expectedErrors . accessibleLabel ] } ,
182
+ { code : '<CustomLabel><span><CustomInput /></span></CustomLabel>' , settings : componentsSettings , errors : [ expectedErrors . accessibleLabel ] } ,
178
183
] ;
179
184
// htmlFor valid
180
185
ruleTester . run ( ruleName , rule , {
@@ -187,8 +192,8 @@ ruleTester.run(ruleName, rule, {
187
192
} ) )
188
193
. map ( parserOptionsMapper ) ,
189
194
invalid : parsers . all ( [ ] . concat (
190
- ...neverValid ,
191
- ...nestingInvalid ,
195
+ ...neverValid ( 'htmlFor' ) ,
196
+ ...nestingInvalid ( 'htmlFor' ) ,
192
197
) )
193
198
. map ( ruleOptionsMapperFactory ( {
194
199
assert : 'htmlFor' ,
@@ -207,8 +212,8 @@ ruleTester.run(ruleName, rule, {
207
212
} ) )
208
213
. map ( parserOptionsMapper ) ,
209
214
invalid : parsers . all ( [ ] . concat (
210
- ...neverValid ,
211
- ...htmlForInvalid ,
215
+ ...neverValid ( 'nesting' ) ,
216
+ ...htmlForInvalid ( 'nesting' ) ,
212
217
) )
213
218
. map ( ruleOptionsMapperFactory ( {
214
219
assert : 'nesting' ,
@@ -228,8 +233,10 @@ ruleTester.run(ruleName, rule, {
228
233
} ) )
229
234
. map ( parserOptionsMapper ) ,
230
235
invalid : parsers . all ( [ ] . concat (
231
- ...neverValid ,
232
- ) ) . map ( parserOptionsMapper ) ,
236
+ ...neverValid ( 'either' ) ,
237
+ ) ) . map ( ruleOptionsMapperFactory ( {
238
+ assert : 'either' ,
239
+ } ) ) . map ( parserOptionsMapper ) ,
233
240
} ) ;
234
241
235
242
// both valid
@@ -243,6 +250,10 @@ ruleTester.run(ruleName, rule, {
243
250
} ) )
244
251
. map ( parserOptionsMapper ) ,
245
252
invalid : parsers . all ( [ ] . concat (
246
- ...neverValid ,
247
- ) ) . map ( parserOptionsMapper ) ,
253
+ ...neverValid ( 'both' ) ,
254
+ ...htmlForInvalid ( 'both' ) ,
255
+ ...nestingInvalid ( 'both' ) ,
256
+ ) ) . map ( ruleOptionsMapperFactory ( {
257
+ assert : 'both' ,
258
+ } ) ) . map ( parserOptionsMapper ) ,
248
259
} ) ;
0 commit comments