From 05c63fe24945ba56ed9ebef5cc6664ffe950afd3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 13:18:18 +0000 Subject: [PATCH] deploy: 238c86008811c8eeffb1e331e45632bfceb2e376 --- 404.html | 4 ++-- assets/js/{c8229a80.dfa70785.js => c8229a80.c8ecfbaa.js} | 2 +- .../{runtime~main.9c5108c1.js => runtime~main.e63caa38.js} | 2 +- docs/api-queries.html | 4 ++-- docs/api.html | 6 +++--- docs/eslint-plugin-testing-library.html | 4 ++-- docs/faq.html | 4 ++-- docs/getting-started.html | 4 ++-- docs/how-should-i-query.html | 4 ++-- docs/migration-v11.html | 4 ++-- docs/migration-v12.html | 4 ++-- docs/migration-v2.html | 4 ++-- docs/migration-v7.html | 4 ++-- docs/migration-v9.html | 4 ++-- docs/react-navigation.html | 4 ++-- docs/redux-integration.html | 4 ++-- docs/testing-env.html | 4 ++-- docs/troubleshooting.html | 4 ++-- docs/understanding-act.html | 4 ++-- docs/user-event.html | 4 ++-- index.html | 4 ++-- search.html | 4 ++-- 22 files changed, 43 insertions(+), 43 deletions(-) rename assets/js/{c8229a80.dfa70785.js => c8229a80.c8ecfbaa.js} (97%) rename assets/js/{runtime~main.9c5108c1.js => runtime~main.e63caa38.js} (96%) diff --git a/404.html b/404.html index 2e37da90..7b77781b 100644 --- a/404.html +++ b/404.html @@ -4,13 +4,13 @@ Page Not Found | React Native Testing Library - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/c8229a80.dfa70785.js b/assets/js/c8229a80.c8ecfbaa.js similarity index 97% rename from assets/js/c8229a80.dfa70785.js rename to assets/js/c8229a80.c8ecfbaa.js index d22fe3c6..ff65f6af 100644 --- a/assets/js/c8229a80.dfa70785.js +++ b/assets/js/c8229a80.c8ecfbaa.js @@ -1 +1 @@ -"use strict";(self.webpackChunkreact_native_testing_library_website=self.webpackChunkreact_native_testing_library_website||[]).push([[298],{3905:function(e,t,n){n.d(t,{Zo:function(){return d},kt:function(){return k}});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),c=p(n),u=i,k=c["".concat(s,".").concat(u)]||c[u]||m[u]||r;return n?a.createElement(k,o(o({ref:t},d),{},{components:n})):a.createElement(k,o({ref:t},d))}));function k(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=u;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[c]="string"==typeof e?e:i,o[1]=l;for(var p=2;prender",id:"render",level:2},{value:"render options",id:"render-options",level:3},{value:"wrapper option",id:"wrapper-option",level:4},{value:"createNodeMock option",id:"createnodemock-option",level:4},{value:"unstable_validateStringsRenderedWithinText option",id:"unstable_validatestringsrenderedwithintext-option",level:4},{value:"...queries",id:"queries",level:3},{value:"Example",id:"example",level:4},{value:"update",id:"update",level:3},{value:"unmount",id:"unmount",level:3},{value:"debug",id:"debug",level:3},{value:"message option",id:"message-option",level:4},{value:"mapProps option",id:"mapprops-option",level:4},{value:"debug.shallow",id:"debugshallow",level:4},{value:"toJSON",id:"tojson",level:3},{value:"root",id:"root",level:3},{value:"UNSAFE_root",id:"unsafe_root",level:3},{value:"screen",id:"screen",level:2},{value:"cleanup",id:"cleanup",level:2},{value:"fireEvent",id:"fireevent",level:2},{value:"fireEvent[eventName]",id:"fireeventeventname",level:2},{value:"fireEvent.press",id:"fireeventpress",level:3},{value:"fireEvent.changeText",id:"fireeventchangetext",level:3},{value:"fireEvent.scroll",id:"fireeventscroll",level:3},{value:"On a ScrollView",id:"on-a-scrollview",level:4},{value:"On a FlatList",id:"on-a-flatlist",level:4},{value:"waitFor",id:"waitfor",level:2},{value:"Using a React Native version < 0.71 with Jest fake timers",id:"using-a-react-native-version--071-with-jest-fake-timers",level:3},{value:"waitForElementToBeRemoved",id:"waitforelementtoberemoved",level:2},{value:"within, getQueriesForElement",id:"within-getqueriesforelement",level:2},{value:"queryBy* APIs",id:"queryby-apis",level:2},{value:"queryAll* APIs",id:"queryall-apis",level:2},{value:"act",id:"act",level:2},{value:"renderHook",id:"renderhook",level:2},{value:"callback",id:"callback",level:3},{value:"options (Optional)",id:"options-optional",level:3},{value:"initialProps",id:"initialprops",level:4},{value:"wrapper",id:"wrapper",level:4},{value:"RenderHookResult object",id:"renderhookresult-object",level:3},{value:"result",id:"result",level:4},{value:"rerender",id:"rerender",level:4},{value:"unmount",id:"unmount-1",level:4},{value:"Examples",id:"examples",level:3},{value:"With initialProps",id:"with-initialprops",level:4},{value:"With wrapper",id:"with-wrapper",level:4},{value:"Configuration",id:"configuration",level:2},{value:"configure",id:"configure",level:3},{value:"asyncUtilTimeout option",id:"asyncutiltimeout-option",level:4},{value:"defaultIncludeHiddenElements option",id:"defaultincludehiddenelements-option",level:4},{value:"defaultDebugOptions option",id:"defaultdebugoptions-option",level:4},{value:"resetToDefaults()",id:"resettodefaults",level:3},{value:"Environment variables",id:"environment-variables",level:3},{value:"RNTL_SKIP_AUTO_CLEANUP",id:"rntl_skip_auto_cleanup",level:4},{value:"RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS",id:"rntl_skip_auto_detect_fake_timers",level:4},{value:"Accessibility",id:"accessibility",level:2},{value:"isHiddenFromAccessibility",id:"ishiddenfromaccessibility",level:3}],m={toc:c},u="wrapper";function k(e){var t=e.components,n=(0,i.Z)(e,o);return(0,r.kt)(u,(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h3",{id:"table-of-contents"},"Table of contents:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#render"},(0,r.kt)("inlineCode",{parentName:"a"},"render")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#render-options"},(0,r.kt)("inlineCode",{parentName:"a"},"render")," options")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#queries"},(0,r.kt)("inlineCode",{parentName:"a"},"...queries"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#update"},(0,r.kt)("inlineCode",{parentName:"a"},"update"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#unmount"},(0,r.kt)("inlineCode",{parentName:"a"},"unmount"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#debug"},(0,r.kt)("inlineCode",{parentName:"a"},"debug"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#tojson"},(0,r.kt)("inlineCode",{parentName:"a"},"toJSON"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#root"},(0,r.kt)("inlineCode",{parentName:"a"},"root"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#unsaferoot"},(0,r.kt)("inlineCode",{parentName:"a"},"UNSAFE_root"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#screen"},(0,r.kt)("inlineCode",{parentName:"a"},"screen"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#cleanup"},(0,r.kt)("inlineCode",{parentName:"a"},"cleanup"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireevent"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireeventeventname"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent[eventName]")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireeventpress"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent.press"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireeventchangetext"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent.changeText"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireeventscroll"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent.scroll"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#waitfor"},(0,r.kt)("inlineCode",{parentName:"a"},"waitFor")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#using-a-react-native-version--071-with-jest-fake-timers"},"Using a React Native version \\< 0.71 with Jest fake timers")))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#waitforelementtoberemoved"},(0,r.kt)("inlineCode",{parentName:"a"},"waitForElementToBeRemoved"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#within-getqueriesforelement"},(0,r.kt)("inlineCode",{parentName:"a"},"within"),", ",(0,r.kt)("inlineCode",{parentName:"a"},"getQueriesForElement"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#queryby-apis"},(0,r.kt)("inlineCode",{parentName:"a"},"queryBy*")," APIs")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#queryall-apis"},(0,r.kt)("inlineCode",{parentName:"a"},"queryAll*")," APIs")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#act"},(0,r.kt)("inlineCode",{parentName:"a"},"act"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#renderhook"},(0,r.kt)("inlineCode",{parentName:"a"},"renderHook")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#callback"},(0,r.kt)("inlineCode",{parentName:"a"},"callback"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#options-optional"},(0,r.kt)("inlineCode",{parentName:"a"},"options")," (Optional)")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#renderhookresult-object"},(0,r.kt)("inlineCode",{parentName:"a"},"RenderHookResult")," object")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#examples"},"Examples")))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#configuration"},"Configuration"),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#configure"},(0,r.kt)("inlineCode",{parentName:"a"},"configure"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#resettodefaults"},(0,r.kt)("inlineCode",{parentName:"a"},"resetToDefaults()"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#environment-variables"},"Environment variables")))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#accessibility"},"Accessibility"),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#ishiddenfromaccessibility"},(0,r.kt)("inlineCode",{parentName:"a"},"isHiddenFromAccessibility")))))),(0,r.kt)("p",null,"This page gathers public API of React Native Testing Library along with usage examples."),(0,r.kt)("h2",{id:"render"},(0,r.kt)("inlineCode",{parentName:"h2"},"render")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/render.test.tsx"},(0,r.kt)("inlineCode",{parentName:"a"},"Example code")))),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"function render(\n component: React.Element,\n options?: RenderOptions\n): RenderResult {}\n")),(0,r.kt)("p",null,"Deeply renders given React element and returns helpers to query the output components structure."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render } from '@testing-library/react-native';\nimport { QuestionsBoard } from '../QuestionsBoard';\n\ntest('should verify two questions', () => {\n render();\n const allQuestions = screen.queryAllByRole('header');\n\n expect(allQuestions).toHaveLength(2);\n});\n")),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"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 ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," method. ",(0,r.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/react-testing-library/setup#custom-render"},"Follow this great guide on how to set this up"),".")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," method returns a ",(0,r.kt)("inlineCode",{parentName:"p"},"RenderResult")," object having properties described below."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Latest ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," result is kept in ",(0,r.kt)("a",{parentName:"p",href:"#screen"},(0,r.kt)("inlineCode",{parentName:"a"},"screen"))," variable that can be imported from ",(0,r.kt)("inlineCode",{parentName:"p"},"@testing-library/react-native")," package."),(0,r.kt)("p",{parentName:"admonition"},"Using ",(0,r.kt)("inlineCode",{parentName:"p"},"screen")," instead of destructuring ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," result is recommended approach. See ",(0,r.kt)("a",{parentName:"p",href:"https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen"},"this article")," from Kent C. Dodds for more details.")),(0,r.kt)("h3",{id:"render-options"},(0,r.kt)("inlineCode",{parentName:"h3"},"render")," options"),(0,r.kt)("p",null,"The behavior of ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," method can be customized by passing various options as a second argument of ",(0,r.kt)("inlineCode",{parentName:"p"},"RenderOptions")," type:"),(0,r.kt)("h4",{id:"wrapper-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"wrapper")," option"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"wrapper?: React.ComponentType,\n")),(0,r.kt)("p",null,"This options allows you to wrap tested component, passed as the first option to the ",(0,r.kt)("inlineCode",{parentName:"p"},"render()")," function, in additional wrapper component. This is most useful for creating reusable custom render functions for common React Context providers."),(0,r.kt)("h4",{id:"createnodemock-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"createNodeMock")," option"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"createNodeMock?: (element: React.Element) => any,\n")),(0,r.kt)("p",null,"This options allows you to pass ",(0,r.kt)("inlineCode",{parentName:"p"},"createNodeMock")," option to ",(0,r.kt)("inlineCode",{parentName:"p"},"ReactTestRenderer.create()")," method in order to allow for custom mock refs. You can learn more about this options from ",(0,r.kt)("a",{parentName:"p",href:"https://reactjs.org/docs/test-renderer.html#ideas"},"React Test Renderer documentation"),"."),(0,r.kt)("h4",{id:"unstable_validatestringsrenderedwithintext-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"unstable_validateStringsRenderedWithinText")," option"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"unstable_validateStringsRenderedWithinText?: boolean;\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"This options is experimental, in some cases it might not work as intended, and its behavior might change without observing ",(0,r.kt)("a",{parentName:"p",href:"https://semver.org/"},"SemVer")," requirements for breaking changes.")),(0,r.kt)("p",null,"This ",(0,r.kt)("strong",{parentName:"p"},"experimental")," option allows you to replicate React Native behavior of throwing ",(0,r.kt)("inlineCode",{parentName:"p"},"Invariant Violation: Text strings must be rendered within a component")," error when you try to render ",(0,r.kt)("inlineCode",{parentName:"p"},"string")," value under components different than ",(0,r.kt)("inlineCode",{parentName:"p"},""),", e.g. under ",(0,r.kt)("inlineCode",{parentName:"p"},""),"."),(0,r.kt)("p",null,"This check is not enforced by React Test Renderer and hence by default React Native Testing Library also does not check this. That might result in runtime errors when running your code on a device, while the code works without errors in tests."),(0,r.kt)("h3",{id:"queries"},(0,r.kt)("inlineCode",{parentName:"h3"},"...queries")),(0,r.kt)("p",null,"The most important feature of ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," is providing a set of helpful queries that allow you to find certain elements in the view hierarchy."),(0,r.kt)("p",null,"See ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/api-queries"},"Queries")," for a complete list."),(0,r.kt)("h4",{id:"example"},"Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render } from '@testing-library/react-native';\n\nconst { getByText, queryByA11yState } = render();\n")),(0,r.kt)("h3",{id:"update"},(0,r.kt)("inlineCode",{parentName:"h3"},"update")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"Also available under ",(0,r.kt)("inlineCode",{parentName:"em"},"rerender")," alias")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"update(element: React.Element): void\nrerender(element: React.Element): void\n")),(0,r.kt)("p",null,"Re-render the in-memory tree with a new root element. This simulates a React update at the root. If the new element has the same type and key as the previous element, the tree will be updated; otherwise, it will re-mount a new tree. This is useful when testing for ",(0,r.kt)("inlineCode",{parentName:"p"},"componentDidUpdate")," behavior, by passing updated props to the component."),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/blob/f96d782d26dd4815dbfd01de6ef7a647efd1f693/src/__tests__/act.test.js#L31-L37"},"Example code")),(0,r.kt)("h3",{id:"unmount"},(0,r.kt)("inlineCode",{parentName:"h3"},"unmount")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"unmount(): void\n")),(0,r.kt)("p",null,"Unmount the in-memory tree, triggering the appropriate lifecycle events."),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Usually you should not need to call ",(0,r.kt)("inlineCode",{parentName:"p"},"unmount")," as it is done automatically if your test runner supports ",(0,r.kt)("inlineCode",{parentName:"p"},"afterEach")," hook (like Jest, mocha, Jasmine).")),(0,r.kt)("h3",{id:"debug"},(0,r.kt)("inlineCode",{parentName:"h3"},"debug")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"interface DebugOptions {\n message?: string;\n mapProps?: MapPropsFunction;\n}\n\ndebug(options?: DebugOptions | string): void\n")),(0,r.kt)("p",null,"Pretty prints deeply rendered component passed to ",(0,r.kt)("inlineCode",{parentName:"p"},"render"),"."),(0,r.kt)("h4",{id:"message-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"message")," option"),(0,r.kt)("p",null,"You can provide a message that will be printed on top."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"render();\nscreen.debug({ message: 'optional message' });\n")),(0,r.kt)("p",null,"logs optional message and colored JSX:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"optional message\n\n\n Press me\n\n")),(0,r.kt)("h4",{id:"mapprops-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"mapProps")," option"),(0,r.kt)("p",null,"You can use the ",(0,r.kt)("inlineCode",{parentName:"p"},"mapProps")," option to transform the props that will be printed :"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"render();\ndebug({ mapProps: ({ style, ...props }) => ({ props }) });\n")),(0,r.kt)("p",null,"This will log the rendered JSX without the ",(0,r.kt)("inlineCode",{parentName:"p"},"style")," props."),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"children")," prop cannot be filtered out so the following will print all rendered components with all props but ",(0,r.kt)("inlineCode",{parentName:"p"},"children")," filtered out."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"debug({ mapProps: (props) => ({}) });\n")),(0,r.kt)("p",null,"This option can be used to target specific props when debugging a query (for instance keeping only ",(0,r.kt)("inlineCode",{parentName:"p"},"children")," prop when debugging a ",(0,r.kt)("inlineCode",{parentName:"p"},"getByText")," query)."),(0,r.kt)("p",null,"You can also transform prop values so that they are more readable (e.g. flatten styles)."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"import { StyleSheet } from 'react-native';\n\ndebug({ mapProps : {({ style, ...props })} => ({ style : StyleSheet.flatten(style), ...props }) });\n")),(0,r.kt)("p",null,"Or remove props that have little value when debugging tests, e.g. path prop for svgs"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"debug({ mapProps: ({ path, ...props }) => ({ ...props }) });\n")),(0,r.kt)("h4",{id:"debugshallow"},(0,r.kt)("inlineCode",{parentName:"h4"},"debug.shallow")),(0,r.kt)("p",null,"Pretty prints shallowly rendered component passed to ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," with optional message on top."),(0,r.kt)("h3",{id:"tojson"},(0,r.kt)("inlineCode",{parentName:"h3"},"toJSON")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"toJSON(): ReactTestRendererJSON | null\n")),(0,r.kt)("p",null,"Get the rendered component JSON representation, e.g. for snapshot testing."),(0,r.kt)("h3",{id:"root"},(0,r.kt)("inlineCode",{parentName:"h3"},"root")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"root: ReactTestInstance;\n")),(0,r.kt)("p",null,"Returns the rendered root ",(0,r.kt)("a",{parentName:"p",href:"testing-env#host-and-composite-components"},"host element"),"."),(0,r.kt)("p",null,"This API is primarily useful in component tests, as it allows you to access root host view without using ",(0,r.kt)("inlineCode",{parentName:"p"},"*ByTestId")," queries or similar methods."),(0,r.kt)("h3",{id:"unsafe_root"},(0,r.kt)("inlineCode",{parentName:"h3"},"UNSAFE_root")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"UNSAFE_root: ReactTestInstance;\n")),(0,r.kt)("p",null,"Returns the rendered ",(0,r.kt)("a",{parentName:"p",href:"testing-env#host-and-composite-components"},"composite root element"),"."),(0,r.kt)("admonition",{type:"caution"},(0,r.kt)("p",{parentName:"admonition"},"This API typically will return a composite view which goes against recommended testing practices. This API is primarily available for legacy test suites that rely on such testing.")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"This API has been previously named ",(0,r.kt)("inlineCode",{parentName:"p"},"container")," for compatibility with ",(0,r.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/react-testing-library/api#container-1"},"React Testing Library"),". However, despite the same name, the actual behavior has been signficantly different, hence the name change to ",(0,r.kt)("inlineCode",{parentName:"p"},"UNSAFE_root"),".")),(0,r.kt)("h2",{id:"screen"},(0,r.kt)("inlineCode",{parentName:"h2"},"screen")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"let screen: RenderResult;\n")),(0,r.kt)("p",null,"Hold the value of latest render call for easier access to query and other functions returned by ",(0,r.kt)("a",{parentName:"p",href:"#render"},(0,r.kt)("inlineCode",{parentName:"a"},"render")),"."),(0,r.kt)("p",null,"Its value is automatically cleared after each test by calling ",(0,r.kt)("a",{parentName:"p",href:"#cleanup"},(0,r.kt)("inlineCode",{parentName:"a"},"cleanup")),". If no ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," call has been made in a given test then it holds a special object that implements ",(0,r.kt)("inlineCode",{parentName:"p"},"RenderResult")," but throws a helpful error on each property and method access."),(0,r.kt)("p",null,"This can also be used to build test utils that would normally require to be in render scope, either in a test file or globally for your project. For instance:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"// Prints the rendered components omitting all props except children.\nconst debugText = () => screen.debug({ mapProps: (props) => ({}) });\n")),(0,r.kt)("h2",{id:"cleanup"},(0,r.kt)("inlineCode",{parentName:"h2"},"cleanup")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"const cleanup: () => void;\n")),(0,r.kt)("p",null,"Unmounts React trees that were mounted with ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," and clears ",(0,r.kt)("inlineCode",{parentName:"p"},"screen")," variable that holds latest ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," output."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Please note that this is done automatically if the testing framework you're using supports the ",(0,r.kt)("inlineCode",{parentName:"p"},"afterEach")," global (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test.")),(0,r.kt)("p",null,"For example, if you're using the ",(0,r.kt)("inlineCode",{parentName:"p"},"jest")," testing framework, then you would need to use the ",(0,r.kt)("inlineCode",{parentName:"p"},"afterEach")," hook like so:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { cleanup, render } from '@testing-library/react-native/pure';\nimport { View } from 'react-native';\n\nafterEach(cleanup);\n\nit('renders a view', () => {\n render();\n // ...\n});\n")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"afterEach(cleanup)")," call also works in ",(0,r.kt)("inlineCode",{parentName:"p"},"describe")," blocks:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"describe('when logged in', () => {\n afterEach(cleanup);\n\n it('renders the user', () => {\n render();\n // ...\n });\n});\n")),(0,r.kt)("p",null,"Failing to call ",(0,r.kt)("inlineCode",{parentName:"p"},"cleanup")," when you've called ",(0,r.kt)("inlineCode",{parentName:"p"},"render"),' could result in a memory leak and tests which are not "idempotent" (which can lead to difficult to debug errors in your tests).'),(0,r.kt)("h2",{id:"fireevent"},(0,r.kt)("inlineCode",{parentName:"h2"},"fireEvent")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function fireEvent(\n element: ReactTestInstance,\n eventName: string,\n ...data: Array\n): void {}\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"For common events like ",(0,r.kt)("inlineCode",{parentName:"p"},"press")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"type")," it's recommended to use ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/user-event"},"User Event API")," as it offers\nmore realistic event simulation by emitting a sequence of events with proper event objects that mimic React Native runtime behavior."),(0,r.kt)("p",{parentName:"admonition"},"Use Fire Event for cases not supported by User Event and for triggering event handlers on composite components.")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent")," API allows you to trigger all kind of event handlers on both host and composite components. It will try to invoke a single event handler traversing the component tree bottom-up from passed element and trying to find enabled event handler named ",(0,r.kt)("inlineCode",{parentName:"p"},"onXxx")," when ",(0,r.kt)("inlineCode",{parentName:"p"},"xxx")," is the name of the event passed."),(0,r.kt)("p",null,"Unlike User Event, this API does not automatically pass event object to event handler, this is responsibility of the user to construct such object."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render, screen, fireEvent } from '@testing-library/react-native';\n\ntest('fire changeText event', () => {\n const onEventMock = jest.fn();\n render(\n // MyComponent renders TextInput which has a placeholder 'Enter details'\n // and with `onChangeText` bound to handleChangeText\n \n );\n\n fireEvent(screen.getByPlaceholderText('change'), 'onChangeText', 'ab');\n expect(onEventMock).toHaveBeenCalledWith('ab');\n});\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Please note that from version ",(0,r.kt)("inlineCode",{parentName:"p"},"7.0")," ",(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent")," performs checks that should prevent events firing on disabled elements.")),(0,r.kt)("p",null,"An example using ",(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent")," with native events that aren't already aliased by the ",(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent")," api."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { TextInput, View } from 'react-native';\nimport { fireEvent, render } from '@testing-library/react-native';\n\nconst onBlurMock = jest.fn();\n\nrender(\n \n \n \n);\n\n// you can omit the `on` prefix\nfireEvent(screen.getByPlaceholderText('my placeholder'), 'blur');\n")),(0,r.kt)("h2",{id:"fireeventeventname"},(0,r.kt)("inlineCode",{parentName:"h2"},"fireEvent[eventName]")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"fireEvent[eventName](element: ReactTestInstance, ...data: Array): void\n")),(0,r.kt)("p",null,"Convenience methods for common events like: ",(0,r.kt)("inlineCode",{parentName:"p"},"press"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"changeText"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"scroll"),"."),(0,r.kt)("h3",{id:"fireeventpress"},(0,r.kt)("inlineCode",{parentName:"h3"},"fireEvent.press")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"fireEvent.press: (element: ReactTestInstance, ...data: Array) => void\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"It is recommended to use the User Event ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/user-event#press"},(0,r.kt)("inlineCode",{parentName:"a"},"press()"))," helper instead as it offers more realistic simulation of press interaction, including pressable support.")),(0,r.kt)("p",null,"Invokes ",(0,r.kt)("inlineCode",{parentName:"p"},"press")," event handler on the element or parent element in the tree."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { View, Text, TouchableOpacity } from 'react-native';\nimport { render, screen, fireEvent } from '@testing-library/react-native';\n\nconst onPressMock = jest.fn();\nconst eventData = {\n nativeEvent: {\n pageX: 20,\n pageY: 30,\n },\n};\n\nrender(\n \n \n Press me\n \n \n);\n\nfireEvent.press(screen.getByText('Press me'), eventData);\nexpect(onPressMock).toHaveBeenCalledWith(eventData);\n")),(0,r.kt)("h3",{id:"fireeventchangetext"},(0,r.kt)("inlineCode",{parentName:"h3"},"fireEvent.changeText")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"fireEvent.changeText: (element: ReactTestInstance, ...data: Array) => void\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"It is recommended to use the User Event ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/user-event#type"},(0,r.kt)("inlineCode",{parentName:"a"},"type()"))," helper instead as it offers more realistic simulation of text change interaction, including key-by-key typing, element focus, and other editing events.")),(0,r.kt)("p",null,"Invokes ",(0,r.kt)("inlineCode",{parentName:"p"},"changeText")," event handler on the element or parent element in the tree."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { View, TextInput } from 'react-native';\nimport { render, screen, fireEvent } from '@testing-library/react-native';\n\nconst onChangeTextMock = jest.fn();\nconst CHANGE_TEXT = 'content';\n\nrender(\n \n \n \n);\n\nfireEvent.changeText(screen.getByPlaceholderText('Enter data'), CHANGE_TEXT);\n")),(0,r.kt)("h3",{id:"fireeventscroll"},(0,r.kt)("inlineCode",{parentName:"h3"},"fireEvent.scroll")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"fireEvent.scroll: (element: ReactTestInstance, ...data: Array) => void\n")),(0,r.kt)("p",null,"Invokes ",(0,r.kt)("inlineCode",{parentName:"p"},"scroll")," event handler on the element or parent element in the tree."),(0,r.kt)("h4",{id:"on-a-scrollview"},"On a ",(0,r.kt)("inlineCode",{parentName:"h4"},"ScrollView")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { ScrollView, Text } from 'react-native';\nimport { render, screen, fireEvent } from '@testing-library/react-native';\n\nconst onScrollMock = jest.fn();\nconst eventData = {\n nativeEvent: {\n contentOffset: {\n y: 200,\n },\n },\n};\n\nrender(\n \n XD\n \n);\n\nfireEvent.scroll(screen.getByText('scroll-view'), eventData);\n")),(0,r.kt)("h4",{id:"on-a-flatlist"},"On a ",(0,r.kt)("inlineCode",{parentName:"h4"},"FlatList")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { FlatList, View } from 'react-native';\nimport { render, screen, fireEvent } from '@testing-library/react-native';\n\nconst onEndReached = jest.fn();\nrender(\n ({ key: `${key}` }))}\n renderItem={() => }\n onEndReached={onEndReached}\n onEndReachedThreshold={0.2}\n testID=\"flat-list\"\n />\n);\nconst eventData = {\n nativeEvent: {\n contentOffset: {\n y: 500,\n },\n contentSize: {\n // Dimensions of the scrollable content\n height: 500,\n width: 100,\n },\n layoutMeasurement: {\n // Dimensions of the device\n height: 100,\n width: 100,\n },\n },\n};\n\nfireEvent.scroll(screen.getByTestId('flat-list'), eventData);\nexpect(onEndReached).toHaveBeenCalled();\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"If you're noticing that components are not being found on a list, even after mocking a scroll event, try changing the ",(0,r.kt)("a",{parentName:"p",href:"https://reactnative.dev/docs/flatlist#initialnumtorender"},(0,r.kt)("inlineCode",{parentName:"a"},"initialNumToRender"))," that you have set. If you aren't comfortable changing the code to accept this prop from the unit test, try using an e2e test that might better suit what use case you're attempting to replicate.")),(0,r.kt)("h2",{id:"waitfor"},(0,r.kt)("inlineCode",{parentName:"h2"},"waitFor")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/waitFor.test.tsx"},(0,r.kt)("inlineCode",{parentName:"a"},"Example code")))),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"function waitFor(\n expectation: () => T,\n { timeout: number = 1000, interval: number = 50 }\n): Promise {}\n")),(0,r.kt)("p",null,"Waits for a period of time for the ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," callback to pass. ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," may run the callback a number of times until timeout is reached, as specified by the ",(0,r.kt)("inlineCode",{parentName:"p"},"timeout")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"interval")," options. The callback must throw an error when the expectation is not met. Returning any value, including a falsy one, will be treated as meeting the expectation, and the callback result will be returned to the caller of ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," function."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"await waitFor(() => expect(mockFunction).toHaveBeenCalledWith()))\n")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," function will be executing ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," callback every ",(0,r.kt)("inlineCode",{parentName:"p"},"interval")," (default: every 50 ms) until ",(0,r.kt)("inlineCode",{parentName:"p"},"timeout")," (default: 1000 ms) is reached. The repeated execution of callback is stopped as soon as it does not throw an error, in such case the value returned by the callback is returned to ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," caller. Otherwise, when it reaches the timeout, the final error thrown by ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," will be re-thrown by ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," to the calling code."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"// \u274c `waitFor` will return immediately because callback does not throw\nawait waitFor(() => false);\n")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," is an async function so you need to ",(0,r.kt)("inlineCode",{parentName:"p"},"await")," the result to pause test execution."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"// \u274c missing `await`: `waitFor` will just return Promise that will be rejected when the timeout is reached\nwaitFor(() => expect(1).toBe(2))\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"You can enforce awaiting ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," by using the ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-utils.md"},"await-async-utils")," rule from ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/testing-library/eslint-plugin-testing-library"},"eslint-plugin-testing-library"),".")),(0,r.kt)("p",null,"Since ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," is likely to run ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," callback multiple times, it is highly recommended for it ",(0,r.kt)("a",{parentName:"p",href:"https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#performing-side-effects-in-waitfor"},"not to perform any side effects")," in ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"await waitFor(() => {\n // \u274c button will be pressed on each waitFor iteration\n fireEvent.press(screen.getByText('press me'))\n expect(mockOnPress).toHaveBeenCalled()\n})\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Avoiding side effects in ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," callback can be partially enforced with the ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-side-effects.md"},(0,r.kt)("inlineCode",{parentName:"a"},"no-wait-for-side-effects")," rule"),".")),(0,r.kt)("p",null,"It is also recommended to have a ",(0,r.kt)("a",{parentName:"p",href:"https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#having-multiple-assertions-in-a-single-waitfor-callback"},"single assertion per each ",(0,r.kt)("inlineCode",{parentName:"a"},"waitFor"))," for more consistency and faster failing tests. If you want to make several assertions, then they should be in seperate ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," calls. In many cases you won't actually need to wrap the second assertion in ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," since the first one will do the waiting required for asynchronous change to happen."),(0,r.kt)("h3",{id:"using-a-react-native-version--071-with-jest-fake-timers"},"Using a React Native version < 0.71 with Jest fake timers"),(0,r.kt)("admonition",{type:"caution"},(0,r.kt)("p",{parentName:"admonition"},"When using a version of React Native < 0.71 and modern fake timers (the default for ",(0,r.kt)("inlineCode",{parentName:"p"},"Jest")," >= 27), ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," won't work (it will always timeout even if ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation()")," doesn't throw) unless you use the custom ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library#custom-jest-preset"},"@testing-library/react-native preset"),". ")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," checks whether Jest fake timers are enabled and adapts its behavior in such case. The following snippet is a simplified version of how it behaves when fake timers are enabled:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"let fakeTimeRemaining = timeout;\nlet lastError;\n\nwhile(fakeTimeRemaining > 0) {\n fakeTimeRemaining = fakeTimeRemaining - interval;\n jest.advanceTimersByTime(interval);\n try {\n // resolve\n return expectation();\n } catch (error) {\n lastError = error;\n }\n}\n\n// reject\nthrow lastError\n")),(0,r.kt)("p",null,"In the following example we test that a function is called after 10 seconds using fake timers. Since we're using fake timers, the test won't depend on real time passing and thus be much faster and more reliable. Also we don't have to advance fake timers through Jest fake timers API because ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," already does this for us. "),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"// in component\nsetTimeout(() => {\n someFunction();\n}, 10000)\n\n// in test\njest.useFakeTimers();\n\nawait waitFor(() => {\n expect(someFunction).toHaveBeenCalledWith();\n}, 10000)\n")),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"In order to properly use ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," you need at least React >=16.9.0 (featuring async ",(0,r.kt)("inlineCode",{parentName:"p"},"act"),") or React Native >=0.61 (which comes with React >=16.9.0).")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"If you receive warnings related to ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function consult our ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/understanding-act"},"Undestanding Act")," function document.")),(0,r.kt)("h2",{id:"waitforelementtoberemoved"},(0,r.kt)("inlineCode",{parentName:"h2"},"waitForElementToBeRemoved")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/waitForElementToBeRemoved.test.tsx"},(0,r.kt)("inlineCode",{parentName:"a"},"Example code")))),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function waitForElementToBeRemoved(\n expectation: () => T,\n { timeout: number = 4500, interval: number = 50 }\n): Promise {}\n")),(0,r.kt)("p",null,"Waits for non-deterministic periods of time until queried element is removed or times out. ",(0,r.kt)("inlineCode",{parentName:"p"},"waitForElementToBeRemoved")," periodically calls ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," every ",(0,r.kt)("inlineCode",{parentName:"p"},"interval")," milliseconds to determine whether the element has been removed or not."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import {\n render,\n screen,\n waitForElementToBeRemoved,\n} from '@testing-library/react-native';\n\ntest('waiting for an Banana to be removed', async () => {\n render();\n\n await waitForElementToBeRemoved(() => screen.getByText('Banana ready'));\n});\n")),(0,r.kt)("p",null,"This method expects that the element is initially present in the render tree and then is removed from it. If the element is not present when you call this method it throws an error."),(0,r.kt)("p",null,"You can use any of ",(0,r.kt)("inlineCode",{parentName:"p"},"getBy"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"getAllBy"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"queryBy")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"queryAllBy")," queries for ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," parameter."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"In order to properly use ",(0,r.kt)("inlineCode",{parentName:"p"},"waitForElementToBeRemoved")," you need at least React >=16.9.0 (featuring async ",(0,r.kt)("inlineCode",{parentName:"p"},"act"),") or React Native >=0.61 (which comes with React >=16.9.0).")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"If you receive warnings related to ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function consult our ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/understanding-act"},"Undestanding Act")," function document.")),(0,r.kt)("h2",{id:"within-getqueriesforelement"},(0,r.kt)("inlineCode",{parentName:"h2"},"within"),", ",(0,r.kt)("inlineCode",{parentName:"h2"},"getQueriesForElement")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/within.test.tsx"},(0,r.kt)("inlineCode",{parentName:"a"},"Example code")))),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"function within(element: ReactTestInstance): Queries {}\n\nfunction getQueriesForElement(element: ReactTestInstance): Queries {}\n")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"within")," (also available as ",(0,r.kt)("inlineCode",{parentName:"p"},"getQueriesForElement")," alias) performs ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/api-queries"},"queries")," scoped to given element."),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Please note that additional ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," specific operations like ",(0,r.kt)("inlineCode",{parentName:"p"},"update"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"unmount"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"debug"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"toJSON")," are ",(0,r.kt)("em",{parentName:"p"},"not")," included.")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"const detailsScreen = within(screen.getByA11yHint('Details Screen'));\nexpect(detailsScreen.getByText('Some Text')).toBeOnTheScreen();\nexpect(detailsScreen.getByDisplayValue('Some Value')).toBeOnTheScreen();\nexpect(detailsScreen.queryByLabelText('Some Label')).toBeOnTheScreen();\nawait expect(detailsScreen.findByA11yHint('Some Label')).resolves.toBeOnTheScreen();\n")),(0,r.kt)("p",null,"Use cases for scoped queries include:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"queries scoped to a single item inside a FlatList containing many items"),(0,r.kt)("li",{parentName:"ul"},"queries scoped to a single screen in tests involving screen transitions (e.g. with react-navigation)")),(0,r.kt)("h2",{id:"queryby-apis"},(0,r.kt)("inlineCode",{parentName:"h2"},"queryBy*")," APIs"),(0,r.kt)("p",null,"Each of the ",(0,r.kt)("inlineCode",{parentName:"p"},"getBy*")," APIs listed in the render section above have a complimentary ",(0,r.kt)("inlineCode",{parentName:"p"},"queryBy*")," API. The ",(0,r.kt)("inlineCode",{parentName:"p"},"getBy*")," 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 ",(0,r.kt)("inlineCode",{parentName:"p"},"queryBy*")," API instead:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render, screen } from '@testing-library/react-native';\n\nrender(
);\nconst submitButton = screen.queryByText('submit');\nexpect(submitButton).not.toBeOnTheScreen(); // it doesn't exist\n")),(0,r.kt)("h2",{id:"queryall-apis"},(0,r.kt)("inlineCode",{parentName:"h2"},"queryAll*")," APIs"),(0,r.kt)("p",null,"Each of the query APIs have a corresponding ",(0,r.kt)("inlineCode",{parentName:"p"},"queryAll*")," version that always returns an array of matching nodes. ",(0,r.kt)("inlineCode",{parentName:"p"},"getAll*")," is the same but throws when the array has a length of 0."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render } from '@testing-library/react-native';\n\nrender();\nconst submitButtons = screen.queryAllByText('submit');\nexpect(submitButtons).toHaveLength(3); // expect 3 elements\n")),(0,r.kt)("h2",{id:"act"},(0,r.kt)("inlineCode",{parentName:"h2"},"act")),(0,r.kt)("p",null,"Useful function to help testing components that use hooks API. By default any ",(0,r.kt)("inlineCode",{parentName:"p"},"render"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"update"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/facebook/react/blob/main/packages/react-test-renderer/src/ReactTestRenderer.js#L567%5D"},(0,r.kt)("inlineCode",{parentName:"a"},"react-test-renderer")),"."),(0,r.kt)("p",null,"Consult our ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/understanding-act"},"Undestanding Act function")," document for more understanding of its intricacies."),(0,r.kt)("h2",{id:"renderhook"},(0,r.kt)("inlineCode",{parentName:"h2"},"renderHook")),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function renderHook(\n callback: (props?: Props) => Result,\n options?: RenderHookOptions\n): RenderHookResult;\n")),(0,r.kt)("p",null,"Renders a test component that will call the provided ",(0,r.kt)("inlineCode",{parentName:"p"},"callback"),", including any hooks it calls, every time it renders. Returns ",(0,r.kt)("a",{parentName:"p",href:"#renderhookresult-object"},(0,r.kt)("inlineCode",{parentName:"a"},"RenderHookResult"))," object, which you can interact with."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"import { renderHook } from '@testing-library/react-native';\nimport { useCount } from '../useCount';\n\nit('should increment count', () => {\n const { result } = renderHook(() => useCount());\n\n expect(result.current.count).toBe(0);\n act(() => {\n // Note that you should wrap the calls to functions your hook returns with `act` if they trigger an update of your hook's state to ensure pending useEffects are run before your next assertion.\n result.current.increment();\n });\n expect(result.current.count).toBe(1);\n});\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"// useCount.js\nexport const useCount = () => {\n const [count, setCount] = useState(0);\n const increment = () => setCount((previousCount) => previousCount + 1);\n\n return { count, increment };\n};\n")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," function accepts the following arguments:"),(0,r.kt)("h3",{id:"callback"},(0,r.kt)("inlineCode",{parentName:"h3"},"callback")),(0,r.kt)("p",null,"The function that is called each ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," of the test component. This function should call one or more hooks for testing."),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"props")," passed into the callback will be the ",(0,r.kt)("inlineCode",{parentName:"p"},"initialProps")," provided in the ",(0,r.kt)("inlineCode",{parentName:"p"},"options")," to ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook"),", unless new props are provided by a subsequent ",(0,r.kt)("inlineCode",{parentName:"p"},"rerender")," call."),(0,r.kt)("h3",{id:"options-optional"},(0,r.kt)("inlineCode",{parentName:"h3"},"options")," (Optional)"),(0,r.kt)("p",null,"A ",(0,r.kt)("inlineCode",{parentName:"p"},"RenderHookOptions")," object to modify the execution of the ",(0,r.kt)("inlineCode",{parentName:"p"},"callback")," function, containing the following properties:"),(0,r.kt)("h4",{id:"initialprops"},(0,r.kt)("inlineCode",{parentName:"h4"},"initialProps")),(0,r.kt)("p",null,"The initial values to pass as ",(0,r.kt)("inlineCode",{parentName:"p"},"props")," to the ",(0,r.kt)("inlineCode",{parentName:"p"},"callback")," function of ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"Props")," type is determined by the type passed to or inferred by the ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," call."),(0,r.kt)("h4",{id:"wrapper"},(0,r.kt)("inlineCode",{parentName:"h4"},"wrapper")),(0,r.kt)("p",null,"A React component to wrap the test component in when rendering. This is usually used to add context providers from ",(0,r.kt)("inlineCode",{parentName:"p"},"React.createContext")," for the hook to access with ",(0,r.kt)("inlineCode",{parentName:"p"},"useContext"),"."),(0,r.kt)("h3",{id:"renderhookresult-object"},(0,r.kt)("inlineCode",{parentName:"h3"},"RenderHookResult")," object"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"interface RenderHookResult {\n result: { current: Result };\n rerender: (props: Props) => void;\n unmount: () => void;\n}\n")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," function returns an object that has the following properties:"),(0,r.kt)("h4",{id:"result"},(0,r.kt)("inlineCode",{parentName:"h4"},"result")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"current")," value of the ",(0,r.kt)("inlineCode",{parentName:"p"},"result")," will reflect the latest of whatever is returned from the ",(0,r.kt)("inlineCode",{parentName:"p"},"callback")," passed to ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"Result")," type is determined by the type passed to or inferred by the ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," call."),(0,r.kt)("h4",{id:"rerender"},(0,r.kt)("inlineCode",{parentName:"h4"},"rerender")),(0,r.kt)("p",null,"A function to rerender the test component, causing any hooks to be recalculated. If ",(0,r.kt)("inlineCode",{parentName:"p"},"newProps")," are passed, they will replace the ",(0,r.kt)("inlineCode",{parentName:"p"},"callback")," function's ",(0,r.kt)("inlineCode",{parentName:"p"},"initialProps")," for subsequent rerenders. The ",(0,r.kt)("inlineCode",{parentName:"p"},"Props")," type is determined by the type passed to or inferred by the ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," call."),(0,r.kt)("h4",{id:"unmount-1"},(0,r.kt)("inlineCode",{parentName:"h4"},"unmount")),(0,r.kt)("p",null,"A function to unmount the test component. This is commonly used to trigger cleanup effects for ",(0,r.kt)("inlineCode",{parentName:"p"},"useEffect")," hooks."),(0,r.kt)("h3",{id:"examples"},"Examples"),(0,r.kt)("p",null,"Here we present some extra examples of using ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," API."),(0,r.kt)("h4",{id:"with-initialprops"},"With ",(0,r.kt)("inlineCode",{parentName:"h4"},"initialProps")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"const useCount = (initialCount: number) => {\n const [count, setCount] = useState(initialCount);\n const increment = () => setCount((previousCount) => previousCount + 1);\n\n useEffect(() => {\n setCount(initialCount);\n }, [initialCount]);\n\n return { count, increment };\n};\n\nit('should increment count', () => {\n const { result, rerender } = renderHook(\n (initialCount: number) => useCount(initialCount),\n { initialProps: 1 }\n );\n\n expect(result.current.count).toBe(1);\n\n act(() => {\n result.current.increment();\n });\n\n expect(result.current.count).toBe(2);\n rerender(5);\n expect(result.current.count).toBe(5);\n});\n")),(0,r.kt)("h4",{id:"with-wrapper"},"With ",(0,r.kt)("inlineCode",{parentName:"h4"},"wrapper")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"it('should use context value', () => {\n function Wrapper({ children }: { children: ReactNode }) {\n return {children};\n }\n\n const { result } = renderHook(() => useHook(), { wrapper: Wrapper });\n // ...\n});\n")),(0,r.kt)("h2",{id:"configuration"},"Configuration"),(0,r.kt)("h3",{id:"configure"},(0,r.kt)("inlineCode",{parentName:"h3"},"configure")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"type Config = {\n asyncUtilTimeout: number;\n defaultHidden: boolean;\n defaultDebugOptions: Partial;\n};\n\nfunction configure(options: Partial) {}\n")),(0,r.kt)("h4",{id:"asyncutiltimeout-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"asyncUtilTimeout")," option"),(0,r.kt)("p",null,"Default timeout, in ms, for async helper functions (",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"waitForElementToBeRemoved"),") and ",(0,r.kt)("inlineCode",{parentName:"p"},"findBy*")," queries. Defaults to 1000 ms."),(0,r.kt)("h4",{id:"defaultincludehiddenelements-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"defaultIncludeHiddenElements")," option"),(0,r.kt)("p",null,"Default value for ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/api-queries#includehiddenelements-option"},"includeHiddenElements")," query option for all queries. The default value is set to ",(0,r.kt)("inlineCode",{parentName:"p"},"false"),", so all queries will not match ",(0,r.kt)("a",{parentName:"p",href:"#ishiddenfromaccessibility"},"elements hidden from accessibility"),". This is because the users of the app would not be able to see such elements."),(0,r.kt)("p",null,"This option is also available as ",(0,r.kt)("inlineCode",{parentName:"p"},"defaultHidden")," alias for compatibility with ",(0,r.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/dom-testing-library/api-configuration/#defaulthidden"},"React Testing Library"),"."),(0,r.kt)("h4",{id:"defaultdebugoptions-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"defaultDebugOptions")," option"),(0,r.kt)("p",null,"Default ",(0,r.kt)("a",{parentName:"p",href:"#debug"},"debug options")," to be used when calling ",(0,r.kt)("inlineCode",{parentName:"p"},"debug()"),". These default options will be overridden by the ones you specify directly when calling ",(0,r.kt)("inlineCode",{parentName:"p"},"debug()"),"."),(0,r.kt)("h3",{id:"resettodefaults"},(0,r.kt)("inlineCode",{parentName:"h3"},"resetToDefaults()")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function resetToDefaults() {}\n")),(0,r.kt)("h3",{id:"environment-variables"},"Environment variables"),(0,r.kt)("h4",{id:"rntl_skip_auto_cleanup"},(0,r.kt)("inlineCode",{parentName:"h4"},"RNTL_SKIP_AUTO_CLEANUP")),(0,r.kt)("p",null,"Set to ",(0,r.kt)("inlineCode",{parentName:"p"},"true")," to disable automatic ",(0,r.kt)("inlineCode",{parentName:"p"},"cleanup()")," after each test. It works the same as importing ",(0,r.kt)("inlineCode",{parentName:"p"},"react-native-testing-library/dont-cleanup-after-each")," or using ",(0,r.kt)("inlineCode",{parentName:"p"},"react-native-testing-library/pure"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-shell"},"$ RNTL_SKIP_AUTO_CLEANUP=true jest\n")),(0,r.kt)("h4",{id:"rntl_skip_auto_detect_fake_timers"},(0,r.kt)("inlineCode",{parentName:"h4"},"RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS")),(0,r.kt)("p",null,"Set to ",(0,r.kt)("inlineCode",{parentName:"p"},"true")," to disable auto-detection of fake timers. This might be useful in rare cases when you want to use non-Jest fake timers. See ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/issues/886"},"issue #886")," for more details."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-shell"},"$ RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS=true jest\n")),(0,r.kt)("h2",{id:"accessibility"},"Accessibility"),(0,r.kt)("h3",{id:"ishiddenfromaccessibility"},(0,r.kt)("inlineCode",{parentName:"h3"},"isHiddenFromAccessibility")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function isHiddenFromAccessibility(\n element: ReactTestInstance | null\n): boolean {}\n")),(0,r.kt)("p",null,"Also available as ",(0,r.kt)("inlineCode",{parentName:"p"},"isInaccessible()")," alias for React Testing Library compatibility."),(0,r.kt)("p",null,"Checks if given element is hidden from assistive technology, e.g. screen readers."),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Like ",(0,r.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/dom-testing-library/api-accessibility/#isinaccessible"},(0,r.kt)("inlineCode",{parentName:"a"},"isInaccessible"))," function from DOM Testing Library this function considers both accessibility elements and presentational elements (regular ",(0,r.kt)("inlineCode",{parentName:"p"},"View"),"s) to be accessible, unless they are hidden in terms of host platform."),(0,r.kt)("p",{parentName:"admonition"},"This covers only part of ",(0,r.kt)("a",{parentName:"p",href:"https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion"},"ARIA notion of Accessiblity Tree"),", as ARIA excludes both hidden and presentational elements from the Accessibility Tree.")),(0,r.kt)("p",null,"For the scope of this function, element is inaccessible when it, or any of its ancestors, meets any of the following conditions:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"it has ",(0,r.kt)("inlineCode",{parentName:"li"},"display: none")," style"),(0,r.kt)("li",{parentName:"ul"},"it has ",(0,r.kt)("a",{parentName:"li",href:"https://reactnative.dev/docs/accessibility#accessibilityelementshidden-ios"},(0,r.kt)("inlineCode",{parentName:"a"},"accessibilityElementsHidden"))," prop set to ",(0,r.kt)("inlineCode",{parentName:"li"},"true")),(0,r.kt)("li",{parentName:"ul"},"it has ",(0,r.kt)("a",{parentName:"li",href:"https://reactnative.dev/docs/accessibility#importantforaccessibility-android"},(0,r.kt)("inlineCode",{parentName:"a"},"importantForAccessibility"))," prop set to ",(0,r.kt)("inlineCode",{parentName:"li"},"no-hide-descendants")),(0,r.kt)("li",{parentName:"ul"},"it has sibling host element with ",(0,r.kt)("a",{parentName:"li",href:"https://reactnative.dev/docs/accessibility#accessibilityviewismodal-ios"},(0,r.kt)("inlineCode",{parentName:"a"},"accessibilityViewIsModal"))," prop set to ",(0,r.kt)("inlineCode",{parentName:"li"},"true"))),(0,r.kt)("p",null,"Specifying ",(0,r.kt)("inlineCode",{parentName:"p"},"accessible={false}"),", ",(0,r.kt)("inlineCode",{parentName:"p"},'accessiblityRole="none"'),", or ",(0,r.kt)("inlineCode",{parentName:"p"},'importantForAccessibility="no"')," props does not cause the element to become inaccessible."))}k.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkreact_native_testing_library_website=self.webpackChunkreact_native_testing_library_website||[]).push([[298],{3905:function(e,t,n){n.d(t,{Zo:function(){return d},kt:function(){return k}});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),c=p(n),u=i,k=c["".concat(s,".").concat(u)]||c[u]||m[u]||r;return n?a.createElement(k,o(o({ref:t},d),{},{components:n})):a.createElement(k,o({ref:t},d))}));function k(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=u;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[c]="string"==typeof e?e:i,o[1]=l;for(var p=2;prender",id:"render",level:2},{value:"render options",id:"render-options",level:3},{value:"wrapper option",id:"wrapper-option",level:4},{value:"createNodeMock option",id:"createnodemock-option",level:4},{value:"unstable_validateStringsRenderedWithinText option",id:"unstable_validatestringsrenderedwithintext-option",level:4},{value:"...queries",id:"queries",level:3},{value:"Example",id:"example",level:4},{value:"update",id:"update",level:3},{value:"unmount",id:"unmount",level:3},{value:"debug",id:"debug",level:3},{value:"message option",id:"message-option",level:4},{value:"mapProps option",id:"mapprops-option",level:4},{value:"debug.shallow",id:"debugshallow",level:4},{value:"toJSON",id:"tojson",level:3},{value:"root",id:"root",level:3},{value:"UNSAFE_root",id:"unsafe_root",level:3},{value:"screen",id:"screen",level:2},{value:"cleanup",id:"cleanup",level:2},{value:"fireEvent",id:"fireevent",level:2},{value:"fireEvent[eventName]",id:"fireeventeventname",level:2},{value:"fireEvent.press",id:"fireeventpress",level:3},{value:"fireEvent.changeText",id:"fireeventchangetext",level:3},{value:"fireEvent.scroll",id:"fireeventscroll",level:3},{value:"On a ScrollView",id:"on-a-scrollview",level:4},{value:"On a FlatList",id:"on-a-flatlist",level:4},{value:"waitFor",id:"waitfor",level:2},{value:"Using a React Native version < 0.71 with Jest fake timers",id:"using-a-react-native-version--071-with-jest-fake-timers",level:3},{value:"waitForElementToBeRemoved",id:"waitforelementtoberemoved",level:2},{value:"within, getQueriesForElement",id:"within-getqueriesforelement",level:2},{value:"queryBy* APIs",id:"queryby-apis",level:2},{value:"queryAll* APIs",id:"queryall-apis",level:2},{value:"act",id:"act",level:2},{value:"renderHook",id:"renderhook",level:2},{value:"callback",id:"callback",level:3},{value:"options (Optional)",id:"options-optional",level:3},{value:"initialProps",id:"initialprops",level:4},{value:"wrapper",id:"wrapper",level:4},{value:"RenderHookResult object",id:"renderhookresult-object",level:3},{value:"result",id:"result",level:4},{value:"rerender",id:"rerender",level:4},{value:"unmount",id:"unmount-1",level:4},{value:"Examples",id:"examples",level:3},{value:"With initialProps",id:"with-initialprops",level:4},{value:"With wrapper",id:"with-wrapper",level:4},{value:"Configuration",id:"configuration",level:2},{value:"configure",id:"configure",level:3},{value:"asyncUtilTimeout option",id:"asyncutiltimeout-option",level:4},{value:"defaultIncludeHiddenElements option",id:"defaultincludehiddenelements-option",level:4},{value:"defaultDebugOptions option",id:"defaultdebugoptions-option",level:4},{value:"resetToDefaults()",id:"resettodefaults",level:3},{value:"Environment variables",id:"environment-variables",level:3},{value:"RNTL_SKIP_AUTO_CLEANUP",id:"rntl_skip_auto_cleanup",level:4},{value:"RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS",id:"rntl_skip_auto_detect_fake_timers",level:4},{value:"Accessibility",id:"accessibility",level:2},{value:"isHiddenFromAccessibility",id:"ishiddenfromaccessibility",level:3}],m={toc:c},u="wrapper";function k(e){var t=e.components,n=(0,i.Z)(e,o);return(0,r.kt)(u,(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h3",{id:"table-of-contents"},"Table of contents:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#render"},(0,r.kt)("inlineCode",{parentName:"a"},"render")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#render-options"},(0,r.kt)("inlineCode",{parentName:"a"},"render")," options")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#queries"},(0,r.kt)("inlineCode",{parentName:"a"},"...queries"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#update"},(0,r.kt)("inlineCode",{parentName:"a"},"update"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#unmount"},(0,r.kt)("inlineCode",{parentName:"a"},"unmount"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#debug"},(0,r.kt)("inlineCode",{parentName:"a"},"debug"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#tojson"},(0,r.kt)("inlineCode",{parentName:"a"},"toJSON"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#root"},(0,r.kt)("inlineCode",{parentName:"a"},"root"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#unsaferoot"},(0,r.kt)("inlineCode",{parentName:"a"},"UNSAFE_root"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#screen"},(0,r.kt)("inlineCode",{parentName:"a"},"screen"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#cleanup"},(0,r.kt)("inlineCode",{parentName:"a"},"cleanup"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireevent"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireeventeventname"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent[eventName]")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireeventpress"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent.press"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireeventchangetext"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent.changeText"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#fireeventscroll"},(0,r.kt)("inlineCode",{parentName:"a"},"fireEvent.scroll"))))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#waitfor"},(0,r.kt)("inlineCode",{parentName:"a"},"waitFor")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#using-a-react-native-version--071-with-jest-fake-timers"},"Using a React Native version \\< 0.71 with Jest fake timers")))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#waitforelementtoberemoved"},(0,r.kt)("inlineCode",{parentName:"a"},"waitForElementToBeRemoved"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#within-getqueriesforelement"},(0,r.kt)("inlineCode",{parentName:"a"},"within"),", ",(0,r.kt)("inlineCode",{parentName:"a"},"getQueriesForElement"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#queryby-apis"},(0,r.kt)("inlineCode",{parentName:"a"},"queryBy*")," APIs")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#queryall-apis"},(0,r.kt)("inlineCode",{parentName:"a"},"queryAll*")," APIs")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#act"},(0,r.kt)("inlineCode",{parentName:"a"},"act"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#renderhook"},(0,r.kt)("inlineCode",{parentName:"a"},"renderHook")),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#callback"},(0,r.kt)("inlineCode",{parentName:"a"},"callback"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#options-optional"},(0,r.kt)("inlineCode",{parentName:"a"},"options")," (Optional)")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#renderhookresult-object"},(0,r.kt)("inlineCode",{parentName:"a"},"RenderHookResult")," object")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#examples"},"Examples")))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#configuration"},"Configuration"),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#configure"},(0,r.kt)("inlineCode",{parentName:"a"},"configure"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#resettodefaults"},(0,r.kt)("inlineCode",{parentName:"a"},"resetToDefaults()"))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#environment-variables"},"Environment variables")))),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#accessibility"},"Accessibility"),(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"#ishiddenfromaccessibility"},(0,r.kt)("inlineCode",{parentName:"a"},"isHiddenFromAccessibility")))))),(0,r.kt)("p",null,"This page gathers public API of React Native Testing Library along with usage examples."),(0,r.kt)("h2",{id:"render"},(0,r.kt)("inlineCode",{parentName:"h2"},"render")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/render.test.tsx"},(0,r.kt)("inlineCode",{parentName:"a"},"Example code")))),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"function render(\n component: React.Element,\n options?: RenderOptions\n): RenderResult {}\n")),(0,r.kt)("p",null,"Deeply renders given React element and returns helpers to query the output components structure."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render } from '@testing-library/react-native';\nimport { QuestionsBoard } from '../QuestionsBoard';\n\ntest('should verify two questions', () => {\n render();\n const allQuestions = screen.queryAllByRole('header');\n\n expect(allQuestions).toHaveLength(2);\n});\n")),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"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 ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," method. ",(0,r.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/react-testing-library/setup#custom-render"},"Follow this great guide on how to set this up"),".")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," method returns a ",(0,r.kt)("inlineCode",{parentName:"p"},"RenderResult")," object having properties described below."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Latest ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," result is kept in ",(0,r.kt)("a",{parentName:"p",href:"#screen"},(0,r.kt)("inlineCode",{parentName:"a"},"screen"))," variable that can be imported from ",(0,r.kt)("inlineCode",{parentName:"p"},"@testing-library/react-native")," package."),(0,r.kt)("p",{parentName:"admonition"},"Using ",(0,r.kt)("inlineCode",{parentName:"p"},"screen")," instead of destructuring ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," result is recommended approach. See ",(0,r.kt)("a",{parentName:"p",href:"https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen"},"this article")," from Kent C. Dodds for more details.")),(0,r.kt)("h3",{id:"render-options"},(0,r.kt)("inlineCode",{parentName:"h3"},"render")," options"),(0,r.kt)("p",null,"The behavior of ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," method can be customized by passing various options as a second argument of ",(0,r.kt)("inlineCode",{parentName:"p"},"RenderOptions")," type:"),(0,r.kt)("h4",{id:"wrapper-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"wrapper")," option"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"wrapper?: React.ComponentType,\n")),(0,r.kt)("p",null,"This options allows you to wrap tested component, passed as the first option to the ",(0,r.kt)("inlineCode",{parentName:"p"},"render()")," function, in additional wrapper component. This is most useful for creating reusable custom render functions for common React Context providers."),(0,r.kt)("h4",{id:"createnodemock-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"createNodeMock")," option"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"createNodeMock?: (element: React.Element) => any,\n")),(0,r.kt)("p",null,"This options allows you to pass ",(0,r.kt)("inlineCode",{parentName:"p"},"createNodeMock")," option to ",(0,r.kt)("inlineCode",{parentName:"p"},"ReactTestRenderer.create()")," method in order to allow for custom mock refs. You can learn more about this options from ",(0,r.kt)("a",{parentName:"p",href:"https://reactjs.org/docs/test-renderer.html#ideas"},"React Test Renderer documentation"),"."),(0,r.kt)("h4",{id:"unstable_validatestringsrenderedwithintext-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"unstable_validateStringsRenderedWithinText")," option"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"unstable_validateStringsRenderedWithinText?: boolean;\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"This options is experimental, in some cases it might not work as intended, and its behavior might change without observing ",(0,r.kt)("a",{parentName:"p",href:"https://semver.org/"},"SemVer")," requirements for breaking changes.")),(0,r.kt)("p",null,"This ",(0,r.kt)("strong",{parentName:"p"},"experimental")," option allows you to replicate React Native behavior of throwing ",(0,r.kt)("inlineCode",{parentName:"p"},"Invariant Violation: Text strings must be rendered within a component")," error when you try to render ",(0,r.kt)("inlineCode",{parentName:"p"},"string")," value under components different than ",(0,r.kt)("inlineCode",{parentName:"p"},""),", e.g. under ",(0,r.kt)("inlineCode",{parentName:"p"},""),"."),(0,r.kt)("p",null,"This check is not enforced by React Test Renderer and hence by default React Native Testing Library also does not check this. That might result in runtime errors when running your code on a device, while the code works without errors in tests."),(0,r.kt)("h3",{id:"queries"},(0,r.kt)("inlineCode",{parentName:"h3"},"...queries")),(0,r.kt)("p",null,"The most important feature of ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," is providing a set of helpful queries that allow you to find certain elements in the view hierarchy."),(0,r.kt)("p",null,"See ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/api-queries"},"Queries")," for a complete list."),(0,r.kt)("h4",{id:"example"},"Example"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render } from '@testing-library/react-native';\n\nconst { getByText, queryByA11yState } = render();\n")),(0,r.kt)("h3",{id:"update"},(0,r.kt)("inlineCode",{parentName:"h3"},"update")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"Also available under ",(0,r.kt)("inlineCode",{parentName:"em"},"rerender")," alias")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"update(element: React.Element): void\nrerender(element: React.Element): void\n")),(0,r.kt)("p",null,"Re-render the in-memory tree with a new root element. This simulates a React update at the root. If the new element has the same type and key as the previous element, the tree will be updated; otherwise, it will re-mount a new tree. This is useful when testing for ",(0,r.kt)("inlineCode",{parentName:"p"},"componentDidUpdate")," behavior, by passing updated props to the component."),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/blob/f96d782d26dd4815dbfd01de6ef7a647efd1f693/src/__tests__/act.test.js#L31-L37"},"Example code")),(0,r.kt)("h3",{id:"unmount"},(0,r.kt)("inlineCode",{parentName:"h3"},"unmount")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"unmount(): void\n")),(0,r.kt)("p",null,"Unmount the in-memory tree, triggering the appropriate lifecycle events."),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Usually you should not need to call ",(0,r.kt)("inlineCode",{parentName:"p"},"unmount")," as it is done automatically if your test runner supports ",(0,r.kt)("inlineCode",{parentName:"p"},"afterEach")," hook (like Jest, mocha, Jasmine).")),(0,r.kt)("h3",{id:"debug"},(0,r.kt)("inlineCode",{parentName:"h3"},"debug")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"interface DebugOptions {\n message?: string;\n mapProps?: MapPropsFunction;\n}\n\ndebug(options?: DebugOptions | string): void\n")),(0,r.kt)("p",null,"Pretty prints deeply rendered component passed to ",(0,r.kt)("inlineCode",{parentName:"p"},"render"),"."),(0,r.kt)("h4",{id:"message-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"message")," option"),(0,r.kt)("p",null,"You can provide a message that will be printed on top."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"render();\nscreen.debug({ message: 'optional message' });\n")),(0,r.kt)("p",null,"logs optional message and colored JSX:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"optional message\n\n\n Press me\n\n")),(0,r.kt)("h4",{id:"mapprops-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"mapProps")," option"),(0,r.kt)("p",null,"You can use the ",(0,r.kt)("inlineCode",{parentName:"p"},"mapProps")," option to transform the props that will be printed :"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"render();\ndebug({ mapProps: ({ style, ...props }) => ({ props }) });\n")),(0,r.kt)("p",null,"This will log the rendered JSX without the ",(0,r.kt)("inlineCode",{parentName:"p"},"style")," props."),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"children")," prop cannot be filtered out so the following will print all rendered components with all props but ",(0,r.kt)("inlineCode",{parentName:"p"},"children")," filtered out."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"debug({ mapProps: (props) => ({}) });\n")),(0,r.kt)("p",null,"This option can be used to target specific props when debugging a query (for instance keeping only ",(0,r.kt)("inlineCode",{parentName:"p"},"children")," prop when debugging a ",(0,r.kt)("inlineCode",{parentName:"p"},"getByText")," query)."),(0,r.kt)("p",null,"You can also transform prop values so that they are more readable (e.g. flatten styles)."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"import { StyleSheet } from 'react-native';\n\ndebug({ mapProps : {({ style, ...props })} => ({ style : StyleSheet.flatten(style), ...props }) });\n")),(0,r.kt)("p",null,"Or remove props that have little value when debugging tests, e.g. path prop for svgs"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"debug({ mapProps: ({ path, ...props }) => ({ ...props }) });\n")),(0,r.kt)("h4",{id:"debugshallow"},(0,r.kt)("inlineCode",{parentName:"h4"},"debug.shallow")),(0,r.kt)("p",null,"Pretty prints shallowly rendered component passed to ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," with optional message on top."),(0,r.kt)("h3",{id:"tojson"},(0,r.kt)("inlineCode",{parentName:"h3"},"toJSON")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"toJSON(): ReactTestRendererJSON | null\n")),(0,r.kt)("p",null,"Get the rendered component JSON representation, e.g. for snapshot testing."),(0,r.kt)("h3",{id:"root"},(0,r.kt)("inlineCode",{parentName:"h3"},"root")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"root: ReactTestInstance;\n")),(0,r.kt)("p",null,"Returns the rendered root ",(0,r.kt)("a",{parentName:"p",href:"testing-env#host-and-composite-components"},"host element"),"."),(0,r.kt)("p",null,"This API is primarily useful in component tests, as it allows you to access root host view without using ",(0,r.kt)("inlineCode",{parentName:"p"},"*ByTestId")," queries or similar methods."),(0,r.kt)("h3",{id:"unsafe_root"},(0,r.kt)("inlineCode",{parentName:"h3"},"UNSAFE_root")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"UNSAFE_root: ReactTestInstance;\n")),(0,r.kt)("p",null,"Returns the rendered ",(0,r.kt)("a",{parentName:"p",href:"testing-env#host-and-composite-components"},"composite root element"),"."),(0,r.kt)("admonition",{type:"caution"},(0,r.kt)("p",{parentName:"admonition"},"This API typically will return a composite view which goes against recommended testing practices. This API is primarily available for legacy test suites that rely on such testing.")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"This API has been previously named ",(0,r.kt)("inlineCode",{parentName:"p"},"container")," for compatibility with ",(0,r.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/react-testing-library/api#container-1"},"React Testing Library"),". However, despite the same name, the actual behavior has been signficantly different, hence the name change to ",(0,r.kt)("inlineCode",{parentName:"p"},"UNSAFE_root"),".")),(0,r.kt)("h2",{id:"screen"},(0,r.kt)("inlineCode",{parentName:"h2"},"screen")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"let screen: RenderResult;\n")),(0,r.kt)("p",null,"Hold the value of latest render call for easier access to query and other functions returned by ",(0,r.kt)("a",{parentName:"p",href:"#render"},(0,r.kt)("inlineCode",{parentName:"a"},"render")),"."),(0,r.kt)("p",null,"Its value is automatically cleared after each test by calling ",(0,r.kt)("a",{parentName:"p",href:"#cleanup"},(0,r.kt)("inlineCode",{parentName:"a"},"cleanup")),". If no ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," call has been made in a given test then it holds a special object that implements ",(0,r.kt)("inlineCode",{parentName:"p"},"RenderResult")," but throws a helpful error on each property and method access."),(0,r.kt)("p",null,"This can also be used to build test utils that would normally require to be in render scope, either in a test file or globally for your project. For instance:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"// Prints the rendered components omitting all props except children.\nconst debugText = () => screen.debug({ mapProps: (props) => ({}) });\n")),(0,r.kt)("h2",{id:"cleanup"},(0,r.kt)("inlineCode",{parentName:"h2"},"cleanup")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"const cleanup: () => void;\n")),(0,r.kt)("p",null,"Unmounts React trees that were mounted with ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," and clears ",(0,r.kt)("inlineCode",{parentName:"p"},"screen")," variable that holds latest ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," output."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Please note that this is done automatically if the testing framework you're using supports the ",(0,r.kt)("inlineCode",{parentName:"p"},"afterEach")," global (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test.")),(0,r.kt)("p",null,"For example, if you're using the ",(0,r.kt)("inlineCode",{parentName:"p"},"jest")," testing framework, then you would need to use the ",(0,r.kt)("inlineCode",{parentName:"p"},"afterEach")," hook like so:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { cleanup, render } from '@testing-library/react-native/pure';\nimport { View } from 'react-native';\n\nafterEach(cleanup);\n\nit('renders a view', () => {\n render();\n // ...\n});\n")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"afterEach(cleanup)")," call also works in ",(0,r.kt)("inlineCode",{parentName:"p"},"describe")," blocks:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"describe('when logged in', () => {\n afterEach(cleanup);\n\n it('renders the user', () => {\n render();\n // ...\n });\n});\n")),(0,r.kt)("p",null,"Failing to call ",(0,r.kt)("inlineCode",{parentName:"p"},"cleanup")," when you've called ",(0,r.kt)("inlineCode",{parentName:"p"},"render"),' could result in a memory leak and tests which are not "idempotent" (which can lead to difficult to debug errors in your tests).'),(0,r.kt)("h2",{id:"fireevent"},(0,r.kt)("inlineCode",{parentName:"h2"},"fireEvent")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function fireEvent(\n element: ReactTestInstance,\n eventName: string,\n ...data: Array\n): void {}\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"For common events like ",(0,r.kt)("inlineCode",{parentName:"p"},"press")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"type")," it's recommended to use ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/user-event"},"User Event API")," as it offers\nmore realistic event simulation by emitting a sequence of events with proper event objects that mimic React Native runtime behavior."),(0,r.kt)("p",{parentName:"admonition"},"Use Fire Event for cases not supported by User Event and for triggering event handlers on composite components.")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent")," API allows you to trigger all kind of event handlers on both host and composite components. It will try to invoke a single event handler traversing the component tree bottom-up from passed element and trying to find enabled event handler named ",(0,r.kt)("inlineCode",{parentName:"p"},"onXxx")," when ",(0,r.kt)("inlineCode",{parentName:"p"},"xxx")," is the name of the event passed."),(0,r.kt)("p",null,"Unlike User Event, this API does not automatically pass event object to event handler, this is responsibility of the user to construct such object."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render, screen, fireEvent } from '@testing-library/react-native';\n\ntest('fire changeText event', () => {\n const onEventMock = jest.fn();\n render(\n // MyComponent renders TextInput which has a placeholder 'Enter details'\n // and with `onChangeText` bound to handleChangeText\n \n );\n\n fireEvent(screen.getByPlaceholderText('change'), 'onChangeText', 'ab');\n expect(onEventMock).toHaveBeenCalledWith('ab');\n});\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Please note that from version ",(0,r.kt)("inlineCode",{parentName:"p"},"7.0")," ",(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent")," performs checks that should prevent events firing on disabled elements.")),(0,r.kt)("p",null,"An example using ",(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent")," with native events that aren't already aliased by the ",(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent")," api."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { TextInput, View } from 'react-native';\nimport { fireEvent, render } from '@testing-library/react-native';\n\nconst onBlurMock = jest.fn();\n\nrender(\n \n \n \n);\n\n// you can omit the `on` prefix\nfireEvent(screen.getByPlaceholderText('my placeholder'), 'blur');\n")),(0,r.kt)("h2",{id:"fireeventeventname"},(0,r.kt)("inlineCode",{parentName:"h2"},"fireEvent[eventName]")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"fireEvent[eventName](element: ReactTestInstance, ...data: Array): void\n")),(0,r.kt)("p",null,"Convenience methods for common events like: ",(0,r.kt)("inlineCode",{parentName:"p"},"press"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"changeText"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"scroll"),"."),(0,r.kt)("h3",{id:"fireeventpress"},(0,r.kt)("inlineCode",{parentName:"h3"},"fireEvent.press")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"fireEvent.press: (element: ReactTestInstance, ...data: Array) => void\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"It is recommended to use the User Event ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/user-event#press"},(0,r.kt)("inlineCode",{parentName:"a"},"press()"))," helper instead as it offers more realistic simulation of press interaction, including pressable support.")),(0,r.kt)("p",null,"Invokes ",(0,r.kt)("inlineCode",{parentName:"p"},"press")," event handler on the element or parent element in the tree."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { View, Text, TouchableOpacity } from 'react-native';\nimport { render, screen, fireEvent } from '@testing-library/react-native';\n\nconst onPressMock = jest.fn();\nconst eventData = {\n nativeEvent: {\n pageX: 20,\n pageY: 30,\n },\n};\n\nrender(\n \n \n Press me\n \n \n);\n\nfireEvent.press(screen.getByText('Press me'), eventData);\nexpect(onPressMock).toHaveBeenCalledWith(eventData);\n")),(0,r.kt)("h3",{id:"fireeventchangetext"},(0,r.kt)("inlineCode",{parentName:"h3"},"fireEvent.changeText")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"fireEvent.changeText: (element: ReactTestInstance, ...data: Array) => void\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"It is recommended to use the User Event ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/user-event#type"},(0,r.kt)("inlineCode",{parentName:"a"},"type()"))," helper instead as it offers more realistic simulation of text change interaction, including key-by-key typing, element focus, and other editing events.")),(0,r.kt)("p",null,"Invokes ",(0,r.kt)("inlineCode",{parentName:"p"},"changeText")," event handler on the element or parent element in the tree."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { View, TextInput } from 'react-native';\nimport { render, screen, fireEvent } from '@testing-library/react-native';\n\nconst onChangeTextMock = jest.fn();\nconst CHANGE_TEXT = 'content';\n\nrender(\n \n \n \n);\n\nfireEvent.changeText(screen.getByPlaceholderText('Enter data'), CHANGE_TEXT);\n")),(0,r.kt)("h3",{id:"fireeventscroll"},(0,r.kt)("inlineCode",{parentName:"h3"},"fireEvent.scroll")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"fireEvent.scroll: (element: ReactTestInstance, ...data: Array) => void\n")),(0,r.kt)("p",null,"Invokes ",(0,r.kt)("inlineCode",{parentName:"p"},"scroll")," event handler on the element or parent element in the tree."),(0,r.kt)("h4",{id:"on-a-scrollview"},"On a ",(0,r.kt)("inlineCode",{parentName:"h4"},"ScrollView")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { ScrollView, Text } from 'react-native';\nimport { render, screen, fireEvent } from '@testing-library/react-native';\n\nconst onScrollMock = jest.fn();\nconst eventData = {\n nativeEvent: {\n contentOffset: {\n y: 200,\n },\n },\n};\n\nrender(\n \n XD\n \n);\n\nfireEvent.scroll(screen.getByText('scroll-view'), eventData);\n")),(0,r.kt)("h4",{id:"on-a-flatlist"},"On a ",(0,r.kt)("inlineCode",{parentName:"h4"},"FlatList")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { FlatList, View } from 'react-native';\nimport { render, screen, fireEvent } from '@testing-library/react-native';\n\nconst onEndReached = jest.fn();\nrender(\n ({ key: `${key}` }))}\n renderItem={() => }\n onEndReached={onEndReached}\n onEndReachedThreshold={0.2}\n testID=\"flat-list\"\n />\n);\nconst eventData = {\n nativeEvent: {\n contentOffset: {\n y: 500,\n },\n contentSize: {\n // Dimensions of the scrollable content\n height: 500,\n width: 100,\n },\n layoutMeasurement: {\n // Dimensions of the device\n height: 100,\n width: 100,\n },\n },\n};\n\nfireEvent.scroll(screen.getByTestId('flat-list'), eventData);\nexpect(onEndReached).toHaveBeenCalled();\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"If you're noticing that components are not being found on a list, even after mocking a scroll event, try changing the ",(0,r.kt)("a",{parentName:"p",href:"https://reactnative.dev/docs/flatlist#initialnumtorender"},(0,r.kt)("inlineCode",{parentName:"a"},"initialNumToRender"))," that you have set. If you aren't comfortable changing the code to accept this prop from the unit test, try using an e2e test that might better suit what use case you're attempting to replicate.")),(0,r.kt)("h2",{id:"waitfor"},(0,r.kt)("inlineCode",{parentName:"h2"},"waitFor")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/waitFor.test.tsx"},(0,r.kt)("inlineCode",{parentName:"a"},"Example code")))),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"function waitFor(\n expectation: () => T,\n { timeout: number = 1000, interval: number = 50 }\n): Promise {}\n")),(0,r.kt)("p",null,"Waits for a period of time for the ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," callback to pass. ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," may run the callback a number of times until timeout is reached, as specified by the ",(0,r.kt)("inlineCode",{parentName:"p"},"timeout")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"interval")," options. The callback must throw an error when the expectation is not met. Returning any value, including a falsy one, will be treated as meeting the expectation, and the callback result will be returned to the caller of ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," function."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"await waitFor(() => expect(mockFunction).toHaveBeenCalledWith()))\n")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," function will be executing ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," callback every ",(0,r.kt)("inlineCode",{parentName:"p"},"interval")," (default: every 50 ms) until ",(0,r.kt)("inlineCode",{parentName:"p"},"timeout")," (default: 1000 ms) is reached. The repeated execution of callback is stopped as soon as it does not throw an error, in such case the value returned by the callback is returned to ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," caller. Otherwise, when it reaches the timeout, the final error thrown by ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," will be re-thrown by ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," to the calling code."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"// \u274c `waitFor` will return immediately because callback does not throw\nawait waitFor(() => false);\n")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," is an async function so you need to ",(0,r.kt)("inlineCode",{parentName:"p"},"await")," the result to pause test execution."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"// \u274c missing `await`: `waitFor` will just return Promise that will be rejected when the timeout is reached\nwaitFor(() => expect(1).toBe(2))\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"You can enforce awaiting ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," by using the ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-utils.md"},"await-async-utils")," rule from ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/testing-library/eslint-plugin-testing-library"},"eslint-plugin-testing-library"),".")),(0,r.kt)("p",null,"Since ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," is likely to run ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," callback multiple times, it is highly recommended for it ",(0,r.kt)("a",{parentName:"p",href:"https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#performing-side-effects-in-waitfor"},"not to perform any side effects")," in ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"await waitFor(() => {\n // \u274c button will be pressed on each waitFor iteration\n fireEvent.press(screen.getByText('press me'))\n expect(mockOnPress).toHaveBeenCalled()\n})\n")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Avoiding side effects in ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," callback can be partially enforced with the ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-side-effects.md"},(0,r.kt)("inlineCode",{parentName:"a"},"no-wait-for-side-effects")," rule"),".")),(0,r.kt)("p",null,"It is also recommended to have a ",(0,r.kt)("a",{parentName:"p",href:"https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#having-multiple-assertions-in-a-single-waitfor-callback"},"single assertion per each ",(0,r.kt)("inlineCode",{parentName:"a"},"waitFor"))," for more consistency and faster failing tests. If you want to make several assertions, then they should be in seperate ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," calls. In many cases you won't actually need to wrap the second assertion in ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," since the first one will do the waiting required for asynchronous change to happen."),(0,r.kt)("h3",{id:"using-a-react-native-version--071-with-jest-fake-timers"},"Using a React Native version < 0.71 with Jest fake timers"),(0,r.kt)("admonition",{type:"caution"},(0,r.kt)("p",{parentName:"admonition"},"When using a version of React Native < 0.71 and modern fake timers (the default for ",(0,r.kt)("inlineCode",{parentName:"p"},"Jest")," >= 27), ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," won't work (it will always timeout even if ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation()")," doesn't throw) unless you use the custom ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library#custom-jest-preset"},"@testing-library/react-native preset"),". ")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," checks whether Jest fake timers are enabled and adapts its behavior in such case. The following snippet is a simplified version of how it behaves when fake timers are enabled:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"let fakeTimeRemaining = timeout;\nlet lastError;\n\nwhile(fakeTimeRemaining > 0) {\n fakeTimeRemaining = fakeTimeRemaining - interval;\n jest.advanceTimersByTime(interval);\n try {\n // resolve\n return expectation();\n } catch (error) {\n lastError = error;\n }\n}\n\n// reject\nthrow lastError\n")),(0,r.kt)("p",null,"In the following example we test that a function is called after 10 seconds using fake timers. Since we're using fake timers, the test won't depend on real time passing and thus be much faster and more reliable. Also we don't have to advance fake timers through Jest fake timers API because ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," already does this for us. "),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"// in component\nsetTimeout(() => {\n someFunction();\n}, 10000)\n\n// in test\njest.useFakeTimers();\n\nawait waitFor(() => {\n expect(someFunction).toHaveBeenCalledWith();\n}, 10000)\n")),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"In order to properly use ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," you need at least React >=16.9.0 (featuring async ",(0,r.kt)("inlineCode",{parentName:"p"},"act"),") or React Native >=0.61 (which comes with React >=16.9.0).")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"If you receive warnings related to ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function consult our ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/understanding-act"},"Undestanding Act")," function document.")),(0,r.kt)("h2",{id:"waitforelementtoberemoved"},(0,r.kt)("inlineCode",{parentName:"h2"},"waitForElementToBeRemoved")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/waitForElementToBeRemoved.test.tsx"},(0,r.kt)("inlineCode",{parentName:"a"},"Example code")))),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function waitForElementToBeRemoved(\n expectation: () => T,\n { timeout: number = 4500, interval: number = 50 }\n): Promise {}\n")),(0,r.kt)("p",null,"Waits for non-deterministic periods of time until queried element is removed or times out. ",(0,r.kt)("inlineCode",{parentName:"p"},"waitForElementToBeRemoved")," periodically calls ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," every ",(0,r.kt)("inlineCode",{parentName:"p"},"interval")," milliseconds to determine whether the element has been removed or not."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import {\n render,\n screen,\n waitForElementToBeRemoved,\n} from '@testing-library/react-native';\n\ntest('waiting for an Banana to be removed', async () => {\n render();\n\n await waitForElementToBeRemoved(() => screen.getByText('Banana ready'));\n});\n")),(0,r.kt)("p",null,"This method expects that the element is initially present in the render tree and then is removed from it. If the element is not present when you call this method it throws an error."),(0,r.kt)("p",null,"You can use any of ",(0,r.kt)("inlineCode",{parentName:"p"},"getBy"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"getAllBy"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"queryBy")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"queryAllBy")," queries for ",(0,r.kt)("inlineCode",{parentName:"p"},"expectation")," parameter."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"In order to properly use ",(0,r.kt)("inlineCode",{parentName:"p"},"waitForElementToBeRemoved")," you need at least React >=16.9.0 (featuring async ",(0,r.kt)("inlineCode",{parentName:"p"},"act"),") or React Native >=0.61 (which comes with React >=16.9.0).")),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"If you receive warnings related to ",(0,r.kt)("inlineCode",{parentName:"p"},"act()")," function consult our ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/understanding-act"},"Undestanding Act")," function document.")),(0,r.kt)("h2",{id:"within-getqueriesforelement"},(0,r.kt)("inlineCode",{parentName:"h2"},"within"),", ",(0,r.kt)("inlineCode",{parentName:"h2"},"getQueriesForElement")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://github.com/callstack/react-native-testing-library/blob/main/src/__tests__/within.test.tsx"},(0,r.kt)("inlineCode",{parentName:"a"},"Example code")))),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"function within(element: ReactTestInstance): Queries {}\n\nfunction getQueriesForElement(element: ReactTestInstance): Queries {}\n")),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"within")," (also available as ",(0,r.kt)("inlineCode",{parentName:"p"},"getQueriesForElement")," alias) performs ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/api-queries"},"queries")," scoped to given element."),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Please note that additional ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," specific operations like ",(0,r.kt)("inlineCode",{parentName:"p"},"update"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"unmount"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"debug"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"toJSON")," are ",(0,r.kt)("em",{parentName:"p"},"not")," included.")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"const detailsScreen = within(screen.getByA11yHint('Details Screen'));\nexpect(detailsScreen.getByText('Some Text')).toBeOnTheScreen();\nexpect(detailsScreen.getByDisplayValue('Some Value')).toBeOnTheScreen();\nexpect(detailsScreen.queryByLabelText('Some Label')).toBeOnTheScreen();\nawait expect(detailsScreen.findByA11yHint('Some Label')).resolves.toBeOnTheScreen();\n")),(0,r.kt)("p",null,"Use cases for scoped queries include:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"queries scoped to a single item inside a FlatList containing many items"),(0,r.kt)("li",{parentName:"ul"},"queries scoped to a single screen in tests involving screen transitions (e.g. with react-navigation)")),(0,r.kt)("h2",{id:"queryby-apis"},(0,r.kt)("inlineCode",{parentName:"h2"},"queryBy*")," APIs"),(0,r.kt)("p",null,"Each of the ",(0,r.kt)("inlineCode",{parentName:"p"},"getBy*")," APIs listed in the render section above have a complimentary ",(0,r.kt)("inlineCode",{parentName:"p"},"queryBy*")," API. The ",(0,r.kt)("inlineCode",{parentName:"p"},"getBy*")," 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 ",(0,r.kt)("inlineCode",{parentName:"p"},"queryBy*")," API instead:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render, screen } from '@testing-library/react-native';\n\nrender();\nconst submitButton = screen.queryByText('submit');\nexpect(submitButton).not.toBeOnTheScreen(); // it doesn't exist\n")),(0,r.kt)("h2",{id:"queryall-apis"},(0,r.kt)("inlineCode",{parentName:"h2"},"queryAll*")," APIs"),(0,r.kt)("p",null,"Each of the query APIs have a corresponding ",(0,r.kt)("inlineCode",{parentName:"p"},"queryAll*")," version that always returns an array of matching nodes. ",(0,r.kt)("inlineCode",{parentName:"p"},"getAll*")," is the same but throws when the array has a length of 0."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-jsx"},"import { render } from '@testing-library/react-native';\n\nrender();\nconst submitButtons = screen.queryAllByText('submit');\nexpect(submitButtons).toHaveLength(3); // expect 3 elements\n")),(0,r.kt)("h2",{id:"act"},(0,r.kt)("inlineCode",{parentName:"h2"},"act")),(0,r.kt)("p",null,"Useful function to help testing components that use hooks API. By default any ",(0,r.kt)("inlineCode",{parentName:"p"},"render"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"update"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"fireEvent"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor")," calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/facebook/react/blob/main/packages/react-test-renderer/src/ReactTestRenderer.js#L567%5D"},(0,r.kt)("inlineCode",{parentName:"a"},"react-test-renderer")),"."),(0,r.kt)("p",null,"Consult our ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/understanding-act"},"Undestanding Act function")," document for more understanding of its intricacies."),(0,r.kt)("h2",{id:"renderhook"},(0,r.kt)("inlineCode",{parentName:"h2"},"renderHook")),(0,r.kt)("p",null,"Defined as:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function renderHook(\n callback: (props?: Props) => Result,\n options?: RenderHookOptions\n): RenderHookResult;\n")),(0,r.kt)("p",null,"Renders a test component that will call the provided ",(0,r.kt)("inlineCode",{parentName:"p"},"callback"),", including any hooks it calls, every time it renders. Returns ",(0,r.kt)("a",{parentName:"p",href:"#renderhookresult-object"},(0,r.kt)("inlineCode",{parentName:"a"},"RenderHookResult"))," object, which you can interact with."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"import { renderHook } from '@testing-library/react-native';\nimport { useCount } from '../useCount';\n\nit('should increment count', () => {\n const { result } = renderHook(() => useCount());\n\n expect(result.current.count).toBe(0);\n act(() => {\n // Note that you should wrap the calls to functions your hook returns with `act` if they trigger an update of your hook's state to ensure pending useEffects are run before your next assertion.\n result.current.increment();\n });\n expect(result.current.count).toBe(1);\n});\n")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"// useCount.js\nexport const useCount = () => {\n const [count, setCount] = useState(0);\n const increment = () => setCount((previousCount) => previousCount + 1);\n\n return { count, increment };\n};\n")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," function accepts the following arguments:"),(0,r.kt)("h3",{id:"callback"},(0,r.kt)("inlineCode",{parentName:"h3"},"callback")),(0,r.kt)("p",null,"The function that is called each ",(0,r.kt)("inlineCode",{parentName:"p"},"render")," of the test component. This function should call one or more hooks for testing."),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"props")," passed into the callback will be the ",(0,r.kt)("inlineCode",{parentName:"p"},"initialProps")," provided in the ",(0,r.kt)("inlineCode",{parentName:"p"},"options")," to ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook"),", unless new props are provided by a subsequent ",(0,r.kt)("inlineCode",{parentName:"p"},"rerender")," call."),(0,r.kt)("h3",{id:"options-optional"},(0,r.kt)("inlineCode",{parentName:"h3"},"options")," (Optional)"),(0,r.kt)("p",null,"A ",(0,r.kt)("inlineCode",{parentName:"p"},"RenderHookOptions")," object to modify the execution of the ",(0,r.kt)("inlineCode",{parentName:"p"},"callback")," function, containing the following properties:"),(0,r.kt)("h4",{id:"initialprops"},(0,r.kt)("inlineCode",{parentName:"h4"},"initialProps")),(0,r.kt)("p",null,"The initial values to pass as ",(0,r.kt)("inlineCode",{parentName:"p"},"props")," to the ",(0,r.kt)("inlineCode",{parentName:"p"},"callback")," function of ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"Props")," type is determined by the type passed to or inferred by the ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," call."),(0,r.kt)("h4",{id:"wrapper"},(0,r.kt)("inlineCode",{parentName:"h4"},"wrapper")),(0,r.kt)("p",null,"A React component to wrap the test component in when rendering. This is usually used to add context providers from ",(0,r.kt)("inlineCode",{parentName:"p"},"React.createContext")," for the hook to access with ",(0,r.kt)("inlineCode",{parentName:"p"},"useContext"),"."),(0,r.kt)("h3",{id:"renderhookresult-object"},(0,r.kt)("inlineCode",{parentName:"h3"},"RenderHookResult")," object"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"interface RenderHookResult {\n result: { current: Result };\n rerender: (props: Props) => void;\n unmount: () => void;\n}\n")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," function returns an object that has the following properties:"),(0,r.kt)("h4",{id:"result"},(0,r.kt)("inlineCode",{parentName:"h4"},"result")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"current")," value of the ",(0,r.kt)("inlineCode",{parentName:"p"},"result")," will reflect the latest of whatever is returned from the ",(0,r.kt)("inlineCode",{parentName:"p"},"callback")," passed to ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"Result")," type is determined by the type passed to or inferred by the ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," call."),(0,r.kt)("h4",{id:"rerender"},(0,r.kt)("inlineCode",{parentName:"h4"},"rerender")),(0,r.kt)("p",null,"A function to rerender the test component, causing any hooks to be recalculated. If ",(0,r.kt)("inlineCode",{parentName:"p"},"newProps")," are passed, they will replace the ",(0,r.kt)("inlineCode",{parentName:"p"},"callback")," function's ",(0,r.kt)("inlineCode",{parentName:"p"},"initialProps")," for subsequent rerenders. The ",(0,r.kt)("inlineCode",{parentName:"p"},"Props")," type is determined by the type passed to or inferred by the ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," call."),(0,r.kt)("h4",{id:"unmount-1"},(0,r.kt)("inlineCode",{parentName:"h4"},"unmount")),(0,r.kt)("p",null,"A function to unmount the test component. This is commonly used to trigger cleanup effects for ",(0,r.kt)("inlineCode",{parentName:"p"},"useEffect")," hooks."),(0,r.kt)("h3",{id:"examples"},"Examples"),(0,r.kt)("p",null,"Here we present some extra examples of using ",(0,r.kt)("inlineCode",{parentName:"p"},"renderHook")," API."),(0,r.kt)("h4",{id:"with-initialprops"},"With ",(0,r.kt)("inlineCode",{parentName:"h4"},"initialProps")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"const useCount = (initialCount: number) => {\n const [count, setCount] = useState(initialCount);\n const increment = () => setCount((previousCount) => previousCount + 1);\n\n useEffect(() => {\n setCount(initialCount);\n }, [initialCount]);\n\n return { count, increment };\n};\n\nit('should increment count', () => {\n const { result, rerender } = renderHook(\n (initialCount: number) => useCount(initialCount),\n { initialProps: 1 }\n );\n\n expect(result.current.count).toBe(1);\n\n act(() => {\n result.current.increment();\n });\n\n expect(result.current.count).toBe(2);\n rerender(5);\n expect(result.current.count).toBe(5);\n});\n")),(0,r.kt)("h4",{id:"with-wrapper"},"With ",(0,r.kt)("inlineCode",{parentName:"h4"},"wrapper")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"it('should use context value', () => {\n function Wrapper({ children }: { children: ReactNode }) {\n return {children};\n }\n\n const { result } = renderHook(() => useHook(), { wrapper: Wrapper });\n // ...\n});\n")),(0,r.kt)("h2",{id:"configuration"},"Configuration"),(0,r.kt)("h3",{id:"configure"},(0,r.kt)("inlineCode",{parentName:"h3"},"configure")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"type Config = {\n asyncUtilTimeout: number;\n defaultHidden: boolean;\n defaultDebugOptions: Partial;\n};\n\nfunction configure(options: Partial) {}\n")),(0,r.kt)("h4",{id:"asyncutiltimeout-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"asyncUtilTimeout")," option"),(0,r.kt)("p",null,"Default timeout, in ms, for async helper functions (",(0,r.kt)("inlineCode",{parentName:"p"},"waitFor"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"waitForElementToBeRemoved"),") and ",(0,r.kt)("inlineCode",{parentName:"p"},"findBy*")," queries. Defaults to 1000 ms."),(0,r.kt)("h4",{id:"defaultincludehiddenelements-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"defaultIncludeHiddenElements")," option"),(0,r.kt)("p",null,"Default value for ",(0,r.kt)("a",{parentName:"p",href:"/react-native-testing-library/docs/api-queries#includehiddenelements-option"},"includeHiddenElements")," query option for all queries. The default value is set to ",(0,r.kt)("inlineCode",{parentName:"p"},"false"),", so all queries will not match ",(0,r.kt)("a",{parentName:"p",href:"#ishiddenfromaccessibility"},"elements hidden from accessibility"),". This is because the users of the app would not be able to see such elements."),(0,r.kt)("p",null,"This option is also available as ",(0,r.kt)("inlineCode",{parentName:"p"},"defaultHidden")," alias for compatibility with ",(0,r.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/dom-testing-library/api-configuration/#defaulthidden"},"React Testing Library"),"."),(0,r.kt)("h4",{id:"defaultdebugoptions-option"},(0,r.kt)("inlineCode",{parentName:"h4"},"defaultDebugOptions")," option"),(0,r.kt)("p",null,"Default ",(0,r.kt)("a",{parentName:"p",href:"#debug"},"debug options")," to be used when calling ",(0,r.kt)("inlineCode",{parentName:"p"},"debug()"),". These default options will be overridden by the ones you specify directly when calling ",(0,r.kt)("inlineCode",{parentName:"p"},"debug()"),"."),(0,r.kt)("h3",{id:"resettodefaults"},(0,r.kt)("inlineCode",{parentName:"h3"},"resetToDefaults()")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function resetToDefaults() {}\n")),(0,r.kt)("h3",{id:"environment-variables"},"Environment variables"),(0,r.kt)("h4",{id:"rntl_skip_auto_cleanup"},(0,r.kt)("inlineCode",{parentName:"h4"},"RNTL_SKIP_AUTO_CLEANUP")),(0,r.kt)("p",null,"Set to ",(0,r.kt)("inlineCode",{parentName:"p"},"true")," to disable automatic ",(0,r.kt)("inlineCode",{parentName:"p"},"cleanup()")," after each test. It works the same as importing ",(0,r.kt)("inlineCode",{parentName:"p"},"react-native-testing-library/dont-cleanup-after-each")," or using ",(0,r.kt)("inlineCode",{parentName:"p"},"react-native-testing-library/pure"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-shell"},"$ RNTL_SKIP_AUTO_CLEANUP=true jest\n")),(0,r.kt)("h4",{id:"rntl_skip_auto_detect_fake_timers"},(0,r.kt)("inlineCode",{parentName:"h4"},"RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS")),(0,r.kt)("p",null,"Set to ",(0,r.kt)("inlineCode",{parentName:"p"},"true")," to disable auto-detection of fake timers. This might be useful in rare cases when you want to use non-Jest fake timers. See ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/callstack/react-native-testing-library/issues/886"},"issue #886")," for more details."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-shell"},"$ RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS=true jest\n")),(0,r.kt)("h2",{id:"accessibility"},"Accessibility"),(0,r.kt)("h3",{id:"ishiddenfromaccessibility"},(0,r.kt)("inlineCode",{parentName:"h3"},"isHiddenFromAccessibility")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"function isHiddenFromAccessibility(\n element: ReactTestInstance | null\n): boolean {}\n")),(0,r.kt)("p",null,"Also available as ",(0,r.kt)("inlineCode",{parentName:"p"},"isInaccessible()")," alias for React Testing Library compatibility."),(0,r.kt)("p",null,"Checks if given element is hidden from assistive technology, e.g. screen readers."),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Like ",(0,r.kt)("a",{parentName:"p",href:"https://testing-library.com/docs/dom-testing-library/api-accessibility/#isinaccessible"},(0,r.kt)("inlineCode",{parentName:"a"},"isInaccessible"))," function from DOM Testing Library this function considers both accessibility elements and presentational elements (regular ",(0,r.kt)("inlineCode",{parentName:"p"},"View"),"s) to be accessible, unless they are hidden in terms of host platform."),(0,r.kt)("p",{parentName:"admonition"},"This covers only part of ",(0,r.kt)("a",{parentName:"p",href:"https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion"},"ARIA notion of Accessiblity Tree"),", as ARIA excludes both hidden and presentational elements from the Accessibility Tree.")),(0,r.kt)("p",null,"For the scope of this function, element is inaccessible when it, or any of its ancestors, meets any of the following conditions:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"it has ",(0,r.kt)("inlineCode",{parentName:"li"},"display: none")," style"),(0,r.kt)("li",{parentName:"ul"},"it has ",(0,r.kt)("a",{parentName:"li",href:"https://reactnative.dev/docs/accessibility#aria-hidden"},(0,r.kt)("inlineCode",{parentName:"a"},"aria-hidden"))," prop set to ",(0,r.kt)("inlineCode",{parentName:"li"},"true")),(0,r.kt)("li",{parentName:"ul"},"it has ",(0,r.kt)("a",{parentName:"li",href:"https://reactnative.dev/docs/accessibility#accessibilityelementshidden-ios"},(0,r.kt)("inlineCode",{parentName:"a"},"accessibilityElementsHidden"))," prop set to ",(0,r.kt)("inlineCode",{parentName:"li"},"true")),(0,r.kt)("li",{parentName:"ul"},"it has ",(0,r.kt)("a",{parentName:"li",href:"https://reactnative.dev/docs/accessibility#importantforaccessibility-android"},(0,r.kt)("inlineCode",{parentName:"a"},"importantForAccessibility"))," prop set to ",(0,r.kt)("inlineCode",{parentName:"li"},"no-hide-descendants")),(0,r.kt)("li",{parentName:"ul"},"it has sibling host element with ",(0,r.kt)("a",{parentName:"li",href:"https://reactnative.dev/docs/accessibility#accessibilityviewismodal-ios"},(0,r.kt)("inlineCode",{parentName:"a"},"accessibilityViewIsModal"))," prop set to ",(0,r.kt)("inlineCode",{parentName:"li"},"true"))),(0,r.kt)("p",null,"Specifying ",(0,r.kt)("inlineCode",{parentName:"p"},"accessible={false}"),", ",(0,r.kt)("inlineCode",{parentName:"p"},'accessiblityRole="none"'),", or ",(0,r.kt)("inlineCode",{parentName:"p"},'importantForAccessibility="no"')," props does not cause the element to become inaccessible."))}k.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.9c5108c1.js b/assets/js/runtime~main.e63caa38.js similarity index 96% rename from assets/js/runtime~main.9c5108c1.js rename to assets/js/runtime~main.e63caa38.js index dc40abe8..f8342f71 100644 --- a/assets/js/runtime~main.9c5108c1.js +++ b/assets/js/runtime~main.e63caa38.js @@ -1 +1 @@ -!function(){"use strict";var e,t,n,r,o,f={},a={};function c(e){var t=a[e];if(void 0!==t)return t.exports;var n=a[e]={id:e,loaded:!1,exports:{}};return f[e].call(n.exports,n,n.exports,c),n.loaded=!0,n.exports}c.m=f,c.c=a,e=[],c.O=function(t,n,r,o){if(!n){var f=1/0;for(d=0;d=o)&&Object.keys(c.O).every((function(e){return c.O[e](n[i])}))?n.splice(i--,1):(a=!1,o0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[n,r,o]},c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},c.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var o=Object.create(null);c.r(o);var f={};t=t||[null,n({}),n([]),n(n)];for(var a=2&r&&e;"object"==typeof a&&!~t.indexOf(a);a=n(a))Object.getOwnPropertyNames(a).forEach((function(t){f[t]=function(){return e[t]}}));return f.default=function(){return e},c.d(o,f),o},c.d=function(e,t){for(var n in t)c.o(t,n)&&!c.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},c.f={},c.e=function(e){return Promise.all(Object.keys(c.f).reduce((function(t,n){return c.f[n](e,t),t}),[]))},c.u=function(e){return"assets/js/"+({53:"935f2afb",63:"8a6052eb",94:"1bdd165f",126:"3c6c3bb0",154:"f2d2b077",195:"c4f5d8e4",278:"01df2f6c",288:"ad895e75",298:"c8229a80",350:"b5dde711",381:"501445cb",430:"7ad239b9",434:"0f1b22a8",456:"14f61f32",471:"d24847ef",494:"698581a0",514:"1be78505",625:"77ba9e12",671:"1c6b47cb",690:"f69ec364",725:"add06ab1",918:"17896441",920:"1a4e3797",940:"aa9e97dd",951:"6bbd6f71"}[e]||e)+"."+{53:"dd90fe95",63:"5fdfedd7",94:"f64ea97d",126:"b89ef05e",154:"a177a475",195:"944835d2",278:"8aa56a66",288:"fc071dca",298:"dfa70785",350:"0ae4c674",381:"d415514b",430:"092041b3",434:"355b7653",456:"c575ac59",471:"6fb8f9f6",494:"8c056572",514:"693a9378",625:"1113f68f",671:"5cc892bb",690:"b7543c30",725:"b4c2a60b",780:"54ff0f63",894:"2c392f8f",918:"45f42e8a",920:"cc685c94",940:"06009e66",945:"51b2aac9",951:"1e54c35d",972:"facdfc3b"}[e]+".js"},c.miniCssF=function(e){},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="react-native-testing-library-website:",c.l=function(e,t,n,f){if(r[e])r[e].push(t);else{var a,i;if(void 0!==n)for(var u=document.getElementsByTagName("script"),d=0;d=o)&&Object.keys(c.O).every((function(e){return c.O[e](n[i])}))?n.splice(i--,1):(a=!1,o0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[n,r,o]},c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},c.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var o=Object.create(null);c.r(o);var f={};t=t||[null,n({}),n([]),n(n)];for(var a=2&r&&e;"object"==typeof a&&!~t.indexOf(a);a=n(a))Object.getOwnPropertyNames(a).forEach((function(t){f[t]=function(){return e[t]}}));return f.default=function(){return e},c.d(o,f),o},c.d=function(e,t){for(var n in t)c.o(t,n)&&!c.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},c.f={},c.e=function(e){return Promise.all(Object.keys(c.f).reduce((function(t,n){return c.f[n](e,t),t}),[]))},c.u=function(e){return"assets/js/"+({53:"935f2afb",63:"8a6052eb",94:"1bdd165f",126:"3c6c3bb0",154:"f2d2b077",195:"c4f5d8e4",278:"01df2f6c",288:"ad895e75",298:"c8229a80",350:"b5dde711",381:"501445cb",430:"7ad239b9",434:"0f1b22a8",456:"14f61f32",471:"d24847ef",494:"698581a0",514:"1be78505",625:"77ba9e12",671:"1c6b47cb",690:"f69ec364",725:"add06ab1",918:"17896441",920:"1a4e3797",940:"aa9e97dd",951:"6bbd6f71"}[e]||e)+"."+{53:"dd90fe95",63:"5fdfedd7",94:"f64ea97d",126:"b89ef05e",154:"a177a475",195:"944835d2",278:"8aa56a66",288:"fc071dca",298:"c8ecfbaa",350:"0ae4c674",381:"d415514b",430:"092041b3",434:"355b7653",456:"c575ac59",471:"6fb8f9f6",494:"8c056572",514:"693a9378",625:"1113f68f",671:"5cc892bb",690:"b7543c30",725:"b4c2a60b",780:"54ff0f63",894:"2c392f8f",918:"45f42e8a",920:"cc685c94",940:"06009e66",945:"51b2aac9",951:"1e54c35d",972:"facdfc3b"}[e]+".js"},c.miniCssF=function(e){},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="react-native-testing-library-website:",c.l=function(e,t,n,f){if(r[e])r[e].push(t);else{var a,i;if(void 0!==n)for(var u=document.getElementsByTagName("script"),d=0;d Queries | React Native Testing Library - + @@ -15,7 +15,7 @@ getByAccessibilityHint, getAllByAccessibilityHint, queryByAccessibilityHint, que getByHintText, getAllByHintText, queryByHintText, queryAllByHintText, findByHintText, findAllByHintText

getByHintText(
hint: TextMatch,
options?: {
exact?: boolean;
normalizer?: (text: string) => string;
includeHiddenElements?: boolean;
}
): ReactTestInstance;

Returns a ReactTestInstance with matching accessibilityHint prop.

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

render(<MyComponent />);
const element = screen.getByHintText('Plays a song');

ByA11yState, ByAccessibilityState (deprecated)​

caution

This query has been marked deprecated, as is typically too general to give meaningful results. Therefore, it's better to use one of following options:

  • *ByRole query with relevant state options: disabled, selected, checked, expanded and busy
  • toHaveAccessibilityState() Jest matcher to check the state of element found using some other query

getByA11yState, getAllByA11yState, queryByA11yState, queryAllByA11yState, findByA11yState, findAllByA11yState getByAccessibilityState, getAllByAccessibilityState, queryByAccessibilityState, queryAllByAccessibilityState, findByAccessibilityState, findAllByAccessibilityState

getByA11yState(
state: {
disabled?: boolean,
selected?: boolean,
checked?: boolean | 'mixed',
expanded?: boolean,
busy?: boolean,
},
options?: {
includeHiddenElements?: boolean;
}
): ReactTestInstance;

Returns a ReactTestInstance with matching accessibilityState prop.

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

render(<Component />);
const element = screen.getByA11yState({ disabled: true });
note

Default state for: disabled, selected, and busy keys​

Passing false matcher value will match both elements with explicit false state value and without explicit state value.

For instance, getByA11yState({ disabled: false }) will match elements with following props:

  • accessibilityState={{ disabled: false, ... }}
  • no disabled key under accessibilityState prop, e.g. accessibilityState={{}}
  • no accessibilityState prop at all

Default state for: checked and expanded keys​

Passing false matcher value will only match elements with explicit false state value.

For instance, getByA11yState({ checked: false }) will only match elements with:

  • accessibilityState={{ checked: false, ... }}

but will not match elements with following props:

  • no checked key under accessibilityState prop, e.g. accessibilityState={{}}
  • no accessibilityState prop at all

The difference in handling default values is made to reflect observed accessibility behaviour on iOS and Android platforms.

ByA11yValue, ByAccessibilityValue (deprecated)​

caution

This query has been marked deprecated, as is typically too general to give meaningful results. Therefore, it's better to use one of following options:

getByA11yValue, getAllByA11yValue, queryByA11yValue, queryAllByA11yValue, findByA11yValue, findAllByA11yValue getByAccessibilityValue, getAllByAccessibilityValue, queryByAccessibilityValue, queryAllByAccessibilityValue, findByAccessibilityValue, findAllByAccessibilityValue

getByA11yValue(
value: {
min?: number;
max?: number;
now?: number;
text?: TextMatch;
},
options?: {
includeHiddenElements?: boolean;
}
): ReactTestInstance;

Returns a host element with matching accessibilityValue prop entries. Only entires provided to the query will be used to match elements. Element might have additional accessibility value entries and still be matched.

When querying by text entry a string or regex might be used.

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

render(
<View accessibilityValue={{ min: 0, max: 100, now: 25, text: '25%' }} />
);
const element = screen.getByA11yValue({ now: 25 });
const element2 = screen.getByA11yValue({ text: /25/ });

Common options​

includeHiddenElements option​

All queries have the includeHiddenElements option which affects whether elements hidden from accessibility are matched by the query. By default queries will not match hidden elements, because the users of the app would not be able to see such elements.

You can configure the default value with the configure function.

This option is also available as hidden alias for compatibility with React Testing Library.

Examples

render(<Text style={{ display: 'none' }}>Hidden from accessibility</Text>);

// Exclude hidden elements
expect(
screen.queryByText('Hidden from accessibility', { includeHiddenElements: false })
).not.toBeOnTheScreen();

// Include hidden elements
expect(screen.getByText('Hidden from accessibility')).toBeOnTheScreen();
expect(
screen.getByText('Hidden from accessibility', { includeHiddenElements: true })
).toBeOnTheScreen();

TextMatch​

type TextMatch = string | RegExp;

Most of the query APIs take a TextMatch as an argument, which means the argument can be either a string or regex.

Examples​

Given the following render:

render(<Text>Hello World</Text>);

Will find a match:

// Matching a string:
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:
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

// substring does not match
screen.getByText('llo Worl');
// full string does not match
screen.getByText('Goodbye World');

// case-sensitive regex with different case
screen.getByText(/hello world/);

Precision​

type TextMatchOptions = {
exact?: boolean;
normalizer?: (text: string) => string;
};

Queries that take a TextMatch also accept an object as the second argument that can contain options that affect the precision of string matching:

  • exact: Defaults to true; matches full strings, case-sensitive. When false, matches substrings and is not case-sensitive.
    • exact has no effect on regex argument.
    • In most cases using a regex instead of a string gives you more control over fuzzy matching and should be preferred over { exact: false }.
  • normalizer: An optional function which overrides normalization behavior. See Normalization.

exact option defaults to true but if you want to search for a text slice or make text matching case-insensitive you can override it. That being said we advise you to use regex in more complex scenarios.

Normalization​

Before running any matching logic against text, it is automatically normalized. By default, normalization consists of trimming whitespace from the start and end of text, and collapsing multiple adjacent whitespace characters into a single space.

If you want to prevent that normalization, or provide alternative normalization (e.g. to remove Unicode control characters), you can provide a normalizer function in the options object. This function will be given a string and is expected to return a normalized version of that string.

info

Specifying a value for normalizer replaces the built-in normalization, but you can call getDefaultNormalizer to obtain a built-in normalizer, either to adjust that normalization or to call it from your own normalizer.

getDefaultNormalizer take options object which allows the selection of behaviour:

  • trim: Defaults to true. Trims leading and trailing whitespace.
  • collapseWhitespace: Defaults to true. Collapses inner whitespace (newlines, tabs repeated spaces) into a single space.

Normalization Examples​

To perform a match against text without trimming:

screen.getByText(node, 'text', {
normalizer: getDefaultNormalizer({ trim: false }),
});

To override normalization to remove some Unicode characters whilst keeping some (but not all) of the built-in normalization behavior:

screen.getByText(node, 'text', {
normalizer: (str) =>
getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''),
});

Unit testing helpers​

Use sparingly and responsibly, escape hatches here

render from @testing-library/react-native exposes additional queries that should not be used in component integration testing, but some users (like component library creators) interested in unit testing some components may find helpful.

Queries helpful in unit testing

The interface is the same as for other queries, but we won't provide full names so that they're harder to find by search engines.

UNSAFE_ByType​

UNSAFE_getByType, UNSAFE_getAllByType, UNSAFE_queryByType, UNSAFE_queryAllByType

Returns a ReactTestInstance with matching a React component type.

caution

This query has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly.

UNSAFE_ByProps​

UNSAFE_getByProps, UNSAFE_getAllByProps, UNSAFE_queryByProps, UNSAFE_queryAllByProps

Returns a ReactTestInstance with matching props object.

caution

This query has been marked unsafe, since it requires knowledge about implementation details of the component. Use responsibly.

- + \ No newline at end of file diff --git a/docs/api.html b/docs/api.html index aa1c9bc3..6133c668 100644 --- a/docs/api.html +++ b/docs/api.html @@ -4,14 +4,14 @@ API | React Native Testing Library - +

API

Table of contents:​

This page gathers public API of React Native Testing Library along with usage examples.

render​

Defined as:

function render(
component: React.Element<any>,
options?: RenderOptions
): RenderResult {}

Deeply renders given React element and returns helpers to query the output components structure.

import { render } from '@testing-library/react-native';
import { QuestionsBoard } from '../QuestionsBoard';

test('should verify two questions', () => {
render(<QuestionsBoard {...props} />);
const allQuestions = screen.queryAllByRole('header');

expect(allQuestions).toHaveLength(2);
});

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.

The render method returns a RenderResult object having properties described below.

info

Latest render result is kept in 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 from Kent C. Dodds for more details.

render options​

The behavior of render method can be customized by passing various options as a second argument of RenderOptions type:

wrapper option​

wrapper?: React.ComponentType<any>,

This options allows you to wrap tested component, passed as the first option to the render() function, in additional wrapper component. This is most useful for creating reusable custom render functions for common React Context providers.

createNodeMock option​

createNodeMock?: (element: React.Element<any>) => any,

This options allows you to pass createNodeMock option to ReactTestRenderer.create() method in order to allow for custom mock refs. You can learn more about this options from React Test Renderer documentation.

unstable_validateStringsRenderedWithinText option​

unstable_validateStringsRenderedWithinText?: boolean;
note

This options is experimental, in some cases it might not work as intended, and its behavior might change without observing SemVer requirements for breaking changes.

This experimental option allows you to replicate React Native behavior of throwing Invariant Violation: Text strings must be rendered within a <Text> component error when you try to render string value under components different than <Text>, e.g. under <View>.

This check is not enforced by React Test Renderer and hence by default React Native Testing Library also does not check this. That might result in runtime errors when running your code on a device, while the code works without errors in tests.

...queries​

The most important feature of render is providing a set of helpful queries that allow you to find certain elements in the view hierarchy.

See Queries for a complete list.

Example​

import { render } from '@testing-library/react-native';

const { getByText, queryByA11yState } = render(<Component />);

update​

Also available under rerender alias

update(element: React.Element<any>): void
rerender(element: React.Element<any>): void

Re-render the in-memory tree with a new root element. This simulates a React update at the root. If the new element has the same type and key as the previous element, the tree will be updated; otherwise, it will re-mount a new tree. This is useful when testing for componentDidUpdate behavior, by passing updated props to the component.

Example code

unmount​

unmount(): void

Unmount the in-memory tree, triggering the appropriate lifecycle events.

note

Usually you should not need to call unmount as it is done automatically if your test runner supports afterEach hook (like Jest, mocha, Jasmine).

debug​

interface DebugOptions {
message?: string;
mapProps?: MapPropsFunction;
}

debug(options?: DebugOptions | string): void

Pretty prints deeply rendered component passed to render.

message option​

You can provide a message that will be printed on top.

render(<Component />);
screen.debug({ message: 'optional message' });

logs optional message and colored JSX:

optional message

<View
onPress={[Function bound fn]}
>
<Text>Press me</Text>
</View>

mapProps option​

You can use the mapProps option to transform the props that will be printed :

render(<View style={{ backgroundColor: 'red' }} />);
debug({ mapProps: ({ style, ...props }) => ({ props }) });

This will log the rendered JSX without the style props.

The children prop cannot be filtered out so the following will print all rendered components with all props but children filtered out.

debug({ mapProps: (props) => ({}) });

This option can be used to target specific props when debugging a query (for instance keeping only children prop when debugging a getByText query).

You can also transform prop values so that they are more readable (e.g. flatten styles).

import { StyleSheet } from 'react-native';

debug({ mapProps : {({ style, ...props })} => ({ style : StyleSheet.flatten(style), ...props }) });

Or remove props that have little value when debugging tests, e.g. path prop for svgs

debug({ mapProps: ({ path, ...props }) => ({ ...props }) });

debug.shallow​

Pretty prints shallowly rendered component passed to render with optional message on top.

toJSON​

toJSON(): ReactTestRendererJSON | null

Get the rendered component JSON representation, e.g. for snapshot testing.

root​

root: ReactTestInstance;

Returns the rendered root host element.

This API is primarily useful in component tests, as it allows you to access root host view without using *ByTestId queries or similar methods.

UNSAFE_root​

UNSAFE_root: ReactTestInstance;

Returns the rendered composite root element.

caution

This API typically will return a composite view which goes against recommended testing practices. This API is primarily available for legacy test suites that rely on such testing.

note

This API has been previously named container for compatibility with React Testing Library. However, despite the same name, the actual behavior has been signficantly different, hence the name change to UNSAFE_root.

screen​

let screen: RenderResult;

Hold the value of latest render call for easier access to query and other functions returned by render.

Its value is automatically cleared after each test by calling 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.

This can also be used to build test utils that would normally require to be in render scope, either in a test file or globally for your project. For instance:

// Prints the rendered components omitting all props except children.
const debugText = () => screen.debug({ mapProps: (props) => ({}) });

cleanup​

const cleanup: () => void;

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.

For example, if you're using the jest testing framework, then you would need to use the afterEach hook like so:

import { cleanup, render } from '@testing-library/react-native/pure';
import { View } from 'react-native';

afterEach(cleanup);

it('renders a view', () => {
render(<View />);
// ...
});

The afterEach(cleanup) call also works in describe blocks:

describe('when logged in', () => {
afterEach(cleanup);

it('renders the user', () => {
render(<SiteHeader />);
// ...
});
});

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).

fireEvent​

function fireEvent(
element: ReactTestInstance,
eventName: string,
...data: Array<any>
): void {}
note

For common events like press or type it's recommended to use User Event API as it offers -more realistic event simulation by emitting a sequence of events with proper event objects that mimic React Native runtime behavior.

Use Fire Event for cases not supported by User Event and for triggering event handlers on composite components.

fireEvent API allows you to trigger all kind of event handlers on both host and composite components. It will try to invoke a single event handler traversing the component tree bottom-up from passed element and trying to find enabled event handler named onXxx when xxx is the name of the event passed.

Unlike User Event, this API does not automatically pass event object to event handler, this is responsibility of the user to construct such object.

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

test('fire changeText event', () => {
const onEventMock = jest.fn();
render(
// MyComponent renders TextInput which has a placeholder 'Enter details'
// and with `onChangeText` bound to handleChangeText
<MyComponent handleChangeText={onEventMock} />
);

fireEvent(screen.getByPlaceholderText('change'), 'onChangeText', 'ab');
expect(onEventMock).toHaveBeenCalledWith('ab');
});
note

Please note that from version 7.0 fireEvent performs checks that should prevent events firing on disabled elements.

An example using fireEvent with native events that aren't already aliased by the fireEvent api.

import { TextInput, View } from 'react-native';
import { fireEvent, render } from '@testing-library/react-native';

const onBlurMock = jest.fn();

render(
<View>
<TextInput placeholder="my placeholder" onBlur={onBlurMock} />
</View>
);

// you can omit the `on` prefix
fireEvent(screen.getByPlaceholderText('my placeholder'), 'blur');

fireEvent[eventName]​

fireEvent[eventName](element: ReactTestInstance, ...data: Array<any>): void

Convenience methods for common events like: press, changeText, scroll.

fireEvent.press​

fireEvent.press: (element: ReactTestInstance, ...data: Array<any>) => void
note

It is recommended to use the User Event press() helper instead as it offers more realistic simulation of press interaction, including pressable support.

Invokes press event handler on the element or parent element in the tree.

import { View, Text, TouchableOpacity } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onPressMock = jest.fn();
const eventData = {
nativeEvent: {
pageX: 20,
pageY: 30,
},
};

render(
<View>
<TouchableOpacity onPress={onPressMock}>
<Text>Press me</Text>
</TouchableOpacity>
</View>
);

fireEvent.press(screen.getByText('Press me'), eventData);
expect(onPressMock).toHaveBeenCalledWith(eventData);

fireEvent.changeText​

fireEvent.changeText: (element: ReactTestInstance, ...data: Array<any>) => void
note

It is recommended to use the User Event type() helper instead as it offers more realistic simulation of text change interaction, including key-by-key typing, element focus, and other editing events.

Invokes changeText event handler on the element or parent element in the tree.

import { View, TextInput } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onChangeTextMock = jest.fn();
const CHANGE_TEXT = 'content';

render(
<View>
<TextInput placeholder="Enter data" onChangeText={onChangeTextMock} />
</View>
);

fireEvent.changeText(screen.getByPlaceholderText('Enter data'), CHANGE_TEXT);

fireEvent.scroll​

fireEvent.scroll: (element: ReactTestInstance, ...data: Array<any>) => void

Invokes scroll event handler on the element or parent element in the tree.

On a ScrollView​

import { ScrollView, Text } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onScrollMock = jest.fn();
const eventData = {
nativeEvent: {
contentOffset: {
y: 200,
},
},
};

render(
<ScrollView onScroll={onScrollMock}>
<Text>XD</Text>
</ScrollView>
);

fireEvent.scroll(screen.getByText('scroll-view'), eventData);

On a FlatList​

import { FlatList, View } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onEndReached = jest.fn();
render(
<FlatList
data={Array.from({ length: 10 }, (_, key) => ({ key: `${key}` }))}
renderItem={() => <View style={{ height: 500, width: 100 }} />}
onEndReached={onEndReached}
onEndReachedThreshold={0.2}
testID="flat-list"
/>
);
const eventData = {
nativeEvent: {
contentOffset: {
y: 500,
},
contentSize: {
// Dimensions of the scrollable content
height: 500,
width: 100,
},
layoutMeasurement: {
// Dimensions of the device
height: 100,
width: 100,
},
},
};

fireEvent.scroll(screen.getByTestId('flat-list'), eventData);
expect(onEndReached).toHaveBeenCalled();
note

If you're noticing that components are not being found on a list, even after mocking a scroll event, try changing the initialNumToRender that you have set. If you aren't comfortable changing the code to accept this prop from the unit test, try using an e2e test that might better suit what use case you're attempting to replicate.

waitFor​

Defined as:

function waitFor<T>(
expectation: () => T,
{ timeout: number = 1000, interval: number = 50 }
): Promise<T> {}

Waits for a period of time for the expectation callback to pass. waitFor may run the callback a number of times until timeout is reached, as specified by the timeout and interval options. The callback must throw an error when the expectation is not met. Returning any value, including a falsy one, will be treated as meeting the expectation, and the callback result will be returned to the caller of waitFor function.

await waitFor(() => expect(mockFunction).toHaveBeenCalledWith()))

waitFor function will be executing expectation callback every interval (default: every 50 ms) until timeout (default: 1000 ms) is reached. The repeated execution of callback is stopped as soon as it does not throw an error, in such case the value returned by the callback is returned to waitFor caller. Otherwise, when it reaches the timeout, the final error thrown by expectation will be re-thrown by waitFor to the calling code.

// ❌ `waitFor` will return immediately because callback does not throw
await waitFor(() => false);

waitFor is an async function so you need to await the result to pause test execution.

// ❌ missing `await`: `waitFor` will just return Promise that will be rejected when the timeout is reached
waitFor(() => expect(1).toBe(2))
note

You can enforce awaiting waitFor by using the await-async-utils rule from eslint-plugin-testing-library.

Since waitFor is likely to run expectation callback multiple times, it is highly recommended for it not to perform any side effects in waitFor.

await waitFor(() => {
// ❌ button will be pressed on each waitFor iteration
fireEvent.press(screen.getByText('press me'))
expect(mockOnPress).toHaveBeenCalled()
})
note

Avoiding side effects in expectation callback can be partially enforced with the no-wait-for-side-effects rule.

It is also recommended to have a single assertion per each waitFor for more consistency and faster failing tests. If you want to make several assertions, then they should be in seperate waitFor calls. In many cases you won't actually need to wrap the second assertion in waitFor since the first one will do the waiting required for asynchronous change to happen.

Using a React Native version < 0.71 with Jest fake timers​

caution

When using a version of React Native < 0.71 and modern fake timers (the default for Jest >= 27), waitFor won't work (it will always timeout even if expectation() doesn't throw) unless you use the custom @testing-library/react-native preset.

waitFor checks whether Jest fake timers are enabled and adapts its behavior in such case. The following snippet is a simplified version of how it behaves when fake timers are enabled:

let fakeTimeRemaining = timeout;
let lastError;

while(fakeTimeRemaining > 0) {
fakeTimeRemaining = fakeTimeRemaining - interval;
jest.advanceTimersByTime(interval);
try {
// resolve
return expectation();
} catch (error) {
lastError = error;
}
}

// reject
throw lastError

In the following example we test that a function is called after 10 seconds using fake timers. Since we're using fake timers, the test won't depend on real time passing and thus be much faster and more reliable. Also we don't have to advance fake timers through Jest fake timers API because waitFor already does this for us.

// in component
setTimeout(() => {
someFunction();
}, 10000)

// in test
jest.useFakeTimers();

await waitFor(() => {
expect(someFunction).toHaveBeenCalledWith();
}, 10000)
info

In order to properly use waitFor you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

note

If you receive warnings related to act() function consult our Undestanding Act function document.

waitForElementToBeRemoved​

Defined as:

function waitForElementToBeRemoved<T>(
expectation: () => T,
{ timeout: number = 4500, interval: number = 50 }
): Promise<T> {}

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.

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

test('waiting for an Banana to be removed', async () => {
render(<Banana />);

await waitForElementToBeRemoved(() => screen.getByText('Banana ready'));
});

This method expects that the element is initially present in the render tree and then is removed from it. If the element is not present when you call this method it throws an error.

You can use any of getBy, getAllBy, queryBy and queryAllBy queries for expectation parameter.

info

In order to properly use waitForElementToBeRemoved you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

note

If you receive warnings related to act() function consult our Undestanding Act function document.

within, getQueriesForElement​

Defined as:

function within(element: ReactTestInstance): Queries {}

function getQueriesForElement(element: ReactTestInstance): Queries {}

within (also available as getQueriesForElement alias) performs queries scoped to given element.

note

Please note that additional render specific operations like update, unmount, debug, toJSON are not included.

const detailsScreen = within(screen.getByA11yHint('Details Screen'));
expect(detailsScreen.getByText('Some Text')).toBeOnTheScreen();
expect(detailsScreen.getByDisplayValue('Some Value')).toBeOnTheScreen();
expect(detailsScreen.queryByLabelText('Some Label')).toBeOnTheScreen();
await expect(detailsScreen.findByA11yHint('Some Label')).resolves.toBeOnTheScreen();

Use cases for scoped queries include:

  • queries scoped to a single item inside a FlatList containing many items
  • queries scoped to a single screen in tests involving screen transitions (e.g. with react-navigation)

queryBy* APIs​

Each of the getBy* APIs listed in the render section above have a complimentary queryBy* API. The getBy* 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 queryBy* API instead:

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

render(<Form />);
const submitButton = screen.queryByText('submit');
expect(submitButton).not.toBeOnTheScreen(); // it doesn't exist

queryAll* APIs​

Each of the query APIs have a corresponding queryAll* version that always returns an array of matching nodes. getAll* is the same but throws when the array has a length of 0.

import { render } from '@testing-library/react-native';

render(<Forms />);
const submitButtons = screen.queryAllByText('submit');
expect(submitButtons).toHaveLength(3); // expect 3 elements

act​

Useful function to help testing components that use hooks API. By default any render, update, fireEvent, and waitFor calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from react-test-renderer.

Consult our Undestanding Act function document for more understanding of its intricacies.

renderHook​

Defined as:

function renderHook<Result, Props>(
callback: (props?: Props) => Result,
options?: RenderHookOptions<Props>
): RenderHookResult<Result, Props>;

Renders a test component that will call the provided callback, including any hooks it calls, every time it renders. Returns RenderHookResult object, which you can interact with.

import { renderHook } from '@testing-library/react-native';
import { useCount } from '../useCount';

it('should increment count', () => {
const { result } = renderHook(() => useCount());

expect(result.current.count).toBe(0);
act(() => {
// Note that you should wrap the calls to functions your hook returns with `act` if they trigger an update of your hook's state to ensure pending useEffects are run before your next assertion.
result.current.increment();
});
expect(result.current.count).toBe(1);
});
// useCount.js
export const useCount = () => {
const [count, setCount] = useState(0);
const increment = () => setCount((previousCount) => previousCount + 1);

return { count, increment };
};

The renderHook function accepts the following arguments:

callback​

The function that is called each render of the test component. This function should call one or more hooks for testing.

The props passed into the callback will be the initialProps provided in the options to renderHook, unless new props are provided by a subsequent rerender call.

options (Optional)​

A RenderHookOptions<Props> object to modify the execution of the callback function, containing the following properties:

initialProps​

The initial values to pass as props to the callback function of renderHook. The Props type is determined by the type passed to or inferred by the renderHook call.

wrapper​

A React component to wrap the test component in when rendering. This is usually used to add context providers from React.createContext for the hook to access with useContext.

RenderHookResult object​

interface RenderHookResult<Result, Props> {
result: { current: Result };
rerender: (props: Props) => void;
unmount: () => void;
}

The renderHook function returns an object that has the following properties:

result​

The current value of the result will reflect the latest of whatever is returned from the callback passed to renderHook. The Result type is determined by the type passed to or inferred by the renderHook call.

rerender​

A function to rerender the test component, causing any hooks to be recalculated. If newProps are passed, they will replace the callback function's initialProps for subsequent rerenders. The Props type is determined by the type passed to or inferred by the renderHook call.

unmount​

A function to unmount the test component. This is commonly used to trigger cleanup effects for useEffect hooks.

Examples​

Here we present some extra examples of using renderHook API.

With initialProps​

const useCount = (initialCount: number) => {
const [count, setCount] = useState(initialCount);
const increment = () => setCount((previousCount) => previousCount + 1);

useEffect(() => {
setCount(initialCount);
}, [initialCount]);

return { count, increment };
};

it('should increment count', () => {
const { result, rerender } = renderHook(
(initialCount: number) => useCount(initialCount),
{ initialProps: 1 }
);

expect(result.current.count).toBe(1);

act(() => {
result.current.increment();
});

expect(result.current.count).toBe(2);
rerender(5);
expect(result.current.count).toBe(5);
});

With wrapper​

it('should use context value', () => {
function Wrapper({ children }: { children: ReactNode }) {
return <Context.Provider value="provided">{children}</Context.Provider>;
}

const { result } = renderHook(() => useHook(), { wrapper: Wrapper });
// ...
});

Configuration​

configure​

type Config = {
asyncUtilTimeout: number;
defaultHidden: boolean;
defaultDebugOptions: Partial<DebugOptions>;
};

function configure(options: Partial<Config>) {}

asyncUtilTimeout option​

Default timeout, in ms, for async helper functions (waitFor, waitForElementToBeRemoved) and findBy* queries. Defaults to 1000 ms.

defaultIncludeHiddenElements option​

Default value for includeHiddenElements query option for all queries. The default value is set to false, so all queries will not match elements hidden from accessibility. This is because the users of the app would not be able to see such elements.

This option is also available as defaultHidden alias for compatibility with React Testing Library.

defaultDebugOptions option​

Default debug options to be used when calling debug(). These default options will be overridden by the ones you specify directly when calling debug().

resetToDefaults()​

function resetToDefaults() {}

Environment variables​

RNTL_SKIP_AUTO_CLEANUP​

Set to true to disable automatic cleanup() after each test. It works the same as importing react-native-testing-library/dont-cleanup-after-each or using react-native-testing-library/pure.

$ RNTL_SKIP_AUTO_CLEANUP=true jest

RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS​

Set to true to disable auto-detection of fake timers. This might be useful in rare cases when you want to use non-Jest fake timers. See issue #886 for more details.

$ RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS=true jest

Accessibility​

isHiddenFromAccessibility​

function isHiddenFromAccessibility(
element: ReactTestInstance | null
): boolean {}

Also available as isInaccessible() alias for React Testing Library compatibility.

Checks if given element is hidden from assistive technology, e.g. screen readers.

note

Like isInaccessible function from DOM Testing Library this function considers both accessibility elements and presentational elements (regular Views) to be accessible, unless they are hidden in terms of host platform.

This covers only part of ARIA notion of Accessiblity Tree, as ARIA excludes both hidden and presentational elements from the Accessibility Tree.

For the scope of this function, element is inaccessible when it, or any of its ancestors, meets any of the following conditions:

Specifying accessible={false}, accessiblityRole="none", or importantForAccessibility="no" props does not cause the element to become inaccessible.

- +more realistic event simulation by emitting a sequence of events with proper event objects that mimic React Native runtime behavior.

Use Fire Event for cases not supported by User Event and for triggering event handlers on composite components.

fireEvent API allows you to trigger all kind of event handlers on both host and composite components. It will try to invoke a single event handler traversing the component tree bottom-up from passed element and trying to find enabled event handler named onXxx when xxx is the name of the event passed.

Unlike User Event, this API does not automatically pass event object to event handler, this is responsibility of the user to construct such object.

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

test('fire changeText event', () => {
const onEventMock = jest.fn();
render(
// MyComponent renders TextInput which has a placeholder 'Enter details'
// and with `onChangeText` bound to handleChangeText
<MyComponent handleChangeText={onEventMock} />
);

fireEvent(screen.getByPlaceholderText('change'), 'onChangeText', 'ab');
expect(onEventMock).toHaveBeenCalledWith('ab');
});
note

Please note that from version 7.0 fireEvent performs checks that should prevent events firing on disabled elements.

An example using fireEvent with native events that aren't already aliased by the fireEvent api.

import { TextInput, View } from 'react-native';
import { fireEvent, render } from '@testing-library/react-native';

const onBlurMock = jest.fn();

render(
<View>
<TextInput placeholder="my placeholder" onBlur={onBlurMock} />
</View>
);

// you can omit the `on` prefix
fireEvent(screen.getByPlaceholderText('my placeholder'), 'blur');

fireEvent[eventName]​

fireEvent[eventName](element: ReactTestInstance, ...data: Array<any>): void

Convenience methods for common events like: press, changeText, scroll.

fireEvent.press​

fireEvent.press: (element: ReactTestInstance, ...data: Array<any>) => void
note

It is recommended to use the User Event press() helper instead as it offers more realistic simulation of press interaction, including pressable support.

Invokes press event handler on the element or parent element in the tree.

import { View, Text, TouchableOpacity } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onPressMock = jest.fn();
const eventData = {
nativeEvent: {
pageX: 20,
pageY: 30,
},
};

render(
<View>
<TouchableOpacity onPress={onPressMock}>
<Text>Press me</Text>
</TouchableOpacity>
</View>
);

fireEvent.press(screen.getByText('Press me'), eventData);
expect(onPressMock).toHaveBeenCalledWith(eventData);

fireEvent.changeText​

fireEvent.changeText: (element: ReactTestInstance, ...data: Array<any>) => void
note

It is recommended to use the User Event type() helper instead as it offers more realistic simulation of text change interaction, including key-by-key typing, element focus, and other editing events.

Invokes changeText event handler on the element or parent element in the tree.

import { View, TextInput } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onChangeTextMock = jest.fn();
const CHANGE_TEXT = 'content';

render(
<View>
<TextInput placeholder="Enter data" onChangeText={onChangeTextMock} />
</View>
);

fireEvent.changeText(screen.getByPlaceholderText('Enter data'), CHANGE_TEXT);

fireEvent.scroll​

fireEvent.scroll: (element: ReactTestInstance, ...data: Array<any>) => void

Invokes scroll event handler on the element or parent element in the tree.

On a ScrollView​

import { ScrollView, Text } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onScrollMock = jest.fn();
const eventData = {
nativeEvent: {
contentOffset: {
y: 200,
},
},
};

render(
<ScrollView onScroll={onScrollMock}>
<Text>XD</Text>
</ScrollView>
);

fireEvent.scroll(screen.getByText('scroll-view'), eventData);

On a FlatList​

import { FlatList, View } from 'react-native';
import { render, screen, fireEvent } from '@testing-library/react-native';

const onEndReached = jest.fn();
render(
<FlatList
data={Array.from({ length: 10 }, (_, key) => ({ key: `${key}` }))}
renderItem={() => <View style={{ height: 500, width: 100 }} />}
onEndReached={onEndReached}
onEndReachedThreshold={0.2}
testID="flat-list"
/>
);
const eventData = {
nativeEvent: {
contentOffset: {
y: 500,
},
contentSize: {
// Dimensions of the scrollable content
height: 500,
width: 100,
},
layoutMeasurement: {
// Dimensions of the device
height: 100,
width: 100,
},
},
};

fireEvent.scroll(screen.getByTestId('flat-list'), eventData);
expect(onEndReached).toHaveBeenCalled();
note

If you're noticing that components are not being found on a list, even after mocking a scroll event, try changing the initialNumToRender that you have set. If you aren't comfortable changing the code to accept this prop from the unit test, try using an e2e test that might better suit what use case you're attempting to replicate.

waitFor​

Defined as:

function waitFor<T>(
expectation: () => T,
{ timeout: number = 1000, interval: number = 50 }
): Promise<T> {}

Waits for a period of time for the expectation callback to pass. waitFor may run the callback a number of times until timeout is reached, as specified by the timeout and interval options. The callback must throw an error when the expectation is not met. Returning any value, including a falsy one, will be treated as meeting the expectation, and the callback result will be returned to the caller of waitFor function.

await waitFor(() => expect(mockFunction).toHaveBeenCalledWith()))

waitFor function will be executing expectation callback every interval (default: every 50 ms) until timeout (default: 1000 ms) is reached. The repeated execution of callback is stopped as soon as it does not throw an error, in such case the value returned by the callback is returned to waitFor caller. Otherwise, when it reaches the timeout, the final error thrown by expectation will be re-thrown by waitFor to the calling code.

// ❌ `waitFor` will return immediately because callback does not throw
await waitFor(() => false);

waitFor is an async function so you need to await the result to pause test execution.

// ❌ missing `await`: `waitFor` will just return Promise that will be rejected when the timeout is reached
waitFor(() => expect(1).toBe(2))
note

You can enforce awaiting waitFor by using the await-async-utils rule from eslint-plugin-testing-library.

Since waitFor is likely to run expectation callback multiple times, it is highly recommended for it not to perform any side effects in waitFor.

await waitFor(() => {
// ❌ button will be pressed on each waitFor iteration
fireEvent.press(screen.getByText('press me'))
expect(mockOnPress).toHaveBeenCalled()
})
note

Avoiding side effects in expectation callback can be partially enforced with the no-wait-for-side-effects rule.

It is also recommended to have a single assertion per each waitFor for more consistency and faster failing tests. If you want to make several assertions, then they should be in seperate waitFor calls. In many cases you won't actually need to wrap the second assertion in waitFor since the first one will do the waiting required for asynchronous change to happen.

Using a React Native version < 0.71 with Jest fake timers​

caution

When using a version of React Native < 0.71 and modern fake timers (the default for Jest >= 27), waitFor won't work (it will always timeout even if expectation() doesn't throw) unless you use the custom @testing-library/react-native preset.

waitFor checks whether Jest fake timers are enabled and adapts its behavior in such case. The following snippet is a simplified version of how it behaves when fake timers are enabled:

let fakeTimeRemaining = timeout;
let lastError;

while(fakeTimeRemaining > 0) {
fakeTimeRemaining = fakeTimeRemaining - interval;
jest.advanceTimersByTime(interval);
try {
// resolve
return expectation();
} catch (error) {
lastError = error;
}
}

// reject
throw lastError

In the following example we test that a function is called after 10 seconds using fake timers. Since we're using fake timers, the test won't depend on real time passing and thus be much faster and more reliable. Also we don't have to advance fake timers through Jest fake timers API because waitFor already does this for us.

// in component
setTimeout(() => {
someFunction();
}, 10000)

// in test
jest.useFakeTimers();

await waitFor(() => {
expect(someFunction).toHaveBeenCalledWith();
}, 10000)
info

In order to properly use waitFor you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

note

If you receive warnings related to act() function consult our Undestanding Act function document.

waitForElementToBeRemoved​

Defined as:

function waitForElementToBeRemoved<T>(
expectation: () => T,
{ timeout: number = 4500, interval: number = 50 }
): Promise<T> {}

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.

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

test('waiting for an Banana to be removed', async () => {
render(<Banana />);

await waitForElementToBeRemoved(() => screen.getByText('Banana ready'));
});

This method expects that the element is initially present in the render tree and then is removed from it. If the element is not present when you call this method it throws an error.

You can use any of getBy, getAllBy, queryBy and queryAllBy queries for expectation parameter.

info

In order to properly use waitForElementToBeRemoved you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

note

If you receive warnings related to act() function consult our Undestanding Act function document.

within, getQueriesForElement​

Defined as:

function within(element: ReactTestInstance): Queries {}

function getQueriesForElement(element: ReactTestInstance): Queries {}

within (also available as getQueriesForElement alias) performs queries scoped to given element.

note

Please note that additional render specific operations like update, unmount, debug, toJSON are not included.

const detailsScreen = within(screen.getByA11yHint('Details Screen'));
expect(detailsScreen.getByText('Some Text')).toBeOnTheScreen();
expect(detailsScreen.getByDisplayValue('Some Value')).toBeOnTheScreen();
expect(detailsScreen.queryByLabelText('Some Label')).toBeOnTheScreen();
await expect(detailsScreen.findByA11yHint('Some Label')).resolves.toBeOnTheScreen();

Use cases for scoped queries include:

  • queries scoped to a single item inside a FlatList containing many items
  • queries scoped to a single screen in tests involving screen transitions (e.g. with react-navigation)

queryBy* APIs​

Each of the getBy* APIs listed in the render section above have a complimentary queryBy* API. The getBy* 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 queryBy* API instead:

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

render(<Form />);
const submitButton = screen.queryByText('submit');
expect(submitButton).not.toBeOnTheScreen(); // it doesn't exist

queryAll* APIs​

Each of the query APIs have a corresponding queryAll* version that always returns an array of matching nodes. getAll* is the same but throws when the array has a length of 0.

import { render } from '@testing-library/react-native';

render(<Forms />);
const submitButtons = screen.queryAllByText('submit');
expect(submitButtons).toHaveLength(3); // expect 3 elements

act​

Useful function to help testing components that use hooks API. By default any render, update, fireEvent, and waitFor calls are wrapped by this function, so there is no need to wrap it manually. This method is re-exported from react-test-renderer.

Consult our Undestanding Act function document for more understanding of its intricacies.

renderHook​

Defined as:

function renderHook<Result, Props>(
callback: (props?: Props) => Result,
options?: RenderHookOptions<Props>
): RenderHookResult<Result, Props>;

Renders a test component that will call the provided callback, including any hooks it calls, every time it renders. Returns RenderHookResult object, which you can interact with.

import { renderHook } from '@testing-library/react-native';
import { useCount } from '../useCount';

it('should increment count', () => {
const { result } = renderHook(() => useCount());

expect(result.current.count).toBe(0);
act(() => {
// Note that you should wrap the calls to functions your hook returns with `act` if they trigger an update of your hook's state to ensure pending useEffects are run before your next assertion.
result.current.increment();
});
expect(result.current.count).toBe(1);
});
// useCount.js
export const useCount = () => {
const [count, setCount] = useState(0);
const increment = () => setCount((previousCount) => previousCount + 1);

return { count, increment };
};

The renderHook function accepts the following arguments:

callback​

The function that is called each render of the test component. This function should call one or more hooks for testing.

The props passed into the callback will be the initialProps provided in the options to renderHook, unless new props are provided by a subsequent rerender call.

options (Optional)​

A RenderHookOptions<Props> object to modify the execution of the callback function, containing the following properties:

initialProps​

The initial values to pass as props to the callback function of renderHook. The Props type is determined by the type passed to or inferred by the renderHook call.

wrapper​

A React component to wrap the test component in when rendering. This is usually used to add context providers from React.createContext for the hook to access with useContext.

RenderHookResult object​

interface RenderHookResult<Result, Props> {
result: { current: Result };
rerender: (props: Props) => void;
unmount: () => void;
}

The renderHook function returns an object that has the following properties:

result​

The current value of the result will reflect the latest of whatever is returned from the callback passed to renderHook. The Result type is determined by the type passed to or inferred by the renderHook call.

rerender​

A function to rerender the test component, causing any hooks to be recalculated. If newProps are passed, they will replace the callback function's initialProps for subsequent rerenders. The Props type is determined by the type passed to or inferred by the renderHook call.

unmount​

A function to unmount the test component. This is commonly used to trigger cleanup effects for useEffect hooks.

Examples​

Here we present some extra examples of using renderHook API.

With initialProps​

const useCount = (initialCount: number) => {
const [count, setCount] = useState(initialCount);
const increment = () => setCount((previousCount) => previousCount + 1);

useEffect(() => {
setCount(initialCount);
}, [initialCount]);

return { count, increment };
};

it('should increment count', () => {
const { result, rerender } = renderHook(
(initialCount: number) => useCount(initialCount),
{ initialProps: 1 }
);

expect(result.current.count).toBe(1);

act(() => {
result.current.increment();
});

expect(result.current.count).toBe(2);
rerender(5);
expect(result.current.count).toBe(5);
});

With wrapper​

it('should use context value', () => {
function Wrapper({ children }: { children: ReactNode }) {
return <Context.Provider value="provided">{children}</Context.Provider>;
}

const { result } = renderHook(() => useHook(), { wrapper: Wrapper });
// ...
});

Configuration​

configure​

type Config = {
asyncUtilTimeout: number;
defaultHidden: boolean;
defaultDebugOptions: Partial<DebugOptions>;
};

function configure(options: Partial<Config>) {}

asyncUtilTimeout option​

Default timeout, in ms, for async helper functions (waitFor, waitForElementToBeRemoved) and findBy* queries. Defaults to 1000 ms.

defaultIncludeHiddenElements option​

Default value for includeHiddenElements query option for all queries. The default value is set to false, so all queries will not match elements hidden from accessibility. This is because the users of the app would not be able to see such elements.

This option is also available as defaultHidden alias for compatibility with React Testing Library.

defaultDebugOptions option​

Default debug options to be used when calling debug(). These default options will be overridden by the ones you specify directly when calling debug().

resetToDefaults()​

function resetToDefaults() {}

Environment variables​

RNTL_SKIP_AUTO_CLEANUP​

Set to true to disable automatic cleanup() after each test. It works the same as importing react-native-testing-library/dont-cleanup-after-each or using react-native-testing-library/pure.

$ RNTL_SKIP_AUTO_CLEANUP=true jest

RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS​

Set to true to disable auto-detection of fake timers. This might be useful in rare cases when you want to use non-Jest fake timers. See issue #886 for more details.

$ RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS=true jest

Accessibility​

isHiddenFromAccessibility​

function isHiddenFromAccessibility(
element: ReactTestInstance | null
): boolean {}

Also available as isInaccessible() alias for React Testing Library compatibility.

Checks if given element is hidden from assistive technology, e.g. screen readers.

note

Like isInaccessible function from DOM Testing Library this function considers both accessibility elements and presentational elements (regular Views) to be accessible, unless they are hidden in terms of host platform.

This covers only part of ARIA notion of Accessiblity Tree, as ARIA excludes both hidden and presentational elements from the Accessibility Tree.

For the scope of this function, element is inaccessible when it, or any of its ancestors, meets any of the following conditions:

Specifying accessible={false}, accessiblityRole="none", or importantForAccessibility="no" props does not cause the element to become inaccessible.

+ \ No newline at end of file diff --git a/docs/eslint-plugin-testing-library.html b/docs/eslint-plugin-testing-library.html index 078592bd..38354f18 100644 --- a/docs/eslint-plugin-testing-library.html +++ b/docs/eslint-plugin-testing-library.html @@ -4,13 +4,13 @@ ESLint Plugin Testing Library Compatibility | React Native Testing Library - +

ESLint Plugin Testing Library Compatibility

Most of the rules of the eslint-plugin-testing-library are compatible with this library except the following:

  • prefer-user-event: userEvent requires a DOM environment 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:

To get the rule consistent-data-testid to work, you need to configure it to check the testID attribute by adding the following in your eslint config file, the testIdPattern being whichever pattern you want to enforce:

{
"testing-library/consistent-data-testid": [
2,
{
"testIdAttribute": ["testID"],
"testIdPattern": "^TestId(__[A-Z]*)?$"
}
]
}
- + \ No newline at end of file diff --git a/docs/faq.html b/docs/faq.html index 88731467..35bdc01a 100644 --- a/docs/faq.html +++ b/docs/faq.html @@ -4,7 +4,7 @@ FAQ | React Native Testing Library - + @@ -14,7 +14,7 @@ or iOS simulator/Android emulator to provision the underlying OS and platform AP using React Test Renderer while providing queries and fireEvent APIs that mimick certain behaviors from the real runtime.

You can learn more about our testing environment here.

This approach has certain benefits and shortfalls. On the positive side:

  • it allows testing most of the logic of regular React Native apps
  • it allows running test on any OS supported by Jest, or other test runner, e.g. on CI
  • it uses much less resources than full runtime simulation
  • you can use Jest fake timers

The the negative side:

  • you cannot test native features
  • certain JavaScript features might not be perfectly simulated, but we are working on it

For instance, react-native's 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 by Kent C. Dodds.

- + \ No newline at end of file diff --git a/docs/getting-started.html b/docs/getting-started.html index 9f171683..d23045a7 100644 --- a/docs/getting-started.html +++ b/docs/getting-started.html @@ -4,13 +4,13 @@ Getting Started | React Native Testing Library - +

Getting Started

The problem​

You want to write maintainable tests for your React Native components. As a part of this goal, you want your tests to avoid including implementation details of your components and rather focus on making your tests give you the confidence for which they are intended. As part of this, you want your testbase to be maintainable in the long run so refactors of your components (changes to implementation but not functionality) don't break your tests and slow you and your team down.

This solution​

The React Native Testing Library (RNTL) is a lightweight solution for testing React Native components. It provides light utility functions on top of react-test-renderer, in a way that encourages better testing practices. Its primary guiding principle is:

The more your tests resemble the way your software is used, the more confidence they can give you.

This project is inspired by React Testing Library. Tested to work with Jest, but it should work with other test runners as well.

You can find the source of QuestionsBoard component and this example here.

Installation​

Open a Terminal in your project's folder and run:

Using yarn​

yarn add --dev @testing-library/react-native

Using npm​

npm install --save-dev @testing-library/react-native

This library has a peerDependencies listing for react-test-renderer. Make sure that your react-test-renderer version matches exactly your react version.

info

In order to properly use helpers for async tests (findBy queries and waitFor) you need at least React >=16.9.0 (featuring async act) or React Native >=0.61 (which comes with React >=16.9.0).

Additional Jest matchers​

In order to use additional React Native-specific Jest matchers from @testing-library/jest-native package add it to your project:

Using yarn​

yarn add --dev @testing-library/jest-native

Using npm​

npm install --save-dev @testing-library/jest-native

Then automatically add it to your jest tests by using setupFilesAfterEnv option in your Jest configuration (it's usually located either in package.json under "jest" key or in a jest.config.js file):

{
"preset": "react-native",
"setupFilesAfterEnv": ["@testing-library/jest-native/extend-expect"]
}

Flow​

Note for Flow users – you'll also need to install typings for react-test-renderer:

flow-typed install react-test-renderer

Example​

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();

render(<QuestionsBoard questions={allQuestions} onSubmit={mockFn} />);

const answerInputs = screen.getAllByLabelText('answer input');

fireEvent.changeText(answerInputs[0], 'a1');
fireEvent.changeText(answerInputs[1], 'a2');
fireEvent.press(screen.getByText('Submit'));

expect(mockFn).toBeCalledWith({
1: { q: 'q1', a: 'a1' },
2: { q: 'q2', a: 'a2' },
});
});

You can find the source of QuestionsBoard component and this example here.

- + \ No newline at end of file diff --git a/docs/how-should-i-query.html b/docs/how-should-i-query.html index fb84a51d..906214be 100644 --- a/docs/how-should-i-query.html +++ b/docs/how-should-i-query.html @@ -4,13 +4,13 @@ How Should I Query? | React Native Testing Library - +

How Should I Query?

Priority​

Based on the Guiding Principles, your test should resemble how users interact with your code (component, page, etc.) as much as possible. With this in mind, we recommend this order of priority:

  1. Queries Accessible to Everyone queries that reflect the experience of visual users as well as those that use assistive technology
    • getByText: This is the number 1 method a user finds any visible text on interactive and non-interactive elements.
    • getByDisplayValue: Useful for the current value of a TextInput.
    • getByPlaceholderText: Only useful for targeting a placeholder of a TextInput.
    • getByLabelText: This can be used to query every element that is exposed in the accessibility tree as a label, usually when there's no visible text.
    • getByHintText: This can be used to query every element that is exposed in the accessibility tree as a hint. Make sure it also has a label set.
    • getByAccessibilityState: This can be used to query every element that is exposed in the accessibility tree as a state of an interactive element, like a checkbox.
    • getByAccessibilityValue: This can be used to query every element that is exposed in the accessibility tree as a value on a range, like a slider.
  2. Queries Users Can Infer
    • getByRole: This can be used to query every element that is exposed in the accessibility tree as a role, like buttons or images.
  3. Test IDs
    • getByTestId: The user cannot see (or hear) these, so this is only recommended for cases where you can't match by text or it doesn't make sense
- + \ No newline at end of file diff --git a/docs/migration-v11.html b/docs/migration-v11.html index 050b787f..92c69fed 100644 --- a/docs/migration-v11.html +++ b/docs/migration-v11.html @@ -4,13 +4,13 @@ Migration to 11.0 | React Native Testing Library - +

Migration to 11.0

Migration to React Native Testing Library version 11 from version 9.x or 10.x should be a relatively easy task due small amount of breaking changes.

Breaking changes

Update to Jest 28 if you use fake timers​

If you use fake timers in any of your tests you should update your Jest dependencies to version 28. This is due to the fact that jest.useFakeTimers() config structure has changed.

Refactor legacy waitForOptions position​

In version 9 we introducted query options parameters for each query type. This affected all findBy and findAllBy queries because their signatures changed e.g. from:

function findByText(text: TextMatch, waitForOptions?: WaitForOptions)
function findAllByText(text: TextMatch, waitForOptions?: WaitForOptions)

to

function findByText(text: TextMatch, options?: TextMatchOptions, waitForOptions?: WaitForOptions)
function findAllByText(text: TextMatch, options?: TextMatchOptions, waitForOptions?: WaitForOptions)

In order to facilitate transition, in version 9 and 10, we provided a temporary possibility to pass WaitForOptions like timeout, interval, etc inside options argument. From this release we require passing these as the proper third parameter.

This change is easy to implement:

findByText(/Text/, { timeout: 1000 })

should become

findByText(/Text/, {}, { timeout: 1000 })

Triggering non-touch events on targets with pointerEvents="box-none" prop​

Up to version 10, RNTL disables all events for a target with pointerEvents="box-none". This behavior is counter to how React Native itself functions.

From version 11, RNTL continues to disable press event for these targets but allows triggering other events, e.g. layout.

All changes

Full Changelog

https://github.com/callstack/react-native-testing-library/compare/v10.1.1...v11.0.0

- + \ No newline at end of file diff --git a/docs/migration-v12.html b/docs/migration-v12.html index c18a6337..87710a34 100644 --- a/docs/migration-v12.html +++ b/docs/migration-v12.html @@ -4,13 +4,13 @@ Migration to 12.0 | React Native Testing Library - +

Migration to 12.0

React Native Testing Library 12 introduces a handful of breaking changes compared to 11.x versions. We believe they were necessary to improve the experience using the library and help the users fall into the pit of success when writing meaningful tests. You will find migration instructions for each and every change described below.

note

If you use Jest Native matchers, which we recommend, then you should upgrade it to version 5.4.2 or higher.

Breaking changes

1. All queries exclude elements hidden from accessibility by default​

Elements that are hidden from accessiblity, e.g. elements on non-active screen when using React Navigation, now will not be matched by default by all queries. This is the effect of switching the default value for global config option defaultIncludeHiddenElements(api#defaultincludehiddenelements-option) to false.

Previous behaviour of matching hidden elements can be enabled on query level using includeHiddenElements query options or globally using defaultIncludeHiddenElements(api#defaultincludehiddenelements-option) configuration option.

2. *ByRole queries now return only accessibility elements​

*ByRole queries now return only accessibility elements, either explicitly marked with accessible prop or implicit ones where this status is derived from component type itself (e.g Text, TextInput, Switch, but not View).

You may need to adjust relevant components under test to make sure they pass isAccessibilityElement check.

Examples​

Let's assume we are using getByRole("button") query.

Following elements will match:

// Explicit "accessible" prop for View
<View accessible accessibilityRole="button" />

// No need to "accessible" prop for Text, as it is implicitly accessible element.
<Text accessibilityRole="button">Button</Text>

While following elements will not match:

// Missing "accessible" prop for View
<View accessibilityRole="button" />

// Explicit "accessible={false}" prop for View
<View accessible={false} accessibilityRole="button" />

// Explicit "accessible={false}" for Text, which is implicitly accessible element
<Text accessible={false} accessibilityRole="button">Button</Text>

3. *ByText, *ByDisplayValue, *ByPlaceholderText queries now return host elements​

*ByText, *ByDisplayValue, *ByPlaceholderText queries now return host elements, which is consistent with other queries.

While potentially breaking, this should not cause issues in tests if you are using recommended queries and Jest Matchers from Jest Native package.

Problematic cases may include: directly checking some prop values (without using Jest Native matchers), referencing other nodes using parent or children props, examining type property of ReactTestInstance, etc.

4. container API has been renamed to UNSAFE_root.​

Historically container was supposed to mimic the RTL's container. However it turned out not so relevant in RNTL's environment, where we actually used it to return React Test Renderer's root instance.

RNTL v12 introduces root API as an alternative that returns a root host element. The difference between root and UNSAFE_root properties is that that root will always represents a host element, while UNSAFE_root will typically represent a composite element.

If you use toBeOnTheScreen matcher from @testing-library/jest-native your tests will fail because it uses the container api. To fix this, update @testing-library/jest-native to version 5.4.2.

Full Changelog

https://github.com/callstack/react-native-testing-library/compare/v11.5.2...v12.0.0

- + \ No newline at end of file diff --git a/docs/migration-v2.html b/docs/migration-v2.html index 49ab07b2..3b7ab410 100644 --- a/docs/migration-v2.html +++ b/docs/migration-v2.html @@ -4,13 +4,13 @@ Migration to 2.0 | React Native Testing Library - +

Migration to 2.0

This guide describes steps necessary to migrate from React Native Testing Library v1.x to v2.0.

Dropping Node 8​

Node 8 reached its EOL more than 5 months ago, so it's about time to target the library to Node 10. If you used lower version, you'll have to upgrade to v10, but we recommend using the latest LTS version.

Auto Cleanup​

cleanup() function is now called automatically after every test if your testing framework supports afterEach hook (like Jest, Mocha, and Jasmine).

You should be able to remove all afterEach(cleanup) calls in your code.

This change might break your code, if you tests are not isolated, i.e. you call render outside test block. Generally, you should keep your tests isolated. But if you can't or don't want to do this right away you can prevent this behavior using any of the following ways:

  • by importing 'react-native-testing-library/pure' instead of 'react-native-testing-library'

  • by importing 'react-native-testing-library/dont-cleanup-after-each' before importing 'react-native-testing-library'. You can do it in a global way by using Jest's setupFiles like this:

    {
    "setupFiles": ["react-native-testing-library/dont-cleanup-after-each"];
    }
  • by setting RNTL_SKIP_AUTO_CLEANUP env variable to true. You can do this with cross-evn like this:

    cross-env RNTL_SKIP_AUTO_CLEANUP=true jest

WaitFor API changes​

We renamed waitForElement function to waitFor for consistency with React Testing Library. Additionally, the signature has slightly changed from:

export default function waitForElement<T>(
expectation: () => T,
timeout?: number,
interval?: number
): Promise<T> {}

to:

export default function waitFor<T>(
expectation: () => T,
options: {
timeout?: number,
interval?: number,
}
): Promise<T> {}

Both changes should improve code readibility.

waitFor calls (and hence also findBy queries) are now wrapped in act by default, so that you should no longer need to use act directly in your tests.

tip

You can usually avoid waitFor by a proper use of findBy asynchronous queries. It will result in more streamlined testing experience.

Removed global debug function​

The debug() method returned from render() function is all you need. We removed the global export to avoid confusion.

Removed global shallow function​

Shallow rendering React component is usually not a good idea, so we decided to remove the API. But, if you find it useful or need to support legacy tests, feel free to use this implementation:

import ShallowRenderer from 'react-test-renderer/shallow';

export function shallow(instance: ReactTestInstance | React.Element<any>) {
const renderer = new ShallowRenderer();
renderer.render(React.createElement(instance.type, instance.props));

return { output: renderer.getRenderOutput() };
}

Removed functions​

Following query functions have been removed after being deprecated for more than a year now:

  • getByName
  • getAllByName
  • queryByName
  • queryAllByName

The *ByType and *ByProps queries has been prefixed with UNSAFE_. These UNSAFE_ functions are not planned for removal in future versions but their usage is discouraged. You can rename them using global search/replace in your project:

  • getByType -> UNSAFE_getByType
  • getAllByType -> UNSAFE_getAllByType
  • queryByType -> UNSAFE_queryByType
  • queryAllByType -> UNSAFE_queryAllByType
  • getByProps -> UNSAFE_getByProps
  • getAllByProps -> UNSAFE_getAllByProps
  • queryByProps -> UNSAFE_queryByProps
  • queryAllByProps -> UNSAFE_queryAllByProps

Some ByTestId queries behavior changes​

In version 1.x the getByTestId and queryByTestId queries could return non-native instances. This was a serious bug. Other query functions like getAllByTestId, queryAllByTestId, findByTestId and findAllByTestId didn't have this issue. These correctly returned only native components instances (e.g. View, Text, etc) that got the testID.

In v2 we fixed this inconsistency, which may result in failing tests, if you relied on this behavior. There are few ways to handle these failures:

  • pass the testID prop down so it can reach a native component, like View or Text
  • replace testID with proper accessibilityHint or accessibilityLabel if it benefits the user
  • use safe queries like *ByText or *ByA11yHint

Deprecated flushMicrotasksQueue​

We have deprecated flushMicrotasksQueue and plan to remove it in the next major. We have better alternatives available for helping you write async tests – findBy async queries and waitFor helper.

If you can't or don't want to migrate your tests, don't worry. You can use the same implementation we have today:

function flushMicrotasksQueue() {
return new Promise((resolve) => setImmediate(resolve));
}
- + \ No newline at end of file diff --git a/docs/migration-v7.html b/docs/migration-v7.html index 54bf2bc9..5e1ed113 100644 --- a/docs/migration-v7.html +++ b/docs/migration-v7.html @@ -4,13 +4,13 @@ Migration to 7.0 | React Native Testing Library - +

Migration to 7.0

caution

We renamed the react-native-testing-library npm package to @testing-library/react-native, officially joining the "Testing Library" family 🎉.

As the version 7.0 involves merging two libraries together, there are two variants for migration guide, dependent on library you used previously:

Guide for react-native-testing-library users

This guide describes steps necessary to migrate from React Native Testing Library v2.x or v6.0 to v7.0.

Renaming the library​

  1. Install @testing-library/react-native.
  2. Uninstall react-native-testing-library.
  3. Rename all references of react-native-testing-library to @testing-library/react-native.

You may have noticed a strange v2 to v7 upgrade, skipping versions 3, 4, 5 and 6. This is because we renamed the react-native-testing-library npm package to @testing-library/react-native, officially joining the "Testing Library" family 🎉. We're merging existing two libraries into a single one. The native-testing-library repository, which had v6, will soon be archived and using @testing-library/react-native below v7, sourced from mentioned repository, is deprecated.

For branding purposes we keep the "React Native Testing Library" name, similar to "React Testing Library". Only the npm published package is changing. The code repository also stays the same under Callstack governance.

New aliases​

To improve compatibility with React Testing Library, and ease the migration for @testing-library/react-native users using version below v7, we've introduced new aliases to our accessibility queries:

  • ByLabelText aliasing ByA11yLabel queries
  • ByHintText aliasing ByA11yHint queries
  • ByRole aliasing ByA11yRole queries

We like the new names and consider removing the aliases in future releases.

Renaming ByPlaceholder queries​

To improve compatibility with React Testing Library, and to ease the migration for @testing-library/react-native users using version below v7, we've renamed following queries:

  • ByPlaceholder -> ByPlaceholderText

Please replace all occurrences of these queries in your codebase.

fireEvent support for disabled components​

To improve compatibility with the real React Native environment fireEvent now performs checks whether the component is "disabled" before firing an event on it. It uses the Responder system to establish should the event fire, which resembles the actual React Native runtime closer than we used to.

If your code contained any workarounds for preventing events firing on disabled events, you should now be able to remove them.

Guide for @testing-library/react-native users

This guide describes steps necessary to migrate from @testing-library/react-native from v6.0 to v7.0. Although the name stays the same, this is a different library, sourced at Callstack GitHub repository. We made sure the upgrade path is as easy for you as possible.

Renaming "wait" helpers​

The wait and waitForElement helpers are replaced by waitFor. Please rename all occurrences of these in your codebase.

Changes to ByTestId queries​

The ByTestId queries don't accept RegExps. Please use strings instead. We're happy to accept PRs adding this functionality :).

No ByTitle queries​

Our library doesn't implement ByTitle queries, which are targetting components with title prop, specifically Button and RefreshControl. If your tests only use ByTitle to target Button components, you can replace them with ByText queries, since React Native renders Text under the hood.

If you need to query RefreshControl component and can't figure out other way around it, you can use e.g. UNSAFE_getByProps({title}) query.

No custom Jest configuration​

Use the official React Native preset for Jest:

{
"jest": {
- "preset": "@testing-library/react-native"
+ "preset": "react-native"
}
}

We're told this also speeds up your tests startup on cold cache. Using official preset has another benefit – the library is compatible with any version of React Native without introducing breaking changes.

Cleanup is included by default​

Cleaning up (unmounting) components after each test is included by default in the same manner as in React Testing Library. Please remove this setup file from Jest config:

{
"jest": {
- "setupFilesAfterEnv": ["@testing-library/react-native/cleanup-after-each"]
}
}

You can opt-out of this behavior by running tests with RNTL_SKIP_AUTO_CLEANUP=true flag or importing from @testing-library/react-native/pure. We encourage you to keep the default though.

No NativeTestInstance abstraction​

We don't provide any abstraction over ReactTestInstance returned by queries, but allow to use it directly to access queried component's props or type for that example.

No container nor baseElement returned from render​

There's no container returned from the render function. If you must, use react-test-renderer directly, although we advise against doing so. We also don't implement baseElement because of that, since there's no document.documentElement nor container.

Firing events changes​

There are slight differences in how fireEvent works in both libraries:

  1. Our library doesn't perform validation checks for events fired upon tested components.
  2. Signature is different:
    -fireEvent[eventName](node: FiberRoot, eventProperties: NativeTestEvent)
    +fireEvent(element: ReactTestInstance, eventName: string, ...data: Array<any>)
  3. There is no NativeTestEvent - second and rest arguments are used instead.
  4. There are only 3 short-hand events: fireEvent.press, fireEvent.changeText and fireEvent.scroll. For all other or custom events you can use the base signature.
- + \ No newline at end of file diff --git a/docs/migration-v9.html b/docs/migration-v9.html index 3c3c1d6e..0e90f365 100644 --- a/docs/migration-v9.html +++ b/docs/migration-v9.html @@ -4,13 +4,13 @@ Migration to 9.0 | React Native Testing Library - +

Migration to 9.0

Version 7.0 brought React Native Testing Library into the @testing-library family. Since it has been implemented independently from its web counterpart – the React Testing Library – there are some differences in the API and behavior. Version 9.0 solves several of these problems.

Support for text match options a.k.a string precision API​

This is a backward compatible change.

When querying text, it is now possible to pass a TextMatch to most text based queries, which lets you configure how @testing-library/react-native should match your text. For instance, passing exact: false will allow matching substrings and will ignore case:

const { getByText } = render(<Text>Hello World</Text>);

getByText('Hello World'); // Matches
getByText('Hello'); // Doesn't match
getByText('hello', { exact: false }); // ignore case-sensitivity and does partial matching

Please note that the findBy* queries used to take a waitForOptions parameter as a second argument, which has now been moved to the third argument:

-findByText('Hello world', { timeout: 3000 }); // old findBy* API
+findByText('Hello world', {}, { timeout: 3000 }); // new findBy* API

For backward compatibility RNTL v9 can still read waitForOptions from the second argument but will print a deprecation warning.

Reverted matching text across several nodes​

caution

This is a breaking change.

In v1.14 we've introduced a feature allowing to match text when it's spread across several nodes:

const { getByText } = render(
<Text>
Hello <Text>world</Text>
</Text>
);
getByText('Hello world'); // matches

However this behavior was different than the web one, and wouldn't always be straightforward to reason about. For instance it could match text nodes far from each other on the screen. It also prevented us from implementing the string precision API. From v9, this type of match will not work.

A work around is to use within:

import {Text} from 'react-native'
import {render, within} from '@testing-library/react-native'

const {getByText} = render(<Text>Hello <Text>world</Text</Text>)

within(getByText('Hello', {exact: false})).getByText('world')

Future plans​

This release changes a lot of internal logic in the library, paving the way for more improvements to bring us closer to our web counterpart, with a possibly better story for accessibility queries.

We're also migrating the codebase to TypeScript. Please let us know if you're interested in helping us with this effort.

Stay safe!

- + \ No newline at end of file diff --git a/docs/react-navigation.html b/docs/react-navigation.html index eec592c4..d57266d2 100644 --- a/docs/react-navigation.html +++ b/docs/react-navigation.html @@ -4,13 +4,13 @@ React Navigation | React Native Testing Library - +

React Navigation

This section deals with integrating @testing-library/react-native with react-navigation, using Jest.

Stack Navigator​

Setting up​

Install the packages required for React Navigation. For this example, we will use a stack navigator to transition to the second page when any of the items are clicked on.

$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/stack react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens

Create an ./AppNavigator.js component which will list the navigation stack:

import 'react-native-gesture-handler';
import * as React from 'react';
import { createStackNavigator } from '@react-navigation/stack';

import HomeScreen from './screens/HomeScreen';
import DetailsScreen from './screens/DetailsScreen';

const { Screen, Navigator } = createStackNavigator();

export default function Navigation() {
const options = {};

return (
<Navigator>
<Screen name="Home" component={HomeScreen} />
<Screen options={options} name="Details" component={DetailsScreen} />
</Navigator>
);
}

Create your two screens which we will transition to and from them. The homescreen, found in ./screens/HomeScreen.js, contains a list of elements presented in a list view. On tap of any of these items will move to the details screen with the item number:

import * as React from 'react';
import {
Text,
View,
FlatList,
TouchableOpacity,
StyleSheet,
} from 'react-native';

export default function HomeScreen({ navigation }) {
const [items] = React.useState(
new Array(20).fill(null).map((_, idx) => idx + 1)
);

const onOpacityPress = (item) => navigation.navigate('Details', item);

return (
<View>
<Text style={styles.header}>List of numbers from 1 to 20</Text>
<FlatList
keyExtractor={(_, idx) => `${idx}`}
data={items}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => onOpacityPress(item)}
style={styles.row}
>
<Text>Item number {item}</Text>
</TouchableOpacity>
)}
/>
</View>
);
}

const divider = '#DDDDDD';

const styles = StyleSheet.create({
header: {
fontSize: 20,
textAlign: 'center',
marginVertical: 16,
},
row: {
paddingVertical: 16,
paddingHorizontal: 24,
borderBottomColor: divider,
borderBottomWidth: 1,
},
});

The details screen, found in ./screens/DetailsScreen.js, contains a header with the item number passed from the home screen:

// ./screens/DetailsScreen.js
import * as React from 'react';
import { Text, StyleSheet, View } from 'react-native';

export default function DetailsScreen(props) {
const item = Number.parseInt(props.route.params, 10);

return (
<View>
<Text style={styles.header}>Showing details for {item}</Text>
<Text style={styles.body}>the number you have chosen is {item}</Text>
</View>
);
}

const styles = StyleSheet.create({
header: {
fontSize: 20,
textAlign: 'center',
marginVertical: 16,
},
body: {
textAlign: 'center',
},
});

Setting up the test environment​

Install required dev dependencies:

$ yarn add -D jest @testing-library/react-native

Create your jest.config.js file (or place the following properties in your package.json as a "jest" property)

module.exports = {
preset: 'react-native',
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?@?react-native|@react-native-community|@react-navigation)',

// For pnpm you need to use inlcude `(?!(?:.pnpm/)?` part like this:
// 'node_modules/(?!(?:.pnpm/)?((jest-)?@?react-native|@react-native-community|@react-navigation))',
],
};

Notice the 2 entries that don't come with the default React Native project:

  • setupFiles – an array of files that Jest is going to execute before running your tests. In this case, we run react-native-gesture-handler/jestSetup.js which sets up necessary mocks for react-native-gesture-handler native module
  • transformIgnorePatterns – an array of paths that Jest ignores when transforming code. In this case, the negative lookahead regular expression is used, to tell Jest to transform (with Babel) every package inside node_modules/ that starts with react-native, @react-native-community or @react-navigation (added by us, the rest is in react-native preset by default, so you don't have to worry about it).

Example tests​

For this example, we are going to test out two things. The first thing is that the page is laid out as expected. The second, and most important, is that the page will transition to the detail screen when any item is tapped on.

Let's add a AppNavigator.test.js file in src/__tests__ directory:

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { render, screen, fireEvent } from '@testing-library/react-native';

import AppNavigator from '../AppNavigator';

// Silence the warning https://github.com/facebook/react-native/issues/11094#issuecomment-263240420
// Use with React Native <= 0.63
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');

// Use this instead with React Native >= 0.64
// jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');

describe('Testing react navigation', () => {
test('page contains the header and 10 items', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);

render(component);

const header = await screen.findByText('List of numbers from 1 to 20');
const items = await screen.findAllByText(/Item number/);

expect(header).toBeOnTheScreen();
expect(items.length).toBe(10);
});

test('clicking on one item takes you to the details screen', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);

render(component);
const toClick = await screen.findByText('Item number 5');

fireEvent(toClick, 'press');
const newHeader = await screen.findByText('Showing details for 5');
const newBody = await screen.findByText('the number you have chosen is 5');

expect(newHeader).toBeOnTheScreen();
expect(newBody).toBeOnTheScreen();
});
});

Drawer Navigator​

Testing the Drawer Navigation requires an additional setup step for mocking the Reanimated library.

Setting up​

Install the packages required for React Navigation. For this example, we will use a drawer navigator to transition between a home screen and an additional screen.

$ yarn add @react-native-community/masked-view @react-navigation/native @react-navigation/drawer react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens

Create a ./DrawerAppNavigator.js component which will list the navigation stack:

import 'react-native-gesture-handler';
import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';

const { Screen, Navigator } = createDrawerNavigator();

export default function Navigation() {
return (
<Navigator>
<Screen name="Home" component={HomeScreen} />
<Screen name="Notifications" component={NotificationsScreen} />
</Navigator>
);
}

Create your two screens which we will transition to and from:

function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Welcome!</Text>
<Button
onPress={() => navigation.navigate('Notifications')}
title="Go to notifications"
/>
</View>
);
}

function NotificationsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>This is the notifications screen</Text>
<Button onPress={() => navigation.goBack()} title="Go back home" />
</View>
);
}

Setting up the test environment​

Install required dev dependencies:

$ yarn add -D jest @testing-library/react-native

Create a mock file necessary for your tests:

import 'react-native-gesture-handler/jestSetup';

jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock');

// The mock for `call` immediately calls the callback which is incorrect
// So we override it with a no-op
Reanimated.default.call = () => {};

return Reanimated;
});

// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');

Create your jest.config.js file (or place the following properties in your package.json as a "jest" property)

module.exports = {
preset: 'react-native',
setupFiles: ['./jest-setup.js'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?react-native|@react-native-community|@react-navigation)',
],
};

Make sure that the path to the file in setupFiles is correct. Jest will run these files before running your tests, so it's the best place to put your global mocks.

This setup is copied from the React Navigation documentation.

Example tests​

For this example, we are going to test out two things. The first thing is that the screen is loaded correctly. The second, and most important, is that the page will transition to the notifications screen when the button is tapped on.

Let's add a DrawerAppNavigator.test.js file in src/__tests__ directory:

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { render, screen, fireEvent } from '@testing-library/react-native';

import DrawerAppNavigator from '../DrawerAppNavigator';

describe('Testing react navigation', () => {
test('screen contains a button linking to the notifications page', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

render(component);
const button = await screen.findByText('Go to notifications');

expect(button).toBeOnTheScreen();
});

test('clicking on the button takes you to the notifications screen', async () => {
const component = (
<NavigationContainer>
<DrawerAppNavigator />
</NavigationContainer>
);

render(component);
const oldScreen = screen.queryByText('Welcome!');
const button = await screen.findByText('Go to notifications');

expect(oldScreen).toBeOnTheScreen();

fireEvent(button, 'press');
const newScreen = await screen.findByText('This is the notifications screen');

expect(newScreen).toBeOnTheScreen();
});
});

Running tests​

To run the tests, place a test script inside your package.json

{
"scripts": {
"test": "jest"
}
}

And run the test script with npm test or yarn test.

- + \ No newline at end of file diff --git a/docs/redux-integration.html b/docs/redux-integration.html index 11a253a3..51262609 100644 --- a/docs/redux-integration.html +++ b/docs/redux-integration.html @@ -4,13 +4,13 @@ Redux Integration | React Native Testing Library - +

Redux Integration

This section deals with testing RN applications developed with Redux. We will be developing a simple TODO application capable of adding and removing an item. Once included, the timestamp is included.

Setting up​

An example of setting up can be found here.

Test cases​

Our test is on the components that either dispatch actions on the redux store or read some data from the redux store. This means we will test ./components/AddTodo.js and ./components/TodoList.js. Thus we will create ./components/AddTodo.test.js and ./components/TodoList.test.js

For ./components/AddTodo.test.js

import * as React from 'react';
import { Provider } from 'react-redux';
import { render, screen, fireEvent } from '@testing-library/react-native';
import configureStore from '../store';
import AddTodo from './AddTodo';

describe('AddTodo component test', () => {
test('adds a new TODO when the button is pressed', () => {
const store = configureStore();

const component = (
<Provider store={store}>
<AddTodo />
</Provider>
);

render(component);

// There is a TextInput.
// https://github.com/callstack/react-native-testing-library/blob/ae3d4af370487e1e8fedd8219f77225690aefc59/examples/redux/components/AddTodo.js#L24
const input = screen.getByPlaceholderText(/repository/i);
expect(input).toBeOnTheScreen();

const textToEnter = 'This is a random element';
fireEvent.changeText(input, textToEnter);
fireEvent.press(screen.getByText('Submit form'));

const todosState = store.getState().todos;

expect(todosState.length).toEqual(1);

expect(todosState).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 1,
text: textToEnter,
date: expect.any(Date),
}),
])
);
});
});

For ./components/TodoList.test.js

import * as React from 'react';
import { Provider } from 'react-redux';
import { render, screen, fireEvent } from '@testing-library/react-native';
import configureStore from '../store';
import TodoList from './TodoList';

describe('TodoList component test', () => {
test('it should execute with a store with 4 elements', () => {
const initialState = {
todos: [
{ id: 1, text: 'Sing something', date: new Date() },
{ id: 2, text: 'Dance something', date: new Date() },
{ id: 3, text: 'Sleep something', date: new Date() },
{ id: 4, text: 'Sleep something', date: new Date() },
],
};
const store = configureStore(initialState);

const component = (
<Provider store={store}>
<TodoList />
</Provider>
);

render(component);
const todoElems = screen.getAllByText(/something/i);

expect(todoElems.length).toEqual(4);
});

test('should execute with 2 elements and end up with 1 after delete', () => {
const initialState = {
todos: [
{ id: 1, text: 'Sing something', date: new Date() },
{ id: 2, text: 'Dance something', date: new Date() },
],
};
const store = configureStore(initialState);

const component = (
<Provider store={store}>
<TodoList />
</Provider>
);

render(component);
const todoElems = screen.getAllByText(/something/i);

expect(todoElems.length).toBe(2);

const buttons = screen.getAllByText('Delete');
expect(buttons.length).toBe(2);

fireEvent.press(buttons[0]);
expect(screen.getAllByText('Delete').length).toBe(1);
});
});

Running tests​

To run the tests, place a test script inside your package.json

{
"scripts": {
"test": "jest"
}
}

And run the test script with npm test or yarn test.

- + \ No newline at end of file diff --git a/docs/testing-env.html b/docs/testing-env.html index e3c575eb..f71042a7 100644 --- a/docs/testing-env.html +++ b/docs/testing-env.html @@ -4,14 +4,14 @@ Testing Environment | React Native Testing Library - +

Testing Environment

info

This document is intended for more advanced audience. You should be able to write integration or component tests without reading this. It is intended for people who want to better understand internals of our testing environment, e.g. in order to contribute to the codebase.

Testing Environment​

React Native Testing Library allows you to write integration and component tests for your React Native app or library. While the JSX code used in tests closely resembles your React Native app, the things are not quite as simple as they might appear. In this document we will describe the key elements of our testing environment and highlight things to be aware of when writing more advanced tests or diagnosing issues.

React renderers​

React allows you to write declarative code using JSX, write function or class components, or use hooks like useState. In order to output the results of your components it needs to work with a renderer. Every React app uses some type of renderer: React Native is a renderer for mobile apps, web apps use React DOM, and there are other more specialised renderers that can e.g. render to console or HTML canvas.

When you run your tests in React Native Testing Library, somewhat contrary to what the name suggest, they are actually not using React Native renderer. This is because this renderer needs to be run on iOS or Android operating system, so it would need to run on device or simulator.

React Test Renderer​

Instead, RNTL uses React Test Renderer which is a specialised renderer that allows rendering to pure JavaScript objects without access to mobile OS, and that can run in a Node.js environment using Jest (or any other JavaScript test runner).

Using React Test Renderer has pros and cons.

Benefits:

  • tests can run on most CIs (linux, etc) and do not require a mobile device or emulator
  • faster test execution
  • light runtime environment

Disadvantages:

  • Tests do not execute native code
  • Tests are not aware of view state that would be managed by native components, e.g. focus, unmanaged text boxes, etc.
  • Assertions do not operate on native view hierarchy
  • Runtime behaviours are simulated, sometimes imperfectly

It’s worth noting that React Testing Library (web one), works a bit different. While RTL also runs in Jest, it also has access to simulated browser DOM environment from jsdom package, so it can use a regular React DOM renderer. Unfortunately, there is no similar React Native runtime environment package. This is probably due to to the fact that while browser environment is well defined and highly standardised, the React Native environment is in constant evolution, in sync with the evolution of underlying OS-es. Maintaining such environment would require duplicating countless React Native behaviours, and keeping that in sync as React Native evolves.

Element tree​

Invoking render() function results in creation of an element tree. This is done internally by invoking TestRenderer.create() method. The output tree represents your React Native component tree, each node of that tree is an “instance” of some React component (to be more precise: each node represents a React fiber, and only class components have instances, while function components store the hook state using fiber).

These tree elements are represented by ReactTestInstance type:

interface ReactTestInstance {
type: ElementType;
props: { [propName: string]: any };
parent: ReactTestInstance | null;
children: Array<ReactTestInstance | string>;

// Other props and methods
}

Based on: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-test-renderer/index.d.ts

Host and composite components​

One of the most important aspects of the element tree is that it is composed of both host and composite components:

  • Host components are components that will have direct counterparts in the native view tree. Typical examples are <View>, <Text> , <TextInput>, and <Image> from React Native. You can think of these as analogue of <div>, <span> etc on the Web. You can also create your own host views as native modules or import them from 3rd party libraries, like React Navigation or React Native Gesture Handler.
  • Composite components are React code organisation units that exist only on the JavaScript side of your app. Typical examples are components you create (both function and class components), components imported from React Native (View, Text, etc) or from 3rd party packages.

That might sound a bit confusing at first, since we put React Native’s View in both categories. There are actually two View components: composite one and host one. The relation between them is as follows:

  • composite View is the type imported from react-native package. It’s a JavaScript component, which renders host View as its only child in the element tree.
  • host View , which you do not render directly. React Native takes the props you pass to the composite View, does some processing on them and passes them to host View.

The part of the tree looks as follows:

* <View> (composite)
* <View> (host)
* children prop passed in JSX

Similar relation exists between other composite and host pairs: e.g. Text , TextInput and Image components:

* <Text> (composite)
* <Text> (host)
* string (or mixed) content

Not all React Native components are organised this way, e.g. when you use Pressable (or TouchableOpacity) there is no host Pressable, but composite Pressable is rendering a host View with certain props being set:

* <Pressable> (composite)
* <View accessible={true} {...}> (host)
* children prop passed in JSX

Differentiating between host and composite elements​

Any easy way to differentiate between host and composite elements is the type prop of ReactTestInstance:

  • for host components it’s always a string value representing component name, e.g. "View"
  • for composite components it’s a function or class corresponding to the component

You can use the following code to check if given element is a host one:

function isHostElement(element: ReactTestInstance) {
return typeof element.type === 'string';
}

Tree nodes​

We encourage you to only assert values on host views in your tests, because they represent the user interface view and controls that the user will be able to see and interact with. Users cannot see, or interact with, composite views as they exist purely in JavaScript domain and do not generate any visible UI.

Asserting props​

As an example, if you make assertions on a style prop of a composite element, there is no guarantee that the style will be visible to the user, as the component author can forget to pass this prop to some underlying View or other host component. Similarly onPress event handler on a composite prop can be unreachable by the user.

function ForgotToPassPropsButton({ title, onPress, style }) {
return (
<Pressable>
<Text>{title}</Text>
</Pressable>
);
}

In the above example user defined components accepts both onPress and style props but does not pass it (through Pressable) to host views, so these props will not affect the user interface. Additionally, React Native and other libraries might pass some of the props under different names or transform their values between composite and host components.

Tree navigation​

caution

You should avoid navigating over element tree, as this makes your testing code fragile and may result in false positives. This section is more relevant for people how want to contribute to our codebase.

When navigating a tree of react elements using parent or children props of a ReactTestInstance element, you will encounter both host and composite elements. You should be careful when navigating the element tree, as the tree structure for 3rd party components and change independently from your code and cause unexpected test failures.

Inside RNTL we have various tree navigation helpers: getHostParent, getHostChildren, etc. These are intentionally not exported as using them is not a recommended practice.

Queries​

Most of the Testing Library queries return host components, in order to encourage best practices described above.

At this stage, there are some noteworthy exceptions:

  • *ByText queries returns composite Text element
  • *ByDisplayValue queries returns composite TextInput element
  • *ByPlaceholderText queries returns composite TextInput element

This will change in the near future, as we make efforts for all queries to return host components. Meanwhile it shouldn't be a huge issue, as composite Text and TextInputgenerally pass their props down to host counterparts.

Additionally, UNSAFE_*ByType and UNSAFE_*ByProps queries can return both host and composite components depending on used predicates. They are marked as unsafe precisely because testing composite components makes your test more fragile.

- + \ No newline at end of file diff --git a/docs/troubleshooting.html b/docs/troubleshooting.html index 1430a5e0..7c4c686e 100644 --- a/docs/troubleshooting.html +++ b/docs/troubleshooting.html @@ -4,13 +4,13 @@ Troubleshooting | React Native Testing Library - +

Troubleshooting

This guide describes common issues found by users when integrating React Native Test Library to their projects.

Matching React Native, React & React Test Renderer versions​

Check that you have matching versions of core dependencies:

  • React Native
  • React
  • React Test Renderer

React Native uses different versioning scheme from React, you can use React Native Upgrade Helper to find the correct matching between React Native & React versions. In case you use Expo, you should use dependency versions recommended by them and set by expo upgrade command.

React Test Renderer usually has same major & minor version as React, as they are closely related and React Test Renderer is part of React monorepo.

Related issues: #1061, #938, #920

Errors that might indicate that you are facing this issue:

  • TypeError: Cannot read property 'current' of undefined when calling render()
  • TypeError: Cannot read property 'isBatchingLegacy' of undefined when calling render()

Example repository​

We maintain an example repository that showcases a modern React Native Testing Library setup with TypeScript, Jest Native, etc.

In case something does not work in your setup you can refer to this repository for recommended configuration.

Act warnings​

When writing tests you may encounter warnings connected with act() function. There are two kinds of these warnings:

  • sync act() warning - Warning: An update to Component inside a test was not wrapped in act(...)
  • async act() warning - Warning: You called act(async () => ...) without await

You can read more about act() function in our understanding act function guide.

Normally, you should not encounter sync act() warnings, but if that happens this probably indicate an issue with your test and should be investigated.

In case of async act() function this might happen more or less randomly, especially if your components contain async logic. So far this warning does not seem to affect test correctness.

- + \ No newline at end of file diff --git a/docs/understanding-act.html b/docs/understanding-act.html index b2103bea..7a40744f 100644 --- a/docs/understanding-act.html +++ b/docs/understanding-act.html @@ -4,13 +4,13 @@ Understanding Act function | React Native Testing Library - +

Understanding Act function

When writing RNTL tests one of the things that confuses developers the most are cryptic act() function errors logged into console. In this article I will try to build an understanding of the purpose and behaviour of act() so you can build your tests with more confidence.

The act warnings​

Let’s start with typical act() warnings logged to console. There are two kinds of these issues, let’s call the first one the "sync act()" warning:

Warning: An update to Component inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
/* fire events that update state */
});
/* assert on the output */

The second one relates to async usage of act so let’s call it the "async act" error:

Warning: You called act(async () => ...) without await. This could lead to unexpected
testing behaviour, interleaving multiple act calls and mixing their scopes. You should
- await act(async () => ...);

Synchronous act​

Responsibility​

This function is intended only for using in automated tests and works only in development mode. Attempting to use it in production build will throw an error.

The responsibility for act function is to make React renders and updates work in tests in a similar way they work in real application by grouping and executing related units of interaction (e.g. renders, effects, etc) together.

To showcase that behaviour let make a small experiment. First we define a function component that uses useEffect hook in a trivial way.

function TestComponent() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setCount((c) => c + 1);
}, []);

return <Text>Count {count}</Text>;
}

In the following tests we will directly use ReactTestRenderer instead of RNTL render function to render our component for tests. In order to expose familiar queries like getByText we will use within function from RNTL.

test('render without act', () => {
const renderer = TestRenderer.create(<TestComponent />);

// Bind RNTL queries for root element.
const view = within(renderer.root);
expect(view.getByText('Count 0')).toBeOnTheScreen();
});

When testing without act call wrapping rendering call, we see that the assertion runs just after the rendering but before useEffecthooks effects are applied. Which is not what we expected in our tests.

test('render with act', () => {
let renderer: ReactTestRenderer;
act(() => {
renderer = TestRenderer.create(<TestComponent />);
});

// Bind RNTL queries for root element.
const view = within(renderer!.root);
expect(view.getByText('Count 1')).toBeOnTheScreen();
});

When wrapping rendering call with act we see that the changes caused by useEffect hook have been applied as we would expect.

When to use act​

The name act comes from Arrange-Act-Assert unit testing pattern. Which means it’s related to part of the test when we execute some actions on the component tree.

So far we learned that act function allows tests to wait for all pending React interactions to be applied before we make our assertions. When using act we get guarantee that any state updates will be executed as well as any enqueued effects will be executed.

Therefore, we should use act whenever there is some action that causes element tree to render, particularly:

  • initial render call - ReactTestRenderer.create call
  • re-rendering of component -renderer.update call
  • triggering any event handlers that cause component tree render

Thankfully, for these basic cases RNTL has got you covered as our render, update and fireEvent methods already wrap their calls in sync act so that you do not have to do it explicitly.

Note that act calls can be safely nested and internally form a stack of calls. However, overlapping act calls, which can be achieved using async version of act, are not supported.

Implementation​

As of React version of 18.1.0, the act implementation is defined in the ReactAct.js source file inside React repository. This implementation has been fairly stable since React 17.0.

RNTL exports act for convenience of the users as defined in the act.ts source file. That file refers to ReactTestRenderer.js source file from React Test Renderer package, which finally leads to React act implementation in ReactAct.js (already mentioned above).

Asynchronous act​

So far we have seen synchronous version of act which runs its callback immediately. This can deal with things like synchronous effects or mocks using already resolved promises. However, not all component code is synchronous. Frequently our components or mocks contain some asynchronous behaviours like setTimeout calls or network calls. Starting from React 16.9, act can also be called in asynchronous mode. In such case act implementation checks that the passed callback returns object resembling promise.

Asynchronous code​

Asynchronous version of act also is executed immediately, but the callback is not yet completed because of some asynchronous operations inside.

Lets look at a simple example with component using setTimeout call to simulate asynchronous behaviour:

function TestAsyncComponent() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setTimeout(() => {
setCount((c) => c + 1);
}, 50);
}, []);

return <Text>Count {count}</Text>;
}
test('render async natively', () => {
const view = render(<TestAsyncComponent />);
expect(view.getByText('Count 0')).toBeOnTheScreen();
});

If we test our component in a native way without handling its asynchronous behaviour we will end up with sync act warning:

Warning: An update to TestAsyncComponent inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
/* fire events that update state */
});
/* assert on the output */

Note that this is not yet the infamous async act warning. It only asks us to wrap our event code with act calls. However, this time our immediate state change does not originate from externally triggered events but rather forms an internal part of the component. So how can we apply act in such scenario?

Solution with fake timers​

First solution is to use Jest's fake timers inside out tests:

test('render with fake timers', () => {
jest.useFakeTimers();
const view = render(<TestAsyncComponent />);

act(() => {
jest.runAllTimers();
});
expect(view.getByText('Count 1')).toBeOnTheScreen();
});

That way we can wrap jest.runAllTimers() call which triggers the setTimeout updates inside an act call, hence resolving the act warning. Note that this whole code is synchronous thanks to usage of Jest fake timers.

Solution with real timers​

If we wanted to stick with real timers then things get a bit more complex. Let’s start by applying a crude solution of opening async act() call for the expected duration of components updates:

test('render with real timers - sleep', async () => {
const view = render(<TestAsyncComponent />);
await act(async () => {
await sleep(100); // Wait a bit longer than setTimeout in `TestAsyncComponent`
});

expect(view.getByText('Count 1')).toBeOnTheScreen();
});

This works correctly as we use an explicit async act() call that resolves the console error. However, it relies on our knowledge of exact implementation details which is a bad practice.

Let’s try more elegant solution using waitFor that will wait for our desired state:

test('render with real timers - waitFor', async () => {
const view = render(<TestAsyncComponent />);

await waitFor(() => view.getByText('Count 1'));
expect(view.getByText('Count 1')).toBeOnTheScreen();
});

This also works correctly, because waitFor call executes async act() call internally.

The above code can be simplified using findBy query:

test('render with real timers - findBy', async () => {
const view = render(<TestAsyncComponent />);

expect(await view.findByText('Count 1')).toBeOnTheScreen();
});

This also works since findByText internally calls waitFor which uses async act().

Note that all of the above examples are async tests using & awaiting async act() function call.

Async act warning​

If we modify any of the above async tests and remove await keyword, then we will trigger the notorious async act()warning:

Warning: You called act(async () => ...) without await. This could lead to unexpected
testing behaviour, interleaving multiple act calls and mixing their scopes. You should
- await act(async () => ...);

React decides to show this error whenever it detects that async act()call has not been awaited.

The exact reasons why you might see async act() warnings vary, but finally it means that act() has been called with callback that returns Promise-like object, but it has not been waited on.

References​

- + \ No newline at end of file diff --git a/docs/user-event.html b/docs/user-event.html index 09feb63c..976343b7 100644 --- a/docs/user-event.html +++ b/docs/user-event.html @@ -4,13 +4,13 @@ User Event | React Native Testing Library - +

User Event

Table of contents​

caution

User Event API is in beta stage.

This means that we plan to keep the public API signatures to remain stable, but we might introduce breaking behavioural changes, e.g. changing the ordering or timing of emitted events, without a major version update. Hopefully, well written code should not rely on such specific details.

Comparison with Fire Event API​

Fire Event is our original event simulation API. It offers ability to invoke any event handler declared on either host or composite elements. If the element does not have onEventName event handler for passed eventName event, or the element is disabled, Fire Event will traverse up the component tree, looking for event handler on both host and composite elements along the way. By default it will not pass any event data, but the user might provide it in the last argument.

In contrast, User Event provides realistic event simulation for main user interactions like press or type. Each of the interactions will trigger a sequence of events corresponding to React Native runtime behavior. These events will be invoked only on host elements, and will automatically receive event data corresponding to each event.

If User Event supports given interaction you should always prefer it over Fire Event counterpart, as it will make your tests much more realistic and hence reliable. In other cases, e.g. when event is not supported by User Event, or when invoking event handlers on composite elements, you have to use Fire Event as the only available option.

setup()​

userEvent.setup(options?: {
delay: number;
advanceTimers: (delay: number) => Promise<void> | void;
})

Example

const user = userEvent.setup();

Creates an User Event object instance which can be used to trigger events.

Options​

  • delay - controls the default delay between subsequent events, e.g. keystrokes.
  • advanceTimers - time advancement utility function that should be used for fake timers. The default setup handles both real timers and Jest fake timers.

press()​

press(
element: ReactTestInstance,
): Promise<void>

Example

const user = userEvent.setup();
await user.press(element);

This helper simulates a press on any pressable element, e.g. Pressable, TouchableOpacity, Text, TextInput, etc. Unlike fireEvent.press() which is a simpler API that will only call the onPress prop, this function simulates the entire press interaction in a more realistic way by reproducing event sequence emitted by React Native runtime. This helper will trigger additional events like pressIn and pressOut.

longPress()​

longPress(
element: ReactTestInstance,
options: { duration: number } = { duration: 500 }
): Promise<void>

Example

const user = userEvent.setup();
await user.longPress(element);

Simulates a long press user interaction. In React Native the longPress event is emitted when the press duration exceeds long press threshold (by default 500 ms). In other aspects this actions behaves similar to regular press action, e.g. by emitting pressIn and pressOut events. The press duration is customisable through the options. This should be useful if you use the delayLongPress prop. When using real timers this will take 500 ms so it is highly recommended to use that API with fake timers to prevent test taking a long time to run.

Options​

  • duration - duration of the press in miliseconds. Default value is 500 ms.

type()​

type(
element: ReactTestInstance,
text: string,
options?: {
skipPress?: boolean
submitEditing?: boolean
}

Example

const user = userEvent.setup();
await user.type(textInput, "Hello world!");

This helper simulates user focusing on TextInput element, typing text one character at a time, and leaving the element.

This function supports only host TextInput elements. Passing other element type will result in throwing error.

note

This function will add text to the text already present in the text input (as specified by value or defaultValue props). In order to replace existing text, use clear() helper first.

Options​

  • skipPress - if true, pressIn and pressOut events will not be triggered.
  • submitEditing - if true, submitEditing event will be triggered after typing the text.

Sequence of events​

The sequence of events depends on multiline prop, as well as passed options.

Events will not be emitted if editable prop is set to false.

Entering the element:

  • pressIn (optional)
  • focus
  • pressOut (optional)

The pressIn and pressOut events are sent by default, but can be skipped by passing skipPress: true option.

Typing (for each character):

  • keyPress
  • textInput (optional)
  • change
  • changeText
  • selectionChange

The textInput event is sent only for mutliline text inputs.

Leaving the element:

  • submitEditing (optional)
  • endEditing
  • blur

The submitEditing event is skipped by default. It can sent by setting submitEditing: true option.

clear()​

clear(
element: ReactTestInstance,
}

Example

const user = userEvent.setup();
await user.clear(textInput);

This helper simulates user clearing content of TextInput element.

This function supports only host TextInput elements. Passing other element type will result in throwing error.

Sequence of events​

The sequence of events depends on multiline prop, as well as passed options.

Events will not be emitted if editable prop is set to false.

Entering the element:

  • focus

Selecting all content:

  • selectionChange

Pressing backspace:

  • keyPress
  • textInput (optional)
  • change
  • changeText
  • selectionChange

The textInput event is sent only for mutliline text inputs.

Leaving the element:

  • endEditing
  • blur
- + \ No newline at end of file diff --git a/index.html b/index.html index 0c9daa0a..f5efefdc 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,13 @@ React Native Testing Library | React Native Testing Library - +

React Native Testing Library

Helps you to write better tests with less effort.

Get Started
Maintainable

Maintainable

Write maintainable tests for your React Native apps

Reliable

Reliable

Promotes testing public APIs and avoiding implementation details

Community Driven

Community Driven

Supported by React Native community and its core contributors

Like the project? ⚛️ Join the team who does amazing stuff for clients and drives React Native Open Source! 🔥
- + \ No newline at end of file diff --git a/search.html b/search.html index 90d66ff2..3af51943 100644 --- a/search.html +++ b/search.html @@ -4,13 +4,13 @@ Search the documentation | React Native Testing Library - +

Search the documentation

- + \ No newline at end of file