Skip to content

Commit fe6e3bf

Browse files
authored
Merge pull request #380 from evcohen/interactive-supports-disabled-element-focus
Do not consider disabled elements in interactive-supports-focus
2 parents 0c025ae + 633aa26 commit fe6e3bf

File tree

4 files changed

+113
-0
lines changed

4 files changed

+113
-0
lines changed

__tests__/src/rules/interactive-supports-focus-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ const alwaysValid = [
115115
{ code: '<div role="switch" tabIndex="0" onClick={() => void 0} />' },
116116
{ code: '<div role="tab" tabIndex="0" onClick={() => void 0} />' },
117117
{ code: '<div role="textbox" tabIndex="0" onClick={() => void 0} />' },
118+
{ code: '<div role="textbox" aria-disabled="true" onClick={() => void 0} />' },
118119
{ code: '<Foo.Bar onClick={() => void 0} aria-hidden={false} />;' },
119120
{ code: '<Input onClick={() => void 0} type="hidden" />;' },
120121
];
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/* eslint-env mocha */
2+
import expect from 'expect';
3+
import isDisabledElement from '../../../src/util/isDisabledElement';
4+
import JSXAttributeMock from '../../../__mocks__/JSXAttributeMock';
5+
6+
describe('isDisabledElement', () => {
7+
describe('HTML5', () => {
8+
describe('disabled', () => {
9+
it('should identify HTML5 disabled elements', () => {
10+
const attributes = [
11+
JSXAttributeMock('disabled', 'disabled'),
12+
];
13+
expect(isDisabledElement(attributes))
14+
.toBe(true);
15+
});
16+
});
17+
describe('not disabled', () => {
18+
it('should identify HTML5 disabled elements with null as the value', () => {
19+
const attributes = [
20+
JSXAttributeMock('disabled', null),
21+
];
22+
expect(isDisabledElement(attributes))
23+
.toBe(true);
24+
});
25+
it('should not identify HTML5 disabled elements with undefined as the value', () => {
26+
const attributes = [
27+
JSXAttributeMock('disabled', undefined),
28+
];
29+
expect(isDisabledElement(attributes))
30+
.toBe(false);
31+
});
32+
});
33+
});
34+
describe('ARIA', () => {
35+
describe('disabled', () => {
36+
it('should not identify ARIA disabled elements', () => {
37+
const attributes = [
38+
JSXAttributeMock('aria-disabled', 'true'),
39+
];
40+
expect(isDisabledElement(attributes))
41+
.toBe(true);
42+
});
43+
it('should not identify ARIA disabled elements', () => {
44+
const attributes = [
45+
JSXAttributeMock('aria-disabled', true),
46+
];
47+
expect(isDisabledElement(attributes))
48+
.toBe(true);
49+
});
50+
});
51+
describe('not disabled', () => {
52+
it('should not identify ARIA disabled elements', () => {
53+
const attributes = [
54+
JSXAttributeMock('aria-disabled', 'false'),
55+
];
56+
expect(isDisabledElement(attributes))
57+
.toBe(false);
58+
});
59+
it('should not identify ARIA disabled elements', () => {
60+
const attributes = [
61+
JSXAttributeMock('aria-disabled', false),
62+
];
63+
expect(isDisabledElement(attributes))
64+
.toBe(false);
65+
});
66+
it('should not identify ARIA disabled elements with null as the value', () => {
67+
const attributes = [
68+
JSXAttributeMock('aria-disabled', null),
69+
];
70+
expect(isDisabledElement(attributes))
71+
.toBe(false);
72+
});
73+
it('should not identify ARIA disabled elements with undefined as the value', () => {
74+
const attributes = [
75+
JSXAttributeMock('aria-disabled', undefined),
76+
];
77+
expect(isDisabledElement(attributes))
78+
.toBe(false);
79+
});
80+
});
81+
});
82+
});

src/rules/interactive-supports-focus.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
enumArraySchema,
2323
generateObjSchema,
2424
} from '../util/schemas';
25+
import isDisabledElement from '../util/isDisabledElement';
2526
import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader';
2627
import isInteractiveElement from '../util/isInteractiveElement';
2728
import isInteractiveRole from '../util/isInteractiveRole';
@@ -72,6 +73,7 @@ module.exports = {
7273
return;
7374
} else if (
7475
!hasInteractiveProps
76+
|| isDisabledElement(attributes)
7577
|| isHiddenFromScreenReader(type, attributes)
7678
|| isPresentationRole(type, attributes)
7779
) {

src/util/isDisabledElement.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @flow
3+
*/
4+
5+
import { getProp, getLiteralPropValue, getPropValue } from 'jsx-ast-utils';
6+
import type { Node } from 'ast-types-flow';
7+
8+
const isDisabledElement = (attributes: Array<Node>): boolean => {
9+
const disabledAttr = getProp(attributes, 'disabled');
10+
const disabledAttrValue = getPropValue(disabledAttr);
11+
const isHTML5Disabled = disabledAttr && disabledAttrValue !== undefined;
12+
if (isHTML5Disabled) {
13+
return true;
14+
}
15+
const ariaDisabledAttr = getProp(attributes, 'aria-disabled');
16+
const ariaDisabledAttrValue = getLiteralPropValue(ariaDisabledAttr);
17+
18+
if (
19+
ariaDisabledAttr
20+
&& ariaDisabledAttrValue !== undefined
21+
&& ariaDisabledAttrValue === true
22+
) {
23+
return true;
24+
}
25+
return false;
26+
};
27+
28+
export default isDisabledElement;

0 commit comments

Comments
 (0)