diff --git a/README.md b/README.md
index 4bb56bd..2695e2c 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,7 @@
- [`toBeEnabled`](#tobeenabled)
- [`toBeEmptyElement`](#tobeemptyelement)
- [`toContainElement`](#tocontainelement)
+ - [`toBeOnTheScreen`](#tobeonthescreen)
- [`toHaveProp`](#tohaveprop)
- [`toHaveTextContent`](#tohavetextcontent)
- [`toHaveStyle`](#tohavestyle)
@@ -72,15 +73,23 @@ These will make your tests more declarative, clear to read and to maintain.
These matchers should, for the most part, be agnostic enough to work with any React Native testing
utilities, but they are primarily intended to be used with
-[RNTL](https://github.com/callstack/react-native-testing-library). Any issues raised with existing
-matchers or any newly proposed matchers must be viewed through compatibility with that library and
-its guiding principles first.
+[React Native Testing Library](https://github.com/callstack/react-native-testing-library). Any
+issues raised with existing matchers or any newly proposed matchers must be viewed through
+compatibility with that library and its guiding principles first.
## Installation
This module should be installed as one of your project's `devDependencies`:
+#### Using `yarn`
+
+```sh
+yarn add --dev @testing-library/jest-native
```
+
+#### Using `npm`
+
+```sh
npm install --save-dev @testing-library/jest-native
```
@@ -108,8 +117,10 @@ expect.extend({ toBeEmptyElement, toHaveTextContent });
## Matchers
-`jest-native` has only been tested to work with `RNTL`. Keep in mind that these queries will only
-work on UI elements that bridge to native.
+`jest-native` has only been tested to work with
+[React Native Testing Library](https://github.com/callstack/react-native-testing-library). Keep in
+mind that these queries are intended only to work with elements corresponding to
+[host components](https://reactnative.dev/architecture/glossary#react-host-components-or-host-components).
### `toBeDisabled`
@@ -120,6 +131,7 @@ toBeDisabled();
Check whether or not an element is disabled from a user perspective.
This matcher will check if the element or its parent has any of the following props :
+
- `disabled`
- `accessibilityState={{ disabled: true }}`
- `editable={false}` (for `TextInput` only)
@@ -183,11 +195,9 @@ expect(getByTestId('empty')).toBeEmptyElement();
---
-**NOTE**
-
-`toBeEmptyElement()` matcher has been renamed from `toBeEmpty()` because of the naming conflict with
-Jest Extended export with the
-[same name](https://github.com/jest-community/jest-extended#tobeempty).
+> **Note**
This matcher has been previously named `toBeEmpty()`, but we changed that name in
+> order to avoid conflict with Jest Extendend matcher with the
+> [same name](https://github.com/jest-community/jest-extended#tobeempty).
---
@@ -224,6 +234,35 @@ expect(parent).toContainElement(child);
expect(parent).not.toContainElement(grandparent);
```
+### `toBeOnTheScreen`
+
+```ts
+toBeOnTheScreen();
+```
+
+Check that the element is present in the element tree.
+
+You can check that an already captured element has not been removed from the element tree.
+
+> **Note**
This matcher requires React Native Testing Library v10.1 or later, as it includes
+> the `screen` object.
+
+#### Examples
+
+```tsx
+render(
+
+
+ ,
+);
+
+const child = screen.getByTestId('child');
+expect(child).toBeOnTheScreen();
+
+screen.update();
+expect(child).not.toBeOnTheScreen();
+```
+
### `toHaveProp`
```typescript
diff --git a/extend-expect.d.ts b/extend-expect.d.ts
index 666d8da..f3337ff 100644
--- a/extend-expect.d.ts
+++ b/extend-expect.d.ts
@@ -6,6 +6,7 @@ export interface JestNativeMatchers {
toBeDisabled(): R;
toBeEmptyElement(): R;
toBeEnabled(): R;
+ toBeOnTheScreen(): R;
toBeVisible(): R;
toContainElement(element: ReactTestInstance | null): R;
toHaveTextContent(text: string | RegExp, options?: { normalizeWhitespace: boolean }): R;
diff --git a/src/__tests__/to-be-on-the-screen-import.tsx b/src/__tests__/to-be-on-the-screen-import.tsx
new file mode 100644
index 0000000..c692764
--- /dev/null
+++ b/src/__tests__/to-be-on-the-screen-import.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import { View } from 'react-native';
+import { render } from '@testing-library/react-native';
+
+jest.mock('@testing-library/react-native', () => ({
+ ...jest.requireActual('@testing-library/react-native'),
+ screen: undefined,
+}));
+
+test('toBeOnTheScreen() on null element', () => {
+ const screen = render();
+
+ const test = screen.getByTestId('test');
+ expect(() => expect(test).toBeOnTheScreen()).toThrowErrorMatchingInlineSnapshot(`
+ "Could not import \`screen\` object from @testing-library/react-native.
+
+ Using toBeOnTheScreen() matcher requires @testing-library/react-native v10.1.0 or later to be added to your devDependencies."
+ `);
+});
diff --git a/src/__tests__/to-be-on-the-screen.tsx b/src/__tests__/to-be-on-the-screen.tsx
new file mode 100644
index 0000000..ccb0610
--- /dev/null
+++ b/src/__tests__/to-be-on-the-screen.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import { View, Text } from 'react-native';
+import { render, screen } from '@testing-library/react-native';
+
+function ShowChildren({ show }: { show: boolean }) {
+ return show ? (
+
+ Hello
+
+ ) : (
+
+ );
+}
+
+test('toBeOnTheScreen() on attached element', () => {
+ render();
+ const element = screen.getByTestId('test');
+ expect(element).toBeOnTheScreen();
+ expect(() => expect(element).not.toBeOnTheScreen()).toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).not.toBeOnTheScreen()
+
+ expected element tree not to contain element but found:
+ "
+ `);
+});
+
+test('toBeOnTheScreen() on detached element', () => {
+ render();
+ const element = screen.getByTestId('text');
+
+ screen.update();
+ expect(element).toBeTruthy();
+ expect(element).not.toBeOnTheScreen();
+ expect(() => expect(element).toBeOnTheScreen()).toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).toBeOnTheScreen()
+
+ element could not be found in the element tree"
+ `);
+});
+
+test('toBeOnTheScreen() on null element', () => {
+ expect(null).not.toBeOnTheScreen();
+ expect(() => expect(null).toBeOnTheScreen()).toThrowErrorMatchingInlineSnapshot(`
+ "expect(element).toBeOnTheScreen()
+
+ element could not be found in the element tree"
+ `);
+});
+
+test('example test', () => {
+ render(
+
+
+ ,
+ );
+
+ const child = screen.getByTestId('child');
+ expect(child).toBeOnTheScreen();
+
+ screen.update();
+ expect(child).not.toBeOnTheScreen();
+});
diff --git a/src/__types__/jest-explicit-extend.test-d.ts b/src/__types__/jest-explicit-extend.test-d.ts
index 989e04b..29dfdee 100644
--- a/src/__types__/jest-explicit-extend.test-d.ts
+++ b/src/__types__/jest-explicit-extend.test-d.ts
@@ -6,6 +6,7 @@ import { expect as jestExpect } from '@jest/globals';
jestExpect(null).toBeDisabled();
jestExpect(null).toBeEmptyElement();
jestExpect(null).toBeEnabled();
+jestExpect(null).toBeOnTheScreen();
jestExpect(null).toBeVisible();
jestExpect(null).toContainElement(null);
jestExpect(null).toHaveTextContent('');
diff --git a/src/__types__/jest-implicit-extend.test-d.ts b/src/__types__/jest-implicit-extend.test-d.ts
index 70609d2..647d7a2 100644
--- a/src/__types__/jest-implicit-extend.test-d.ts
+++ b/src/__types__/jest-implicit-extend.test-d.ts
@@ -3,6 +3,7 @@
expect(null).toBeDisabled();
expect(null).toBeEmptyElement();
expect(null).toBeEnabled();
+expect(null).toBeOnTheScreen();
expect(null).toBeVisible();
expect(null).toContainElement(null);
expect(null).toHaveTextContent('');
diff --git a/src/extend-expect.ts b/src/extend-expect.ts
index 3f4b7ee..65e14e4 100644
--- a/src/extend-expect.ts
+++ b/src/extend-expect.ts
@@ -1,5 +1,6 @@
import { toBeDisabled, toBeEnabled } from './to-be-disabled';
import { toBeEmptyElement, toBeEmpty } from './to-be-empty-element';
+import { toBeOnTheScreen } from './to-be-on-the-screen';
import { toContainElement } from './to-contain-element';
import { toHaveProp } from './to-have-prop';
import { toHaveStyle } from './to-have-style';
@@ -13,6 +14,7 @@ expect.extend({
toBeEnabled,
toBeEmptyElement,
toBeEmpty, // Deprecated
+ toBeOnTheScreen,
toContainElement,
toHaveProp,
toHaveStyle,
diff --git a/src/to-be-on-the-screen.ts b/src/to-be-on-the-screen.ts
new file mode 100644
index 0000000..35d824c
--- /dev/null
+++ b/src/to-be-on-the-screen.ts
@@ -0,0 +1,55 @@
+import type { ReactTestInstance } from 'react-test-renderer';
+import { matcherHint, RECEIVED_COLOR } from 'jest-matcher-utils';
+import { checkReactElement, printElement } from './utils';
+
+export function toBeOnTheScreen(this: jest.MatcherContext, element: ReactTestInstance) {
+ if (element !== null) {
+ checkReactElement(element, toBeOnTheScreen, this);
+ }
+
+ const pass = element === null ? false : getScreen().container === getRootElement(element);
+
+ const errorFound = () => {
+ return `expected element tree not to contain element but found:\n${printElement(element)}`;
+ };
+
+ const errorNotFound = () => {
+ return `element could not be found in the element tree`;
+ };
+
+ return {
+ pass,
+ message: () => {
+ return [
+ matcherHint(`${this.isNot ? '.not' : ''}.toBeOnTheScreen`, 'element', ''),
+ '',
+ RECEIVED_COLOR(this.isNot ? errorFound() : errorNotFound()),
+ ].join('\n');
+ },
+ };
+}
+
+function getRootElement(element: ReactTestInstance) {
+ let root = element;
+ while (root.parent) {
+ root = root.parent;
+ }
+ return root;
+}
+
+function getScreen() {
+ try {
+ // eslint-disable-next-line import/no-extraneous-dependencies
+ const { screen } = require('@testing-library/react-native');
+ if (!screen) {
+ throw new Error('screen is undefined');
+ }
+
+ return screen;
+ } catch (error) {
+ throw new Error(
+ 'Could not import `screen` object from @testing-library/react-native.\n\n' +
+ 'Using toBeOnTheScreen() matcher requires @testing-library/react-native v10.1.0 or later to be added to your devDependencies.',
+ );
+ }
+}