diff --git a/README.md b/README.md index 8799430..382f97f 100644 --- a/README.md +++ b/README.md @@ -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 `` 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 diff --git a/__tests__/src/rules/has-valid-accessibility-state-test.js b/__tests__/src/rules/has-valid-accessibility-state-test.js new file mode 100644 index 0000000..42caf72 --- /dev/null +++ b/__tests__/src/rules/has-valid-accessibility-state-test.js @@ -0,0 +1,77 @@ +/* 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: ';' }, + { code: ';' }, + { code: ';' }, + { + code: + ';' + } + ].map(parserOptionsMapper), + invalid: [ + { + code: '', + errors: [propMustBeAnObject] + }, + { + code: '', + errors: [propMustBeAnObject] + }, + { + code: '', + errors: [valueMustBeBoolean('disabled')] + }, + { + code: '', + errors: [checkedMustBeBooleanOrMixed] + }, + { + code: '', + errors: [invalidObjectKey('foo')] + }, + { + code: '', + errors: [invalidObjectKey('foo')] + } + ].map(parserOptionsMapper) +}); diff --git a/docs/rules/has-valid-accessibility-state.md b/docs/rules/has-valid-accessibility-state.md new file mode 100644 index 0000000..28d6744 --- /dev/null +++ b/docs/rules/has-valid-accessibility-state.md @@ -0,0 +1,43 @@ +# 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 + + + + +``` + +### Fail + +```jsx + + + + + + +``` + +*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. diff --git a/src/index.js b/src/index.js index 3f63a1d..66efe43 100644 --- a/src/index.js +++ b/src/index.js @@ -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', diff --git a/src/rules/has-valid-accessibility-state.js b/src/rules/has-valid-accessibility-state.js index b8f50f9..e8f0756 100644 --- a/src/rules/has-valid-accessibility-state.js +++ b/src/rules/has-valid-accessibility-state.js @@ -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' || value === 'mixed') + ) { + error( + `accessibilityState object: "checked" value is not either a boolean or 'mixed'` + ); + } else if (key !== 'checked' && typeof value !== 'boolean') { + error( + `accessibilityState object: "${key}" value is not a boolean` + ); + } + }); + } + } + } + }) +};