Skip to content

Commit b8df153

Browse files
authored
add has-valid-accessibility-descriptors (#128)
1 parent a8c292d commit b8df153

File tree

5 files changed

+161
-0
lines changed

5 files changed

+161
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ For more information on configuring behaviour of an individual rule, please refe
8585
- [has-valid-accessibility-traits](docs/rules/has-valid-accessibility-traits.md): Enforce `accessibilityTraits` and `accessibilityComponentType` prop values must be valid
8686
- [has-valid-accessibility-value](docs/rules/has-valid-accessibility-value.md): Enforce `accessibilityValue` property value is valid
8787
- [no-nested-touchables](docs/rules/no-nested-touchables.md): Enforce if a view has `accessible={true}`, that there are no touchable elements inside
88+
- [has-valid-accessibility-descriptors](docs/rules/has-valid-accessibility-descriptors.md): Ensures that Touchable* components have appropriate props to communicate with assistive technologies
8889

8990
### iOS
9091
- [has-valid-accessibility-ignores-invert-colors](docs/rules/has-valid-accessibility-ignores-invert-colors.md): Enforce that certain elements use `accessibilityIgnoresInvertColors` to avoid being inverted by device color settings.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/* eslint-env jest */
2+
/**
3+
* @fileoverview Ensures that Touchable* components have appropriate props to communicate with assistive technologies
4+
* @author JP Driver
5+
*/
6+
7+
// -----------------------------------------------------------------------------
8+
// Requirements
9+
// -----------------------------------------------------------------------------
10+
11+
import { RuleTester } from 'eslint';
12+
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
13+
import rule from '../../../src/rules/has-valid-accessibility-descriptors';
14+
15+
// -----------------------------------------------------------------------------
16+
// Tests
17+
// -----------------------------------------------------------------------------
18+
19+
const ruleTester = new RuleTester();
20+
21+
const expectedError = {
22+
message:
23+
'Missing a11y props. Expected one of: accessibilityRole OR BOTH accessibilityLabel + accessibilityHint OR BOTH accessibilityActions + onAccessibilityAction',
24+
type: 'JSXOpeningElement',
25+
};
26+
27+
ruleTester.run('has-valid-accessibility-descriptors', rule, {
28+
valid: [
29+
{
30+
code: '<View></View>;',
31+
},
32+
{
33+
code: `<Pressable accessibilityRole="button">
34+
<Text>Back</Text>
35+
</Pressable>`,
36+
},
37+
{
38+
code: `<TouchableOpacity
39+
accessibilityLabel="Accessibility label."
40+
accessibilityHint="Accessibility hint.">
41+
<Text>Back</Text>
42+
</TouchableOpacity>`,
43+
},
44+
{
45+
code: `<TouchableOpacity
46+
accessibilityActions={[
47+
{name: 'cut', label: 'cut'},
48+
{name: 'copy', label: 'copy'},
49+
{name: 'paste', label: 'paste'},
50+
]}
51+
onAccessibilityAction={(event) => {
52+
switch (event.nativeEvent.actionName) {
53+
case 'cut':
54+
Alert.alert('Alert', 'cut action success');
55+
break;
56+
case 'copy':
57+
Alert.alert('Alert', 'copy action success');
58+
break;
59+
case 'paste':
60+
Alert.alert('Alert', 'paste action success');
61+
break;
62+
}
63+
}}
64+
/>`,
65+
},
66+
{
67+
code: `<TextInput accessibilityLabel="Accessibility label." />`,
68+
errors: [expectedError],
69+
},
70+
].map(parserOptionsMapper),
71+
invalid: [
72+
{
73+
code: `<TouchableOpacity>
74+
<Text>Back</Text>
75+
</TouchableOpacity>`,
76+
errors: [expectedError],
77+
},
78+
{
79+
code: `<TextInput />`,
80+
errors: [expectedError],
81+
},
82+
].map(parserOptionsMapper),
83+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# has-valid-accessibility-descriptors
2+
3+
Ensures that Touchable* components have appropriate props to communicate with assistive technologies.
4+
5+
The rule will trigger when a Touchable* component does not have **any** of the following props:-
6+
7+
- `accessibiltyRole`
8+
- `accessibilityLabel`
9+
- `accessibilityActions`
10+
11+
In some cases, fixing this may then trigger other rules for related props (e.g. if you add `accessibilityActions` to fix this but are missing `onAccessibilityAction`)
12+
13+
## Rule details
14+
15+
This rule takes no arguments.
16+
17+
### Succeed
18+
```jsx
19+
<Pressable accessibilityRole="button">
20+
<Text>Back</Text>
21+
</Pressable>
22+
```
23+
24+
### Fail
25+
```jsx
26+
<Pressable>
27+
<Text>Back</Text>
28+
</Pressable>
29+
```

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const basicRules = {
1414
'react-native-a11y/has-accessibility-props': 'error',
1515
'react-native-a11y/has-valid-accessibility-actions': 'error',
1616
'react-native-a11y/has-valid-accessibility-component-type': 'error',
17+
'react-native-a11y/has-valid-accessibility-descriptors': 'error',
1718
'react-native-a11y/has-valid-accessibility-role': 'error',
1819
'react-native-a11y/has-valid-accessibility-state': 'error',
1920
'react-native-a11y/has-valid-accessibility-states': 'error',
@@ -37,6 +38,7 @@ module.exports = {
3738
'has-accessibility-props': require('./rules/has-accessibility-props'),
3839
'has-valid-accessibility-actions': require('./rules/has-valid-accessibility-actions'),
3940
'has-valid-accessibility-component-type': require('./rules/has-valid-accessibility-component-type'),
41+
'has-valid-accessibility-descriptors': require('./rules/has-valid-accessibility-descriptors'),
4042
'has-valid-accessibility-ignores-invert-colors': require('./rules/has-valid-accessibility-ignores-invert-colors'),
4143
'has-valid-accessibility-live-region': require('./rules/has-valid-accessibility-live-region'),
4244
'has-valid-accessibility-role': require('./rules/has-valid-accessibility-role'),
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @fileoverview Ensures that Touchable* components have appropriate props to communicate with assistive technologies
3+
* @author JP Driver
4+
* @flow
5+
*/
6+
7+
// ----------------------------------------------------------------------------
8+
// Rule Definition
9+
// ----------------------------------------------------------------------------
10+
11+
import type { JSXOpeningElement } from 'ast-types-flow';
12+
import { elementType, hasAnyProp } from 'jsx-ast-utils';
13+
import type { ESLintContext } from '../../flow/eslint';
14+
import isTouchable from '../util/isTouchable';
15+
import { generateObjSchema } from '../util/schemas';
16+
17+
const errorMessage =
18+
'Missing a11y props. Expected one of: accessibilityRole OR BOTH accessibilityLabel + accessibilityHint OR BOTH accessibilityActions + onAccessibilityAction';
19+
20+
const schema = generateObjSchema();
21+
22+
module.exports = {
23+
meta: {
24+
docs: {},
25+
schema: [schema],
26+
},
27+
28+
create: (context: ESLintContext) => ({
29+
JSXOpeningElement: (node: JSXOpeningElement) => {
30+
if (isTouchable(node, context) || elementType(node) === 'TextInput') {
31+
if (
32+
!hasAnyProp(node.attributes, [
33+
'accessibilityRole',
34+
'accessibilityLabel',
35+
'accessibilityActions',
36+
])
37+
) {
38+
context.report({
39+
node,
40+
message: errorMessage,
41+
});
42+
}
43+
}
44+
},
45+
}),
46+
};

0 commit comments

Comments
 (0)