Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ coverage:
target: 80% # Required patch coverage target
project:
default:
threshold: 0.5% # Allowable coverage drop in percentage points
threshold: 0.5% # Allowable coverage drop in percentage points

comment:
behavior: default
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-deps-rn-nightly/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ runs:

- name: Switch to React Native Nightly
run: |
yarn add -D react-native@nightly @react-native/babel-preset@nightly react@19.1.1 react-test-renderer@19.1.1
yarn add -D react-native@nightly @react-native/babel-preset@nightly react@19.1.1 react-test-renderer@19.1.1
shell: bash
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
- name: Lint
run: yarn lint

- name: Prettier
run: yarn prettier

typecheck:
runs-on: ubuntu-latest
name: Typecheck
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@ jobs:
# The GH actions bot is used by default if you didn't specify the two fields.
# You can swap them out with your own user credentials.
user_name: github-actions[bot]
user_email: 41898282+github-actions[bot]@users.noreply.github.com
user_email: 41898282+github-actions[bot]@users.noreply.github.com
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
.yarn

flow-typed/
16 changes: 14 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@ This is the **React Native Testing Library (RNTL)** - a comprehensive testing so
## Key Development Commands

### Testing

- `yarn test` - Run all tests
- `yarn test:ci` - Run tests with CI optimizations (maxWorkers=2)
- `yarn test:ci:coverage` - Run tests with coverage reporting

### Building

- `yarn build` - Full build process (clean, build JS, build types, copy flow types)
- `yarn build:js` - Build JavaScript using Babel
- `yarn build:ts` - Build TypeScript declarations
- `yarn clean` - Clean build directory

### Code Quality

- `yarn typecheck` - Run TypeScript compiler
- `yarn lint` - Run ESLint with caching
- `yarn validate` - Run lint + typecheck + test (pre-commit validation)

### Testing Single Files

To test a specific file: `yarn test path/to/test.test.tsx`

## Architecture Overview
Expand All @@ -47,6 +51,7 @@ To test a specific file: `yarn test path/to/test.test.tsx`
### Query System

The library provides three query variants for each selector:

- `get*` - Throws if not found (for assertions)
- `query*` - Returns null if not found (for conditional logic)
- `find*` - Returns Promise, waits for element (for async operations)
Expand All @@ -59,37 +64,43 @@ The library provides three query variants for each selector:
## Configuration

### Jest Setup

- Main Jest config: `jest.config.js`
- Setup file: `jest-setup.ts`
- Uses React Native preset with custom transform ignore patterns

### TypeScript

- Main config: `tsconfig.json` (development)
- Release config: `tsconfig.release.json` (for builds)
- Strict mode enabled with ES2022 target

### ESLint

- Config: `eslint.config.mjs`
- Uses Callstack config + TypeScript strict rules
- Custom rules for import sorting and test files

## Testing Patterns

### Component Testing

```jsx
import { render, screen, userEvent } from '@testing-library/react-native';

test('component behavior', async () => {
const user = userEvent.setup();
render(<MyComponent />);

await user.press(screen.getByRole('button'));
expect(screen.getByText('Expected text')).toBeOnTheScreen();
});
```

### Async Testing

Use `findBy*` queries or `waitFor` for async operations:

```jsx
const element = await screen.findByText('Async content');
await waitFor(() => expect(mockFn).toHaveBeenCalled());
Expand All @@ -105,6 +116,7 @@ await waitFor(() => expect(mockFn).toHaveBeenCalled());
## Build Process

The build creates:

- `build/` - Compiled JavaScript and TypeScript declarations
- `matchers.js` - Jest matchers for separate import
- `pure.js` - Pure version without auto-cleanup
Expand All @@ -122,4 +134,4 @@ The build creates:
- Uses `react-test-renderer` for component rendering
- Fake timers recommended for user events
- String validation available for text rendering checks
- Supports both concurrent and legacy React rendering modes
- Supports both concurrent and legacy React rendering modes
5 changes: 1 addition & 4 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ export default [
'react-native-a11y/has-valid-accessibility-ignores-invert-colors': 'off',
'react-native-a11y/has-valid-accessibility-value': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'jest/no-standalone-expect': [
'error',
{ additionalTestBlockFunctions: ['testGateReact19'] },
],
'jest/no-standalone-expect': ['error', { additionalTestBlockFunctions: ['testGateReact19'] }],
},
},
];
4 changes: 1 addition & 3 deletions examples/basic/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module.exports = {
preset: 'react-native',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?react-native|@react-native(-community)?)',
],
transformIgnorePatterns: ['node_modules/(?!(jest-)?react-native|@react-native(-community)?)'],
setupFilesAfterEnv: ['./jest-setup.ts'],
};
6 changes: 5 additions & 1 deletion examples/cookbook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
</p>

# React Native Testing Library Cookbook App

Welcome to the React Native Testing Library (RNTL) Cookbook! This app is designed to provide developers with a collection of best practices, ready-made recipes, and tips & tricks to help you effectively test your React Native applications. Whether you’re just starting out with testing or looking to deepen your skills, this cookbook offers something for everyone.

Each recipe described in the Cookbook should have a corresponding code example screen in this repo.
Expand All @@ -12,19 +13,22 @@ Since examples will showcase usage of different dependencies, the dependencies i
file will grow much larger that in a normal React Native. This is fine 🐶☕️🔥.

## Running the App

1. Clone the repo `git clone git@github.com:callstack/react-native-testing-library.git`
2. Go to the `examples/cookbook` directory `cd examples/cookbook`
3. Install dependencies `yarn`
4. Run the app `yarn start`
5. Run the app either on iOS or Android by clicking on `i` or `a` in the terminal.

## How to Contribute

We invite all developers, from beginners to experts, to contribute your own recipes! If you have a clever solution, best practice, or useful tip, we encourage you to:

1. Submit a Pull Request with your recipe.
2. Join the conversation on GitHub [here](https://github.com/callstack/react-native-testing-library/issues/1624) to discuss ideas, ask questions, or provide feedback.

## Screenshots From the App

| Home Screen | Phonebook with Net. Req. Example |
|-------------------------------------------------------|-----------------------------------------------------------------|
| ----------------------------------------------------- | --------------------------------------------------------------- |
| ![home-screenshot](assets/readme/home-screenshot.png) | ![phonebook-screenshot](assets/readme/phonebook-screenshot.png) |
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { User } from '../types';
import {http, HttpResponse} from "msw";
import {setupServer} from "msw/node";
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';

// Define request handlers and response resolvers for random user API.
// By default, we always return the happy path response.
Expand Down
8 changes: 4 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ module.exports = {
snapshotSerializers: ['@relmify/jest-serializer-strip-ansi/always'],
clearMocks: true,
collectCoverageFrom: [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/__tests__/**",
"!src/**/*.test.js",
"!src/test-utils/**", // Exclude setup files
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/__tests__/**',
'!src/**/*.test.js',
'!src/test-utils/**', // Exclude setup files
],
};
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@
"typecheck": "tsc",
"copy-flowtypes": "cp typings/index.flow.js build",
"lint": "eslint src --cache",
"validate": "yarn lint && yarn typecheck && yarn test",
"validate": "yarn prettier && yarn lint && yarn typecheck && yarn test",
"build:js": "babel src --out-dir build --extensions \".js,.ts,.jsx,.tsx\" --source-maps --ignore \"**/__tests__/**\"",
"build:ts": "tsc --build tsconfig.release.json",
"build": "yarn clean && yarn build:js && yarn build:ts && yarn copy-flowtypes",
"release": "release-it",
"release:rc": "release-it --preRelease=rc"
"release:rc": "release-it --preRelease=rc",
"prettier": "prettier --check .",
"prettier:write": "prettier --write ."
},
"files": [
"build/",
Expand Down
8 changes: 4 additions & 4 deletions typings/index.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ declare type A11yRole =

declare type A11yState = {|
disabled?: boolean,
selected ?: boolean,
checked ?: boolean | 'mixed',
busy ?: boolean,
expanded ?: boolean,
selected?: boolean,
checked?: boolean | 'mixed',
busy?: boolean,
expanded?: boolean,
|};

declare type A11yValue = {
Expand Down
2 changes: 1 addition & 1 deletion website/docs/12.x/cookbook/advanced/network-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ describe('PhoneBook', () => {
});
});

````
```

## Global guarding against unwanted API requests

Expand Down
4 changes: 2 additions & 2 deletions website/docs/12.x/cookbook/state-management/jotai.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ export interface RenderWithAtomsOptions {
*/
export const renderWithAtoms = <T,>(
component: React.ReactElement,
options: RenderWithAtomsOptions,
options: RenderWithAtomsOptions
) => {
return render(
<HydrateAtomsWrapper initialValues={options.initialValues}>{component}</HydrateAtomsWrapper>,
<HydrateAtomsWrapper initialValues={options.initialValues}>{component}</HydrateAtomsWrapper>
);
};

Expand Down
6 changes: 0 additions & 6 deletions website/docs/12.x/docs/api/queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -195,32 +195,26 @@ const element3 = screen.getByRole('button', { name: 'Hello', disabled: true });
- `name`: Finds an element with given `role`/`accessibilityRole` and an accessible name (= accessability label or text content).

- `disabled`: You can filter elements by their disabled state (coming either from `aria-disabled` prop or `accessbilityState.disabled` prop). The possible values are `true` or `false`. Querying `disabled: false` will also match elements with `disabled: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `disabled` state.
- This option can alternatively be expressed using the [`toBeEnabled()` / `toBeDisabled()`](docs/api/jest-matchers#tobeenabled) Jest matchers.

- `selected`: You can filter elements by their selected state (coming either from `aria-selected` prop or `accessbilityState.selected` prop). The possible values are `true` or `false`. Querying `selected: false` will also match elements with `selected: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `selected` state.
- This option can alternatively be expressed using the [`toBeSelected()`](docs/api/jest-matchers#tobeselected) Jest matcher.

* `checked`: You can filter elements by their checked state (coming either from `aria-checked` prop or `accessbilityState.checked` prop). The possible values are `true`, `false`, or `"mixed"`.

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `checked` state.
- This option can alternatively be expressed using the [`toBeChecked()` / `toBePartiallyChecked()`](docs/api/jest-matchers#tobechecked) Jest matchers.

* `busy`: You can filter elements by their busy state (coming either from `aria-busy` prop or `accessbilityState.busy` prop). The possible values are `true` or `false`. Querying `busy: false` will also match elements with `busy: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `busy` state.
- This option can alternatively be expressed using the [`toBeBusy()`](docs/api/jest-matchers#tobebusy) Jest matcher.

* `expanded`: You can filter elements by their expanded state (coming either from `aria-expanded` prop or `accessbilityState.expanded` prop). The possible values are `true` or `false`.

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `expanded` state.
- This option can alternatively be expressed using the [`toBeExpanded()` / `toBeCollapsed()`](docs/api/jest-matchers#tobeexpanded) Jest matchers.

* `value`: Filter elements by their accessibility value, based on either `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext` or `accessibilityValue` props. Accessiblity value conceptually consists of numeric `min`, `max` and `now` entries, as well as string `text` entry.

- See React Native [accessibilityValue](https://reactnative.dev/docs/accessibility#accessibilityvalue) docs to learn more about the accessibility value concept.
- This option can alternatively be expressed using the [`toHaveAccessibilityValue()`](docs/api/jest-matchers#tohaveaccessibilityvalue) Jest matcher.

Expand Down
2 changes: 1 addition & 1 deletion website/docs/13.x/cookbook/advanced/network-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ describe('PhoneBook', () => {
});
});

````
```

## Global guarding against unwanted API requests

Expand Down
4 changes: 2 additions & 2 deletions website/docs/13.x/cookbook/state-management/jotai.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ export interface RenderWithAtomsOptions {
*/
export const renderWithAtoms = <T,>(
component: React.ReactElement,
options: RenderWithAtomsOptions,
options: RenderWithAtomsOptions
) => {
return render(
<HydrateAtomsWrapper initialValues={options.initialValues}>{component}</HydrateAtomsWrapper>,
<HydrateAtomsWrapper initialValues={options.initialValues}>{component}</HydrateAtomsWrapper>
);
};

Expand Down
2 changes: 1 addition & 1 deletion website/docs/13.x/docs/api/events/fire-event.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ import { renderAsync, screen, fireEventAsync } from '@testing-library/react-nati

test('fire event test', async () => {
await renderAsync(<MySuspenseComponent />);

await fireEventAsync(screen.getByText('Button'), 'press');
expect(screen.getByText('Action completed')).toBeOnTheScreen();
});
Expand Down
6 changes: 0 additions & 6 deletions website/docs/13.x/docs/api/queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -195,32 +195,26 @@ const element3 = screen.getByRole('button', { name: 'Hello', disabled: true });
- `name`: Finds an element with given `role`/`accessibilityRole` and an accessible name (= accessability label or text content).

- `disabled`: You can filter elements by their disabled state (coming either from `aria-disabled` prop or `accessbilityState.disabled` prop). The possible values are `true` or `false`. Querying `disabled: false` will also match elements with `disabled: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `disabled` state.
- This option can alternatively be expressed using the [`toBeEnabled()` / `toBeDisabled()`](docs/api/jest-matchers#tobeenabled) Jest matchers.

- `selected`: You can filter elements by their selected state (coming either from `aria-selected` prop or `accessbilityState.selected` prop). The possible values are `true` or `false`. Querying `selected: false` will also match elements with `selected: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `selected` state.
- This option can alternatively be expressed using the [`toBeSelected()`](docs/api/jest-matchers#tobeselected) Jest matcher.

* `checked`: You can filter elements by their checked state (coming either from `aria-checked` prop or `accessbilityState.checked` prop). The possible values are `true`, `false`, or `"mixed"`.

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `checked` state.
- This option can alternatively be expressed using the [`toBeChecked()` / `toBePartiallyChecked()`](docs/api/jest-matchers#tobechecked) Jest matchers.

* `busy`: You can filter elements by their busy state (coming either from `aria-busy` prop or `accessbilityState.busy` prop). The possible values are `true` or `false`. Querying `busy: false` will also match elements with `busy: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `busy` state.
- This option can alternatively be expressed using the [`toBeBusy()`](docs/api/jest-matchers#tobebusy) Jest matcher.

* `expanded`: You can filter elements by their expanded state (coming either from `aria-expanded` prop or `accessbilityState.expanded` prop). The possible values are `true` or `false`.

- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `expanded` state.
- This option can alternatively be expressed using the [`toBeExpanded()` / `toBeCollapsed()`](docs/api/jest-matchers#tobeexpanded) Jest matchers.

* `value`: Filter elements by their accessibility value, based on either `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext` or `accessibilityValue` props. Accessiblity value conceptually consists of numeric `min`, `max` and `now` entries, as well as string `text` entry.

- See React Native [accessibilityValue](https://reactnative.dev/docs/accessibility#accessibilityvalue) docs to learn more about the accessibility value concept.
- This option can alternatively be expressed using the [`toHaveAccessibilityValue()`](docs/api/jest-matchers#tohaveaccessibilityvalue) Jest matcher.

Expand Down
Loading