@@ -21,10 +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' ,
24
+ const errorMessages = {
25
+ accessibleLabel : 'A form label must be associated with a control.' ,
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.' ,
27
30
} ;
31
+ const expectedErrors = { } ;
32
+ Object . keys ( errorMessages ) . forEach ( ( key ) => {
33
+ expectedErrors [ key ] = {
34
+ message : errorMessages [ key ] ,
35
+ type : 'JSXOpeningElement' ,
36
+ } ;
37
+ } ) ;
28
38
29
39
const componentsSettings = {
30
40
'jsx-a11y' : {
@@ -101,56 +111,56 @@ const alwaysValid = [
101
111
{ code : '<input type="hidden" />' } ,
102
112
] ;
103
113
104
- const htmlForInvalid = [
105
- { code : '<label htmlFor="js_id"><span><span><span>A label</span></span></span></label>' , options : [ { depth : 4 } ] , errors : [ expectedError ] } ,
106
- { code : '<label htmlFor="js_id" aria-label="A label" />' , errors : [ expectedError ] } ,
107
- { code : '<label htmlFor="js_id" aria-labelledby="A label" />' , errors : [ expectedError ] } ,
114
+ const htmlForInvalid = ( assertType ) => [
115
+ { code : '<label htmlFor="js_id"><span><span><span>A label</span></span></span></label>' , options : [ { depth : 4 } ] , errors : [ expectedErrors [ assertType ] ] } ,
116
+ { code : '<label htmlFor="js_id" aria-label="A label" />' , errors : [ expectedErrors [ assertType ] ] } ,
117
+ { code : '<label htmlFor="js_id" aria-labelledby="A label" />' , errors : [ expectedErrors [ assertType ] ] } ,
108
118
// Custom label component.
109
- { code : '<CustomLabel htmlFor="js_id" aria-label="A label" />' , options : [ { labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
110
- { code : '<CustomLabel htmlFor="js_id" label="A label" />' , options : [ { labelAttributes : [ 'label' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
111
- { code : '<CustomLabel htmlFor="js_id" aria-label="A label" />' , settings : componentsSettings , errors : [ expectedError ] } ,
119
+ { code : '<CustomLabel htmlFor="js_id" aria-label="A label" />' , options : [ { labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
120
+ { code : '<CustomLabel htmlFor="js_id" label="A label" />' , options : [ { labelAttributes : [ 'label' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
121
+ { code : '<CustomLabel htmlFor="js_id" aria-label="A label" />' , settings : componentsSettings , errors : [ expectedErrors [ assertType ] ] } ,
112
122
// Custom label attributes.
113
- { code : '<label htmlFor="js_id" label="A label" />' , options : [ { labelAttributes : [ 'label' ] } ] , errors : [ expectedError ] } ,
123
+ { code : '<label htmlFor="js_id" label="A label" />' , options : [ { labelAttributes : [ 'label' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
114
124
] ;
115
- const nestingInvalid = [
116
- { code : '<label>A label<input /></label>' , errors : [ expectedError ] } ,
117
- { code : '<label>A label<textarea /></label>' , errors : [ expectedError ] } ,
118
- { code : '<label><img alt="A label" /><input /></label>' , errors : [ expectedError ] } ,
119
- { code : '<label><img aria-label="A label" /><input /></label>' , errors : [ expectedError ] } ,
120
- { code : '<label><span>A label<input /></span></label>' , errors : [ expectedError ] } ,
121
- { code : '<label><span><span>A label<input /></span></span></label>' , options : [ { depth : 3 } ] , errors : [ expectedError ] } ,
122
- { code : '<label><span><span><span>A label<input /></span></span></span></label>' , options : [ { depth : 4 } ] , errors : [ expectedError ] } ,
123
- { code : '<label><span><span><span><span>A label</span><input /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedError ] } ,
124
- { code : '<label><span><span><span><span aria-label="A label" /><input /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedError ] } ,
125
- { code : '<label><span><span><span><input aria-label="A label" /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedError ] } ,
125
+ const nestingInvalid = ( assertType ) => [
126
+ { code : '<label>A label<input /></label>' , errors : [ expectedErrors [ assertType ] ] } ,
127
+ { code : '<label>A label<textarea /></label>' , errors : [ expectedErrors [ assertType ] ] } ,
128
+ { code : '<label><img alt="A label" /><input /></label>' , errors : [ expectedErrors [ assertType ] ] } ,
129
+ { code : '<label><img aria-label="A label" /><input /></label>' , errors : [ expectedErrors [ assertType ] ] } ,
130
+ { code : '<label><span>A label<input /></span></label>' , errors : [ expectedErrors [ assertType ] ] } ,
131
+ { code : '<label><span><span>A label<input /></span></span></label>' , options : [ { depth : 3 } ] , errors : [ expectedErrors [ assertType ] ] } ,
132
+ { code : '<label><span><span><span>A label<input /></span></span></span></label>' , options : [ { depth : 4 } ] , errors : [ expectedErrors [ assertType ] ] } ,
133
+ { code : '<label><span><span><span><span>A label</span><input /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedErrors [ assertType ] ] } ,
134
+ { code : '<label><span><span><span><span aria-label="A label" /><input /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedErrors [ assertType ] ] } ,
135
+ { code : '<label><span><span><span><input aria-label="A label" /></span></span></span></label>' , options : [ { depth : 5 } ] , errors : [ expectedErrors [ assertType ] ] } ,
126
136
// Custom controlComponents.
127
- { code : '<label><span>A label<CustomInput /></span></label>' , options : [ { controlComponents : [ 'CustomInput' ] } ] , errors : [ expectedError ] } ,
128
- { code : '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
129
- { code : '<CustomLabel><span label="A label"><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] , labelAttributes : [ 'label' ] } ] , errors : [ expectedError ] } ,
130
- { code : '<label><span>A label<CustomInput /></span></label>' , settings : componentsSettings , errors : [ expectedError ] } ,
131
- { code : '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>' , settings : componentsSettings , errors : [ expectedError ] } ,
137
+ { code : '<label><span>A label<CustomInput /></span></label>' , options : [ { controlComponents : [ 'CustomInput' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
138
+ { code : '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
139
+ { code : '<CustomLabel><span label="A label"><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] , labelAttributes : [ 'label' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
140
+ { code : '<label><span>A label<CustomInput /></span></label>' , settings : componentsSettings , errors : [ expectedErrors [ assertType ] ] } ,
141
+ { code : '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>' , settings : componentsSettings , errors : [ expectedErrors [ assertType ] ] } ,
132
142
] ;
133
143
134
- 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 ] } ,
139
- { code : '<label>A label</label>' , errors : [ expectedError ] } ,
140
- { code : '<div><label /><input /></div>' , errors : [ expectedError ] } ,
141
- { code : '<div><label>A label</label><input /></div>' , errors : [ expectedError ] } ,
144
+ const neverValid = ( assertType ) => [
145
+ { code : '<label htmlFor="js_id" />' , errors : [ expectedErrors . accessibleLabel ] } ,
146
+ { code : '<label htmlFor="js_id"><input /></label>' , errors : [ expectedErrors . accessibleLabel ] } ,
147
+ { code : '<label htmlFor="js_id"><textarea /></label>' , errors : [ expectedErrors . accessibleLabel ] } ,
148
+ { code : '<label></label>' , errors : [ expectedErrors . accessibleLabel ] } ,
149
+ { code : '<label>A label</label>' , errors : [ expectedErrors [ assertType ] ] } ,
150
+ { code : '<div><label /><input /></div>' , errors : [ expectedErrors . accessibleLabel ] } ,
151
+ { code : '<div><label>A label</label><input /></div>' , errors : [ expectedErrors [ assertType ] ] } ,
142
152
// Custom label component.
143
- { code : '<CustomLabel aria-label="A label" />' , options : [ { labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
144
- { code : '<CustomLabel label="A label" />' , options : [ { labelAttributes : [ 'label' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedError ] } ,
145
- { code : '<CustomLabel aria-label="A label" />' , settings : componentsSettings , errors : [ expectedError ] } ,
153
+ { code : '<CustomLabel aria-label="A label" />' , options : [ { labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
154
+ { code : '<CustomLabel label="A label" />' , options : [ { labelAttributes : [ 'label' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
155
+ { code : '<CustomLabel aria-label="A label" />' , settings : componentsSettings , errors : [ expectedErrors [ assertType ] ] } ,
146
156
// Custom label attributes.
147
- { code : '<label label="A label" />' , options : [ { labelAttributes : [ 'label' ] } ] , errors : [ expectedError ] } ,
157
+ { code : '<label label="A label" />' , options : [ { labelAttributes : [ 'label' ] } ] , errors : [ expectedErrors [ assertType ] ] } ,
148
158
// 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 ] } ,
159
+ { code : '<label><span><CustomInput /></span></label>' , options : [ { controlComponents : [ 'CustomInput' ] } ] , errors : [ expectedErrors . accessibleLabel ] } ,
160
+ { code : '<CustomLabel><span><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] } ] , errors : [ expectedErrors . accessibleLabel ] } ,
161
+ { code : '<CustomLabel><span><CustomInput /></span></CustomLabel>' , options : [ { controlComponents : [ 'CustomInput' ] , labelComponents : [ 'CustomLabel' ] , labelAttributes : [ 'label' ] } ] , errors : [ expectedErrors . accessibleLabel ] } ,
162
+ { code : '<label><span><CustomInput /></span></label>' , settings : componentsSettings , errors : [ expectedErrors . accessibleLabel ] } ,
163
+ { code : '<CustomLabel><span><CustomInput /></span></CustomLabel>' , settings : componentsSettings , errors : [ expectedErrors . accessibleLabel ] } ,
154
164
] ;
155
165
// htmlFor valid
156
166
ruleTester . run ( ruleName , rule , {
@@ -163,8 +173,8 @@ ruleTester.run(ruleName, rule, {
163
173
} ) )
164
174
. map ( parserOptionsMapper ) ,
165
175
invalid : parsers . all ( [ ] . concat (
166
- ...neverValid ,
167
- ...nestingInvalid ,
176
+ ...neverValid ( 'htmlFor' ) ,
177
+ ...nestingInvalid ( 'htmlFor' ) ,
168
178
) )
169
179
. map ( ruleOptionsMapperFactory ( {
170
180
assert : 'htmlFor' ,
@@ -183,8 +193,8 @@ ruleTester.run(ruleName, rule, {
183
193
} ) )
184
194
. map ( parserOptionsMapper ) ,
185
195
invalid : parsers . all ( [ ] . concat (
186
- ...neverValid ,
187
- ...htmlForInvalid ,
196
+ ...neverValid ( 'nesting' ) ,
197
+ ...htmlForInvalid ( 'nesting' ) ,
188
198
) )
189
199
. map ( ruleOptionsMapperFactory ( {
190
200
assert : 'nesting' ,
@@ -204,8 +214,10 @@ ruleTester.run(ruleName, rule, {
204
214
} ) )
205
215
. map ( parserOptionsMapper ) ,
206
216
invalid : parsers . all ( [ ] . concat (
207
- ...neverValid ,
208
- ) ) . map ( parserOptionsMapper ) ,
217
+ ...neverValid ( 'either' ) ,
218
+ ) ) . map ( ruleOptionsMapperFactory ( {
219
+ assert : 'either' ,
220
+ } ) ) . map ( parserOptionsMapper ) ,
209
221
} ) ;
210
222
211
223
// both valid
@@ -219,6 +231,10 @@ ruleTester.run(ruleName, rule, {
219
231
} ) )
220
232
. map ( parserOptionsMapper ) ,
221
233
invalid : parsers . all ( [ ] . concat (
222
- ...neverValid ,
223
- ) ) . map ( parserOptionsMapper ) ,
234
+ ...neverValid ( 'both' ) ,
235
+ ...htmlForInvalid ( 'both' ) ,
236
+ ...nestingInvalid ( 'both' ) ,
237
+ ) ) . map ( ruleOptionsMapperFactory ( {
238
+ assert : 'both' ,
239
+ } ) ) . map ( parserOptionsMapper ) ,
224
240
} ) ;
0 commit comments