Skip to content

Commit bdc1cf4

Browse files
committed
[fix] Refine implicit role of select to include combobox scenarios
Encode implicit roles for `select` elements based on roles defined in https://www.w3.org/TR/html-aria/#el-select - `select` (with a multiple attribute or a size attribute having value greater than 1) will have the implicit role 'listbox' - `select` (with NO multiple attribute and NO size attribute having value greater than 1) will have the implicit role 'combobox' Fixes #949
1 parent 7f3d698 commit bdc1cf4

File tree

3 files changed

+196
-3
lines changed

3 files changed

+196
-3
lines changed

__tests__/src/rules/no-redundant-roles-test.js

+17
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,29 @@ const alwaysValid = [
4141
{ code: '<MyComponent role="button" />' },
4242
{ code: '<button role={`${foo}button`} />' },
4343
{ code: '<Button role={`${foo}button`} />', settings: componentsSettings },
44+
{ code: '<select role="menu"><option>1</option><option>2</option></select>' },
4445
];
4546

4647
const neverValid = [
4748
{ code: '<button role="button" />', errors: [expectedError('button', 'button')] },
4849
{ code: '<body role="DOCUMENT" />', errors: [expectedError('body', 'document')] },
4950
{ code: '<Button role="button" />', settings: componentsSettings, errors: [expectedError('button', 'button')] },
51+
{ code: '<select role="combobox" size />', errors: [expectedError('select', 'combobox')] },
52+
{ code: '<select role="combobox" size={false} />', errors: [expectedError('select', 'combobox')] },
53+
{ code: '<select role="combobox" size="" />', errors: [expectedError('select', 'combobox')] },
54+
{ code: '<select role="combobox" size={1} />', errors: [expectedError('select', 'combobox')] },
55+
{ code: '<select role="combobox" size="1" />', errors: [expectedError('select', 'combobox')] },
56+
{ code: '<select role="combobox" size={null}></select>', errors: [expectedError('select', 'combobox')] },
57+
{ code: '<select role="combobox" size=""></select>', errors: [expectedError('select', 'combobox')] },
58+
{ code: '<select role="combobox"><option>1</option><option>2</option></select>', errors: [expectedError('select', 'combobox')] },
59+
{ code: '<select role="combobox" multiple={undefined}></select>', errors: [expectedError('select', 'combobox')] },
60+
{ code: '<select role="listbox" size="3" />', errors: [expectedError('select', 'listbox')] },
61+
{ code: '<select role="listbox" size={2} />', errors: [expectedError('select', 'listbox')] },
62+
{ code: '<select role="listbox" multiple><option>1</option><option>2</option></select>', errors: [expectedError('select', 'listbox')] },
63+
{ code: '<select role="listbox" multiple></select>', errors: [expectedError('select', 'listbox')] },
64+
{ code: '<select role="listbox" multiple=""></select>', errors: [expectedError('select', 'listbox')] },
65+
{ code: '<select role="listbox" multiple={false}></select>', errors: [expectedError('select', 'listbox')] },
66+
{ code: '<select role="listbox" multiple={true}></select>', errors: [expectedError('select', 'listbox')] },
5067
];
5168

5269
ruleTester.run(`${ruleName}:recommended`, rule, {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import test from 'tape';
2+
3+
import JSXAttributeMock from '../../../../__mocks__/JSXAttributeMock';
4+
import getImplicitRoleForSelect from '../../../../src/util/implicitRoles/select';
5+
6+
test('isAbstractRole', (t) => {
7+
t.test('works for combobox', (st) => {
8+
st.equal(
9+
getImplicitRoleForSelect([]),
10+
'combobox',
11+
'defaults to combobox',
12+
);
13+
14+
st.equal(
15+
getImplicitRoleForSelect([JSXAttributeMock('multiple', null)]),
16+
'combobox',
17+
'is combobox when multiple attribute is set to not be present',
18+
);
19+
20+
st.equal(
21+
getImplicitRoleForSelect([JSXAttributeMock('multiple', undefined)]),
22+
'combobox',
23+
'is combobox when multiple attribute is set to not be present',
24+
);
25+
26+
st.equal(
27+
getImplicitRoleForSelect([JSXAttributeMock('size', '1')]),
28+
'combobox',
29+
'is combobox when size is not greater than 1',
30+
);
31+
32+
st.equal(
33+
getImplicitRoleForSelect([JSXAttributeMock('size', 1)]),
34+
'combobox',
35+
'is combobox when size is not greater than 1',
36+
);
37+
38+
st.equal(
39+
getImplicitRoleForSelect([JSXAttributeMock('size', 0)]),
40+
'combobox',
41+
'is combobox when size is not greater than 1',
42+
);
43+
44+
st.equal(
45+
getImplicitRoleForSelect([JSXAttributeMock('size', '0')]),
46+
'combobox',
47+
'is combobox when size is not greater than 1',
48+
);
49+
50+
st.equal(
51+
getImplicitRoleForSelect([JSXAttributeMock('size', '-1')]),
52+
'combobox',
53+
'is combobox when size is not greater than 1',
54+
);
55+
56+
st.equal(
57+
getImplicitRoleForSelect([JSXAttributeMock('size', '')]),
58+
'combobox',
59+
'is combobox when size is a valid number',
60+
);
61+
62+
st.equal(
63+
getImplicitRoleForSelect([JSXAttributeMock('size', 'true')]),
64+
'combobox',
65+
'is combobox when size is a valid number',
66+
);
67+
68+
st.equal(
69+
getImplicitRoleForSelect([JSXAttributeMock('size', true)]),
70+
'combobox',
71+
'is combobox when size is a valid number',
72+
);
73+
74+
st.equal(
75+
getImplicitRoleForSelect([JSXAttributeMock('size', NaN)]),
76+
'combobox',
77+
'is combobox when size is a valid number',
78+
);
79+
80+
st.equal(
81+
getImplicitRoleForSelect([JSXAttributeMock('size', '')]),
82+
'combobox',
83+
'is combobox when size is a valid number',
84+
);
85+
86+
st.equal(
87+
getImplicitRoleForSelect([JSXAttributeMock('size', undefined)]),
88+
'combobox',
89+
'is combobox when size is a valid number',
90+
);
91+
92+
st.equal(
93+
getImplicitRoleForSelect([JSXAttributeMock('size', false)]),
94+
'combobox',
95+
'is combobox when size is a valid number',
96+
);
97+
98+
st.end();
99+
});
100+
101+
t.test('works for listbox based on multiple attribute', (st) => {
102+
st.equal(
103+
getImplicitRoleForSelect([JSXAttributeMock('multiple', true)]),
104+
'listbox',
105+
'is listbox when multiple is true',
106+
);
107+
108+
st.equal(
109+
getImplicitRoleForSelect([JSXAttributeMock('multiple', 'true')]),
110+
'listbox',
111+
'is listbox when multiple is true',
112+
);
113+
114+
st.equal(
115+
getImplicitRoleForSelect([JSXAttributeMock('multiple', '')]),
116+
'listbox',
117+
'is listbox when multiple attribute is present',
118+
);
119+
120+
st.equal(
121+
getImplicitRoleForSelect([JSXAttributeMock('multiple', false)]),
122+
'listbox',
123+
'is listbox when multiple attribute is present',
124+
);
125+
126+
st.equal(
127+
getImplicitRoleForSelect([JSXAttributeMock('multiple', 'false')]),
128+
'listbox',
129+
'is listbox when multiple attribute is present',
130+
);
131+
132+
st.end();
133+
});
134+
135+
t.test('works for listbox based on size attribute', (st) => {
136+
st.equal(
137+
getImplicitRoleForSelect([JSXAttributeMock('size', 2)]),
138+
'listbox',
139+
'is listbox when size is greater than 1',
140+
);
141+
142+
st.equal(
143+
getImplicitRoleForSelect([JSXAttributeMock('size', '3')]),
144+
'listbox',
145+
'is listbox when size is greater than 1',
146+
);
147+
148+
st.equal(
149+
getImplicitRoleForSelect([JSXAttributeMock('size', 40)]),
150+
'listbox',
151+
'is listbox when size is greater than 1',
152+
);
153+
154+
st.end();
155+
});
156+
157+
t.end();
158+
});

src/util/implicitRoles/select.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
1+
import { getProp, getLiteralPropValue } from 'jsx-ast-utils';
2+
13
/**
2-
* Returns the implicit role for a select tag.
4+
* Returns the implicit role for a select tag depending on attributes.
5+
*
6+
* @see https://www.w3.org/TR/html-aria/#el-select
37
*/
4-
export default function getImplicitRoleForSelect() {
5-
return 'listbox';
8+
export default function getImplicitRoleForSelect(attributes) {
9+
const multiple = getProp(attributes, 'multiple');
10+
if (multiple) {
11+
const value = getLiteralPropValue(multiple);
12+
if (value !== undefined && value !== null) {
13+
return 'listbox';
14+
}
15+
}
16+
17+
const size = getProp(attributes, 'size');
18+
const sizeValue = size && getLiteralPropValue(size);
19+
if (sizeValue && (Number(sizeValue) > 1)) {
20+
return 'listbox';
21+
}
22+
23+
return 'combobox';
624
}

0 commit comments

Comments
 (0)