diff --git a/README.md b/README.md index d929418e8..6560a836e 100644 --- a/README.md +++ b/README.md @@ -102,22 +102,20 @@ flow-typed install react-test-renderer ## Example ```jsx -import { render, fireEvent } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; import { QuestionsBoard } from '../QuestionsBoard'; test('form submits two answers', () => { const allQuestions = ['q1', 'q2']; const mockFn = jest.fn(); - const { getAllByLabelText, getByText } = render( - - ); + render(); - const answerInputs = getAllByLabelText('answer input'); + const answerInputs = screen.getAllByLabelText('answer input'); fireEvent.changeText(answerInputs[0], 'a1'); fireEvent.changeText(answerInputs[1], 'a2'); - fireEvent.press(getByText('Submit')); + fireEvent.press(screen.getByText('Submit')); expect(mockFn).toBeCalledWith({ '1': { q: 'q1', a: 'a1' }, diff --git a/src/__tests__/screen.test.tsx b/src/__tests__/screen.test.tsx new file mode 100644 index 000000000..dbde73551 --- /dev/null +++ b/src/__tests__/screen.test.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { View, Text } from 'react-native'; +import { render, screen } from '..'; + +test('screen has the same queries as render result', () => { + const result = render(Mt. Everest); + expect(screen).toBe(result); + + expect(screen.getByText('Mt. Everest')).toBeTruthy(); + expect(screen.queryByText('Mt. Everest')).toBeTruthy(); + expect(screen.getAllByText('Mt. Everest')).toHaveLength(1); + expect(screen.queryAllByText('Mt. Everest')).toHaveLength(1); +}); + +test('screen holds last render result', () => { + render(Mt. Everest); + render(Mt. Blanc); + const finalResult = render(Śnieżka); + expect(screen).toBe(finalResult); + + expect(screen.getByText('Śnieżka')).toBeTruthy(); + expect(screen.queryByText('Mt. Everest')).toBeFalsy(); + expect(screen.queryByText('Mt. Blanc')).toBeFalsy(); +}); + +test('screen works with updating rerender', () => { + const result = render(Mt. Everest); + expect(screen).toBe(result); + + screen.rerender(Śnieżka); + expect(screen).toBe(result); + expect(screen.getByText('Śnieżka')).toBeTruthy(); +}); + +test('screen works with nested re-mounting rerender', () => { + const result = render( + + Mt. Everest + + ); + expect(screen).toBe(result); + + screen.rerender( + + + Śnieżka + + + ); + expect(screen).toBe(result); + expect(screen.getByText('Śnieżka')).toBeTruthy(); +}); + +test('screen throws without render', () => { + expect(() => screen.container).toThrowError( + '`render` method has not been called' + ); + expect(() => screen.debug()).toThrowError( + '`render` method has not been called' + ); + expect(() => screen.debug.shallow()).toThrowError( + '`render` method has not been called' + ); + expect(() => screen.getByText('Mt. Everest')).toThrowError( + '`render` method has not been called' + ); +}); diff --git a/src/cleanup.ts b/src/cleanup.ts index 04cdfa208..681d22a47 100644 --- a/src/cleanup.ts +++ b/src/cleanup.ts @@ -1,9 +1,11 @@ import * as React from 'react'; +import { clearRenderResult } from './screen'; type CleanUpFunction = (nextElement?: React.ReactElement) => void; let cleanupQueue = new Set(); export default function cleanup() { + clearRenderResult(); cleanupQueue.forEach((fn) => fn()); cleanupQueue.clear(); } diff --git a/src/pure.ts b/src/pure.ts index 32d51c0e1..34e48f8d7 100644 --- a/src/pure.ts +++ b/src/pure.ts @@ -1,12 +1,16 @@ import act from './act'; import cleanup from './cleanup'; import fireEvent from './fireEvent'; -import render from './render'; +import render, { RenderResult } from './render'; import waitFor from './waitFor'; import waitForElementToBeRemoved from './waitForElementToBeRemoved'; import { within, getQueriesForElement } from './within'; import { getDefaultNormalizer } from './matches'; import { renderHook } from './renderHook'; +import { screen } from './screen'; + +export type { RenderResult }; +export type RenderAPI = RenderResult; export { act }; export { cleanup }; @@ -17,4 +21,4 @@ export { waitForElementToBeRemoved }; export { within, getQueriesForElement }; export { getDefaultNormalizer }; export { renderHook }; -export type RenderAPI = ReturnType; +export { screen }; diff --git a/src/render.tsx b/src/render.tsx index e674ede01..7f8142c60 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -6,6 +6,7 @@ import { addToCleanupQueue } from './cleanup'; import debugShallow from './helpers/debugShallow'; import debugDeep from './helpers/debugDeep'; import { getQueriesForElement } from './within'; +import { setRenderResult } from './screen'; type Options = { wrapper?: React.ComponentType; @@ -15,6 +16,8 @@ type TestRendererOptions = { createNodeMock: (element: React.ReactElement) => any; }; +export type RenderResult = ReturnType; + /** * Renders test component deeply using react-test-renderer and exposes helpers * to assert on the output. @@ -40,7 +43,7 @@ export default function render( addToCleanupQueue(unmount); - return { + const result = { ...getQueriesForElement(instance), update, unmount, @@ -49,6 +52,9 @@ export default function render( toJSON: renderer.toJSON, debug: debug(instance, renderer), }; + + setRenderResult(result); + return result; } function renderWithAct( diff --git a/src/screen.ts b/src/screen.ts new file mode 100644 index 000000000..6d44957e4 --- /dev/null +++ b/src/screen.ts @@ -0,0 +1,108 @@ +import { ReactTestInstance } from 'react-test-renderer'; +import { RenderResult } from './render'; + +const SCREEN_ERROR = '`render` method has not been called'; + +const notImplemented = () => { + throw new Error(SCREEN_ERROR); +}; + +const notImplementedDebug = () => { + throw new Error(SCREEN_ERROR); +}; +notImplementedDebug.shallow = notImplemented; + +const defaultScreen: RenderResult = { + get container(): ReactTestInstance { + throw new Error(SCREEN_ERROR); + }, + debug: notImplementedDebug, + update: notImplemented, + unmount: notImplemented, + rerender: notImplemented, + toJSON: notImplemented, + getByLabelText: notImplemented, + getAllByLabelText: notImplemented, + queryByLabelText: notImplemented, + queryAllByLabelText: notImplemented, + findByLabelText: notImplemented, + findAllByLabelText: notImplemented, + getByA11yHint: notImplemented, + getByHintText: notImplemented, + getAllByA11yHint: notImplemented, + getAllByHintText: notImplemented, + queryByA11yHint: notImplemented, + queryByHintText: notImplemented, + queryAllByA11yHint: notImplemented, + queryAllByHintText: notImplemented, + findByA11yHint: notImplemented, + findByHintText: notImplemented, + findAllByA11yHint: notImplemented, + findAllByHintText: notImplemented, + getByRole: notImplemented, + getAllByRole: notImplemented, + queryByRole: notImplemented, + queryAllByRole: notImplemented, + findByRole: notImplemented, + findAllByRole: notImplemented, + getByA11yStates: notImplemented, + getAllByA11yStates: notImplemented, + queryByA11yStates: notImplemented, + queryAllByA11yStates: notImplemented, + findByA11yStates: notImplemented, + findAllByA11yStates: notImplemented, + getByA11yState: notImplemented, + getAllByA11yState: notImplemented, + queryByA11yState: notImplemented, + queryAllByA11yState: notImplemented, + findByA11yState: notImplemented, + findAllByA11yState: notImplemented, + getByA11yValue: notImplemented, + getAllByA11yValue: notImplemented, + queryByA11yValue: notImplemented, + queryAllByA11yValue: notImplemented, + findByA11yValue: notImplemented, + findAllByA11yValue: notImplemented, + UNSAFE_getByProps: notImplemented, + UNSAFE_getAllByProps: notImplemented, + UNSAFE_queryByProps: notImplemented, + UNSAFE_queryAllByProps: notImplemented, + UNSAFE_getByType: notImplemented, + UNSAFE_getAllByType: notImplemented, + UNSAFE_queryByType: notImplemented, + UNSAFE_queryAllByType: notImplemented, + getByPlaceholderText: notImplemented, + getAllByPlaceholderText: notImplemented, + queryByPlaceholderText: notImplemented, + queryAllByPlaceholderText: notImplemented, + findByPlaceholderText: notImplemented, + findAllByPlaceholderText: notImplemented, + getByDisplayValue: notImplemented, + getAllByDisplayValue: notImplemented, + queryByDisplayValue: notImplemented, + queryAllByDisplayValue: notImplemented, + findByDisplayValue: notImplemented, + findAllByDisplayValue: notImplemented, + getByTestId: notImplemented, + getAllByTestId: notImplemented, + queryByTestId: notImplemented, + queryAllByTestId: notImplemented, + findByTestId: notImplemented, + findAllByTestId: notImplemented, + getByText: notImplemented, + getAllByText: notImplemented, + queryByText: notImplemented, + queryAllByText: notImplemented, + findByText: notImplemented, + findAllByText: notImplemented, +}; + +export let screen: RenderResult = defaultScreen; + +export function setRenderResult(output: RenderResult) { + screen = output; +} + +export function clearRenderResult() { + screen = defaultScreen; +} diff --git a/typings/index.flow.js b/typings/index.flow.js index 286ecb6f9..ead24d516 100644 --- a/typings/index.flow.js +++ b/typings/index.flow.js @@ -363,6 +363,8 @@ declare module '@testing-library/react-native' { options?: RenderOptions ) => RenderAPI; + declare export var screen: RenderAPI; + declare export var cleanup: () => void; declare export var fireEvent: FireEventAPI; diff --git a/website/docs/API.md b/website/docs/API.md index cbaf87f1a..e634f44d6 100644 --- a/website/docs/API.md +++ b/website/docs/API.md @@ -6,30 +6,41 @@ title: API ### Table of contents: - [`render`](#render) + - [`...queries`](#queries) + - [Example](#example) - [`update`](#update) - [`unmount`](#unmount) - [`debug`](#debug) + - [`debug.shallow`](#debugshallow) - [`toJSON`](#tojson) - [`container`](#container) +- [`screen`](#screen) - [`cleanup`](#cleanup) - [`fireEvent`](#fireevent) - [`fireEvent[eventName]`](#fireeventeventname) - [`fireEvent.press`](#fireeventpress) - [`fireEvent.changeText`](#fireeventchangetext) - [`fireEvent.scroll`](#fireeventscroll) + - [On a `ScrollView`](#on-a-scrollview) + - [On a `FlatList`](#on-a-flatlist) - [`waitFor`](#waitfor) - [`waitForElementToBeRemoved`](#waitforelementtoberemoved) -- [`within, getQueriesForElement`](#within-getqueriesforelement) +- [`within`, `getQueriesForElement`](#within-getqueriesforelement) - [`query` APIs](#query-apis) - [`queryAll` APIs](#queryall-apis) - [`act`](#act) - [`renderHook`](#renderhook) - [`callback`](#callback) - - [`options`](#options-optional) + - [`options` (Optional)](#options-optional) + - [`initialProps`](#initialprops) + - [`wrapper`](#wrapper) - [`RenderHookResult` object](#renderhookresult-object) - [`result`](#result) - [`rerender`](#rerender) - [`unmount`](#unmount-1) + - [Examples](#examples) + - [With `initialProps`](#with-initialprops) + - [With `wrapper`](#with-wrapper) This page gathers public API of React Native Testing Library along with usage examples. @@ -58,8 +69,8 @@ import { render } from '@testing-library/react-native'; import { QuestionsBoard } from '../QuestionsBoard'; test('should verify two questions', () => { - const { queryAllByRole } = render(); - const allQuestions = queryAllByRole('header'); + render(); + const allQuestions = screen.queryAllByRole('header'); expect(allQuestions).toHaveLength(2); }); @@ -67,7 +78,13 @@ test('should verify two questions', () => { > When using React context providers, like Redux Provider, you'll likely want to wrap rendered component with them. In such cases it's convenient to create your custom `render` method. [Follow this great guide on how to set this up](https://testing-library.com/docs/react-testing-library/setup#custom-render). -The `render` method returns a `RenderResult` object that has a few properties: +The `render` method returns a `RenderResult` object having properties described below. + +:::info +Latest `render` result is kept in [`screen`](#screen) variable that can be imported from `@testing-library/react-native` package. + +Using `screen` instead of destructuring `render` result is recommended approach. See [this article](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen) from Kent C. Dodds for more details. +::: ### `...queries` @@ -117,9 +134,9 @@ debug(message?: string): void Pretty prints deeply rendered component passed to `render` with optional message on top. ```jsx -const { debug } = render(); +render(); -debug('optional message'); +screen.debug('optional message'); ``` logs optional message and colored JSX: @@ -154,13 +171,23 @@ container: ReactTestInstance; A reference to the rendered root element. +## `screen` + +```ts +let screen: RenderResult; +``` + +Hold the value of latest render call for easier access to query and other functions returned by [`render`](#render). + +Its value is automatically cleared after each test by calling [`cleanup`](#cleanup). If no `render` call has been made in a given test then it holds a special object that implements `RenderResult` but throws a helpful error on each property and method access. + ## `cleanup` ```ts const cleanup: () => void; ``` -Unmounts React trees that were mounted with `render`. +Unmounts React trees that were mounted with `render` and clears `screen` variable that holds latest `render` output. :::info Please note that this is done automatically if the testing framework you're using supports the `afterEach` global (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test. @@ -195,8 +222,6 @@ describe('when logged in', () => { Failing to call `cleanup` when you've called `render` could result in a memory leak and tests which are not "idempotent" (which can lead to difficult to debug errors in your tests). -The alternative to `cleanup` is balancing every `render` with an `unmount` method call. - ## `fireEvent` ```ts @@ -208,17 +233,17 @@ Fires native-like event with data. Invokes a given event handler (whether native or custom) on the element, bubbling to the root of the rendered tree. ```jsx -import { render, fireEvent } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; test('fire changeText event', () => { const onEventMock = jest.fn(); - const { getByPlaceholderText } = render( + render( // MyComponent renders TextInput which has a placeholder 'Enter details' // and with `onChangeText` bound to handleChangeText ); - fireEvent(getByPlaceholderText('change'), 'onChangeText', 'ab'); + fireEvent(screen.getByPlaceholderText('change'), 'onChangeText', 'ab'); expect(onEventMock).toHaveBeenCalledWith('ab'); }); ``` @@ -235,14 +260,14 @@ import { fireEvent, render } from '@testing-library/react-native'; const onBlurMock = jest.fn(); -const { getByPlaceholderText } = render( +render( ); // you can omit the `on` prefix -fireEvent(getByPlaceholderText('my placeholder'), 'blur'); +fireEvent(screen.getByPlaceholderText('my placeholder'), 'blur'); ``` ## `fireEvent[eventName]` @@ -263,7 +288,7 @@ Invokes `press` event handler on the element or parent element in the tree. ```jsx import { View, Text, TouchableOpacity } from 'react-native'; -import { render, fireEvent } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; const onPressMock = jest.fn(); const eventData = { @@ -273,7 +298,7 @@ const eventData = { }, }; -const { getByText } = render( +render( Press me @@ -281,7 +306,7 @@ const { getByText } = render( ); -fireEvent.press(getByText('Press me'), eventData); +fireEvent.press(screen.getByText('Press me'), eventData); expect(onPressMock).toHaveBeenCalledWith(eventData); ``` @@ -295,18 +320,18 @@ Invokes `changeText` event handler on the element or parent element in the tree. ```jsx import { View, TextInput } from 'react-native'; -import { render, fireEvent } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; const onChangeTextMock = jest.fn(); const CHANGE_TEXT = 'content'; -const { getByPlaceholderText } = render( +render( ); -fireEvent.changeText(getByPlaceholderText('Enter data'), CHANGE_TEXT); +fireEvent.changeText(screen.getByPlaceholderText('Enter data'), CHANGE_TEXT); ``` ### `fireEvent.scroll` @@ -321,7 +346,7 @@ Invokes `scroll` event handler on the element or parent element in the tree. ```jsx import { ScrollView, Text } from 'react-native'; -import { render, fireEvent } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; const onScrollMock = jest.fn(); const eventData = { @@ -332,23 +357,23 @@ const eventData = { }, }; -const { getByText } = render( +render( XD ); -fireEvent.scroll(getByText('scroll-view'), eventData); +fireEvent.scroll(screen.getByText('scroll-view'), eventData); ``` #### On a `FlatList` ```jsx import { FlatList, View } from 'react-native'; -import { render, fireEvent } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; const onEndReached = jest.fn(); -const { getByTestId } = render( +render( ({ key: `${key}` }))} renderItem={() => } @@ -375,7 +400,7 @@ const eventData = { }, }; -fireEvent.scroll(getByTestId('flat-list'), eventData); +fireEvent.scroll(screen.getByTestId('flat-list'), eventData); expect(onEndReached).toHaveBeenCalled(); ``` @@ -399,12 +424,12 @@ function waitFor( Waits for non-deterministic periods of time until your element appears or times out. `waitFor` periodically calls `expectation` every `interval` milliseconds to determine whether the element appeared or not. ```jsx -import { render, waitFor } from '@testing-library/react-native'; +import { render, screen, waitFor } from '@testing-library/react-native'; test('waiting for an Banana to be ready', async () => { - const { getByText } = render(); + render(); - await waitFor(() => getByText('Banana ready')); + await waitFor(() => screen.getByText('Banana ready')); }); ``` @@ -428,15 +453,12 @@ function waitForElementToBeRemoved( Waits for non-deterministic periods of time until queried element is removed or times out. `waitForElementToBeRemoved` periodically calls `expectation` every `interval` milliseconds to determine whether the element has been removed or not. ```jsx -import { - render, - waitForElementToBeRemoved, -} from '@testing-library/react-native'; +import { render, screen, waitForElementToBeRemoved } from '@testing-library/react-native'; test('waiting for an Banana to be removed', async () => { - const { getByText } = render(); + render(); - await waitForElementToBeRemoved(() => getByText('Banana ready')); + await waitForElementToBeRemoved(() => screen.getByText('Banana ready')); }); ``` @@ -466,7 +488,7 @@ Please note that additional `render` specific operations like `update`, `unmount ::: ```jsx -const detailsScreen = within(getByA11yHint('Details Screen')); +const detailsScreen = within(screen.getByA11yHint('Details Screen')); expect(detailsScreen.getByText('Some Text')).toBeTruthy(); expect(detailsScreen.getByDisplayValue('Some Value')).toBeTruthy(); expect(detailsScreen.queryByLabelText('Some Label')).toBeTruthy(); @@ -483,10 +505,10 @@ Use cases for scoped queries include: Each of the get APIs listed in the render section above have a complimentary query API. The get APIs will throw errors if a proper node cannot be found. This is normally the desired effect. However, if you want to make an assertion that an element is not present in the hierarchy, then you can use the query API instead: ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { queryByText } = render(
); -const submitButton = queryByText('submit'); +render(); +const submitButton = screen.queryByText('submit'); expect(submitButton).toBeNull(); // it doesn't exist ``` @@ -497,8 +519,8 @@ Each of the query APIs have a corresponding queryAll version that always returns ```jsx import { render } from '@testing-library/react-native'; -const { queryAllByText } = render(); -const submitButtons = queryAllByText('submit'); +render(); +const submitButtons = screen.queryAllByText('submit'); expect(submitButtons).toHaveLength(3); // expect 3 elements ``` diff --git a/website/docs/EslintPLluginTestingLibrary.md b/website/docs/EslintPLluginTestingLibrary.md index 43419a863..2a4c934cc 100644 --- a/website/docs/EslintPLluginTestingLibrary.md +++ b/website/docs/EslintPLluginTestingLibrary.md @@ -6,8 +6,6 @@ title: ESLint Plugin Testing Library Compatibility Most of the rules of the [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library) are compatible with this library except the followings: -- [prefer-screen-queries](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-screen-queries.md): there is no screen object so this rule shouldn't be used - - [prefer-user-event](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-user-event.md): userEvent requires a dom environement so it is not compatible with this library Also, some rules have become useless, unless maybe you're using an old version of the library: diff --git a/website/docs/FAQ.md b/website/docs/FAQ.md index 6e2f7e331..078650055 100644 --- a/website/docs/FAQ.md +++ b/website/docs/FAQ.md @@ -31,3 +31,15 @@ The the negative side: For instance, [react-native's ScrollView](https://reactnative.dev/docs/scrollview) has several props that depend on native calls. While you can trigger `onScroll` call with `fireEvent.scroll`, `onMomentumScrollBegin` is called from the native side and will therefore not be called. + +
+ Should I use/migrate to `screen` queries? + +
+ +There is no need to migrate existing test code to use `screen`-bases queries. You can still use +queries and other functions returned by `render`. In fact `screen` hold just that value, the latest `render` result. + +For newer code you can either use `screen` or `render` result destructuring. However, there are some good reasons to use `screen`, which are described in [this article](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen) by Kent C. Dodds. + +
diff --git a/website/docs/GettingStarted.md b/website/docs/GettingStarted.md index f88e7513b..2110ec98c 100644 --- a/website/docs/GettingStarted.md +++ b/website/docs/GettingStarted.md @@ -75,22 +75,20 @@ flow-typed install react-test-renderer ## Example ```jsx -import { render, fireEvent } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; import { QuestionsBoard } from '../QuestionsBoard'; test('form submits two answers', () => { const allQuestions = ['q1', 'q2']; const mockFn = jest.fn(); - const { getAllByLabelText, getByText } = render( - - ); + render(); - const answerInputs = getAllByLabelText('answer input'); + const answerInputs = screen.getAllByLabelText('answer input'); fireEvent.changeText(answerInputs[0], 'a1'); fireEvent.changeText(answerInputs[1], 'a2'); - fireEvent.press(getByText('Submit')); + fireEvent.press(screen.getByText('Submit')); expect(mockFn).toBeCalledWith({ '1': { q: 'q1', a: 'a1' }, diff --git a/website/docs/Queries.md b/website/docs/Queries.md index c848e3de8..d1409e76c 100644 --- a/website/docs/Queries.md +++ b/website/docs/Queries.md @@ -6,14 +6,14 @@ title: Queries ### Table of contents: - [Variants](#variants) - - [`getBy`](#getby) - - [`getAllBy`](#getallby) - - [`queryBy`](#queryby) - - [`queryAllBy`](#queryallby) - - [`findBy`](#findby) - - [`findAllBy`](#findallby) + - [getBy](#getby) + - [getAllBy](#getallby) + - [queryBy](#queryby) + - [queryAllBy](#queryallby) + - [findBy](#findby) + - [findAllBy](#findallby) - [Queries](#queries) - - [`options`](#options) + - [Options](#options) - [`ByText`](#bytext) - [`ByPlaceholderText`](#byplaceholdertext) - [`ByDisplayValue`](#bydisplayvalue) @@ -24,11 +24,14 @@ title: Queries - [`ByRole`](#byrole) - [`ByA11yState`, `ByAccessibilityState`](#bya11ystate-byaccessibilitystate) - [`ByA11Value`, `ByAccessibilityValue`](#bya11value-byaccessibilityvalue) -- [`TextMatch`](#textmatch) +- [TextMatch](#textmatch) - [Examples](#examples) - [Precision](#precision) - [Normalization](#normalization) + - [Normalization Examples](#normalization-examples) - [Unit testing helpers](#unit-testing-helpers) + - [`UNSAFE_ByType`](#unsafe_bytype) + - [`UNSAFE_ByProps`](#unsafe_byprops) ## Variants @@ -93,10 +96,10 @@ Returns a `ReactTestInstance` with matching text – may be a string or regular This method will join `` siblings to find matches, similarly to [how React Native handles these components](https://reactnative.dev/docs/text#containers). This will allow for querying for strings that will be visually rendered together, but may be semantically separate React components. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByText } = render(); -const element = getByText('banana'); +render(); +const element = screen.getByText('banana'); ``` ### `ByPlaceholderText` @@ -106,10 +109,10 @@ const element = getByText('banana'); Returns a `ReactTestInstance` for a `TextInput` with a matching placeholder – may be a string or regular expression. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByPlaceholderText } = render(); -const element = getByPlaceholderText('username'); +render(); +const element = screen.getByPlaceholderText('username'); ``` ### `ByDisplayValue` @@ -119,10 +122,10 @@ const element = getByPlaceholderText('username'); Returns a `ReactTestInstance` for a `TextInput` with a matching display value – may be a string or regular expression. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByDisplayValue } = render(); -const element = getByDisplayValue('username'); +render(); +const element = screen.getByDisplayValue('username'); ``` ### `ByTestId` @@ -132,10 +135,10 @@ const element = getByDisplayValue('username'); Returns a `ReactTestInstance` with matching `testID` prop. `testID` – may be a string or a regular expression. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByTestId } = render(); -const element = getByTestId('unique-id'); +render(); +const element = screen.getByTestId('unique-id'); ``` :::info @@ -149,10 +152,10 @@ In the spirit of [the guiding principles](https://testing-library.com/docs/guidi Returns a `ReactTestInstance` with matching `accessibilityLabel` prop. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByLabelText } = render(); -const element = getByLabelText('my-label'); +render(); +const element = screen.getByLabelText('my-label'); ``` ### `ByHintText`, `ByA11yHint`, `ByAccessibilityHint` @@ -164,10 +167,10 @@ const element = getByLabelText('my-label'); Returns a `ReactTestInstance` with matching `accessibilityHint` prop. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByHintText } = render(); -const element = getByHintText('Plays a song'); +render(); +const element = screen.getByHintText('Plays a song'); ``` :::info @@ -182,11 +185,11 @@ Please consult [Apple guidelines on how `accessibilityHint` should be used](http Returns a `ReactTestInstance` with matching `accessibilityStates` prop. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByA11yStates } = render(); -const element = getByA11yStates(['checked']); -const element2 = getByA11yStates('checked'); +render(); +const element = screen.getByA11yStates(['checked']); +const element2 = screen.getByA11yStates('checked'); ``` ### `ByRole` @@ -196,10 +199,10 @@ const element2 = getByA11yStates('checked'); Returns a `ReactTestInstance` with matching `accessibilityRole` prop. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByRole } = render(); -const element = getByRole('button'); +render(); +const element = screen.getByRole('button'); ``` ### `ByA11yState`, `ByAccessibilityState` @@ -210,10 +213,10 @@ const element = getByRole('button'); Returns a `ReactTestInstance` with matching `accessibilityState` prop. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByA11yState } = render(); -const element = getByA11yState({ disabled: true }); +render(); +const element = screen.getByA11yState({ disabled: true }); ``` ### `ByA11Value`, `ByAccessibilityValue` @@ -224,10 +227,10 @@ const element = getByA11yState({ disabled: true }); Returns a `ReactTestInstance` with matching `accessibilityValue` prop. ```jsx -import { render } from '@testing-library/react-native'; +import { render, screen } from '@testing-library/react-native'; -const { getByA11yValue } = render(); -const element = getByA11yValue({ min: 40 }); +render(); +const element = screen.getByA11yValue({ min: 40 }); ``` ## TextMatch @@ -248,34 +251,34 @@ type TextMatchOptions = { Given the following render: ```jsx -const { getByText } = render(Hello World); +render(Hello World); ``` Will **find a match**: ```js // Matching a string: -getByText('Hello World'); // full string match -getByText('llo Worl', { exact: false }); // substring match -getByText('hello world', { exact: false }); // ignore case-sensitivity +screen.getByText('Hello World'); // full string match +screen.getByText('llo Worl', { exact: false }); // substring match +screen.getByText('hello world', { exact: false }); // ignore case-sensitivity // Matching a regex: -getByText(/World/); // substring match -getByText(/world/i); // substring match, ignore case -getByText(/^hello world$/i); // full string match, ignore case-sensitivity -getByText(/Hello W?oRlD/i); // advanced regex +screen.getByText(/World/); // substring match +screen.getByText(/world/i); // substring match, ignore case +screen.getByText(/^hello world$/i); // full string match, ignore case-sensitivity +screen.getByText(/Hello W?oRlD/i); // advanced regex ``` Will **NOT find a match** ```js // substring does not match -getByText('llo Worl'); +screen.getByText('llo Worl'); // full string does not match -getByText('Goodbye World'); +screen.getByText('Goodbye World'); // case-sensitive regex with different case -getByText(/hello world/); +screen.getByText(/hello world/); ``` ### Precision @@ -309,7 +312,7 @@ Specifying a value for `normalizer` replaces the built-in normalization, but you To perform a match against text without trimming: ```typescript -getByText(node, 'text', { +screen.getByText(node, 'text', { normalizer: getDefaultNormalizer({ trim: false }), }); ``` @@ -317,9 +320,8 @@ getByText(node, 'text', { To override normalization to remove some Unicode characters whilst keeping some (but not all) of the built-in normalization behavior: ```typescript -getByText(node, 'text', { - normalizer: (str) => - getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''), +screen.getByText(node, 'text', { + normalizer: (str) => getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''), }); ``` diff --git a/website/docs/ReactNavigation.md b/website/docs/ReactNavigation.md index b6a519de5..a7c942c20 100644 --- a/website/docs/ReactNavigation.md +++ b/website/docs/ReactNavigation.md @@ -158,7 +158,7 @@ Let's add a [`AppNavigator.test.js`](https://github.com/callstack/react-native-t ```jsx import * as React from 'react'; import { NavigationContainer } from '@react-navigation/native'; -import { render, fireEvent } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; import AppNavigator from '../AppNavigator'; @@ -177,10 +177,10 @@ describe('Testing react navigation', () => { ); - const { findByText, findAllByText } = render(component); + render(component); - const header = await findByText('List of numbers from 1 to 20'); - const items = await findAllByText(/Item number/); + const header = await screen.findByText('List of numbers from 1 to 20'); + const items = await screen.findAllByText(/Item number/); expect(header).toBeTruthy(); expect(items.length).toBe(10); @@ -193,12 +193,12 @@ describe('Testing react navigation', () => { ); - const { findByText } = render(component); - const toClick = await findByText('Item number 5'); + render(component); + const toClick = await screen.findByText('Item number 5'); fireEvent(toClick, 'press'); - const newHeader = await findByText('Showing details for 5'); - const newBody = await findByText('the number you have chosen is 5'); + const newHeader = await screen.findByText('Showing details for 5'); + const newBody = await screen.findByText('the number you have chosen is 5'); expect(newHeader).toBeTruthy(); expect(newBody).toBeTruthy(); @@ -314,7 +314,7 @@ Let's add a [`DrawerAppNavigator.test.js`](https://github.com/callstack/react-na ```jsx import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; -import { render, fireEvent } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; import DrawerAppNavigator from '../DrawerAppNavigator'; @@ -326,8 +326,8 @@ describe('Testing react navigation', () => { ); - const { findByText, findAllByText } = render(component); - const button = await findByText('Go to notifications'); + render(component); + const button = await screen.findByText('Go to notifications'); expect(button).toBeTruthy(); }); @@ -339,14 +339,14 @@ describe('Testing react navigation', () => { ); - const { queryByText, findByText } = render(component); - const oldScreen = queryByText('Welcome!'); - const button = await findByText('Go to notifications'); + render(component); + const oldScreen = screen.queryByText('Welcome!'); + const button = await screen.findByText('Go to notifications'); expect(oldScreen).toBeTruthy(); fireEvent(button, 'press'); - const newScreen = await findByText('This is the notifications screen'); + const newScreen = await screen.findByText('This is the notifications screen'); expect(newScreen).toBeTruthy(); }); diff --git a/website/docs/ReduxIntegration.md b/website/docs/ReduxIntegration.md index 704f35f52..18bc20aca 100644 --- a/website/docs/ReduxIntegration.md +++ b/website/docs/ReduxIntegration.md @@ -18,7 +18,7 @@ For `./components/AddTodo.test.js` ```jsx import * as React from 'react'; import { Provider } from 'react-redux'; -import { cleanup, fireEvent, render } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; import configureStore from '../store'; import AddTodo from './AddTodo'; @@ -32,16 +32,16 @@ describe('AddTodo component test', () => { ); - const { getByPlaceholderText, getByText } = render(component); + render(component); // There is a TextInput. // https://github.com/callstack/react-native-testing-library/blob/ae3d4af370487e1e8fedd8219f77225690aefc59/examples/redux/components/AddTodo.js#L24 - const input = getByPlaceholderText(/repository/i); + const input = screen.getByPlaceholderText(/repository/i); expect(input).toBeTruthy(); const textToEnter = 'This is a random element'; fireEvent.changeText(input, textToEnter); - fireEvent.press(getByText('Submit form')); + fireEvent.press(screen.getByText('Submit form')); const todosState = store.getState().todos; @@ -65,7 +65,7 @@ For `./components/TodoList.test.js` ```jsx import * as React from 'react'; import { Provider } from 'react-redux'; -import { fireEvent, render } from '@testing-library/react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; import configureStore from '../store'; import TodoList from './TodoList'; @@ -87,8 +87,8 @@ describe('TodoList component test', () => { ); - const { getAllByText } = render(component); - const todoElems = getAllByText(/something/i); + render(component); + const todoElems = screen.getAllByText(/something/i); expect(todoElems.length).toEqual(4); }); @@ -108,16 +108,16 @@ describe('TodoList component test', () => { ); - const { getAllByText } = render(component); - const todoElems = getAllByText(/something/i); + render(component); + const todoElems = screen.getAllByText(/something/i); expect(todoElems.length).toBe(2); - const buttons = getAllByText('Delete'); + const buttons = screen.getAllByText('Delete'); expect(buttons.length).toBe(2); fireEvent.press(buttons[0]); - expect(getAllByText('Delete').length).toBe(1); + expect(screen.getAllByText('Delete').length).toBe(1); }); }); ```