@@ -21,15 +21,20 @@ const ruleTester = new RuleTester();
2121
2222const ruleName = 'label-has-associated-control' ;
2323
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.' ,
3230} ;
31+ const expectedErrors = { } ;
32+ Object . keys ( errorMessages ) . forEach ( ( key ) => {
33+ expectedErrors [ key ] = {
34+ message : errorMessages [ key ] ,
35+ type : 'JSXOpeningElement' ,
36+ } ;
37+ } ) ;
3338
3439const componentsSettings = {
3540 'jsx-a11y' : {
@@ -123,58 +128,58 @@ const alwaysValid = [
123128 { code : '<input type="hidden" />' } ,
124129] ;
125130
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 ] ] } ,
130135 // 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 ] ] } ,
134139 // 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 ] ] } ,
136141] ;
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 ] ] } ,
148153 // 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 ] ] } ,
155160] ;
156161
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 ] ] } ,
165170 // 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 ] ] } ,
170175 // 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 ] ] } ,
172177 // 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 ] } ,
178183] ;
179184// htmlFor valid
180185ruleTester . run ( ruleName , rule , {
@@ -187,8 +192,8 @@ ruleTester.run(ruleName, rule, {
187192 } ) )
188193 . map ( parserOptionsMapper ) ,
189194 invalid : parsers . all ( [ ] . concat (
190- ...neverValid ,
191- ...nestingInvalid ,
195+ ...neverValid ( 'htmlFor' ) ,
196+ ...nestingInvalid ( 'htmlFor' ) ,
192197 ) )
193198 . map ( ruleOptionsMapperFactory ( {
194199 assert : 'htmlFor' ,
@@ -207,8 +212,8 @@ ruleTester.run(ruleName, rule, {
207212 } ) )
208213 . map ( parserOptionsMapper ) ,
209214 invalid : parsers . all ( [ ] . concat (
210- ...neverValid ,
211- ...htmlForInvalid ,
215+ ...neverValid ( 'nesting' ) ,
216+ ...htmlForInvalid ( 'nesting' ) ,
212217 ) )
213218 . map ( ruleOptionsMapperFactory ( {
214219 assert : 'nesting' ,
@@ -228,8 +233,10 @@ ruleTester.run(ruleName, rule, {
228233 } ) )
229234 . map ( parserOptionsMapper ) ,
230235 invalid : parsers . all ( [ ] . concat (
231- ...neverValid ,
232- ) ) . map ( parserOptionsMapper ) ,
236+ ...neverValid ( 'either' ) ,
237+ ) ) . map ( ruleOptionsMapperFactory ( {
238+ assert : 'either' ,
239+ } ) ) . map ( parserOptionsMapper ) ,
233240} ) ;
234241
235242// both valid
@@ -243,6 +250,10 @@ ruleTester.run(ruleName, rule, {
243250 } ) )
244251 . map ( parserOptionsMapper ) ,
245252 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 ) ,
248259} ) ;
0 commit comments