Skip to content

Commit

Permalink
Remove normalization of component names when mapping to DOM elements. (
Browse files Browse the repository at this point in the history
…#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
  • Loading branch information
beefancohen committed May 19, 2016
1 parent 5234789 commit 5d4fa08
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 123 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
1.2.1 / 2016-05-19
==================
- [fix] Avoid testing interactivity of wrapper components with same name but different casing
as DOM elements (such as `Button` vs `button`).


1.2.0 / 2016-05-06
==================
- [new] Import all roles from DPUB-ARIA.
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/onclick-has-focus.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Elements that are inherently focusable are as follows:
- `<input>`, `<button>`, `<select>` and `<textarea>` elements which are not disabled
- `<a>` or `<area>` elements with an `href` attribute.

This rule will only test low-level DOM components, as we can not deterministically map wrapper JSX components to their correct DOM element.

#### References
1. [AX_FOCUS_02](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_focus_02)
2. [MDN](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role#Keyboard_and_focus)
Expand Down
3 changes: 2 additions & 1 deletion docs/rules/onclick-has-role.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# onclick-has-role

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.
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.

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

### Fail
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-jsx-a11y",
"version": "1.2.0",
"version": "1.2.1",
"description": "A static analysis linter of jsx and their accessibility with screen readers.",
"keywords": [
"eslint",
Expand Down
2 changes: 1 addition & 1 deletion src/rules/aria-unsupported-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const errorMessage = 'This element does not support ARIA roles, states and prope
module.exports = context => ({
JSXOpeningElement: node => {
const nodeType = getNodeType(node);
const nodeAttrs = DOM[nodeType.toUpperCase()];
const nodeAttrs = DOM[nodeType];
const isReservedNodeType = nodeAttrs && nodeAttrs.reserved || false;

// If it's not reserved, then it can have ARIA-* roles, states, and properties
Expand Down
226 changes: 113 additions & 113 deletions src/util/attributes/DOM.json
Original file line number Diff line number Diff line change
@@ -1,145 +1,145 @@
{
"A": {},
"ABBR": {},
"ADDRESS": {},
"AREA": {},
"ARTICLE": {},
"ASIDE": {},
"AUDIO": {},
"B": {},
"BASE": {
"a": {},
"abbr": {},
"address": {},
"area": {},
"article": {},
"aside": {},
"audio": {},
"b": {},
"base": {
"reserved": true
},
"BDI": {},
"BDO": {},
"BIG": {},
"BLOCKQUOTE": {},
"BODY": {},
"BR": {},
"BUTTON": {},
"CANVAS": {},
"CAPTION": {},
"CITE": {},
"CODE": {},
"COL": {
"bdi": {},
"bdo": {},
"big": {},
"blockquote": {},
"body": {},
"br": {},
"button": {},
"canvas": {},
"caption": {},
"cite": {},
"code": {},
"col": {
"reserved": true
},
"COLGROUP": {
"colgroup": {
"reserved": true
},
"DATA": {},
"DATALIST": {},
"DD": {},
"DEL": {},
"DETAILS": {},
"DFN": {},
"DIALOG": {},
"DIV": {},
"DL": {},
"DT": {},
"EM": {},
"EMBED": {},
"FIELDSET": {},
"FIGCAPTION": {},
"FIGURE": {},
"FOOTER": {},
"FORM": {},
"H1": {},
"H2": {},
"H3": {},
"H4": {},
"H5": {},
"H6": {},
"HEAD": {
"data": {},
"datalist": {},
"dd": {},
"del": {},
"details": {},
"dfn": {},
"dialog": {},
"div": {},
"dl": {},
"dt": {},
"em": {},
"embed": {},
"fieldset": {},
"figcaption": {},
"figure": {},
"footer": {},
"form": {},
"h1": {},
"h2": {},
"h3": {},
"h4": {},
"h5": {},
"h6": {},
"head": {
"reserved": true
},
"HEADER": {},
"HGROUP": {},
"HR": {},
"HTML": {
"header": {},
"hgroup": {},
"hr": {},
"html": {
"reserved": true
},
"I": {},
"IFRAME": {},
"IMG": {},
"INPUT": {},
"INS": {},
"KBD": {},
"KEYGEN": {},
"LABEL": {},
"LEGEND": {},
"LI": {},
"LINK": {
"i": {},
"iframe": {},
"img": {},
"input": {},
"ins": {},
"kbd": {},
"keygen": {},
"label": {},
"legend": {},
"li": {},
"link": {
"reserved": true
},
"MAIN": {},
"MAP": {},
"MARK": {},
"MENU": {},
"MENUITEM": {},
"META": {
"main": {},
"map": {},
"mark": {},
"menu": {},
"menuitem": {},
"meta": {
"reserved": true
},
"METER": {},
"NAV": {},
"NOSCRIPT": {
"meter": {},
"nav": {},
"noscript": {
"reserved": true
},
"OBJECT": {},
"OL": {},
"OPTGROUP": {},
"OPTION": {},
"OUTPUT": {},
"P": {},
"PARAM": {
"object": {},
"ol": {},
"optgroup": {},
"option": {},
"output": {},
"p": {},
"param": {
"reserved": true
},
"PICTURE": {
"picture": {
"reserved": true
},
"PRE": {},
"PROGRESS": {},
"Q": {},
"RP": {},
"RT": {},
"RUBY": {},
"S": {},
"SAMP": {},
"SCRIPT": {
"pre": {},
"progress": {},
"q": {},
"rp": {},
"rt": {},
"ruby": {},
"s": {},
"samp": {},
"script": {
"reserved": true
},
"SECTION": {},
"SELECT": {},
"SMALL": {},
"SOURCE": {
"section": {},
"select": {},
"small": {},
"source": {
"reserved": true
},
"SPAN": {},
"STRONG": {},
"STYLE": {
"span": {},
"strong": {},
"style": {
"reserved": true
},
"SUB": {},
"SUMMARY": {},
"SUP": {},
"TABLE": {},
"TBODY": {},
"TD": {},
"TEXTAREA": {},
"TFOOT": {},
"TH": {},
"THEAD": {},
"TIME": {},
"TITLE": {
"sub": {},
"summary": {},
"sup": {},
"table": {},
"tbody": {},
"td": {},
"textarea": {},
"tfoot": {},
"th": {},
"thead": {},
"time": {},
"title": {
"reserved": true
},
"TR": {},
"TRACK": {
"tr": {},
"track": {
"reserved": true
},
"U": {},
"UL": {},
"VAR": {},
"VIDEO": {},
"WBR": {}
"u": {},
"ul": {},
"var": {},
"video": {},
"wbr": {}
}
2 changes: 1 addition & 1 deletion src/util/isInteractiveElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const interactiveMap = {
const isInteractiveElement = (tagName, attributes) => {
// Do not test higher level JSX components, as we do not know what
// low-level DOM element this maps to.
if (Object.keys(DOMElements).indexOf(tagName.toUpperCase()) === -1) {
if (Object.keys(DOMElements).indexOf(tagName) === -1) {
return true;
}

Expand Down
8 changes: 4 additions & 4 deletions tests/src/rules/aria-unsupported-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const roleValidityTests = Object.keys(DOM).map(element => {
const role = isReserved ? '' : 'role';

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

return {
code: `<${element.toLowerCase()} ${aria} />`,
code: `<${element} ${aria} />`,
parserOptions
};
});
Expand All @@ -57,15 +57,15 @@ const ariaValidityTests = Object.keys(DOM).map(element => {
const invalidRoleValidityTests = Object.keys(DOM)
.filter(element => Boolean(DOM[element].reserved))
.map(reservedElem => ({
code: `<${reservedElem.toLowerCase()} role />`,
code: `<${reservedElem} role />`,
errors: [ errorMessage ],
parserOptions
}));

const invalidAriaValidityTests = Object.keys(DOM)
.filter(element => Boolean(DOM[element].reserved))
.map(reservedElem => ({
code: `<${reservedElem.toLowerCase()} aria-hidden />`,
code: `<${reservedElem} aria-hidden />`,
errors: [ errorMessage ],
parserOptions
}));
Expand Down
3 changes: 2 additions & 1 deletion tests/src/rules/onclick-has-focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ ruleTester.run('onclick-has-focus', rule, {
{ code: '<span onClick="doSomething();" tabIndex="0">Click me!</span>', parserOptions },
{ code: '<span onClick="doSomething();" tabIndex="-1">Click me too!</span>', parserOptions },
{ code: '<a href="javascript:void(0);" onClick="doSomething();">Click ALL the things!</a>', parserOptions },
{ code: '<Foo.Bar onClick={() => void 0} aria-hidden={false} />;', parserOptions }
{ code: '<Foo.Bar onClick={() => void 0} aria-hidden={false} />;', parserOptions },
{ code: '<Input onClick={() => void 0} type="hidden" />;', parserOptions }
],

invalid: [
Expand Down
3 changes: 2 additions & 1 deletion tests/src/rules/onclick-has-role.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ ruleTester.run('onclick-has-role', rule, {
{ code: '<a onClick={() => void 0} href="http://x.y.z" />', parserOptions },
{ code: '<a onClick={() => void 0} href="http://x.y.z" tabIndex="0" />', parserOptions },
{ code: '<input onClick={() => void 0} type="hidden" />;', parserOptions },
{ code: '<TestComponent onClick={doFoo} />', parserOptions }
{ code: '<TestComponent onClick={doFoo} />', parserOptions },
{ code: '<Button onClick={doFoo} />', parserOptions }
],
invalid: [
{ code: '<div onClick={() => void 0} />;', errors: [ expectedError ], parserOptions },
Expand Down

0 comments on commit 5d4fa08

Please sign in to comment.