Skip to content

Commit 78f0f6f

Browse files
committed
Big moves to introduce static, non-interactive and interactive semantic distinctions
1 parent 0c979f1 commit 78f0f6f

18 files changed

+745
-193
lines changed

__mocks__/genInteractives.js

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,31 @@ Object.keys(interactiveElementsMap)
9898
(name: string) => delete indeterminantInteractiveElementsMap[name]
9999
);
100100

101-
const interactiveRoles = roleNames.filter(
102-
role => roles.get(role).interactive === true
103-
);
101+
const abstractRoles = roleNames
102+
.filter(role => roles.get(role).abstract);
104103

105-
const nonInteractiveRoles = roleNames.filter(
106-
role => roles.get(role).interactive === false
107-
);
104+
const nonAbstractRoles = roleNames
105+
.filter(role => !roles.get(role).abstract);
106+
107+
const interactiveRoles = roleNames
108+
.filter(role => !roles.get(role).abstract)
109+
.filter(role => roles.get(role).interactive);
110+
111+
const nonInteractiveRoles = roleNames
112+
.filter(role => !roles.get(role).abstract)
113+
.filter(role => !roles.get(role).interactive);
114+
115+
export function genElementSymbol (
116+
openingElement: Object,
117+
) {
118+
return openingElement.name.name + (
119+
(openingElement.attributes.length > 0)
120+
? `${openingElement.attributes.map(
121+
attr => `[${attr.name.name}=\"${attr.value.value}\"]` ).join('')
122+
}`
123+
: ''
124+
);
125+
};
108126

109127
export function genInteractiveElements () {
110128
return Object.keys(interactiveElementsMap)
@@ -152,6 +170,22 @@ export function genNonInteractiveRoleElements () {
152170
);
153171
}
154172

173+
export function genAbstractRoleElements () {
174+
return abstractRoles.map(
175+
value => JSXElementMock('div', [
176+
JSXAttributeMock('role', value)
177+
])
178+
);
179+
};
180+
181+
export function genNonAbstractRoleElements () {
182+
return nonAbstractRoles.map(
183+
value => JSXElementMock('div', [
184+
JSXAttributeMock('role', value)
185+
])
186+
);
187+
};
188+
155189
export function genIndeterminantInteractiveElements () {
156190
return Object.keys(indeterminantInteractiveElementsMap)
157191
.map(name => {

__tests__/src/rules/click-events-have-key-events-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const expectedError = {
2626
type: 'JSXOpeningElement',
2727
};
2828

29-
ruleTester.run('onclick-has-role', rule, {
29+
ruleTester.run('click-events-have-key-events', rule, {
3030
valid: [
3131
{ code: '<div onClick={() => void 0} onKeyDown={foo}/>;' },
3232
{ code: '<div onClick={() => void 0} onKeyUp={foo} />;' },

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

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,168 @@ const expectedError = {
2525

2626
ruleTester.run('no-noninteractive-element-handlers', rule, {
2727
valid: [
28-
{ code: '<div />;' },
28+
{ code: '<div className="foo" />;' },
29+
{ code: '<div className="foo" {...props} />;' },
30+
{ code: '<div onClick={() => void 0} aria-hidden />;' },
31+
{ 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} />;' },
36+
/* All flavors of input */
37+
{ code: '<input onClick={() => void 0} />' },
38+
{ code: '<input type="button" onClick={() => void 0} />' },
39+
{ code: '<input type="checkbox" onClick={() => void 0} />' },
40+
{ code: '<input type="color" onClick={() => void 0} />' },
41+
{ code: '<input type="date" onClick={() => void 0} />' },
42+
{ code: '<input type="datetime" onClick={() => void 0} />' },
43+
{ code: '<input type="datetime-local" onClick={() => void 0} />' },
44+
{ code: '<input type="email" onClick={() => void 0} />' },
45+
{ code: '<input type="file" onClick={() => void 0} />' },
46+
{ code: '<input type="image" onClick={() => void 0} />' },
47+
{ code: '<input type="month" onClick={() => void 0} />' },
48+
{ code: '<input type="number" onClick={() => void 0} />' },
49+
{ code: '<input type="password" onClick={() => void 0} />' },
50+
{ code: '<input type="radio" onClick={() => void 0} />' },
51+
{ code: '<input type="range" onClick={() => void 0} />' },
52+
{ code: '<input type="reset" onClick={() => void 0} />' },
53+
{ code: '<input type="search" onClick={() => void 0} />' },
54+
{ code: '<input type="submit" onClick={() => void 0} />' },
55+
{ code: '<input type="tel" onClick={() => void 0} />' },
56+
{ code: '<input type="text" onClick={() => void 0} />' },
57+
{ code: '<input type="time" onClick={() => void 0} />' },
58+
{ code: '<input type="url" onClick={() => void 0} />' },
59+
{ code: '<input type="week" onClick={() => void 0} />' },
60+
/* End all flavors of input */
61+
{ code: '<input type="hidden" onClick={() => void 0} />' },
62+
{ code: '<button onClick={() => void 0} className="foo" />' },
63+
{ code: '<option onClick={() => void 0} className="foo" />' },
64+
{ code: '<select onClick={() => void 0} className="foo" />' },
65+
{ code: '<textarea onClick={() => void 0} className="foo" />' },
66+
{ code: '<a tabIndex="0" onClick={() => void 0} />' },
67+
{ code: '<a onClick={() => void 0} href="http://x.y.z" />' },
68+
{ code: '<a onClick={() => void 0} href="http://x.y.z" tabIndex="0" />' },
69+
{ code: '<input onClick={() => void 0} type="hidden" />;' },
70+
{ code: '<form onClick={() => {}} />;' },
71+
{ code: '<section onClick={() => void 0} />;' },
72+
{ code: '<header onKeyDown={() => void 0} />;' },
73+
{ code: '<footer onKeyPress={() => void 0} />;' },
74+
{ code: '<TestComponent onClick={doFoo} />' },
75+
{ code: '<Button onClick={doFoo} />' },
76+
/* HTML elements attributed with an interactive role */
77+
{ code: '<div role="button" onClick={() => {}} />;' },
78+
{ code: '<div role="checkbox" onClick={() => {}} />;' },
79+
{ code: '<div role="columnheader" onClick={() => {}} />;' },
80+
{ code: '<div role="combobox" onClick={() => {}} />;' },
81+
{ code: '<div role="form" onClick={() => {}} />;' },
82+
{ code: '<div role="gridcell" onClick={() => {}} />;' },
83+
{ code: '<div role="link" onClick={() => {}} />;' },
84+
{ code: '<div role="menuitem" onClick={() => {}} />;' },
85+
{ code: '<div role="menuitemcheckbox" onClick={() => {}} />;' },
86+
{ code: '<div role="menuitemradio" onClick={() => {}} />;' },
87+
{ code: '<div role="option" onClick={() => {}} />;' },
88+
{ code: '<div role="radio" onClick={() => {}} />;' },
89+
{ code: '<div role="rowheader" onClick={() => {}} />;' },
90+
{ code: '<div role="searchbox" onClick={() => {}} />;' },
91+
{ code: '<div role="slider" onClick={() => {}} />;' },
92+
{ code: '<div role="spinbutton" onClick={() => {}} />;' },
93+
{ code: '<div role="switch" onClick={() => {}} />;' },
94+
{ code: '<div role="tab" onClick={() => {}} />;' },
95+
{ code: '<div role="textbox" onClick={() => {}} />;' },
96+
{ code: '<div role="treeitem" onClick={() => {}} />;' },
97+
/* Presentation is a special case role that indicates intentional static semantics */
98+
{ code: '<div role="presentation" onClick={() => {}} />;' },
99+
].map(parserOptionsMapper),
100+
invalid: [
101+
/* HTML elements with an inherent, non-interactive role */
102+
{ code: '<main onClick={() => void 0} />;', errors: [expectedError] },
103+
{ code: '<a onClick={() => void 0} />', errors: [expectedError] },
104+
{ code: '<a onClick={() => {}} />;', errors: [expectedError] },
105+
{ code: '<area onClick={() => {}} />;', errors: [expectedError] },
106+
{ code: '<article onClick={() => {}} />;', errors: [expectedError] },
107+
{ code: '<article onDblClick={() => void 0} />;', errors: [expectedError] },
108+
{ code: '<dd onClick={() => {}} />;', errors: [expectedError] },
109+
{ code: '<dfn onClick={() => {}} />;', errors: [expectedError] },
110+
{ code: '<dt onClick={() => {}} />;', errors: [expectedError] },
111+
{ code: '<fieldset onClick={() => {}} />;', errors: [expectedError] },
112+
{ code: '<figure onClick={() => {}} />;', errors: [expectedError] },
113+
{ code: '<frame onClick={() => {}} />;', errors: [expectedError] },
114+
{ code: '<h1 onClick={() => {}} />;', errors: [expectedError] },
115+
{ code: '<h2 onClick={() => {}} />;', errors: [expectedError] },
116+
{ code: '<h3 onClick={() => {}} />;', errors: [expectedError] },
117+
{ code: '<h4 onClick={() => {}} />;', errors: [expectedError] },
118+
{ code: '<h5 onClick={() => {}} />;', errors: [expectedError] },
119+
{ code: '<h6 onClick={() => {}} />;', errors: [expectedError] },
120+
{ code: '<hr onClick={() => {}} />;', errors: [expectedError] },
121+
{ code: '<img onClick={() => {}} />;', errors: [expectedError] },
122+
{ code: '<li onClick={() => {}} />;', errors: [expectedError] },
123+
{ code: '<nav onClick={() => {}} />;', errors: [expectedError] },
124+
{ code: '<ol onClick={() => {}} />;', errors: [expectedError] },
125+
{ code: '<table onClick={() => {}} />;', errors: [expectedError] },
126+
{ code: '<tbody onClick={() => {}} />;', errors: [expectedError] },
127+
{ code: '<tfoot onClick={() => {}} />;', errors: [expectedError] },
128+
{ code: '<thead onClick={() => {}} />;', errors: [expectedError] },
129+
{ code: '<tr onClick={() => {}} />;', errors: [expectedError] },
130+
{ code: '<ul onClick={() => {}} />;', errors: [expectedError] },
131+
/* HTML elements attributed with a non-interactive role */
132+
{ code: '<div role="alert" onClick={() => {}} />;', errors: [expectedError] },
133+
{ code: '<div role="alertdialog" onClick={() => {}} />;', errors: [expectedError] },
134+
{ code: '<div role="application" onClick={() => {}} />;', errors: [expectedError] },
135+
{ code: '<div role="article" onClick={() => {}} />;', errors: [expectedError] },
136+
{ code: '<div role="banner" onClick={() => {}} />;', errors: [expectedError] },
137+
{ code: '<div role="cell" onClick={() => {}} />;', errors: [expectedError] },
138+
{ code: '<div role="command" onClick={() => {}} />;', errors: [expectedError] },
139+
{ code: '<div role="complementary" onClick={() => {}} />;', errors: [expectedError] },
140+
{ code: '<div role="composite" onClick={() => {}} />;', errors: [expectedError] },
141+
{ code: '<div role="contentinfo" onClick={() => {}} />;', errors: [expectedError] },
142+
{ code: '<div role="definition" onClick={() => {}} />;', errors: [expectedError] },
143+
{ code: '<div role="dialog" onClick={() => {}} />;', errors: [expectedError] },
144+
{ code: '<div role="directory" onClick={() => {}} />;', errors: [expectedError] },
145+
{ code: '<div role="document" onClick={() => {}} />;', errors: [expectedError] },
146+
{ code: '<div role="feed" onClick={() => {}} />;', errors: [expectedError] },
147+
{ code: '<div role="figure" onClick={() => {}} />;', errors: [expectedError] },
148+
{ code: '<div role="grid" onClick={() => {}} />;', errors: [expectedError] },
149+
{ code: '<div role="group" onClick={() => {}} />;', errors: [expectedError] },
150+
{ code: '<div role="heading" onClick={() => {}} />;', errors: [expectedError] },
151+
{ code: '<div role="img" onClick={() => {}} />;', errors: [expectedError] },
152+
{ code: '<div role="input" onClick={() => {}} />;', errors: [expectedError] },
153+
{ code: '<div role="landmark" onClick={() => {}} />;', errors: [expectedError] },
154+
{ code: '<div role="list" onClick={() => {}} />;', errors: [expectedError] },
155+
{ code: '<div role="listbox" onClick={() => {}} />;', errors: [expectedError] },
156+
{ code: '<div role="listitem" onClick={() => {}} />;', errors: [expectedError] },
157+
{ code: '<div role="log" onClick={() => {}} />;', errors: [expectedError] },
158+
{ code: '<div role="main" onClick={() => {}} />;', errors: [expectedError] },
159+
{ code: '<div role="marquee" onClick={() => {}} />;', errors: [expectedError] },
160+
{ code: '<div role="math" onClick={() => {}} />;', errors: [expectedError] },
161+
{ code: '<div role="menu" onClick={() => {}} />;', errors: [expectedError] },
162+
{ code: '<div role="menubar" onClick={() => {}} />;', errors: [expectedError] },
163+
{ code: '<div role="navigation" onClick={() => {}} />;', errors: [expectedError] },
164+
{ code: '<div role="note" onClick={() => {}} />;', errors: [expectedError] },
165+
{ code: '<div role="progressbar" onClick={() => {}} />;', errors: [expectedError] },
166+
{ code: '<div role="radiogroup" onClick={() => {}} />;', errors: [expectedError] },
167+
{ code: '<div role="range" onClick={() => {}} />;', errors: [expectedError] },
168+
{ code: '<div role="region" onClick={() => {}} />;', errors: [expectedError] },
169+
{ code: '<div role="roletype" onClick={() => {}} />;', errors: [expectedError] },
170+
{ code: '<div role="row" onClick={() => {}} />;', errors: [expectedError] },
171+
{ code: '<div role="rowgroup" onClick={() => {}} />;', errors: [expectedError] },
172+
{ 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] },
176+
{ code: '<div role="separator" onClick={() => {}} />;', errors: [expectedError] },
177+
{ code: '<div role="scrollbar" onClick={() => {}} />;', errors: [expectedError] },
178+
{ code: '<div role="status" onClick={() => {}} />;', errors: [expectedError] },
179+
{ code: '<div role="structure" onClick={() => {}} />;', errors: [expectedError] },
180+
{ code: '<div role="table" onClick={() => {}} />;', errors: [expectedError] },
181+
{ code: '<div role="tablist" onClick={() => {}} />;', errors: [expectedError] },
182+
{ code: '<div role="tabpanel" onClick={() => {}} />;', errors: [expectedError] },
183+
{ code: '<div role="term" onClick={() => {}} />;', errors: [expectedError] },
184+
{ code: '<div role="timer" onClick={() => {}} />;', errors: [expectedError] },
185+
{ code: '<div role="toolbar" onClick={() => {}} />;', errors: [expectedError] },
186+
{ code: '<div role="tooltip" onClick={() => {}} />;', errors: [expectedError] },
187+
{ code: '<div role="tree" onClick={() => {}} />;', errors: [expectedError] },
188+
{ code: '<div role="treegrid" onClick={() => {}} />;', errors: [expectedError] },
189+
{ code: '<div role="widget" onClick={() => {}} />;', errors: [expectedError] },
190+
{ code: '<div role="window" onClick={() => {}} />;', errors: [expectedError] },
29191
].map(parserOptionsMapper),
30-
invalid: [].map(parserOptionsMapper),
31192
});

0 commit comments

Comments
 (0)