Skip to content

Commit 5d4fa08

Browse files
committed
Remove normalization of component names when mapping to DOM elements. (#47)
* [fix] - Remove normalization of DOM element checks to avoid differences in capitalization subtleties between components and DOM elements Fixes #46 I think we can safely assume that pure DOM elements will be lower-cased, especially in the React world. * Update docs & CHANGELOG * 1.2.1
1 parent 5234789 commit 5d4fa08

File tree

10 files changed

+134
-123
lines changed

10 files changed

+134
-123
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
1.2.1 / 2016-05-19
2+
==================
3+
- [fix] Avoid testing interactivity of wrapper components with same name but different casing
4+
as DOM elements (such as `Button` vs `button`).
5+
6+
17
1.2.0 / 2016-05-06
28
==================
39
- [new] Import all roles from DPUB-ARIA.

docs/rules/onclick-has-focus.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Elements that are inherently focusable are as follows:
88
- `<input>`, `<button>`, `<select>` and `<textarea>` elements which are not disabled
99
- `<a>` or `<area>` elements with an `href` attribute.
1010

11+
This rule will only test low-level DOM components, as we can not deterministically map wrapper JSX components to their correct DOM element.
12+
1113
#### References
1214
1. [AX_FOCUS_02](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_focus_02)
1315
2. [MDN](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role#Keyboard_and_focus)

docs/rules/onclick-has-role.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# onclick-has-role
22

3-
Enforce visible, non-interactive elements with click handlers use role attribute. Visible means that it is not hidden from a screen reader. Examples of non-interactive elements are `div`, `section`, and `a` elements without a href prop. The purpose of the role attribute is to identify to screenreaders the exact function of an element.
3+
Enforce visible, non-interactive elements with click handlers use role attribute. Visible means that it is not hidden from a screen reader. Examples of non-interactive elements are `div`, `section`, and `a` elements without a href prop. The purpose of the role attribute is to identify to screenreaders the exact function of an element. This rule will only test low-level DOM components, as we can not deterministically map wrapper JSX components to their correct DOM element.
44

55
#### References
66
1. [MDN](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role#Keyboard_and_focus)
@@ -16,6 +16,7 @@ This rule takes no arguments.
1616
<a tabIndex="0" onClick={() => void 0} /> // tabIndex makes this interactive.
1717
<button onClick={() => void 0} className="foo" /> // button is interactive.
1818
<div onClick={() => void 0} role="button" aria-hidden /> // This is hidden from screenreader.
19+
<Input onClick={() => void 0} type="hidden" /> // This is a higher-level DOM component
1920
```
2021

2122
### Fail

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-jsx-a11y",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"description": "A static analysis linter of jsx and their accessibility with screen readers.",
55
"keywords": [
66
"eslint",

src/rules/aria-unsupported-elements.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const errorMessage = 'This element does not support ARIA roles, states and prope
1818
module.exports = context => ({
1919
JSXOpeningElement: node => {
2020
const nodeType = getNodeType(node);
21-
const nodeAttrs = DOM[nodeType.toUpperCase()];
21+
const nodeAttrs = DOM[nodeType];
2222
const isReservedNodeType = nodeAttrs && nodeAttrs.reserved || false;
2323

2424
// If it's not reserved, then it can have ARIA-* roles, states, and properties

src/util/attributes/DOM.json

Lines changed: 113 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,145 @@
11
{
2-
"A": {},
3-
"ABBR": {},
4-
"ADDRESS": {},
5-
"AREA": {},
6-
"ARTICLE": {},
7-
"ASIDE": {},
8-
"AUDIO": {},
9-
"B": {},
10-
"BASE": {
2+
"a": {},
3+
"abbr": {},
4+
"address": {},
5+
"area": {},
6+
"article": {},
7+
"aside": {},
8+
"audio": {},
9+
"b": {},
10+
"base": {
1111
"reserved": true
1212
},
13-
"BDI": {},
14-
"BDO": {},
15-
"BIG": {},
16-
"BLOCKQUOTE": {},
17-
"BODY": {},
18-
"BR": {},
19-
"BUTTON": {},
20-
"CANVAS": {},
21-
"CAPTION": {},
22-
"CITE": {},
23-
"CODE": {},
24-
"COL": {
13+
"bdi": {},
14+
"bdo": {},
15+
"big": {},
16+
"blockquote": {},
17+
"body": {},
18+
"br": {},
19+
"button": {},
20+
"canvas": {},
21+
"caption": {},
22+
"cite": {},
23+
"code": {},
24+
"col": {
2525
"reserved": true
2626
},
27-
"COLGROUP": {
27+
"colgroup": {
2828
"reserved": true
2929
},
30-
"DATA": {},
31-
"DATALIST": {},
32-
"DD": {},
33-
"DEL": {},
34-
"DETAILS": {},
35-
"DFN": {},
36-
"DIALOG": {},
37-
"DIV": {},
38-
"DL": {},
39-
"DT": {},
40-
"EM": {},
41-
"EMBED": {},
42-
"FIELDSET": {},
43-
"FIGCAPTION": {},
44-
"FIGURE": {},
45-
"FOOTER": {},
46-
"FORM": {},
47-
"H1": {},
48-
"H2": {},
49-
"H3": {},
50-
"H4": {},
51-
"H5": {},
52-
"H6": {},
53-
"HEAD": {
30+
"data": {},
31+
"datalist": {},
32+
"dd": {},
33+
"del": {},
34+
"details": {},
35+
"dfn": {},
36+
"dialog": {},
37+
"div": {},
38+
"dl": {},
39+
"dt": {},
40+
"em": {},
41+
"embed": {},
42+
"fieldset": {},
43+
"figcaption": {},
44+
"figure": {},
45+
"footer": {},
46+
"form": {},
47+
"h1": {},
48+
"h2": {},
49+
"h3": {},
50+
"h4": {},
51+
"h5": {},
52+
"h6": {},
53+
"head": {
5454
"reserved": true
5555
},
56-
"HEADER": {},
57-
"HGROUP": {},
58-
"HR": {},
59-
"HTML": {
56+
"header": {},
57+
"hgroup": {},
58+
"hr": {},
59+
"html": {
6060
"reserved": true
6161
},
62-
"I": {},
63-
"IFRAME": {},
64-
"IMG": {},
65-
"INPUT": {},
66-
"INS": {},
67-
"KBD": {},
68-
"KEYGEN": {},
69-
"LABEL": {},
70-
"LEGEND": {},
71-
"LI": {},
72-
"LINK": {
62+
"i": {},
63+
"iframe": {},
64+
"img": {},
65+
"input": {},
66+
"ins": {},
67+
"kbd": {},
68+
"keygen": {},
69+
"label": {},
70+
"legend": {},
71+
"li": {},
72+
"link": {
7373
"reserved": true
7474
},
75-
"MAIN": {},
76-
"MAP": {},
77-
"MARK": {},
78-
"MENU": {},
79-
"MENUITEM": {},
80-
"META": {
75+
"main": {},
76+
"map": {},
77+
"mark": {},
78+
"menu": {},
79+
"menuitem": {},
80+
"meta": {
8181
"reserved": true
8282
},
83-
"METER": {},
84-
"NAV": {},
85-
"NOSCRIPT": {
83+
"meter": {},
84+
"nav": {},
85+
"noscript": {
8686
"reserved": true
8787
},
88-
"OBJECT": {},
89-
"OL": {},
90-
"OPTGROUP": {},
91-
"OPTION": {},
92-
"OUTPUT": {},
93-
"P": {},
94-
"PARAM": {
88+
"object": {},
89+
"ol": {},
90+
"optgroup": {},
91+
"option": {},
92+
"output": {},
93+
"p": {},
94+
"param": {
9595
"reserved": true
9696
},
97-
"PICTURE": {
97+
"picture": {
9898
"reserved": true
9999
},
100-
"PRE": {},
101-
"PROGRESS": {},
102-
"Q": {},
103-
"RP": {},
104-
"RT": {},
105-
"RUBY": {},
106-
"S": {},
107-
"SAMP": {},
108-
"SCRIPT": {
100+
"pre": {},
101+
"progress": {},
102+
"q": {},
103+
"rp": {},
104+
"rt": {},
105+
"ruby": {},
106+
"s": {},
107+
"samp": {},
108+
"script": {
109109
"reserved": true
110110
},
111-
"SECTION": {},
112-
"SELECT": {},
113-
"SMALL": {},
114-
"SOURCE": {
111+
"section": {},
112+
"select": {},
113+
"small": {},
114+
"source": {
115115
"reserved": true
116116
},
117-
"SPAN": {},
118-
"STRONG": {},
119-
"STYLE": {
117+
"span": {},
118+
"strong": {},
119+
"style": {
120120
"reserved": true
121121
},
122-
"SUB": {},
123-
"SUMMARY": {},
124-
"SUP": {},
125-
"TABLE": {},
126-
"TBODY": {},
127-
"TD": {},
128-
"TEXTAREA": {},
129-
"TFOOT": {},
130-
"TH": {},
131-
"THEAD": {},
132-
"TIME": {},
133-
"TITLE": {
122+
"sub": {},
123+
"summary": {},
124+
"sup": {},
125+
"table": {},
126+
"tbody": {},
127+
"td": {},
128+
"textarea": {},
129+
"tfoot": {},
130+
"th": {},
131+
"thead": {},
132+
"time": {},
133+
"title": {
134134
"reserved": true
135135
},
136-
"TR": {},
137-
"TRACK": {
136+
"tr": {},
137+
"track": {
138138
"reserved": true
139139
},
140-
"U": {},
141-
"UL": {},
142-
"VAR": {},
143-
"VIDEO": {},
144-
"WBR": {}
140+
"u": {},
141+
"ul": {},
142+
"var": {},
143+
"video": {},
144+
"wbr": {}
145145
}

src/util/isInteractiveElement.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const interactiveMap = {
3434
const isInteractiveElement = (tagName, attributes) => {
3535
// Do not test higher level JSX components, as we do not know what
3636
// low-level DOM element this maps to.
37-
if (Object.keys(DOMElements).indexOf(tagName.toUpperCase()) === -1) {
37+
if (Object.keys(DOMElements).indexOf(tagName) === -1) {
3838
return true;
3939
}
4040

tests/src/rules/aria-unsupported-elements.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const roleValidityTests = Object.keys(DOM).map(element => {
3838
const role = isReserved ? '' : 'role';
3939

4040
return {
41-
code: `<${element.toLowerCase()} ${role} />`,
41+
code: `<${element} ${role} />`,
4242
parserOptions
4343
};
4444
});
@@ -48,7 +48,7 @@ const ariaValidityTests = Object.keys(DOM).map(element => {
4848
const aria = isReserved ? '' : 'aria-hidden';
4949

5050
return {
51-
code: `<${element.toLowerCase()} ${aria} />`,
51+
code: `<${element} ${aria} />`,
5252
parserOptions
5353
};
5454
});
@@ -57,15 +57,15 @@ const ariaValidityTests = Object.keys(DOM).map(element => {
5757
const invalidRoleValidityTests = Object.keys(DOM)
5858
.filter(element => Boolean(DOM[element].reserved))
5959
.map(reservedElem => ({
60-
code: `<${reservedElem.toLowerCase()} role />`,
60+
code: `<${reservedElem} role />`,
6161
errors: [ errorMessage ],
6262
parserOptions
6363
}));
6464

6565
const invalidAriaValidityTests = Object.keys(DOM)
6666
.filter(element => Boolean(DOM[element].reserved))
6767
.map(reservedElem => ({
68-
code: `<${reservedElem.toLowerCase()} aria-hidden />`,
68+
code: `<${reservedElem} aria-hidden />`,
6969
errors: [ errorMessage ],
7070
parserOptions
7171
}));

tests/src/rules/onclick-has-focus.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ ruleTester.run('onclick-has-focus', rule, {
6262
{ code: '<span onClick="doSomething();" tabIndex="0">Click me!</span>', parserOptions },
6363
{ code: '<span onClick="doSomething();" tabIndex="-1">Click me too!</span>', parserOptions },
6464
{ code: '<a href="javascript:void(0);" onClick="doSomething();">Click ALL the things!</a>', parserOptions },
65-
{ code: '<Foo.Bar onClick={() => void 0} aria-hidden={false} />;', parserOptions }
65+
{ code: '<Foo.Bar onClick={() => void 0} aria-hidden={false} />;', parserOptions },
66+
{ code: '<Input onClick={() => void 0} type="hidden" />;', parserOptions }
6667
],
6768

6869
invalid: [

tests/src/rules/onclick-has-role.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ ruleTester.run('onclick-has-role', rule, {
5454
{ code: '<a onClick={() => void 0} href="http://x.y.z" />', parserOptions },
5555
{ code: '<a onClick={() => void 0} href="http://x.y.z" tabIndex="0" />', parserOptions },
5656
{ code: '<input onClick={() => void 0} type="hidden" />;', parserOptions },
57-
{ code: '<TestComponent onClick={doFoo} />', parserOptions }
57+
{ code: '<TestComponent onClick={doFoo} />', parserOptions },
58+
{ code: '<Button onClick={doFoo} />', parserOptions }
5859
],
5960
invalid: [
6061
{ code: '<div onClick={() => void 0} />;', errors: [ expectedError ], parserOptions },

0 commit comments

Comments
 (0)