@arcaauth/eslint-plugin-jsx-a11y 6.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/.babelrc +17 -0
  2. package/.eslintrc +44 -0
  3. package/CHANGELOG.md +774 -0
  4. package/LICENSE.md +8 -0
  5. package/README.md +423 -0
  6. package/__mocks__/IdentifierMock.js +15 -0
  7. package/__mocks__/JSXAttributeMock.js +39 -0
  8. package/__mocks__/JSXElementMock.js +37 -0
  9. package/__mocks__/JSXExpressionContainerMock.js +15 -0
  10. package/__mocks__/JSXSpreadAttributeMock.js +18 -0
  11. package/__mocks__/JSXTextMock.js +17 -0
  12. package/__mocks__/LiteralMock.js +17 -0
  13. package/__mocks__/genInteractives.js +218 -0
  14. package/__tests__/__util__/axeMapping.js +6 -0
  15. package/__tests__/__util__/helpers/getESLintCoreRule.js +9 -0
  16. package/__tests__/__util__/helpers/parsers.js +186 -0
  17. package/__tests__/__util__/parserOptionsMapper.js +53 -0
  18. package/__tests__/__util__/ruleOptionsMapperFactory.js +33 -0
  19. package/__tests__/index-test.js +40 -0
  20. package/__tests__/src/rules/accessible-emoji-test.js +66 -0
  21. package/__tests__/src/rules/alt-text-test.js +291 -0
  22. package/__tests__/src/rules/anchor-ambiguous-text-test.js +117 -0
  23. package/__tests__/src/rules/anchor-has-content-test.js +54 -0
  24. package/__tests__/src/rules/anchor-is-valid-test.js +532 -0
  25. package/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js +95 -0
  26. package/__tests__/src/rules/aria-props-test.js +69 -0
  27. package/__tests__/src/rules/aria-proptypes-test.js +311 -0
  28. package/__tests__/src/rules/aria-role-test.js +118 -0
  29. package/__tests__/src/rules/aria-unsupported-elements-test.js +75 -0
  30. package/__tests__/src/rules/autocomplete-valid-test.js +77 -0
  31. package/__tests__/src/rules/click-events-have-key-events-test.js +77 -0
  32. package/__tests__/src/rules/control-has-associated-label-test.js +327 -0
  33. package/__tests__/src/rules/heading-has-content-test.js +85 -0
  34. package/__tests__/src/rules/html-has-lang-test.js +42 -0
  35. package/__tests__/src/rules/iframe-has-title-test.js +55 -0
  36. package/__tests__/src/rules/img-redundant-alt-test.js +137 -0
  37. package/__tests__/src/rules/interactive-supports-focus-test.js +267 -0
  38. package/__tests__/src/rules/label-has-associated-control-test.js +243 -0
  39. package/__tests__/src/rules/label-has-for-test.js +235 -0
  40. package/__tests__/src/rules/lang-test.js +59 -0
  41. package/__tests__/src/rules/media-has-caption-test.js +220 -0
  42. package/__tests__/src/rules/mouse-events-have-key-events-test.js +154 -0
  43. package/__tests__/src/rules/no-access-key-test.js +48 -0
  44. package/__tests__/src/rules/no-aria-hidden-on-focusable-test.js +44 -0
  45. package/__tests__/src/rules/no-autofocus-test.js +68 -0
  46. package/__tests__/src/rules/no-distracting-elements-test.js +51 -0
  47. package/__tests__/src/rules/no-interactive-element-to-noninteractive-role-test.js +405 -0
  48. package/__tests__/src/rules/no-noninteractive-element-interactions-test.js +502 -0
  49. package/__tests__/src/rules/no-noninteractive-element-to-interactive-role-test.js +500 -0
  50. package/__tests__/src/rules/no-noninteractive-tabindex-test.js +123 -0
  51. package/__tests__/src/rules/no-onchange-test.js +57 -0
  52. package/__tests__/src/rules/no-redundant-roles-test.js +98 -0
  53. package/__tests__/src/rules/no-static-element-interactions-test.js +501 -0
  54. package/__tests__/src/rules/prefer-tag-over-role-test.js +63 -0
  55. package/__tests__/src/rules/role-has-required-aria-props-test.js +134 -0
  56. package/__tests__/src/rules/role-supports-aria-props-test.js +570 -0
  57. package/__tests__/src/rules/scope-test.js +50 -0
  58. package/__tests__/src/rules/tabindex-no-positive-test.js +55 -0
  59. package/__tests__/src/util/attributesComparator-test.js +91 -0
  60. package/__tests__/src/util/getAccessibleChildText-test.js +174 -0
  61. package/__tests__/src/util/getComputedRole-test.js +71 -0
  62. package/__tests__/src/util/getElementType-test.js +154 -0
  63. package/__tests__/src/util/getExplicitRole-test.js +35 -0
  64. package/__tests__/src/util/getImplicitRole-test.js +25 -0
  65. package/__tests__/src/util/getSuggestion-test.js +33 -0
  66. package/__tests__/src/util/getTabIndex-test.js +85 -0
  67. package/__tests__/src/util/hasAccessibleChild-test.js +157 -0
  68. package/__tests__/src/util/implicitRoles/input-test.js +87 -0
  69. package/__tests__/src/util/implicitRoles/menu-test.js +20 -0
  70. package/__tests__/src/util/implicitRoles/menuitem-test.js +38 -0
  71. package/__tests__/src/util/isAbstractRole-test.js +51 -0
  72. package/__tests__/src/util/isContentEditable-test.js +52 -0
  73. package/__tests__/src/util/isDOMElement-test.js +30 -0
  74. package/__tests__/src/util/isDisabledElement-test.js +88 -0
  75. package/__tests__/src/util/isFocusable-test.js +111 -0
  76. package/__tests__/src/util/isInteractiveElement-test.js +104 -0
  77. package/__tests__/src/util/isInteractiveRole-test.js +59 -0
  78. package/__tests__/src/util/isNonInteractiveElement-test.js +97 -0
  79. package/__tests__/src/util/isNonInteractiveRole-test.js +59 -0
  80. package/__tests__/src/util/isNonLiteralProperty-test.js +52 -0
  81. package/__tests__/src/util/isSemanticRoleElement-test.js +72 -0
  82. package/__tests__/src/util/mayContainChildComponent-test.js +219 -0
  83. package/__tests__/src/util/mayHaveAccessibleLabel-test.js +256 -0
  84. package/__tests__/src/util/parserOptionsMapper-test.js +93 -0
  85. package/__tests__/src/util/schemas-test.js +35 -0
  86. package/docs/rules/accessible-emoji.md +30 -0
  87. package/docs/rules/alt-text.md +168 -0
  88. package/docs/rules/anchor-ambiguous-text.md +91 -0
  89. package/docs/rules/anchor-has-content.md +64 -0
  90. package/docs/rules/anchor-is-valid.md +270 -0
  91. package/docs/rules/aria-activedescendant-has-tabindex.md +52 -0
  92. package/docs/rules/aria-props.md +29 -0
  93. package/docs/rules/aria-proptypes.md +30 -0
  94. package/docs/rules/aria-role.md +51 -0
  95. package/docs/rules/aria-unsupported-elements.md +30 -0
  96. package/docs/rules/autocomplete-valid.md +49 -0
  97. package/docs/rules/click-events-have-key-events.md +28 -0
  98. package/docs/rules/control-has-associated-label.md +113 -0
  99. package/docs/rules/heading-has-content.md +67 -0
  100. package/docs/rules/html-has-lang.md +31 -0
  101. package/docs/rules/iframe-has-title.md +37 -0
  102. package/docs/rules/img-redundant-alt.md +48 -0
  103. package/docs/rules/interactive-supports-focus.md +156 -0
  104. package/docs/rules/label-has-associated-control.md +152 -0
  105. package/docs/rules/label-has-for.md +130 -0
  106. package/docs/rules/lang.md +31 -0
  107. package/docs/rules/media-has-caption.md +48 -0
  108. package/docs/rules/mouse-events-have-key-events.md +58 -0
  109. package/docs/rules/no-access-key.md +30 -0
  110. package/docs/rules/no-aria-hidden-on-focusable.md +37 -0
  111. package/docs/rules/no-autofocus.md +43 -0
  112. package/docs/rules/no-distracting-elements.md +41 -0
  113. package/docs/rules/no-interactive-element-to-noninteractive-role.md +73 -0
  114. package/docs/rules/no-noninteractive-element-interactions.md +145 -0
  115. package/docs/rules/no-noninteractive-element-to-interactive-role.md +76 -0
  116. package/docs/rules/no-noninteractive-tabindex.md +115 -0
  117. package/docs/rules/no-onchange.md +36 -0
  118. package/docs/rules/no-redundant-roles.md +46 -0
  119. package/docs/rules/no-static-element-interactions.md +114 -0
  120. package/docs/rules/prefer-tag-over-role.md +32 -0
  121. package/docs/rules/role-has-required-aria-props.md +31 -0
  122. package/docs/rules/role-supports-aria-props.md +39 -0
  123. package/docs/rules/scope.md +30 -0
  124. package/docs/rules/tabindex-no-positive.md +32 -0
  125. package/lib/configs/flat-config-base.js +11 -0
  126. package/lib/configs/legacy-config-base.js +9 -0
  127. package/lib/index.js +209 -0
  128. package/lib/rules/accessible-emoji.js +63 -0
  129. package/lib/rules/alt-text.js +218 -0
  130. package/lib/rules/anchor-ambiguous-text.js +64 -0
  131. package/lib/rules/anchor-has-content.js +60 -0
  132. package/lib/rules/anchor-is-valid.js +122 -0
  133. package/lib/rules/aria-activedescendant-has-tabindex.js +66 -0
  134. package/lib/rules/aria-props.js +59 -0
  135. package/lib/rules/aria-proptypes.js +114 -0
  136. package/lib/rules/aria-role.js +89 -0
  137. package/lib/rules/aria-unsupported-elements.js +64 -0
  138. package/lib/rules/autocomplete-valid.js +67 -0
  139. package/lib/rules/click-events-have-key-events.js +68 -0
  140. package/lib/rules/control-has-associated-label.js +103 -0
  141. package/lib/rules/heading-has-content.js +61 -0
  142. package/lib/rules/html-has-lang.js +50 -0
  143. package/lib/rules/iframe-has-title.js +50 -0
  144. package/lib/rules/img-redundant-alt.js +88 -0
  145. package/lib/rules/interactive-supports-focus.js +87 -0
  146. package/lib/rules/label-has-associated-control.js +127 -0
  147. package/lib/rules/label-has-for.js +150 -0
  148. package/lib/rules/lang.js +68 -0
  149. package/lib/rules/media-has-caption.js +96 -0
  150. package/lib/rules/mouse-events-have-key-events.js +94 -0
  151. package/lib/rules/no-access-key.js +43 -0
  152. package/lib/rules/no-aria-hidden-on-focusable.js +47 -0
  153. package/lib/rules/no-autofocus.js +62 -0
  154. package/lib/rules/no-distracting-elements.js +54 -0
  155. package/lib/rules/no-interactive-element-to-noninteractive-role.js +81 -0
  156. package/lib/rules/no-noninteractive-element-interactions.js +95 -0
  157. package/lib/rules/no-noninteractive-element-to-interactive-role.js +80 -0
  158. package/lib/rules/no-noninteractive-tabindex.js +109 -0
  159. package/lib/rules/no-onchange.js +52 -0
  160. package/lib/rules/no-redundant-roles.js +86 -0
  161. package/lib/rules/no-static-element-interactions.js +102 -0
  162. package/lib/rules/prefer-tag-over-role.js +75 -0
  163. package/lib/rules/role-has-required-aria-props.js +88 -0
  164. package/lib/rules/role-supports-aria-props.js +78 -0
  165. package/lib/rules/scope.js +58 -0
  166. package/lib/rules/tabindex-no-positive.js +53 -0
  167. package/lib/util/attributesComparator.js +34 -0
  168. package/lib/util/getAccessibleChildText.js +55 -0
  169. package/lib/util/getComputedRole.js +19 -0
  170. package/lib/util/getElementType.js +30 -0
  171. package/lib/util/getExplicitRole.js +27 -0
  172. package/lib/util/getImplicitRole.js +24 -0
  173. package/lib/util/getSuggestion.js +32 -0
  174. package/lib/util/getTabIndex.js +34 -0
  175. package/lib/util/hasAccessibleChild.js +30 -0
  176. package/lib/util/implicitRoles/a.js +17 -0
  177. package/lib/util/implicitRoles/area.js +17 -0
  178. package/lib/util/implicitRoles/article.js +13 -0
  179. package/lib/util/implicitRoles/aside.js +13 -0
  180. package/lib/util/implicitRoles/body.js +13 -0
  181. package/lib/util/implicitRoles/button.js +13 -0
  182. package/lib/util/implicitRoles/datalist.js +13 -0
  183. package/lib/util/implicitRoles/details.js +13 -0
  184. package/lib/util/implicitRoles/dialog.js +13 -0
  185. package/lib/util/implicitRoles/form.js +13 -0
  186. package/lib/util/implicitRoles/h1.js +13 -0
  187. package/lib/util/implicitRoles/h2.js +13 -0
  188. package/lib/util/implicitRoles/h3.js +13 -0
  189. package/lib/util/implicitRoles/h4.js +13 -0
  190. package/lib/util/implicitRoles/h5.js +13 -0
  191. package/lib/util/implicitRoles/h6.js +13 -0
  192. package/lib/util/implicitRoles/hr.js +13 -0
  193. package/lib/util/implicitRoles/img.js +31 -0
  194. package/lib/util/implicitRoles/index.js +82 -0
  195. package/lib/util/implicitRoles/input.js +38 -0
  196. package/lib/util/implicitRoles/li.js +13 -0
  197. package/lib/util/implicitRoles/link.js +17 -0
  198. package/lib/util/implicitRoles/menu.js +19 -0
  199. package/lib/util/implicitRoles/menuitem.js +28 -0
  200. package/lib/util/implicitRoles/meter.js +13 -0
  201. package/lib/util/implicitRoles/nav.js +13 -0
  202. package/lib/util/implicitRoles/ol.js +13 -0
  203. package/lib/util/implicitRoles/option.js +13 -0
  204. package/lib/util/implicitRoles/output.js +13 -0
  205. package/lib/util/implicitRoles/progress.js +13 -0
  206. package/lib/util/implicitRoles/section.js +13 -0
  207. package/lib/util/implicitRoles/select.js +13 -0
  208. package/lib/util/implicitRoles/tbody.js +13 -0
  209. package/lib/util/implicitRoles/textarea.js +13 -0
  210. package/lib/util/implicitRoles/tfoot.js +13 -0
  211. package/lib/util/implicitRoles/thead.js +13 -0
  212. package/lib/util/implicitRoles/ul.js +13 -0
  213. package/lib/util/isAbstractRole.js +23 -0
  214. package/lib/util/isContentEditable.js +13 -0
  215. package/lib/util/isDOMElement.js +15 -0
  216. package/lib/util/isDisabledElement.js +23 -0
  217. package/lib/util/isFocusable.js +23 -0
  218. package/lib/util/isHiddenFromScreenReader.js +26 -0
  219. package/lib/util/isInteractiveElement.js +116 -0
  220. package/lib/util/isInteractiveRole.js +54 -0
  221. package/lib/util/isNonInteractiveElement.js +131 -0
  222. package/lib/util/isNonInteractiveRole.js +55 -0
  223. package/lib/util/isNonLiteralProperty.js +29 -0
  224. package/lib/util/isPresentationRole.js +13 -0
  225. package/lib/util/isSemanticRoleElement.js +54 -0
  226. package/lib/util/mayContainChildComponent.js +50 -0
  227. package/lib/util/mayHaveAccessibleLabel.js +95 -0
  228. package/lib/util/schemas.js +52 -0
  229. package/package.json +120 -0
@@ -0,0 +1,235 @@
1
+ /**
2
+ * @fileoverview Enforce label tags have htmlFor attribute.
3
+ * @author Ethan Cohen
4
+ */
5
+
6
+ // -----------------------------------------------------------------------------
7
+ // Requirements
8
+ // -----------------------------------------------------------------------------
9
+
10
+ import { RuleTester } from 'eslint';
11
+ import assign from 'object.assign';
12
+ import parserOptionsMapper from '../../__util__/parserOptionsMapper';
13
+ import parsers from '../../__util__/helpers/parsers';
14
+ import rule from '../../../src/rules/label-has-for';
15
+
16
+ // -----------------------------------------------------------------------------
17
+ // Tests
18
+ // -----------------------------------------------------------------------------
19
+
20
+ const ruleTester = new RuleTester();
21
+
22
+ const expectedNestingError = {
23
+ message: 'Form label must have the following type of associated control: nesting',
24
+ type: 'JSXOpeningElement',
25
+ };
26
+
27
+ const expectedSomeError = {
28
+ message: 'Form label must have ANY of the following types of associated control: nesting, id',
29
+ type: 'JSXOpeningElement',
30
+ };
31
+
32
+ const expectedEveryError = {
33
+ message: 'Form label must have ALL of the following types of associated control: nesting, id',
34
+ type: 'JSXOpeningElement',
35
+ };
36
+
37
+ const optionsComponents = [{
38
+ components: ['Label', 'Descriptor'],
39
+ }];
40
+ const optionsRequiredNesting = [{
41
+ required: 'nesting',
42
+ }];
43
+ const optionsRequiredSome = [{
44
+ required: { some: ['nesting', 'id'] },
45
+ }];
46
+ const optionsRequiredEvery = [{
47
+ required: { every: ['nesting', 'id'] },
48
+ }];
49
+ const optionsChildrenAllowed = [{
50
+ allowChildren: true,
51
+ }];
52
+
53
+ const attributesSettings = {
54
+ 'jsx-a11y': {
55
+ attributes: {
56
+ for: ['htmlFor', 'for'],
57
+ },
58
+ },
59
+ };
60
+
61
+ ruleTester.run('label-has-for', rule, {
62
+ valid: parsers.all([].concat(
63
+ // DEFAULT ELEMENT 'label' TESTS
64
+ { code: '<div />' },
65
+ { code: '<label htmlFor="foo"><input /></label>' },
66
+ { code: '<label htmlFor="foo"><textarea /></label>' },
67
+ { code: '<label for="foo"><input /></label>', settings: attributesSettings },
68
+ { code: '<label for="foo"><textarea /></label>', settings: attributesSettings },
69
+ { code: '<Label />' }, // lower-case convention refers to real HTML elements.
70
+ { code: '<Label htmlFor="foo" />' },
71
+ { code: '<Label for="foo" />', settings: attributesSettings },
72
+ { code: '<Descriptor />' },
73
+ { code: '<Descriptor htmlFor="foo">Test!</Descriptor>' },
74
+ { code: '<Descriptor for="foo">Test!</Descriptor>', settings: attributesSettings },
75
+ { code: '<UX.Layout>test</UX.Layout>' },
76
+
77
+ // CUSTOM ELEMENT ARRAY OPTION TESTS
78
+ { code: '<Label htmlFor="foo" />', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
79
+ { code: '<Label htmlFor={"foo"} />', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
80
+ { code: '<Label htmlFor={foo} />', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
81
+ { code: '<Label htmlFor={`${id}`} />', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
82
+ { code: '<div />', options: optionsComponents },
83
+ { code: '<Label htmlFor="something"><input /></Label>', options: optionsComponents },
84
+ { code: '<Label htmlFor="foo">Test!</Label>', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
85
+ { code: '<Descriptor htmlFor="foo" />', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
86
+ { code: '<Descriptor htmlFor={"foo"} />', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
87
+ { code: '<Descriptor htmlFor={foo} />', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
88
+ { code: '<Descriptor htmlFor={`${id}`} />', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
89
+ { code: '<Descriptor htmlFor="foo">Test!</Descriptor>', options: [assign({}, optionsComponents[0], optionsRequiredSome[0])] },
90
+ { code: '<label htmlFor="foo" />', options: optionsRequiredSome },
91
+ { code: '<label htmlFor={"foo"} />', options: optionsRequiredSome },
92
+ { code: '<label htmlFor={foo} />', options: optionsRequiredSome },
93
+ { code: '<label htmlFor={`${id}`} />', options: optionsRequiredSome },
94
+ { code: '<label htmlFor="foo">Test!</label>', options: optionsRequiredSome },
95
+ { code: '<label><input /></label>', options: optionsRequiredSome },
96
+ { code: '<label><input /></label>', options: optionsRequiredNesting },
97
+ { code: '<label htmlFor="input"><input /></label>', options: optionsRequiredEvery },
98
+ { code: '<label><input /></label>', options: optionsChildrenAllowed },
99
+ { code: '<Descriptor htmlFor="foo">Test!</Descriptor>', options: [assign({}, optionsComponents, optionsChildrenAllowed)] },
100
+ { code: '<label>Test!</label>', options: optionsChildrenAllowed },
101
+ { code: '<label htmlFor="foo">Test!</label>', options: optionsChildrenAllowed },
102
+ { code: '<label>{children}</label>', options: optionsChildrenAllowed },
103
+ { code: '<label htmlFor="children">{children}</label>', options: optionsChildrenAllowed },
104
+ { code: '<label htmlFor={id}>{ labelText }<div><input id={id} type="checkbox" name={id} value={value} /></div></label>', options: optionsRequiredEvery },
105
+ { code: '<label htmlFor={id}>{ labelText }<div><div><div><div><input id={id} type="checkbox" name={id} value={value} /></div></div></div></div></label>', options: optionsRequiredEvery },
106
+ )).map(parserOptionsMapper),
107
+ invalid: parsers.all([].concat(
108
+ // DEFAULT ELEMENT 'label' TESTS
109
+ { code: '<label id="foo" />', errors: [expectedEveryError], options: optionsRequiredEvery },
110
+ { code: '<label htmlFor={undefined} />', errors: [expectedEveryError], options: optionsRequiredEvery },
111
+ { code: '<label htmlFor={`${undefined}`} />', errors: [expectedEveryError], options: optionsRequiredEvery },
112
+ { code: '<label>First Name</label>', errors: [expectedEveryError], options: optionsRequiredEvery },
113
+ { code: '<label {...props}>Foo</label>', errors: [expectedEveryError], options: optionsRequiredEvery },
114
+ { code: '<label><input /></label>', errors: [expectedEveryError], options: optionsRequiredEvery },
115
+ { code: '<label><textarea /></label>', errors: [expectedEveryError], options: optionsRequiredEvery },
116
+ { code: '<label>{children}</label>', errors: [expectedEveryError], options: optionsRequiredEvery },
117
+ { code: '<label htmlFor="foo" />', errors: [expectedEveryError], options: optionsRequiredEvery },
118
+ { code: '<label htmlFor={"foo"} />', errors: [expectedEveryError], options: optionsRequiredEvery },
119
+ { code: '<label htmlFor={foo} />', errors: [expectedEveryError], options: optionsRequiredEvery },
120
+ { code: '<label htmlFor={`${id}`} />', errors: [expectedEveryError], options: optionsRequiredEvery },
121
+ { code: '<label htmlFor="foo">Test!</label>', errors: [expectedEveryError], options: optionsRequiredEvery },
122
+ { code: '<label htmlFor={id}>{ labelText }<div><div><div><div><div id={id} type="checkbox" name={id} value={value} /></div></div></div></div></label>', errors: [expectedEveryError], options: optionsRequiredEvery },
123
+ //
124
+ // // CUSTOM ELEMENT ARRAY OPTION TESTS
125
+ {
126
+ code: '<Label></Label>',
127
+ errors: [expectedEveryError],
128
+ options: optionsComponents,
129
+ },
130
+ {
131
+ code: '<Label htmlFor="foo" />',
132
+ errors: [expectedEveryError],
133
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
134
+ },
135
+ {
136
+ code: '<Label htmlFor={"foo"} />',
137
+ errors: [expectedEveryError],
138
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
139
+ },
140
+ {
141
+ code: '<Label htmlFor={foo} />',
142
+ errors: [expectedEveryError],
143
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
144
+ },
145
+ {
146
+ code: '<Label htmlFor={`${id}`} />',
147
+ errors: [expectedEveryError],
148
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
149
+ },
150
+ {
151
+ code: '<Label htmlFor="foo">Test!</Label>',
152
+ errors: [expectedEveryError],
153
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
154
+ },
155
+ {
156
+ code: '<Descriptor htmlFor="foo" />',
157
+ errors: [expectedEveryError],
158
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
159
+ },
160
+ {
161
+ code: '<Descriptor htmlFor={"foo"} />',
162
+ errors: [expectedEveryError],
163
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
164
+ },
165
+ {
166
+ code: '<Descriptor htmlFor={foo} />',
167
+ errors: [expectedEveryError],
168
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
169
+ },
170
+ {
171
+ code: '<Descriptor htmlFor={`${id}`} />',
172
+ errors: [expectedEveryError],
173
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
174
+ },
175
+ {
176
+ code: '<Descriptor htmlFor="foo">Test!</Descriptor>',
177
+ errors: [expectedEveryError],
178
+ options: [{ ...optionsComponents[0], ...optionsRequiredEvery[0] }],
179
+ },
180
+ { code: '<Label id="foo" />', errors: [expectedEveryError], options: optionsComponents },
181
+ {
182
+ code: '<Label htmlFor={undefined} />',
183
+ errors: [expectedEveryError],
184
+ options: optionsComponents,
185
+ },
186
+ {
187
+ code: '<Label htmlFor={`${undefined}`} />',
188
+ errors: [expectedEveryError],
189
+ options: optionsComponents,
190
+ },
191
+ { code: '<Label>First Name</Label>', errors: [expectedEveryError], options: optionsComponents },
192
+ {
193
+ code: '<Label {...props}>Foo</Label>',
194
+ errors: [expectedEveryError],
195
+ options: optionsComponents,
196
+ },
197
+ { code: '<Descriptor id="foo" />', errors: [expectedEveryError], options: optionsComponents },
198
+ {
199
+ code: '<Descriptor htmlFor={undefined} />',
200
+ errors: [expectedEveryError],
201
+ options: optionsComponents,
202
+ },
203
+ {
204
+ code: '<Descriptor htmlFor={`${undefined}`} />',
205
+ errors: [expectedEveryError],
206
+ options: optionsComponents,
207
+ },
208
+ {
209
+ code: '<Descriptor>First Name</Descriptor>',
210
+ errors: [expectedEveryError],
211
+ options: optionsComponents,
212
+ },
213
+ {
214
+ code: '<Descriptor {...props}>Foo</Descriptor>',
215
+ errors: [expectedEveryError],
216
+ options: optionsComponents,
217
+ },
218
+ { code: '<label>{children}</label>', errors: [expectedEveryError], options: optionsComponents },
219
+ { code: '<label htmlFor="foo" />', errors: [expectedNestingError], options: optionsRequiredNesting },
220
+ { code: '<label>First Name</label>', errors: [expectedNestingError], options: optionsRequiredNesting },
221
+ { code: '<label>First Name</label>', errors: [expectedSomeError], options: optionsRequiredSome },
222
+ { code: '<label>{children}</label>', errors: [expectedSomeError], options: optionsRequiredSome },
223
+ { code: '<label>{children}</label>', errors: [expectedNestingError], options: optionsRequiredNesting },
224
+ {
225
+ code: '<form><input type="text" id="howmuch" value="1" /><label htmlFor="howmuch">How much ?</label></form>',
226
+ errors: [expectedEveryError],
227
+ options: optionsRequiredEvery,
228
+ },
229
+ {
230
+ code: '<form><input type="text" id="howmuch" value="1" /><label htmlFor="howmuch">How much ?<span /></label></form>',
231
+ errors: [expectedEveryError],
232
+ options: optionsRequiredEvery,
233
+ },
234
+ )).map(parserOptionsMapper),
235
+ });
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @fileoverview Enforce lang attribute has a valid value.
3
+ * @author Ethan Cohen
4
+ */
5
+
6
+ // -----------------------------------------------------------------------------
7
+ // Requirements
8
+ // -----------------------------------------------------------------------------
9
+
10
+ import { RuleTester } from 'eslint';
11
+ import parserOptionsMapper from '../../__util__/parserOptionsMapper';
12
+ import parsers from '../../__util__/helpers/parsers';
13
+ import rule from '../../../src/rules/lang';
14
+
15
+ // -----------------------------------------------------------------------------
16
+ // Tests
17
+ // -----------------------------------------------------------------------------
18
+
19
+ const ruleTester = new RuleTester();
20
+
21
+ const expectedError = {
22
+ message: 'lang attribute must have a valid value.',
23
+ type: 'JSXAttribute',
24
+ };
25
+
26
+ const componentsSettings = {
27
+ 'jsx-a11y': {
28
+ polymorphicPropName: 'as',
29
+ components: {
30
+ Foo: 'html',
31
+ },
32
+ },
33
+ };
34
+
35
+ ruleTester.run('lang', rule, {
36
+ valid: parsers.all([].concat(
37
+ { code: '<div />;' },
38
+ { code: '<div foo="bar" />;' },
39
+ { code: '<div lang="foo" />;' },
40
+ { code: '<html lang="en" />' },
41
+ { code: '<html lang="en-US" />' },
42
+ { code: '<html lang="zh-Hans" />' },
43
+ { code: '<html lang="zh-Hant-HK" />' },
44
+ { code: '<html lang="zh-yue-Hant" />' },
45
+ { code: '<html lang="ja-Latn" />' },
46
+ { code: '<html lang={foo} />' },
47
+ { code: '<HTML lang="foo" />' },
48
+ { code: '<Foo lang={undefined} />' },
49
+ { code: '<Foo lang="en" />', settings: componentsSettings },
50
+ { code: '<Box as="html" lang="en" />', settings: componentsSettings },
51
+ )).map(parserOptionsMapper),
52
+ invalid: parsers.all([].concat(
53
+ { code: '<html lang="foo" />', errors: [expectedError] },
54
+ { code: '<html lang="zz-LL" />', errors: [expectedError] },
55
+ { code: '<html lang={undefined} />', errors: [expectedError] },
56
+ { code: '<Foo lang={undefined} />', settings: componentsSettings, errors: [expectedError] },
57
+ { code: '<Box as="html" lang="foo" />', settings: componentsSettings, errors: [expectedError] },
58
+ )).map(parserOptionsMapper),
59
+ });
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @fileoverview <audio> and <video> elements must have a <track> for captions.
3
+ * @author Ethan Cohen
4
+ */
5
+
6
+ // -----------------------------------------------------------------------------
7
+ // Requirements
8
+ // -----------------------------------------------------------------------------
9
+
10
+ import { RuleTester } from 'eslint';
11
+ import parserOptionsMapper from '../../__util__/parserOptionsMapper';
12
+ import parsers from '../../__util__/helpers/parsers';
13
+ import rule from '../../../src/rules/media-has-caption';
14
+
15
+ // -----------------------------------------------------------------------------
16
+ // Tests
17
+ // -----------------------------------------------------------------------------
18
+
19
+ const ruleTester = new RuleTester();
20
+
21
+ const expectedError = {
22
+ message: 'Media elements such as <audio> and <video> must have a <track> for captions.',
23
+ type: 'JSXOpeningElement',
24
+ };
25
+
26
+ const customSchema = [
27
+ {
28
+ audio: ['Audio'],
29
+ video: ['Video'],
30
+ track: ['Track'],
31
+ },
32
+ ];
33
+
34
+ const componentsSettings = {
35
+ 'jsx-a11y': {
36
+ polymorphicPropName: 'as',
37
+ components: {
38
+ Audio: 'audio',
39
+ Video: 'video',
40
+ Track: 'track',
41
+ },
42
+ },
43
+ };
44
+
45
+ ruleTester.run('media-has-caption', rule, {
46
+ valid: parsers.all([].concat(
47
+ { code: '<div />;' },
48
+ { code: '<MyDiv />;' },
49
+ { code: '<audio><track kind="captions" /></audio>' },
50
+ { code: '<audio><track kind="Captions" /></audio>' },
51
+ {
52
+ code: '<audio><track kind="Captions" /><track kind="subtitles" /></audio>',
53
+ },
54
+ { code: '<video><track kind="captions" /></video>' },
55
+ { code: '<video><track kind="Captions" /></video>' },
56
+ {
57
+ code: '<video><track kind="Captions" /><track kind="subtitles" /></video>',
58
+ },
59
+ {
60
+ code: '<audio muted={true}></audio>',
61
+ },
62
+ {
63
+ code: '<video muted={true}></video>',
64
+ },
65
+ {
66
+ code: '<video muted></video>',
67
+ },
68
+ {
69
+ code: '<Audio><track kind="captions" /></Audio>',
70
+ options: customSchema,
71
+ },
72
+ {
73
+ code: '<audio><Track kind="captions" /></audio>',
74
+ options: customSchema,
75
+ },
76
+ {
77
+ code: '<Video><track kind="captions" /></Video>',
78
+ options: customSchema,
79
+ },
80
+ {
81
+ code: '<video><Track kind="captions" /></video>',
82
+ options: customSchema,
83
+ },
84
+ {
85
+ code: '<Audio><Track kind="captions" /></Audio>',
86
+ options: customSchema,
87
+ },
88
+ {
89
+ code: '<Video><Track kind="captions" /></Video>',
90
+ options: customSchema,
91
+ },
92
+ {
93
+ code: '<Video muted></Video>',
94
+ options: customSchema,
95
+ },
96
+ {
97
+ code: '<Video muted={true}></Video>',
98
+ options: customSchema,
99
+ },
100
+ {
101
+ code: '<Audio muted></Audio>',
102
+ options: customSchema,
103
+ },
104
+ {
105
+ code: '<Audio muted={true}></Audio>',
106
+ options: customSchema,
107
+ },
108
+ {
109
+ code: '<Audio><track kind="captions" /></Audio>',
110
+ settings: componentsSettings,
111
+ },
112
+ {
113
+ code: '<audio><Track kind="captions" /></audio>',
114
+ settings: componentsSettings,
115
+ },
116
+ {
117
+ code: '<Video><track kind="captions" /></Video>',
118
+ settings: componentsSettings,
119
+ },
120
+ {
121
+ code: '<video><Track kind="captions" /></video>',
122
+ settings: componentsSettings,
123
+ },
124
+ {
125
+ code: '<Audio><Track kind="captions" /></Audio>',
126
+ settings: componentsSettings,
127
+ },
128
+ {
129
+ code: '<Video><Track kind="captions" /></Video>',
130
+ settings: componentsSettings,
131
+ },
132
+ {
133
+ code: '<Video muted></Video>',
134
+ settings: componentsSettings,
135
+ },
136
+ {
137
+ code: '<Video muted={true}></Video>',
138
+ settings: componentsSettings,
139
+ },
140
+ {
141
+ code: '<Audio muted></Audio>',
142
+ settings: componentsSettings,
143
+ },
144
+ {
145
+ code: '<Audio muted={true}></Audio>',
146
+ settings: componentsSettings,
147
+ },
148
+ {
149
+ code: '<Box as="audio" muted={true}></Box>',
150
+ settings: componentsSettings,
151
+ },
152
+ )).map(parserOptionsMapper),
153
+ invalid: parsers.all([].concat(
154
+ { code: '<audio><track /></audio>', errors: [expectedError] },
155
+ {
156
+ code: '<audio><track kind="subtitles" /></audio>',
157
+ errors: [expectedError],
158
+ },
159
+ { code: '<audio />', errors: [expectedError] },
160
+ { code: '<video><track /></video>', errors: [expectedError] },
161
+ {
162
+ code: '<video><track kind="subtitles" /></video>',
163
+ errors: [expectedError],
164
+ },
165
+ {
166
+ code: '<Audio muted={false}></Audio>',
167
+ options: customSchema,
168
+ errors: [expectedError],
169
+ },
170
+ {
171
+ code: '<Video muted={false}></Video>',
172
+ options: customSchema,
173
+ errors: [expectedError],
174
+ },
175
+ {
176
+ code: '<Audio muted={false}></Audio>',
177
+ settings: componentsSettings,
178
+ errors: [expectedError],
179
+ },
180
+ {
181
+ code: '<Video muted={false}></Video>',
182
+ settings: componentsSettings,
183
+ errors: [expectedError],
184
+ },
185
+ { code: '<video />', errors: [expectedError] },
186
+ { code: '<audio>Foo</audio>', errors: [expectedError] },
187
+ { code: '<video>Foo</video>', errors: [expectedError] },
188
+ { code: '<Audio />', options: customSchema, errors: [expectedError] },
189
+ { code: '<Video />', options: customSchema, errors: [expectedError] },
190
+ { code: '<Audio />', settings: componentsSettings, errors: [expectedError] },
191
+ { code: '<Video />', settings: componentsSettings, errors: [expectedError] },
192
+ { code: '<audio><Track /></audio>', options: customSchema, errors: [expectedError] },
193
+ { code: '<video><Track /></video>', options: customSchema, errors: [expectedError] },
194
+ {
195
+ code: '<Audio><Track kind="subtitles" /></Audio>',
196
+ options: customSchema,
197
+ errors: [expectedError],
198
+ },
199
+ {
200
+ code: '<Video><Track kind="subtitles" /></Video>',
201
+ options: customSchema,
202
+ errors: [expectedError],
203
+ },
204
+ {
205
+ code: '<Audio><Track kind="subtitles" /></Audio>',
206
+ settings: componentsSettings,
207
+ errors: [expectedError],
208
+ },
209
+ {
210
+ code: '<Video><Track kind="subtitles" /></Video>',
211
+ settings: componentsSettings,
212
+ errors: [expectedError],
213
+ },
214
+ {
215
+ code: '<Box as="audio"><Track kind="subtitles" /></Box>',
216
+ settings: componentsSettings,
217
+ errors: [expectedError],
218
+ },
219
+ )).map(parserOptionsMapper),
220
+ });
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @fileoverview Enforce onmouseover/onmouseout are accompanied
3
+ * by onfocus/onblur.
4
+ * @author Ethan Cohen
5
+ */
6
+
7
+ // -----------------------------------------------------------------------------
8
+ // Requirements
9
+ // -----------------------------------------------------------------------------
10
+
11
+ import { RuleTester } from 'eslint';
12
+ import parserOptionsMapper from '../../__util__/parserOptionsMapper';
13
+ import parsers from '../../__util__/helpers/parsers';
14
+ import rule from '../../../src/rules/mouse-events-have-key-events';
15
+
16
+ // -----------------------------------------------------------------------------
17
+ // Tests
18
+ // -----------------------------------------------------------------------------
19
+
20
+ const ruleTester = new RuleTester();
21
+
22
+ const mouseOverError = {
23
+ message: 'onMouseOver must be accompanied by onFocus for accessibility.',
24
+ type: 'JSXAttribute',
25
+ };
26
+ const pointerEnterError = {
27
+ message: 'onPointerEnter must be accompanied by onFocus for accessibility.',
28
+ type: 'JSXAttribute',
29
+ };
30
+ const mouseOutError = {
31
+ message: 'onMouseOut must be accompanied by onBlur for accessibility.',
32
+ type: 'JSXAttribute',
33
+ };
34
+ const pointerLeaveError = {
35
+ message: 'onPointerLeave must be accompanied by onBlur for accessibility.',
36
+ type: 'JSXAttribute',
37
+ };
38
+
39
+ ruleTester.run('mouse-events-have-key-events', rule, {
40
+ valid: parsers.all([].concat(
41
+ { code: '<div onMouseOver={() => void 0} onFocus={() => void 0} />;' },
42
+ {
43
+ code: '<div onMouseOver={() => void 0} onFocus={() => void 0} {...props} />;',
44
+ },
45
+ { code: '<div onMouseOver={handleMouseOver} onFocus={handleFocus} />;' },
46
+ {
47
+ code: '<div onMouseOver={handleMouseOver} onFocus={handleFocus} {...props} />;',
48
+ },
49
+ { code: '<div />;' },
50
+ { code: '<div onBlur={() => {}} />' },
51
+ { code: '<div onFocus={() => {}} />' },
52
+ { code: '<div onMouseOut={() => void 0} onBlur={() => void 0} />' },
53
+ { code: '<div onMouseOut={() => void 0} onBlur={() => void 0} {...props} />' },
54
+ { code: '<div onMouseOut={handleMouseOut} onBlur={handleOnBlur} />' },
55
+ { code: '<div onMouseOut={handleMouseOut} onBlur={handleOnBlur} {...props} />' },
56
+ { code: '<MyElement />' },
57
+ { code: '<MyElement onMouseOver={() => {}} />' },
58
+ { code: '<MyElement onMouseOut={() => {}} />' },
59
+ { code: '<MyElement onBlur={() => {}} />' },
60
+ { code: '<MyElement onFocus={() => {}} />' },
61
+ { code: '<MyElement onMouseOver={() => {}} {...props} />' },
62
+ { code: '<MyElement onMouseOut={() => {}} {...props} />' },
63
+ { code: '<MyElement onBlur={() => {}} {...props} />' },
64
+ { code: '<MyElement onFocus={() => {}} {...props} />' },
65
+ /* Passing in empty options doesn't check any event handlers */
66
+ {
67
+ code: '<div onMouseOver={() => {}} onMouseOut={() => {}} />',
68
+ options: [{ hoverInHandlers: [], hoverOutHandlers: [] }],
69
+ },
70
+ /* Passing in custom handlers */
71
+ {
72
+ code: '<div onMouseOver={() => {}} onFocus={() => {}} />',
73
+ options: [{ hoverInHandlers: ['onMouseOver'] }],
74
+ },
75
+ {
76
+ code: '<div onMouseEnter={() => {}} onFocus={() => {}} />',
77
+ options: [{ hoverInHandlers: ['onMouseEnter'] }],
78
+ },
79
+ {
80
+ code: '<div onMouseOut={() => {}} onBlur={() => {}} />',
81
+ options: [{ hoverOutHandlers: ['onMouseOut'] }],
82
+ },
83
+ {
84
+ code: '<div onMouseLeave={() => {}} onBlur={() => {}} />',
85
+ options: [{ hoverOutHandlers: ['onMouseLeave'] }],
86
+ },
87
+ {
88
+ code: '<div onMouseOver={() => {}} onMouseOut={() => {}} />',
89
+ options: [
90
+ { hoverInHandlers: ['onPointerEnter'], hoverOutHandlers: ['onPointerLeave'] },
91
+ ],
92
+ },
93
+ /* Custom options only checks the handlers passed in */
94
+ {
95
+ code: '<div onMouseLeave={() => {}} />',
96
+ options: [{ hoverOutHandlers: ['onPointerLeave'] }],
97
+ },
98
+ )).map(parserOptionsMapper),
99
+ invalid: parsers.all([].concat(
100
+ { code: '<div onMouseOver={() => void 0} />;', errors: [mouseOverError] },
101
+ { code: '<div onMouseOut={() => void 0} />', errors: [mouseOutError] },
102
+ {
103
+ code: '<div onMouseOver={() => void 0} onFocus={undefined} />;',
104
+ errors: [mouseOverError],
105
+ },
106
+ {
107
+ code: '<div onMouseOut={() => void 0} onBlur={undefined} />',
108
+ errors: [mouseOutError],
109
+ },
110
+ {
111
+ code: '<div onMouseOver={() => void 0} {...props} />',
112
+ errors: [mouseOverError],
113
+ },
114
+ {
115
+ code: '<div onMouseOut={() => void 0} {...props} />',
116
+ errors: [mouseOutError],
117
+ },
118
+ /* Custom options */
119
+ {
120
+ code: '<div onMouseOver={() => {}} onMouseOut={() => {}} />',
121
+ options: [
122
+ { hoverInHandlers: ['onMouseOver'], hoverOutHandlers: ['onMouseOut'] },
123
+ ],
124
+ errors: [mouseOverError, mouseOutError],
125
+ },
126
+ {
127
+ code: '<div onPointerEnter={() => {}} onPointerLeave={() => {}} />',
128
+ options: [
129
+ { hoverInHandlers: ['onPointerEnter'], hoverOutHandlers: ['onPointerLeave'] },
130
+ ],
131
+ errors: [pointerEnterError, pointerLeaveError],
132
+ },
133
+ {
134
+ code: '<div onMouseOver={() => {}} />',
135
+ options: [{ hoverInHandlers: ['onMouseOver'] }],
136
+ errors: [mouseOverError],
137
+ },
138
+ {
139
+ code: '<div onPointerEnter={() => {}} />',
140
+ options: [{ hoverInHandlers: ['onPointerEnter'] }],
141
+ errors: [pointerEnterError],
142
+ },
143
+ {
144
+ code: '<div onMouseOut={() => {}} />',
145
+ options: [{ hoverOutHandlers: ['onMouseOut'] }],
146
+ errors: [mouseOutError],
147
+ },
148
+ {
149
+ code: '<div onPointerLeave={() => {}} />',
150
+ options: [{ hoverOutHandlers: ['onPointerLeave'] }],
151
+ errors: [pointerLeaveError],
152
+ },
153
+ )).map(parserOptionsMapper),
154
+ });