Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Alternatively, you can enable all the recommended rules at once by adding `plugi
- [accessibility-label](docs/rules/accessibility-label.md): Enforce that views that have `accessible={true}`, also have an `accessibilityLabel` prop
- [has-accessibility-props](docs/rules/has-accessibility-props.md): Enforce all `<Touchable\*>` components have `accessibilityRole` prop or both `accessibilityTraits` and `accessibilityComponentType` props set
- [has-valid-accessibility-role](docs/rules/has-valid-accessibility-role.md): Enforce `accessibilityRole` property value is valid
- [has-valid-accessibility-state](docs/rules/has-valid-accessibility-state.md): Enforce `accessibilityState` property value is valid
- [has-valid-accessibility-states](docs/rules/has-valid-accessibility-states.md): Enforce `accessibilityStates` property value is valid
- [has-valid-accessibility-component-type](docs/rules/has-valid-accessibility-component-type.md): Enforce `accessibilityComponentType` property value is valid
- [has-valid-accessibility-traits](docs/rules/has-valid-accessibility-traits.md): Enforce `accessibilityTraits` and `accessibilityComponentType` prop values must be valid
Expand Down
73 changes: 73 additions & 0 deletions __tests__/src/rules/has-valid-accessibility-state-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-env jest */
/**
* @fileoverview Describes the current state of a component to the user of an assistive technology.
* @author JP Driver
*/

// -----------------------------------------------------------------------------
// Requirements
// -----------------------------------------------------------------------------

import { RuleTester } from 'eslint';
import parserOptionsMapper from '../../__util__/parserOptionsMapper';
import rule from '../../../src/rules/has-valid-accessibility-state';

// -----------------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------------

const ruleTester = new RuleTester();

const propMustBeAnObject = {
message: 'accessibilityState must be an object',
type: 'JSXAttribute'
};

const invalidObjectKey = key => ({
message: `accessibilityState object: "${key}" is not a valid key`,
type: 'JSXAttribute'
});

const valueMustBeBoolean = key => ({
message: `accessibilityState object: "${key}" value is not a boolean`,
type: 'JSXAttribute'
});

const checkedMustBeBooleanOrMixed = {
message: `accessibilityState object: "checked" value is not either a boolean or 'mixed'`,
type: 'JSXAttribute'
};

ruleTester.run('has-valid-accessibility-state', rule, {
valid: [
{ code: '<TouchableOpacity accessibilityState={{ disabled: true }} />;' },
{ code: '<TouchableOpacity accessibilityState={{ checked: true }} />;' },
{ code: '<TouchableOpacity accessibilityState={{ checked: "mixed" }} />;' }
].map(parserOptionsMapper),
invalid: [
{
code: '<TouchableOpacity accessibilityState="disabled" />',
errors: [propMustBeAnObject]
},
{
code: '<TouchableOpacity accessibilityState={["disabled"]} />',
errors: [propMustBeAnObject]
},
{
code: '<TouchableOpacity accessibilityState={{ disabled: "yes" }} />',
errors: [valueMustBeBoolean('disabled')]
},
{
code: '<TouchableOpacity accessibilityState={{ checked: "yes" }} />',
errors: [checkedMustBeBooleanOrMixed]
},
{
code: '<TouchableOpacity accessibilityState={{ foo: true }} />',
errors: [invalidObjectKey('foo')]
},
{
code: '<TouchableOpacity accessibilityState={{ foo: "yes" }} />',
errors: [invalidObjectKey('foo')]
}
].map(parserOptionsMapper)
});
42 changes: 42 additions & 0 deletions docs/rules/has-valid-accessibility-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# has-valid-accessibility-state

Describes the current state of a component to the user of an assistive technology.

## `accessibilityState` is an object. It contains the following fields:

NAME|TYPE|REQUIRED
-|-|-
disabled|boolean|No
selecte|boolean|No
checked|boolean or 'mixed'|No
busy|boolean|No
expanded|boolean|No

### References

1. [React Native Docs - accessibilityState (iOS, Android)](https://facebook.github.io/react-native/docs/accessibility#accessibilitystate-ios-android)

## Rule details

This rule takes no arguments.

### Succeed

```jsx
<TouchableOpacity accessibilityState={{ disabled: true }} />
<TouchableOpacity accessibilityState={{ checked: true }} />
<TouchableOpacity accessibilityState={{ checked: "mixed" }} />
```

### Fail

```jsx
<TouchableOpacity accessibilityState="disabled" />
<TouchableOpacity accessibilityState={["disabled"]} />
<TouchableOpacity accessibilityState={{ disabled: "yes" }} />
<TouchableOpacity accessibilityState={{ checked: "yes" }} />
<TouchableOpacity accessibilityState={{ foo: true }} />
<TouchableOpacity accessibilityState={{ foo: "yes" }} />
```

*Note*: This plugin previously defined a rule with this name which is now `has-valid-accessibility-states` (see [#42](https://github.com/FormidableLabs/eslint-plugin-react-native-a11y/pull/42)). React Native v0.61 introduced a new `accessibilityState` prop (see [commit](https://github.com/facebook/react-native/commit/099be9b35634851b178e990c47358c2129c0dd7d)) -- which is now covered by this rule.
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
'react-native-a11y/has-valid-accessibility-component-type': 'error',
'react-native-a11y/has-valid-accessibility-live-region': 'error',
'react-native-a11y/has-valid-accessibility-role': 'error',
'react-native-a11y/has-valid-accessibility-state': 'error',
'react-native-a11y/has-valid-accessibility-states': 'error',
'react-native-a11y/has-valid-accessibility-traits': 'error',
'react-native-a11y/has-valid-important-for-accessibility': 'error',
Expand Down
74 changes: 47 additions & 27 deletions src/rules/has-valid-accessibility-state.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,60 @@
/**
* @fileoverview Used to tell Talkback or Voiceover the state a UI Element is in
* @author Jen Luker
* @fileoverview Describes the current state of a component to the user of an assistive technology.
* @author JP Driver
* @flow
*/

import createValidPropRule from '../factory/valid-prop';
import type { JSXAttribute } from 'ast-types-flow';
import { elementType, getPropValue, getLiteralPropValue } from 'jsx-ast-utils';
import { generateObjSchema } from '../util/schemas';
import type { ESLintContext } from '../../flow/eslint';

// ----------------------------------------------------------------------------
// Rule Definition
// ----------------------------------------------------------------------------

const errorMessage =
'accessibilityStates must be one or both of the defined values';
const validKeys = ['disabled', 'selected', 'checked', 'busy', 'expanded'];

const validValues = ['selected', 'disabled'];
module.exports = {
meta: {
docs: {},
schema: [generateObjSchema()]
},

let deprecationHasBeenWarned = false;
create: (context: ESLintContext) => ({
JSXAttribute: (node: JSXAttribute) => {
const attrName = elementType(node);
if (attrName === 'accessibilityState') {
const attrValue = getPropValue(node);
const test = getLiteralPropValue(node);

const rule = createValidPropRule(
'accessibilityStates',
validValues,
errorMessage,
{
deprecated: true
},
{
Program: () => {
if (deprecationHasBeenWarned) return;
// eslint-disable-next-line no-console
console.log(
'The react-native-a11y/has-valid-accessibility-state rule is deprecated. ' +
'Please use the react-native-a11y/has-valid-accessibility-states rule instead.'
);
deprecationHasBeenWarned = true;
}
}
);
const error = message =>
context.report({
node,
message
});

module.exports = rule;
if (typeof attrValue !== 'object' || Array.isArray(attrValue)) {
error('accessibilityState must be an object');
} else {
Object.entries(attrValue).map(([key, value]) => {
if (validKeys.indexOf(key) < 0) {
error(`accessibilityState object: "${key}" is not a valid key`);
} else if (key !== 'checked' && typeof value !== 'boolean') {
error(
`accessibilityState object: "${key}" value is not a boolean`
);
} else if (
key === 'checked' &&
!(typeof value === 'boolean' || value === 'mixed')
) {
error(
`accessibilityState object: "checked" value is not either a boolean or 'mixed'`
);
}
});
}
}
}
})
};