Installations
npm install react-native-accessibility-engine
Score
59.5
Supply Chain
63.9
Quality
68.6
Maintenance
50
Vulnerability
95.3
License
Releases
Developer
aryella-lacerda
Developer Guide
Module System
CommonJS
Min. Node Version
Typescript Support
Yes
Node Version
16.18.0
NPM Version
8.19.2
Statistics
166 Stars
679 Commits
13 Forks
6 Watching
8 Branches
9 Contributors
Updated on 13 Nov 2024
Bundle Size
19.28 kB
Minified
6.96 kB
Minified + Gzipped
Languages
TypeScript (71.74%)
Java (13.08%)
C++ (6%)
Objective-C++ (3.65%)
Objective-C (1.99%)
Ruby (1.36%)
JavaScript (0.93%)
Shell (0.51%)
Starlark (0.49%)
CMake (0.24%)
Total Downloads
Cumulative downloads
Total Downloads
797,132
Last day
-16.1%
1,420
Compared to previous day
Last week
-16%
8,446
Compared to previous week
Last month
6%
39,356
Compared to previous month
Last year
48.1%
402,941
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Dependencies
1
Peer Dependencies
3
Dev Dependencies
34
React Native Accessibility Engine
Make accessibility-related assertions in React Native
Table of Contents
Intro
The React Native ecosystem is massive but it's still lagging behind React Web when it comes to accessibility tools. As mobile developers, we're still braving the challenge of mapping robust, time-tested web guidelines into equally robust guidelines for mobile. In React Native, we also face the challenge of adhering to the accessibility guidelines of multiple platforms using only React Native's Accessibility API. There aren't many practical tutorials on the best use of this API, which means there are limited resources for React Native developers who want to make their apps more accessible. Indeed, there's still a lot of confusion about what makes an app accessible or what accessibility even is.
This project aims to make solving these problems a little easier.
Goals
- Create an engine capable of traversing a component tree making accessibility-related checks
- Create an app to showcase accessiblity best-practices
- Keep it open-source!
How to use
Installation
React 18 introduced a breaking change related to React Test Renderer, which this engine uses. To accommodate all users, React Test Renderer is now a peer dependency. You should install the version compatible with your version of React.
React >= 18
1npm install react-native-accessibility-engine react-test-renderer --save-dev 2# or 3yarn add react-native-accessibility-engine react-test-renderer --dev
React < 18
1npm install react-native-accessibility-engine react-test-renderer@^17.0.2 --save-dev 2# or 3yarn add react-native-accessibility-engine react-test-renderer@^17.0.2 --dev
Configuration
Javascript
Extend Jest's expect
with a new toBeAccessible()
matcher by adding this to your Jest config's setupFilesAfterEnv
array:
1{ 2 ... 3 "setupFilesAfterEnv": [..., "react-native-accessibility-engine"], 4}
Typescript
Adding the lib directly to Jest's setupFilesAfterEnv
array extends Jest's matcher but doesn't import the new matcher types.
You need to import react-native-accessibilit-engine
at least once in your codebase for the types to be imported. You can do that in an entry file if you'd like. If you have a Jest setup file, however, you could kill two birds with one stone by importing it there:
1{ 2 ... 3 "setupFilesAfterEnv": ["path/to/your/setup/file"], 4}
1// At the top of your setup file 2import 'react-native-accessibility-engine';
General Usage
With React elements
1import React from 'react'; 2import { Image, TouchableOpacity } from 'react-native'; 3import Icons from './assets'; 4 5const Button = () => ( 6 <TouchableOpacity accessible={false}> 7 <Image source={Icons.filledHeart['32px']} /> 8 </TouchableOpacity> 9); 10 11it('should be accessible', () => { 12 expect(<Button />)).toBeAccessible(); 13});
With React test instances
You can also pass test instances from react-test-renderer
and
@testing-library/react-native
:
1import React from 'react'; 2import { Image, TouchableOpacity } from 'react-native'; 3 4import TestRenderer, { ReactTestInstance } from 'react-test-renderer'; 5import { render } from '@testing-library/react-native'; 6 7import Icons from './assets'; 8 9const Button = () => ( 10 <TouchableOpacity accessible={false} accessibilityRole={'button'}> 11 <Image source={Icons.filledHeart['32px']} /> 12 </TouchableOpacity> 13); 14 15it('should be accessible, using react-test-renderer', () => { 16 const button = TestRenderer.create(<Button />).root; 17 expect(button).toBeAccessible(); 18}); 19 20it('should be accessible, using @testing-library/react-native', () => { 21 const { getByA11yRole } = render(<Test />); 22 const button = getByA11yRole('button'); 23 expect(button).toBeAccessible(); 24});
Usage with custom rules or violation handlers
Option | Description | Default |
---|---|---|
rules | Pass an array of rule ids you wish to enable for your jest test. See rule ids in Current Rules section of Readme. | all rules |
customViolationHandler | Overrides the return of the jest matcher to have a custom handling with the violation array. | n/a |
Custom default behaviors
These changes will apply for every usage of the .toBeAccessible
matcher. This would be useful for repetitive configurations. Individual overrides can still be used (as shown in the section below) to change individual behavior from the custom default behavior.
customViolationHandler
and rules
can be customized in jest.setup.ts|js
:
1global.__CUSTOM_VIOLATION_HANDLER__ = (violations) => { 2 console.log('violations', violations); 3 4 return violations; 5}; 6 7global.__A11Y_RULES__ = [`no-empty-text`];
rules
can also be configured injest.config.ts|js
, or package.json
using jest globals
(See globals).
1 "globals": { 2 "__A11Y_RULES__": ["no-empty-text"] 3 }
Per matcher overrides
We can run .toBeAccessible
and pass one or more of these options to customize the behavior of the jest matcher for the individual execution of that matcher.
Available options:
1export type Options = { 2 // Pass in the subset of rules you want to run 3 rules?: RuleId[]; 4 // Utilize for custom handling of jest test matcher output 5 customViolationHandler?: (violations: Violation[]) => Violation[]; 6};
Example usage:
1it('should be accessible, only run against no-empty-text rule', () => { 2 expect(button).toBeAccessible({ rules: ['no-empty-text'] }); 3}); 4 5it('should be accessible, and handle violations uniquely', () => { 6 const customViolationHandler = (violations) => { 7 console.error(violations); 8 return []; 9 }; 10 expect(button).toBeAccessible({ customViolationHandler }); 11});
Migration guides
From 0.x to 1.x
- Though the
check
function's optional second argument was never officially documented, a breaking change has occurred to it. If you are using it, I'm afraid we are deprecating thecheck
function in favor of the.toBeAccessible()
matcher, which does not currently recieve any arguments. This is intentional.
1{ 2 // 0.x 3 it('should contain no accessibility errors', () => { 4 expect(() => Engine.check(<Component />, [...rules])).not.toThrow(); 5 }); 6 7 // 1.x 8 it('should contain no accessibility errors', () => { 9 expect(<Component />).toBeAccessible(); 10 }); 11}
From 1.x to 2.x
- Change the path from which you import the new matcher:
1{ 2 // 1.x 3 "setupFilesAfterEnv": [..., "react-native-accessibility-engine/lib/commonjs/extend-expect"], 4 // 2.x 5 "setupFilesAfterEnv": [..., "react-native-accessibility-engine"], 6}
- The
check
function, which was deprecated in 1.x, has been removed from 2.x. It is still used internally, but the.toBeAccessible()
matcher is the only thing exposed in 2.x.
1{ 2 // 0.x and possibly 1.x 3 it('should contain no accessibility errors', () => { 4 expect(() => Engine.check(<Component />)).not.toThrow(); 5 }); 6 7 // 2.x 8 it('should contain no accessibility errors', () => { 9 expect(<Component />).toBeAccessible(); 10 }); 11}
From 2.x to 3.x
Because of breaking changes introducted in React 18, react-test-renderer
is now a peer dependency.
1 2## If you are using React < 18 3 4npm install react-native-accessibility-engine react-test-renderer@^17.0.2 --save-dev 5# or 6yarn add react-native-accessibility-engine react-test-renderer@^17.0.2 --dev 7 8## If you are using React >= 18 9 10npm install react-native-accessibility-engine react-test-renderer --save-dev 11# or 12yarn add react-native-accessibility-engine react-test-renderer --dev
Current rules
ID | Description |
---|---|
link-role-required | If text is clickable, we should inform the user that it behaves like a link |
link-role-misused | We should only use the 'link' role when text is clickable |
pressable-accessible-required | Make the button accessible (selectable) to the user |
pressable-role-required | If a component is touchable/pressable, we should inform the user that it behaves like a button or link |
pressable-label-required | If a button has no text content, an accessibility label can't be inferred so we should explicitly define one |
adjustable-role-required | If a component has a value that can be adjusted, we should inform the user that it is adjustable |
adjustable-value-required | If a component has a value that can be adjusted, we should inform the user of its min, max, and current value |
checked-state-required | If a component has an accessibilityRole of 'checkbox', we should inform the user of the check state |
disabled-state-required | If a component has a disabled state, we should expose its enabled/disabled state to the user |
no-empty-text | If a text node doesn't contain text, we should add text or prevent it from rendering when it has no content |
Contributing
RNAE is totally open to questions, sugestions, corrections, and community pull requests. Though the goal of this project is eventually to cover a wide variety of components and situations, that's still a work in progress. Feel free to suggest any rules you feel could be helpful. ✌️
What's a rule anyway?
Rules are objects that represent a single assertion on a component tree. Let's take the link-role-required
rule, for example:
1import { Text } from 'react-native'; 2 3const rule: Rule = { 4 id: 'link-role-required', 5 matcher: (node) => isText(node.type), 6 assertion: (node) => { 7 const { onPress, accessibilityRole } = node.props; 8 if (onPress) { 9 return accessibilityRole === 'link'; 10 } 11 return true; 12 }, 13 help: { 14 problem: 15 "The text is clickable, but the user wasn't informed that it behaves like a link", 16 solution: 17 "Set the 'accessibilityRole' prop to 'link' or remove the 'onPress' prop", 18 link: '', 19 }, 20};
ID
First, we define an id
, which doubles as the rule's name and should be as simple and self-explanatory as possible. It should also be unique, so take a look at the rules catalog to make sure it isn't already in use.
Matcher
A matcher is a function that accepts a ReactTestInstance node and returns true
or false
.
- If you return
true
, that means that this node is relevant to the rule and should be tested using the assertion defined below. - If you return
false
, the node will be ignored.
In our link-role-required
example, we only want to test Text
nodes.
Assertion
An assertion is a function that accepts one of the nodes selected by the matcher
function, tests for some condition, and returns true
or false
.
- If you return
true
, that means the condition is met and no error is thrown. - If you return
false
, the assertion fails and the engine will eventually (after traversing the whole tree) throw an error with the data contained in thehelp
field.
In our link-role-required
example, we test the following:
- if the text component contains an onPress prop
- return true if the accessibilityRole prop equals 'link'
- return false otherwise
- return true otherwise
Help
The help
field is an object containing three fields: problem, solution, and link.
- The
problem
field is a one-sentence string explaining in simple, clear language why the assertion failed. - The
solution
field is a one-sentence string explaining what the developer needs to do to correct the oversight. - The
link
field is a link to support material.
Note: For now, most rules do not have a link.
Proposing a new rule
Just clone the project, create your own branch off of main
and get to work. 💪 Go into the src/rules
directory and create a folder named with the ID of your rule. Inside this folder, create two files:
- index.ts (make sure the rule object is the default export)
- index.test.tsx
Every rule needs to be tested. If you need to define a helper, put it in the src/helper
folder and remember to test that, too. Also remember to run all the code quality scripts before you open a PR.
1yarn lint 2yarn test 3yarn typescript
ReactTestInstance
For reference, this the type of the node
object passed to the matcher
and assertion
functions.
1export interface ReactTestInstance { 2 instance: any; 3 type: ElementType; 4 props: { [propName: string]: any }; 5 parent: null | ReactTestInstance; 6 children: Array<ReactTestInstance | string>; 7 8 find(predicate: (node: ReactTestInstance) => boolean): ReactTestInstance; 9 findByType(type: ElementType): ReactTestInstance; 10 findByProps(props: { [propName: string]: any }): ReactTestInstance; 11 12 findAll( 13 predicate: (node: ReactTestInstance) => boolean, 14 options?: { deep: boolean } 15 ): ReactTestInstance[]; 16 findAllByType( 17 type: ElementType, 18 options?: { deep: boolean } 19 ): ReactTestInstance[]; 20 findAllByProps( 21 props: { [propName: string]: any }, 22 options?: { deep: boolean } 23 ): ReactTestInstance[]; 24}
Related projects
- eslint-plugin-react-native-a11y is an Eslint plugin that lints your components and accessibility-related props.
- axe-core is the project that inspired this one. It's a similiar accessbility engine made for HTML-based languages like React Web.
- storybook-addon-a11y is a Storybook add-on that uses Axe under the hood and allows you to inspect your components for accessibility problems as you develop them in Storybook.
License
MIT
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: MIT License: LICENSE:0
Reason
binaries present in source code
Details
- Warn: binary detected: example/android/gradle/wrapper/gradle-wrapper.jar:1
Reason
Found 3/8 approved changesets -- score normalized to 3
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/build.yml:1
- Warn: no topLevel permission defined: .github/workflows/code-quality.yml:1
- Warn: no topLevel permission defined: .github/workflows/prerelease.yml:1
- Warn: no topLevel permission defined: .github/workflows/release.yml:1
- Info: no jobLevel write permissions found
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/build.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/build.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/build.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:43: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/build.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:46: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/build.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:51: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/build.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/code-quality.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/code-quality.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/code-quality.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/code-quality.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/code-quality.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/code-quality.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/code-quality.yml:34: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/code-quality.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/prerelease.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/prerelease.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/prerelease.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/prerelease.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/prerelease.yml:50: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/prerelease.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/release.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/release.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:50: update your workflow using https://app.stepsecurity.io/secureworkflow/aryella-lacerda/react-native-accessibility-engine/release.yml/main?enable=pin
- Info: 0 out of 5 GitHub-owned GitHubAction dependencies pinned
- Info: 0 out of 11 third-party GitHubAction dependencies pinned
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 27 are checked with a SAST tool
Reason
26 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92
- Warn: Project is vulnerable to: GHSA-c2jc-4fpr-4vhg
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq
- Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22
- Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp
- Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
- Warn: Project is vulnerable to: GHSA-rxrc-rgv4-jpvx
- Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw
- Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg
- Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p
- Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q
- Warn: Project is vulnerable to: GHSA-phwq-j96m-2c2q
- Warn: Project is vulnerable to: GHSA-ghr5-ch3p-vcr6
- Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97
- Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j
- Warn: Project is vulnerable to: GHSA-7jxr-cg7f-gpgv
- Warn: Project is vulnerable to: GHSA-xj72-wvfv-8985
- Warn: Project is vulnerable to: GHSA-ch3r-j5x3-6q2m
- Warn: Project is vulnerable to: GHSA-p5gc-c584-jj6v
- Warn: Project is vulnerable to: GHSA-whpj-8f3w-67p5
- Warn: Project is vulnerable to: GHSA-cchq-frgv-rjh5
- Warn: Project is vulnerable to: GHSA-g644-9gfx-q4q4
- Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7
Score
3
/10
Last Scanned on 2024-11-25
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More