source: frontend/node_modules/eslint-plugin-jsx-a11y/__tests__/src/rules/label-has-associated-control-test.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 2 weeks ago

Fix frontend appearance

  • Property mode set to 100644
File size: 12.8 KB
Line 
1/**
2 * @fileoverview Enforce label tags have an associated control.
3 * @author Jesse Beach
4 */
5
6// -----------------------------------------------------------------------------
7// Requirements
8// -----------------------------------------------------------------------------
9
10import { RuleTester } from 'eslint';
11import parserOptionsMapper from '../../__util__/parserOptionsMapper';
12import parsers from '../../__util__/helpers/parsers';
13import rule from '../../../src/rules/label-has-associated-control';
14import ruleOptionsMapperFactory from '../../__util__/ruleOptionsMapperFactory';
15
16// -----------------------------------------------------------------------------
17// Tests
18// -----------------------------------------------------------------------------
19
20const ruleTester = new RuleTester();
21
22const ruleName = 'label-has-associated-control';
23
24const expectedError = {
25 message: 'A form label must be associated with a control.',
26 type: 'JSXOpeningElement',
27};
28
29const expectedErrorNoLabel = {
30 message: 'A form label must have accessible text.',
31 type: 'JSXOpeningElement',
32};
33
34const componentsSettings = {
35 'jsx-a11y': {
36 components: {
37 CustomInput: 'input',
38 CustomLabel: 'label',
39 },
40 },
41};
42
43const attributesSettings = {
44 'jsx-a11y': {
45 attributes: {
46 for: ['htmlFor', 'for'],
47 },
48 },
49};
50
51const htmlForValid = [
52 { code: '<label htmlFor="js_id"><span><span><span>A label</span></span></span></label>', options: [{ depth: 4 }] },
53 { code: '<label htmlFor="js_id" aria-label="A label" />' },
54 { code: '<label htmlFor="js_id" aria-labelledby="A label" />' },
55 { code: '<div><label htmlFor="js_id">A label</label><input id="js_id" /></div>' },
56 { code: '<label for="js_id"><span><span><span>A label</span></span></span></label>', options: [{ depth: 4 }], settings: attributesSettings },
57 { code: '<label for="js_id" aria-label="A label" />', settings: attributesSettings },
58 { code: '<label for="js_id" aria-labelledby="A label" />', settings: attributesSettings },
59 { code: '<div><label for="js_id">A label</label><input id="js_id" /></div>', settings: attributesSettings },
60 // Custom label component.
61 { code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', options: [{ labelComponents: ['CustomLabel'] }] },
62 { code: '<CustomLabel htmlFor="js_id" label="A label" />', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }] },
63 { code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', settings: componentsSettings },
64 // Custom label attributes.
65 { code: '<label htmlFor="js_id" label="A label" />', options: [{ labelAttributes: ['label'] }] },
66 // Glob support for controlComponents option.
67 { code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', options: [{ controlComponents: ['Custom*'] }] },
68 { code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', options: [{ controlComponents: ['*Label'] }] },
69 // Rule does not error if presence of accessible label cannot be determined
70 { code: '<div><label htmlFor="js_id"><CustomText /></label><input id="js_id" /></div>' },
71];
72const nestingValid = [
73 { code: '<label>A label<input /></label>' },
74 { code: '<label>A label<textarea /></label>' },
75 { code: '<label><img alt="A label" /><input /></label>' },
76 { code: '<label><img aria-label="A label" /><input /></label>' },
77 { code: '<label><span>A label<input /></span></label>' },
78 { code: '<label><span><span>A label<input /></span></span></label>', options: [{ depth: 3 }] },
79 { code: '<label><span><span><span>A label<input /></span></span></span></label>', options: [{ depth: 4 }] },
80 { code: '<label><span><span><span><span>A label</span><input /></span></span></span></label>', options: [{ depth: 5 }] },
81 { code: '<label><span><span><span><span aria-label="A label" /><input /></span></span></span></label>', options: [{ depth: 5 }] },
82 { code: '<label><span><span><span><input aria-label="A label" /></span></span></span></label>', options: [{ depth: 5 }] },
83 // Other controls
84 { code: '<label>foo<meter /></label>' },
85 { code: '<label>foo<output /></label>' },
86 { code: '<label>foo<progress /></label>' },
87 { code: '<label>foo<textarea /></label>' },
88 // Custom controlComponents.
89 { code: '<label>A label<CustomInput /></label>', options: [{ controlComponents: ['CustomInput'] }] },
90 { code: '<label><span>A label<CustomInput /></span></label>', options: [{ controlComponents: ['CustomInput'] }] },
91 { code: '<label><span>A label<CustomInput /></span></label>', settings: componentsSettings },
92 { code: '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }] },
93 { code: '<CustomLabel><span label="A label"><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }] },
94 // Glob support for controlComponents option.
95 { code: '<label><span>A label<CustomInput /></span></label>', options: [{ controlComponents: ['Custom*'] }] },
96 { code: '<label><span>A label<CustomInput /></span></label>', options: [{ controlComponents: ['*Input'] }] },
97 // Rule does not error if presence of accessible label cannot be determined
98 { code: '<label><CustomText /><input /></label>' },
99];
100
101const bothValid = [
102 { code: '<label htmlFor="js_id"><span><span><span>A label<input /></span></span></span></label>', options: [{ depth: 4 }] },
103 { code: '<label htmlFor="js_id" aria-label="A label"><input /></label>' },
104 { code: '<label htmlFor="js_id" aria-labelledby="A label"><input /></label>' },
105 { code: '<label htmlFor="js_id" aria-labelledby="A label"><textarea /></label>' },
106 // Custom label component.
107 { code: '<CustomLabel htmlFor="js_id" aria-label="A label"><input /></CustomLabel>', options: [{ labelComponents: ['CustomLabel'] }] },
108 { code: '<CustomLabel htmlFor="js_id" label="A label"><input /></CustomLabel>', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }] },
109 { code: '<CustomLabel htmlFor="js_id" aria-label="A label"><input /></CustomLabel>', settings: componentsSettings },
110 { code: '<CustomLabel htmlFor="js_id" aria-label="A label"><CustomInput /></CustomLabel>', settings: componentsSettings },
111 // Custom label attributes.
112 { code: '<label htmlFor="js_id" label="A label"><input /></label>', options: [{ labelAttributes: ['label'] }] },
113 { code: '<label htmlFor="selectInput">Some text<select id="selectInput" /></label>' },
114];
115
116const alwaysValid = [
117 { code: '<div />' },
118 { code: '<CustomElement />' },
119 { code: '<input type="hidden" />' },
120];
121
122const htmlForInvalid = [
123 { code: '<label htmlFor="js_id"><span><span><span>A label</span></span></span></label>', options: [{ depth: 4 }], errors: [expectedError] },
124 { code: '<label htmlFor="js_id" aria-label="A label" />', errors: [expectedError] },
125 { code: '<label htmlFor="js_id" aria-labelledby="A label" />', errors: [expectedError] },
126 // Custom label component.
127 { code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', options: [{ labelComponents: ['CustomLabel'] }], errors: [expectedError] },
128 { code: '<CustomLabel htmlFor="js_id" label="A label" />', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
129 { code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', settings: componentsSettings, errors: [expectedError] },
130 // Custom label attributes.
131 { code: '<label htmlFor="js_id" label="A label" />', options: [{ labelAttributes: ['label'] }], errors: [expectedError] },
132];
133const nestingInvalid = [
134 { code: '<label>A label<input /></label>', errors: [expectedError] },
135 { code: '<label>A label<textarea /></label>', errors: [expectedError] },
136 { code: '<label><img alt="A label" /><input /></label>', errors: [expectedError] },
137 { code: '<label><img aria-label="A label" /><input /></label>', errors: [expectedError] },
138 { code: '<label><span>A label<input /></span></label>', errors: [expectedError] },
139 { code: '<label><span><span>A label<input /></span></span></label>', options: [{ depth: 3 }], errors: [expectedError] },
140 { code: '<label><span><span><span>A label<input /></span></span></span></label>', options: [{ depth: 4 }], errors: [expectedError] },
141 { code: '<label><span><span><span><span>A label</span><input /></span></span></span></label>', options: [{ depth: 5 }], errors: [expectedError] },
142 { code: '<label><span><span><span><span aria-label="A label" /><input /></span></span></span></label>', options: [{ depth: 5 }], errors: [expectedError] },
143 { code: '<label><span><span><span><input aria-label="A label" /></span></span></span></label>', options: [{ depth: 5 }], errors: [expectedError] },
144 // Custom controlComponents.
145 { code: '<label>A label<OtherCustomInput /></label>', options: [{ controlComponents: ['CustomInput'] }], errors: [expectedError] },
146 { code: '<label><span>A label<CustomInput /></span></label>', options: [{ controlComponents: ['CustomInput'] }], errors: [expectedError] },
147 { code: '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
148 { code: '<CustomLabel><span label="A label"><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }], errors: [expectedError] },
149 { code: '<label><span>A label<CustomInput /></span></label>', settings: componentsSettings, errors: [expectedError] },
150 { code: '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>', settings: componentsSettings, errors: [expectedError] },
151];
152
153const neverValid = [
154 { code: '<label htmlFor="js_id" />', errors: [expectedErrorNoLabel] },
155 { code: '<label htmlFor="js_id"><input /></label>', errors: [expectedErrorNoLabel] },
156 { code: '<label htmlFor="js_id"><textarea /></label>', errors: [expectedErrorNoLabel] },
157 { code: '<label></label>', errors: [expectedErrorNoLabel] },
158 { code: '<label>A label</label>', errors: [expectedError] },
159 { code: '<div><label /><input /></div>', errors: [expectedErrorNoLabel] },
160 { code: '<div><label>A label</label><input /></div>', errors: [expectedError] },
161 // Custom label component.
162 { code: '<CustomLabel aria-label="A label" />', options: [{ labelComponents: ['CustomLabel'] }], errors: [expectedError] },
163 { code: '<CustomLabel label="A label" />', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
164 { code: '<CustomLabel aria-label="A label" />', settings: componentsSettings, errors: [expectedError] },
165 // Custom label attributes.
166 { code: '<label label="A label" />', options: [{ labelAttributes: ['label'] }], errors: [expectedError] },
167 // Custom controlComponents.
168 { code: '<label><span><CustomInput /></span></label>', options: [{ controlComponents: ['CustomInput'] }], errors: [expectedErrorNoLabel] },
169 { code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }], errors: [expectedErrorNoLabel] },
170 { code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }], errors: [expectedErrorNoLabel] },
171 { code: '<label><span><CustomInput /></span></label>', settings: componentsSettings, errors: [expectedErrorNoLabel] },
172 { code: '<CustomLabel><span><CustomInput /></span></CustomLabel>', settings: componentsSettings, errors: [expectedErrorNoLabel] },
173];
174// htmlFor valid
175ruleTester.run(ruleName, rule, {
176 valid: parsers.all([].concat(
177 ...alwaysValid,
178 ...htmlForValid,
179 ))
180 .map(ruleOptionsMapperFactory({
181 assert: 'htmlFor',
182 }))
183 .map(parserOptionsMapper),
184 invalid: parsers.all([].concat(
185 ...neverValid,
186 ...nestingInvalid,
187 ))
188 .map(ruleOptionsMapperFactory({
189 assert: 'htmlFor',
190 }))
191 .map(parserOptionsMapper),
192});
193
194// nesting valid
195ruleTester.run(ruleName, rule, {
196 valid: parsers.all([].concat(
197 ...alwaysValid,
198 ...nestingValid,
199 ))
200 .map(ruleOptionsMapperFactory({
201 assert: 'nesting',
202 }))
203 .map(parserOptionsMapper),
204 invalid: parsers.all([].concat(
205 ...neverValid,
206 ...htmlForInvalid,
207 ))
208 .map(ruleOptionsMapperFactory({
209 assert: 'nesting',
210 }))
211 .map(parserOptionsMapper),
212});
213
214// either valid
215ruleTester.run(ruleName, rule, {
216 valid: parsers.all([].concat(
217 ...alwaysValid,
218 ...htmlForValid,
219 ...nestingValid,
220 ))
221 .map(ruleOptionsMapperFactory({
222 assert: 'either',
223 }))
224 .map(parserOptionsMapper),
225 invalid: parsers.all([].concat(
226 ...neverValid,
227 )).map(parserOptionsMapper),
228});
229
230// both valid
231ruleTester.run(ruleName, rule, {
232 valid: parsers.all([].concat(
233 ...alwaysValid,
234 ...bothValid,
235 ))
236 .map(ruleOptionsMapperFactory({
237 assert: 'both',
238 }))
239 .map(parserOptionsMapper),
240 invalid: parsers.all([].concat(
241 ...neverValid,
242 )).map(parserOptionsMapper),
243});
Note: See TracBrowser for help on using the repository browser.