Skip to content

Commit 3750a8d

Browse files
jessebeachljharb
authored andcommitted
Allow configuration override on no-noninteractive-element-interactions
1 parent fe6e3bf commit 3750a8d

File tree

3 files changed

+33
-13
lines changed

3 files changed

+33
-13
lines changed

__tests__/src/rules/no-noninteractive-element-interactions-test.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ const alwaysValid = [
117117
{ code: '<html onClick={() => {}} />;' },
118118
{ code: '<i onClick={() => {}} />;' },
119119
{ code: '<iframe onLoad={() => {}} />;' },
120+
{ code: '<img onError={() => {}} />;' },
120121
{ code: '<img onLoad={() => {}} />;' },
121122
{ code: '<ins onClick={() => {}} />;' },
122123
{ code: '<kbd onClick={() => {}} />;' },
@@ -229,7 +230,6 @@ const alwaysValid = [
229230
{ code: '<div role="article" onEmptied={() => {}} />;' },
230231
{ code: '<div role="article" onEncrypted={() => {}} />;' },
231232
{ code: '<div role="article" onEnded={() => {}} />;' },
232-
{ code: '<div role="article" onError={() => {}} />;' },
233233
{ code: '<div role="article" onLoadedData={() => {}} />;' },
234234
{ code: '<div role="article" onLoadedMetadata={() => {}} />;' },
235235
{ code: '<div role="article" onLoadStart={() => {}} />;' },
@@ -245,8 +245,6 @@ const alwaysValid = [
245245
{ code: '<div role="article" onTimeUpdate={() => {}} />;' },
246246
{ code: '<div role="article" onVolumeChange={() => {}} />;' },
247247
{ code: '<div role="article" onWaiting={() => {}} />;' },
248-
{ code: '<div role="article" onLoad={() => {}} />;' },
249-
{ code: '<div role="article" onError={() => {}} />;' },
250248
{ code: '<div role="article" onAnimationStart={() => {}} />;' },
251249
{ code: '<div role="article" onAnimationEnd={() => {}} />;' },
252250
{ code: '<div role="article" onAnimationIteration={() => {}} />;' },
@@ -344,6 +342,8 @@ const neverValid = [
344342
{ code: '<div role="article" onKeyPress={() => {}} />;', errors: [expectedError] },
345343
{ code: '<div role="article" onKeyUp={() => {}} />;', errors: [expectedError] },
346344
{ code: '<div role="article" onClick={() => {}} />;', errors: [expectedError] },
345+
{ code: '<div role="article" onLoad={() => {}} />;', errors: [expectedError] },
346+
{ code: '<div role="article" onError={() => {}} />;', errors: [expectedError] },
347347
{ code: '<div role="article" onMouseDown={() => {}} />;', errors: [expectedError] },
348348
{ code: '<div role="article" onMouseUp={() => {}} />;', errors: [expectedError] },
349349
];
@@ -395,7 +395,6 @@ ruleTester.run(`${ruleName}:recommended`, rule, {
395395
{ code: '<div role="article" onEmptied={() => {}} />;' },
396396
{ code: '<div role="article" onEncrypted={() => {}} />;' },
397397
{ code: '<div role="article" onEnded={() => {}} />;' },
398-
{ code: '<div role="article" onError={() => {}} />;' },
399398
{ code: '<div role="article" onLoadedData={() => {}} />;' },
400399
{ code: '<div role="article" onLoadedMetadata={() => {}} />;' },
401400
{ code: '<div role="article" onLoadStart={() => {}} />;' },
@@ -411,8 +410,6 @@ ruleTester.run(`${ruleName}:recommended`, rule, {
411410
{ code: '<div role="article" onTimeUpdate={() => {}} />;' },
412411
{ code: '<div role="article" onVolumeChange={() => {}} />;' },
413412
{ code: '<div role="article" onWaiting={() => {}} />;' },
414-
{ code: '<div role="article" onLoad={() => {}} />;' },
415-
{ code: '<div role="article" onError={() => {}} />;' },
416413
{ code: '<div role="article" onAnimationStart={() => {}} />;' },
417414
{ code: '<div role="article" onAnimationEnd={() => {}} />;' },
418415
{ code: '<div role="article" onAnimationIteration={() => {}} />;' },
@@ -427,10 +424,14 @@ ruleTester.run(`${ruleName}:recommended`, rule, {
427424
.map(parserOptionsMapper),
428425
});
429426

427+
const strictOptions =
428+
(configs.strict.rules[`jsx-a11y/${ruleName}`][1] || {});
430429
ruleTester.run(`${ruleName}:strict`, rule, {
431430
valid: [
432431
...alwaysValid,
433-
].map(parserOptionsMapper),
432+
]
433+
.map(ruleOptionsMapperFactory(strictOptions))
434+
.map(parserOptionsMapper),
434435
invalid: [
435436
...neverValid,
436437
// All the possible handlers
@@ -452,5 +453,7 @@ ruleTester.run(`${ruleName}:strict`, rule, {
452453
{ code: '<div role="article" onMouseMove={() => {}} />;', errors: [expectedError] },
453454
{ code: '<div role="article" onMouseOut={() => {}} />;', errors: [expectedError] },
454455
{ code: '<div role="article" onMouseOver={() => {}} />;', errors: [expectedError] },
455-
].map(parserOptionsMapper),
456+
]
457+
.map(ruleOptionsMapperFactory(strictOptions))
458+
.map(parserOptionsMapper),
456459
});

src/index.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,17 @@ module.exports = {
9090
{
9191
handlers: [
9292
'onClick',
93+
'onError',
94+
'onLoad',
9395
'onMouseDown',
9496
'onMouseUp',
9597
'onKeyPress',
9698
'onKeyDown',
9799
'onKeyUp',
98100
],
101+
body: ['onError', 'onLoad'],
102+
iframe: ['onError', 'onLoad'],
103+
img: ['onError', 'onLoad'],
99104
},
100105
],
101106
'jsx-a11y/no-noninteractive-element-to-interactive-role': [
@@ -196,7 +201,14 @@ module.exports = {
196201
'jsx-a11y/no-autofocus': 'error',
197202
'jsx-a11y/no-distracting-elements': 'error',
198203
'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
199-
'jsx-a11y/no-noninteractive-element-interactions': 'error',
204+
'jsx-a11y/no-noninteractive-element-interactions': [
205+
'error',
206+
{
207+
body: ['onError', 'onLoad'],
208+
iframe: ['onError', 'onLoad'],
209+
img: ['onError', 'onLoad'],
210+
},
211+
],
200212
'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error',
201213
'jsx-a11y/no-noninteractive-tabindex': 'error',
202214
'jsx-a11y/no-onchange': 'error',

src/rules/no-noninteractive-element-interactions.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
getPropValue,
1717
getProp,
1818
hasProp,
19+
propName,
1920
} from 'jsx-ast-utils';
2021
import type { JSXOpeningElement } from 'ast-types-flow';
2122
import includes from 'array-includes';
@@ -35,6 +36,7 @@ const errorMessage =
3536
const domElements = [...dom.keys()];
3637
const defaultInteractiveProps = [
3738
...eventHandlersByType.focus,
39+
...eventHandlersByType.image,
3840
...eventHandlersByType.keyboard,
3941
...eventHandlersByType.mouse,
4042
];
@@ -52,11 +54,14 @@ module.exports = {
5254
const { options } = context;
5355
return {
5456
JSXOpeningElement: (node: JSXOpeningElement) => {
55-
const { attributes } = node;
57+
let { attributes } = node;
5658
const type = elementType(node);
57-
const interactiveProps = options[0]
58-
? options[0].handlers
59-
: defaultInteractiveProps;
59+
const config = (options[0] || {});
60+
const interactiveProps = config.handlers || defaultInteractiveProps;
61+
// Allow overrides from rule configuration for specific elements and roles.
62+
if (Object.prototype.hasOwnProperty.call(config, type)) {
63+
attributes = attributes.filter(attr => !includes(config[type], propName(attr)));
64+
}
6065

6166
const hasInteractiveProps = interactiveProps
6267
.some(prop => (

0 commit comments

Comments
 (0)