Skip to content

Commit de75c0a

Browse files
committed
isInteractiveElement and isNonInteractivElement should only evaluate html elements, not roles as well
1 parent 78f0f6f commit de75c0a

11 files changed

+248
-158
lines changed

__mocks__/genInteractives.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,10 @@ import JSXElementMock from './JSXElementMock';
1212
const domElements = [...dom.keys()];
1313
const roleNames = [...roles.keys()];
1414

15-
const pureInteractiveElements = domElements
16-
.filter(name => dom.get(name).interactive === true)
17-
.reduce((interactiveElements, name) => {
18-
interactiveElements[name] = [];
19-
return interactiveElements;
20-
}, {});
21-
2215
const interactiveElementsMap = {
23-
...pureInteractiveElements,
2416
a: [{prop: 'href', value: '#'}],
2517
area: [{prop: 'href', value: '#'}],
18+
button: [],
2619
form: [],
2720
input: [],
2821
'input[type=\"button\"]': [{prop: 'type', value: 'button'}],
@@ -47,6 +40,11 @@ const interactiveElementsMap = {
4740
'input[type=\"time\"]': [{prop: 'type', value: 'time'}],
4841
'input[type=\"url\"]': [{prop: 'type', value: 'url'}],
4942
'input[type=\"week\"]': [{prop: 'type', value: 'week'}],
43+
menuitem:[],
44+
option: [],
45+
select: [],
46+
'td[role="gridcell"]': [{prop: 'role', value: 'gridcell'}],
47+
textarea: [],
5048
};
5149

5250
const nonInteractiveElementsMap = {

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

Lines changed: 119 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-env jest */
22
/**
3-
* @fileoverview $DESCRIPTION
4-
* @author $AUTHOR
3+
* @fileoverview Enforce non-interactive elements have no interactive handlers.
4+
* @author Ethan Cohen
55
*/
66

77
// -----------------------------------------------------------------------------
@@ -18,21 +18,21 @@ import rule from '../../../src/rules/no-noninteractive-element-handlers';
1818

1919
const ruleTester = new RuleTester();
2020

21+
const errorMessage =
22+
'Visible, non-interactive elements should not have mouse or keyboard event listeners';
23+
2124
const expectedError = {
22-
message: '',
25+
message: errorMessage,
2326
type: 'JSXOpeningElement',
2427
};
2528

2629
ruleTester.run('no-noninteractive-element-handlers', rule, {
2730
valid: [
31+
{ code: '<div />;' },
2832
{ code: '<div className="foo" />;' },
2933
{ code: '<div className="foo" {...props} />;' },
3034
{ code: '<div onClick={() => void 0} aria-hidden />;' },
3135
{ code: '<div onClick={() => void 0} aria-hidden={true} />;' },
32-
{ code: '<div onClick={() => void 0} />;' },
33-
{ code: '<div onClick={() => void 0} role={undefined} />;' },
34-
{ code: '<div onClick={() => void 0} {...props} />;' },
35-
{ code: '<div onKeyUp={() => void 0} aria-hidden={false} />;' },
3636
/* All flavors of input */
3737
{ code: '<input onClick={() => void 0} />' },
3838
{ code: '<input type="button" onClick={() => void 0} />' },
@@ -68,9 +68,6 @@ ruleTester.run('no-noninteractive-element-handlers', rule, {
6868
{ code: '<a onClick={() => void 0} href="http://x.y.z" tabIndex="0" />' },
6969
{ code: '<input onClick={() => void 0} type="hidden" />;' },
7070
{ code: '<form onClick={() => {}} />;' },
71-
{ code: '<section onClick={() => void 0} />;' },
72-
{ code: '<header onKeyDown={() => void 0} />;' },
73-
{ code: '<footer onKeyPress={() => void 0} />;' },
7471
{ code: '<TestComponent onClick={doFoo} />' },
7572
{ code: '<Button onClick={doFoo} />' },
7673
/* HTML elements attributed with an interactive role */
@@ -96,8 +93,120 @@ ruleTester.run('no-noninteractive-element-handlers', rule, {
9693
{ code: '<div role="treeitem" onClick={() => {}} />;' },
9794
/* Presentation is a special case role that indicates intentional static semantics */
9895
{ code: '<div role="presentation" onClick={() => {}} />;' },
96+
/* HTML elements attributed with an abstract role */
97+
{ code: '<div role="command" onClick={() => {}} />;' },
98+
{ code: '<div role="composite" onClick={() => {}} />;' },
99+
{ code: '<div role="input" onClick={() => {}} />;' },
100+
{ code: '<div role="landmark" onClick={() => {}} />;' },
101+
{ code: '<div role="range" onClick={() => {}} />;' },
102+
{ code: '<div role="roletype" onClick={() => {}} />;' },
103+
{ code: '<div role="section" onClick={() => {}} />;' },
104+
{ code: '<div role="sectionhead" onClick={() => {}} />;' },
105+
{ code: '<div role="select" onClick={() => {}} />;' },
106+
{ code: '<div role="structure" onClick={() => {}} />;' },
107+
{ code: '<div role="widget" onClick={() => {}} />;' },
108+
{ code: '<div role="window" onClick={() => {}} />;' },
99109
].map(parserOptionsMapper),
100110
invalid: [
111+
{ code: '<div onClick={() => void 0} />;', errors: [expectedError] },
112+
{ code: '<div onClick={() => void 0} role={undefined} />;', errors: [expectedError] },
113+
{ code: '<div onClick={() => void 0} {...props} />;', errors: [expectedError] },
114+
{ code: '<div onKeyUp={() => void 0} aria-hidden={false} />;', errors: [expectedError] },
115+
/* Static elements; no inherent role */
116+
{ code: '<acronym onClick={() => {}} />;', errors: [expectedError] },
117+
{ code: '<address onClick={() => {}} />;', errors: [expectedError] },
118+
{ code: '<applet onClick={() => {}} />;', errors: [expectedError] },
119+
{ code: '<aside onClick={() => {}} />;', errors: [expectedError] },
120+
{ code: '<audio onClick={() => {}} />;', errors: [expectedError] },
121+
{ code: '<b onClick={() => {}} />;', errors: [expectedError] },
122+
{ code: '<base onClick={() => {}} />;', errors: [expectedError] },
123+
{ code: '<bdi onClick={() => {}} />;', errors: [expectedError] },
124+
{ code: '<bdo onClick={() => {}} />;', errors: [expectedError] },
125+
{ code: '<big onClick={() => {}} />;', errors: [expectedError] },
126+
{ code: '<blink onClick={() => {}} />;', errors: [expectedError] },
127+
{ code: '<blockquote onClick={() => {}} />;', errors: [expectedError] },
128+
{ code: '<body onClick={() => {}} />;', errors: [expectedError] },
129+
{ code: '<br onClick={() => {}} />;', errors: [expectedError] },
130+
{ code: '<canvas onClick={() => {}} />;', errors: [expectedError] },
131+
{ code: '<caption onClick={() => {}} />;', errors: [expectedError] },
132+
{ code: '<center onClick={() => {}} />;', errors: [expectedError] },
133+
{ code: '<cite onClick={() => {}} />;', errors: [expectedError] },
134+
{ code: '<code onClick={() => {}} />;', errors: [expectedError] },
135+
{ code: '<col onClick={() => {}} />;', errors: [expectedError] },
136+
{ code: '<colgroup onClick={() => {}} />;', errors: [expectedError] },
137+
{ code: '<content onClick={() => {}} />;', errors: [expectedError] },
138+
{ code: '<data onClick={() => {}} />;', errors: [expectedError] },
139+
{ code: '<datalist onClick={() => {}} />;', errors: [expectedError] },
140+
{ code: '<del onClick={() => {}} />;', errors: [expectedError] },
141+
{ code: '<details onClick={() => {}} />;', errors: [expectedError] },
142+
{ code: '<dir onClick={() => {}} />;', errors: [expectedError] },
143+
{ code: '<div onClick={() => {}} />;', errors: [expectedError] },
144+
{ code: '<dl onClick={() => {}} />;', errors: [expectedError] },
145+
{ code: '<em onClick={() => {}} />;', errors: [expectedError] },
146+
{ code: '<embed onClick={() => {}} />;', errors: [expectedError] },
147+
{ code: '<figcaption onClick={() => {}} />;', errors: [expectedError] },
148+
{ code: '<font onClick={() => {}} />;', errors: [expectedError] },
149+
{ code: '<footer onClick={() => {}} />;', errors: [expectedError] },
150+
{ code: '<frameset onClick={() => {}} />;', errors: [expectedError] },
151+
{ code: '<head onClick={() => {}} />;', errors: [expectedError] },
152+
{ code: '<header onClick={() => {}} />;', errors: [expectedError] },
153+
{ code: '<hgroup onClick={() => {}} />;', errors: [expectedError] },
154+
{ code: '<html onClick={() => {}} />;', errors: [expectedError] },
155+
{ code: '<i onClick={() => {}} />;', errors: [expectedError] },
156+
{ code: '<iframe onClick={() => {}} />;', errors: [expectedError] },
157+
{ code: '<ins onClick={() => {}} />;', errors: [expectedError] },
158+
{ code: '<kbd onClick={() => {}} />;', errors: [expectedError] },
159+
{ code: '<keygen onClick={() => {}} />;', errors: [expectedError] },
160+
{ code: '<label onClick={() => {}} />;', errors: [expectedError] },
161+
{ code: '<legend onClick={() => {}} />;', errors: [expectedError] },
162+
{ code: '<link onClick={() => {}} />;', errors: [expectedError] },
163+
{ code: '<map onClick={() => {}} />;', errors: [expectedError] },
164+
{ code: '<mark onClick={() => {}} />;', errors: [expectedError] },
165+
{ code: '<marquee onClick={() => {}} />;', errors: [expectedError] },
166+
{ code: '<menu onClick={() => {}} />;', errors: [expectedError] },
167+
{ code: '<menuitem onClick={() => {}} />;', errors: [expectedError] },
168+
{ code: '<meta onClick={() => {}} />;', errors: [expectedError] },
169+
{ code: '<meter onClick={() => {}} />;', errors: [expectedError] },
170+
{ code: '<noembed onClick={() => {}} />;', errors: [expectedError] },
171+
{ code: '<noscript onClick={() => {}} />;', errors: [expectedError] },
172+
{ code: '<object onClick={() => {}} />;', errors: [expectedError] },
173+
{ code: '<optgroup onClick={() => {}} />;', errors: [expectedError] },
174+
{ code: '<output onClick={() => {}} />;', errors: [expectedError] },
175+
{ code: '<p onClick={() => {}} />;', errors: [expectedError] },
176+
{ code: '<param onClick={() => {}} />;', errors: [expectedError] },
177+
{ code: '<picture onClick={() => {}} />;', errors: [expectedError] },
178+
{ code: '<pre onClick={() => {}} />;', errors: [expectedError] },
179+
{ code: '<progress onClick={() => {}} />;', errors: [expectedError] },
180+
{ code: '<q onClick={() => {}} />;', errors: [expectedError] },
181+
{ code: '<rp onClick={() => {}} />;', errors: [expectedError] },
182+
{ code: '<rt onClick={() => {}} />;', errors: [expectedError] },
183+
{ code: '<rtc onClick={() => {}} />;', errors: [expectedError] },
184+
{ code: '<ruby onClick={() => {}} />;', errors: [expectedError] },
185+
{ code: '<s onClick={() => {}} />;', errors: [expectedError] },
186+
{ code: '<samp onClick={() => {}} />;', errors: [expectedError] },
187+
{ code: '<script onClick={() => {}} />;', errors: [expectedError] },
188+
{ code: '<section onClick={() => {}} />;', errors: [expectedError] },
189+
{ code: '<small onClick={() => {}} />;', errors: [expectedError] },
190+
{ code: '<source onClick={() => {}} />;', errors: [expectedError] },
191+
{ code: '<spacer onClick={() => {}} />;', errors: [expectedError] },
192+
{ code: '<span onClick={() => {}} />;', errors: [expectedError] },
193+
{ code: '<strike onClick={() => {}} />;', errors: [expectedError] },
194+
{ code: '<strong onClick={() => {}} />;', errors: [expectedError] },
195+
{ code: '<style onClick={() => {}} />;', errors: [expectedError] },
196+
{ code: '<sub onClick={() => {}} />;', errors: [expectedError] },
197+
{ code: '<summary onClick={() => {}} />;', errors: [expectedError] },
198+
{ code: '<sup onClick={() => {}} />;', errors: [expectedError] },
199+
{ code: '<td onClick={() => {}} />;', errors: [expectedError] },
200+
{ code: '<th onClick={() => {}} />;', errors: [expectedError] },
201+
{ code: '<time onClick={() => {}} />;', errors: [expectedError] },
202+
{ code: '<title onClick={() => {}} />;', errors: [expectedError] },
203+
{ code: '<track onClick={() => {}} />;', errors: [expectedError] },
204+
{ code: '<tt onClick={() => {}} />;', errors: [expectedError] },
205+
{ code: '<u onClick={() => {}} />;', errors: [expectedError] },
206+
{ code: '<var onClick={() => {}} />;', errors: [expectedError] },
207+
{ code: '<video onClick={() => {}} />;', errors: [expectedError] },
208+
{ code: '<wbr onClick={() => {}} />;', errors: [expectedError] },
209+
{ code: '<xmp onClick={() => {}} />;', errors: [expectedError] },
101210
/* HTML elements with an inherent, non-interactive role */
102211
{ code: '<main onClick={() => void 0} />;', errors: [expectedError] },
103212
{ code: '<a onClick={() => void 0} />', errors: [expectedError] },
@@ -135,9 +244,7 @@ ruleTester.run('no-noninteractive-element-handlers', rule, {
135244
{ code: '<div role="article" onClick={() => {}} />;', errors: [expectedError] },
136245
{ code: '<div role="banner" onClick={() => {}} />;', errors: [expectedError] },
137246
{ code: '<div role="cell" onClick={() => {}} />;', errors: [expectedError] },
138-
{ code: '<div role="command" onClick={() => {}} />;', errors: [expectedError] },
139247
{ code: '<div role="complementary" onClick={() => {}} />;', errors: [expectedError] },
140-
{ code: '<div role="composite" onClick={() => {}} />;', errors: [expectedError] },
141248
{ code: '<div role="contentinfo" onClick={() => {}} />;', errors: [expectedError] },
142249
{ code: '<div role="definition" onClick={() => {}} />;', errors: [expectedError] },
143250
{ code: '<div role="dialog" onClick={() => {}} />;', errors: [expectedError] },
@@ -149,8 +256,6 @@ ruleTester.run('no-noninteractive-element-handlers', rule, {
149256
{ code: '<div role="group" onClick={() => {}} />;', errors: [expectedError] },
150257
{ code: '<div role="heading" onClick={() => {}} />;', errors: [expectedError] },
151258
{ code: '<div role="img" onClick={() => {}} />;', errors: [expectedError] },
152-
{ code: '<div role="input" onClick={() => {}} />;', errors: [expectedError] },
153-
{ code: '<div role="landmark" onClick={() => {}} />;', errors: [expectedError] },
154259
{ code: '<div role="list" onClick={() => {}} />;', errors: [expectedError] },
155260
{ code: '<div role="listbox" onClick={() => {}} />;', errors: [expectedError] },
156261
{ code: '<div role="listitem" onClick={() => {}} />;', errors: [expectedError] },
@@ -164,19 +269,13 @@ ruleTester.run('no-noninteractive-element-handlers', rule, {
164269
{ code: '<div role="note" onClick={() => {}} />;', errors: [expectedError] },
165270
{ code: '<div role="progressbar" onClick={() => {}} />;', errors: [expectedError] },
166271
{ code: '<div role="radiogroup" onClick={() => {}} />;', errors: [expectedError] },
167-
{ code: '<div role="range" onClick={() => {}} />;', errors: [expectedError] },
168272
{ code: '<div role="region" onClick={() => {}} />;', errors: [expectedError] },
169-
{ code: '<div role="roletype" onClick={() => {}} />;', errors: [expectedError] },
170273
{ code: '<div role="row" onClick={() => {}} />;', errors: [expectedError] },
171274
{ code: '<div role="rowgroup" onClick={() => {}} />;', errors: [expectedError] },
172275
{ code: '<div role="search" onClick={() => {}} />;', errors: [expectedError] },
173-
{ code: '<div role="section" onClick={() => {}} />;', errors: [expectedError] },
174-
{ code: '<div role="sectionhead" onClick={() => {}} />;', errors: [expectedError] },
175-
{ code: '<div role="select" onClick={() => {}} />;', errors: [expectedError] },
176276
{ code: '<div role="separator" onClick={() => {}} />;', errors: [expectedError] },
177277
{ code: '<div role="scrollbar" onClick={() => {}} />;', errors: [expectedError] },
178278
{ code: '<div role="status" onClick={() => {}} />;', errors: [expectedError] },
179-
{ code: '<div role="structure" onClick={() => {}} />;', errors: [expectedError] },
180279
{ code: '<div role="table" onClick={() => {}} />;', errors: [expectedError] },
181280
{ code: '<div role="tablist" onClick={() => {}} />;', errors: [expectedError] },
182281
{ code: '<div role="tabpanel" onClick={() => {}} />;', errors: [expectedError] },
@@ -186,7 +285,5 @@ ruleTester.run('no-noninteractive-element-handlers', rule, {
186285
{ code: '<div role="tooltip" onClick={() => {}} />;', errors: [expectedError] },
187286
{ code: '<div role="tree" onClick={() => {}} />;', errors: [expectedError] },
188287
{ code: '<div role="treegrid" onClick={() => {}} />;', errors: [expectedError] },
189-
{ code: '<div role="widget" onClick={() => {}} />;', errors: [expectedError] },
190-
{ code: '<div role="window" onClick={() => {}} />;', errors: [expectedError] },
191288
].map(parserOptionsMapper),
192289
});

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-env jest */
22
/**
3-
* @fileoverview Enforce non-interactive elements have no interactive handlers.
3+
* @fileoverview Enforce static elements have no interactive handlers.
44
* @author Ethan Cohen
55
*/
66

@@ -19,7 +19,7 @@ import rule from '../../../src/rules/no-static-element-interactions';
1919
const ruleTester = new RuleTester();
2020

2121
const errorMessage =
22-
'Visible, non-interactive elements should not have mouse or keyboard event listeners';
22+
'Visible, static elements should not have mouse or keyboard event listeners';
2323

2424
const expectedError = {
2525
message: errorMessage,
@@ -123,16 +123,27 @@ ruleTester.run('no-static-element-interactions', rule, {
123123
{ code: '<thead onClick={() => {}} />;' },
124124
{ code: '<tr onClick={() => {}} />;' },
125125
{ code: '<ul onClick={() => {}} />;' },
126+
/* HTML elements attributed with an abstract role */
127+
{ code: '<div role="command" onClick={() => {}} />;' },
128+
{ code: '<div role="composite" onClick={() => {}} />;' },
129+
{ code: '<div role="input" onClick={() => {}} />;' },
130+
{ code: '<div role="landmark" onClick={() => {}} />;' },
131+
{ code: '<div role="range" onClick={() => {}} />;' },
132+
{ code: '<div role="roletype" onClick={() => {}} />;' },
133+
{ code: '<div role="section" onClick={() => {}} />;' },
134+
{ code: '<div role="sectionhead" onClick={() => {}} />;' },
135+
{ code: '<div role="select" onClick={() => {}} />;' },
136+
{ code: '<div role="structure" onClick={() => {}} />;' },
137+
{ code: '<div role="widget" onClick={() => {}} />;' },
138+
{ code: '<div role="window" onClick={() => {}} />;' },
126139
/* HTML elements attributed with a non-interactive role */
127140
{ code: '<div role="alert" onClick={() => {}} />;' },
128141
{ code: '<div role="alertdialog" onClick={() => {}} />;' },
129142
{ code: '<div role="application" onClick={() => {}} />;' },
130143
{ code: '<div role="article" onClick={() => {}} />;' },
131144
{ code: '<div role="banner" onClick={() => {}} />;' },
132145
{ code: '<div role="cell" onClick={() => {}} />;' },
133-
{ code: '<div role="command" onClick={() => {}} />;' },
134146
{ code: '<div role="complementary" onClick={() => {}} />;' },
135-
{ code: '<div role="composite" onClick={() => {}} />;' },
136147
{ code: '<div role="contentinfo" onClick={() => {}} />;' },
137148
{ code: '<div role="definition" onClick={() => {}} />;' },
138149
{ code: '<div role="dialog" onClick={() => {}} />;' },
@@ -144,8 +155,6 @@ ruleTester.run('no-static-element-interactions', rule, {
144155
{ code: '<div role="group" onClick={() => {}} />;' },
145156
{ code: '<div role="heading" onClick={() => {}} />;' },
146157
{ code: '<div role="img" onClick={() => {}} />;' },
147-
{ code: '<div role="input" onClick={() => {}} />;' },
148-
{ code: '<div role="landmark" onClick={() => {}} />;' },
149158
{ code: '<div role="list" onClick={() => {}} />;' },
150159
{ code: '<div role="listbox" onClick={() => {}} />;' },
151160
{ code: '<div role="listitem" onClick={() => {}} />;' },
@@ -159,19 +168,13 @@ ruleTester.run('no-static-element-interactions', rule, {
159168
{ code: '<div role="note" onClick={() => {}} />;' },
160169
{ code: '<div role="progressbar" onClick={() => {}} />;' },
161170
{ code: '<div role="radiogroup" onClick={() => {}} />;' },
162-
{ code: '<div role="range" onClick={() => {}} />;' },
163171
{ code: '<div role="region" onClick={() => {}} />;' },
164-
{ code: '<div role="roletype" onClick={() => {}} />;' },
165172
{ code: '<div role="row" onClick={() => {}} />;' },
166173
{ code: '<div role="rowgroup" onClick={() => {}} />;' },
167174
{ code: '<div role="search" onClick={() => {}} />;' },
168-
{ code: '<div role="section" onClick={() => {}} />;' },
169-
{ code: '<div role="sectionhead" onClick={() => {}} />;' },
170-
{ code: '<div role="select" onClick={() => {}} />;' },
171175
{ code: '<div role="separator" onClick={() => {}} />;' },
172176
{ code: '<div role="scrollbar" onClick={() => {}} />;' },
173177
{ code: '<div role="status" onClick={() => {}} />;' },
174-
{ code: '<div role="structure" onClick={() => {}} />;' },
175178
{ code: '<div role="table" onClick={() => {}} />;' },
176179
{ code: '<div role="tablist" onClick={() => {}} />;' },
177180
{ code: '<div role="tabpanel" onClick={() => {}} />;' },
@@ -181,8 +184,6 @@ ruleTester.run('no-static-element-interactions', rule, {
181184
{ code: '<div role="tooltip" onClick={() => {}} />;' },
182185
{ code: '<div role="tree" onClick={() => {}} />;' },
183186
{ code: '<div role="treegrid" onClick={() => {}} />;' },
184-
{ code: '<div role="widget" onClick={() => {}} />;' },
185-
{ code: '<div role="window" onClick={() => {}} />;' },
186187
].map(parserOptionsMapper),
187188
invalid: [
188189
{ code: '<div onClick={() => void 0} />;', errors: [expectedError] },

0 commit comments

Comments
 (0)