MCPcopy Index your code
hub / github.com/aryella-lacerda/react-native-accessibility-engine

github.com/aryella-lacerda/react-native-accessibility-engine @v3.2.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v3.2.0 ↗ · + Follow
138 symbols 325 edges 105 files 7 documented · 5% 1 cross-repo links updated 27d agov3.2.0 · 2022-11-15★ 1768 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

React Native Accessibility Engine

React Native Accessibility Engine

Make accessibility-related assertions in React Native

npm

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

  • [x] Create an engine capable of traversing a component tree making accessibility-related checks
  • [ ] Create an app to showcase accessiblity best-practices
  • [x] 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

npm install react-native-accessibility-engine react-test-renderer --save-dev
# or
yarn add react-native-accessibility-engine react-test-renderer --dev

React < 18

npm install react-native-accessibility-engine react-test-renderer@^17.0.2 --save-dev
# or
yarn 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:

{
  ...
  "setupFilesAfterEnv": [..., "react-native-accessibility-engine"],
}

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:

{
  ...
  "setupFilesAfterEnv": ["path/to/your/setup/file"],
}
// At the top of your setup file
import 'react-native-accessibility-engine';

General Usage

With React elements

import React from 'react';
import { Image, TouchableOpacity } from 'react-native';
import Icons from './assets';

const Button = () => (
  <TouchableOpacity accessible={false}>
    <Image source={Icons.filledHeart['32px']} />
  </TouchableOpacity>
);

it('should be accessible', () => {
  expect(<Button />)).toBeAccessible();
});

With React test instances

You can also pass test instances from react-test-renderer and @testing-library/react-native:

import React from 'react';
import { Image, TouchableOpacity } from 'react-native';

import TestRenderer, { ReactTestInstance } from 'react-test-renderer';
import { render } from '@testing-library/react-native';

import Icons from './assets';

const Button = () => (
  <TouchableOpacity accessible={false} accessibilityRole={'button'}>
    <Image source={Icons.filledHeart['32px']} />
  </TouchableOpacity>
);

it('should be accessible, using react-test-renderer', () => {
  const button = TestRenderer.create(<Button />).root;
  expect(button).toBeAccessible();
});

it('should be accessible, using @testing-library/react-native', () => {
  const { getByA11yRole } = render(<Test />);
  const button = getByA11yRole('button');
  expect(button).toBeAccessible();
});

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:

global.__CUSTOM_VIOLATION_HANDLER__ = (violations) => {
  console.log('violations', violations);

  return violations;
};

global.__A11Y_RULES__ = [`no-empty-text`];

rules can also be configured injest.config.ts|js, or package.json using jest globals (See globals).

 "globals": {
      "__A11Y_RULES__": ["no-empty-text"]
    }

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:

export type Options = {
  // Pass in the subset of rules you want to run
  rules?: RuleId[];
  // Utilize for custom handling of jest test matcher output
  customViolationHandler?: (violations: Violation[]) => Violation[];
};

Example usage:

it('should be accessible, only run against no-empty-text rule', () => {
  expect(button).toBeAccessible({ rules: ['no-empty-text'] });
});

it('should be accessible, and handle violations uniquely', () => {
  const customViolationHandler = (violations) => {
    console.error(violations);
    return [];
  };
  expect(button).toBeAccessible({ customViolationHandler });
});

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 the check function in favor of the .toBeAccessible() matcher, which does not currently recieve any arguments. This is intentional.
{
  // 0.x
  it('should contain no accessibility errors', () => {
    expect(() => Engine.check(<Component />, [...rules])).not.toThrow();
  });

  // 1.x
  it('should contain no accessibility errors', () => {
    expect(<Component />).toBeAccessible();
  });
}

From 1.x to 2.x

  • Change the path from which you import the new matcher:
{
  // 1.x
  "setupFilesAfterEnv": [..., "react-native-accessibility-engine/lib/commonjs/extend-expect"],
  // 2.x
  "setupFilesAfterEnv": [..., "react-native-accessibility-engine"],
}
  • 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.
{
  // 0.x and possibly 1.x
  it('should contain no accessibility errors', () => {
    expect(() => Engine.check(<Component />)).not.toThrow();
  });

  // 2.x
  it('should contain no accessibility errors', () => {
    expect(<Component />).toBeAccessible();
  });
}

From 2.x to 3.x

Because of breaking changes introducted in React 18, react-test-renderer is now a peer dependency.


## If you are using React < 18

npm install react-native-accessibility-engine react-test-renderer@^17.0.2 --save-dev
# or
yarn add react-native-accessibility-engine react-test-renderer@^17.0.2 --dev

## If you are using React >= 18

npm install react-native-accessibility-engine react-test-renderer --save-dev
# or
yarn 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:

import { Text } from 'react-native';

const rule: Rule = {
  id: 'link-role-required',
  matcher: (node) => isText(node.type),
  assertion: (node) => {
    const { onPress, accessibilityRole } = node.props;
    if (onPress) {
      return accessibilityRole === 'link';
    }
    return true;
  },
  help: {
    problem:
      "The text is clickable, but the user wasn't informed that it behaves like a link",
    solution:
      "Set the 'accessibilityRole' prop to 'link' or remove the 'onPress' prop",
    link: '',
  },
};

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 co

Extension points exported contracts — how you extend this code

Props (Interface)
(no doc)
example/src/TestingGround.tsx
Rule (Interface)
(no doc)
src/types/Rule.ts
Props (Interface)
(no doc)
example/src/common/card.tsx
Violation (Interface)
(no doc)
src/types/Violation.ts
Props (Interface)
(no doc)
example/src/common/section.tsx
Result (Interface)
(no doc)
src/types/Result.ts
ScreenProps (Interface)
(no doc)
example/src/examples/index.ts
Matchers (Interface)
(no doc)
src/types/Matchers.ts

Core symbols most depended-on inside this repo

getComponentName
called by 16
src/helpers/getComponentName/getComponentName.tsx
toBeAccessible
called by 13
src/types/Matchers.ts
isText
called by 8
src/helpers/isText/isText.ts
getPathToComponent
called by 8
src/helpers/getPathToComponent/getPathToComponent.tsx
isHidden
called by 5
src/helpers/isHidden/isHidden.ts
isPressable
called by 4
src/helpers/isPressable.ts
isReactTestInstance
called by 4
src/helpers/isReactTestInstance/isReactTestInstance.ts
isCheckbox
called by 4
src/helpers/isCheckbox/isCheckbox.ts

Shape

Function 74
Method 43
Class 12
Interface 9

Languages

TypeScript62%
Java30%
C++9%

Modules by API surface

example/android/app/src/main/java/com/reactnativeaccessibilityengine/newarchitecture/MainApplicationReactNativeHost.java10 symbols
example/android/app/src/main/java/com/reactnativeaccessibilityengine/MainActivity.java8 symbols
example/android/app/src/main/java/com/reactnativeaccessibilityengine/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java7 symbols
example/android/app/src/main/java/com/reactnativeaccessibilityengine/MainApplication.java7 symbols
example/android/app/src/debug/java/com/reactnativeaccessibilityengine/ReactNativeFlipper.java5 symbols
src/engine/index.tsx4 symbols
example/src/TestingGround.tsx4 symbols
example/android/app/src/main/jni/MainComponentsRegistry.cpp4 symbols
example/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp4 symbols
example/android/app/src/main/java/com/reactnativeaccessibilityengine/newarchitecture/components/MainComponentsRegistry.java4 symbols
src/rules/disabled-state-required/index.test.tsx3 symbols
src/matchers/toBeAccessible.test.tsx3 symbols

Used by 1 indexed graphs manifest dependencies, hub-wide

For agents

$ claude mcp add react-native-accessibility-engine \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page