diff --git a/jest-setup.ts b/jest-setup.ts new file mode 100644 index 000000000..5d2c4d5b2 --- /dev/null +++ b/jest-setup.ts @@ -0,0 +1,7 @@ +import { resetToDefaults } from './src/pure'; + +jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); + +beforeEach(() => { + resetToDefaults(); +}); diff --git a/jestSetup.js b/jestSetup.js deleted file mode 100644 index 7060fd312..000000000 --- a/jestSetup.js +++ /dev/null @@ -1 +0,0 @@ -jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); diff --git a/package.json b/package.json index ee2c6f152..9112914d4 100644 --- a/package.json +++ b/package.json @@ -87,9 +87,7 @@ }, "jest": { "preset": "./jest-preset", - "setupFiles": [ - "./jestSetup.js" - ], + "setupFilesAfterEnv": ["./jest-setup.ts"], "testPathIgnorePatterns": [ "timerUtils", "examples/" diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts index c197b9c3a..03de47adc 100644 --- a/src/__tests__/config.test.ts +++ b/src/__tests__/config.test.ts @@ -5,10 +5,6 @@ import { configureInternal, } from '../config'; -beforeEach(() => { - resetToDefaults(); -}); - test('getConfig() returns existing configuration', () => { expect(getConfig().useBreakingChanges).toEqual(false); expect(getConfig().asyncUtilTimeout).toEqual(1000); diff --git a/src/__tests__/host-component-names.test.tsx b/src/__tests__/host-component-names.test.tsx new file mode 100644 index 000000000..98b1cbc6d --- /dev/null +++ b/src/__tests__/host-component-names.test.tsx @@ -0,0 +1,48 @@ +import { View } from 'react-native'; +import TestRenderer from 'react-test-renderer'; +import { configureInternal, getConfig } from '../config'; +import { getHostComponentNames } from '../helpers/host-component-names'; + +const mockCreate = jest.spyOn(TestRenderer, 'create') as jest.Mock; + +describe('getHostComponentNames', () => { + test('updates internal config with host component names when they are not defined', () => { + expect(getConfig().hostComponentNames).toBeUndefined(); + + getHostComponentNames(); + + expect(getConfig().hostComponentNames).toEqual({ + text: 'Text', + textInput: 'TextInput', + }); + }); + + test('does not update internal config when host component names are already configured', () => { + configureInternal({ + hostComponentNames: { text: 'banana', textInput: 'banana' }, + }); + + getHostComponentNames(); + + expect(getConfig().hostComponentNames).toEqual({ + text: 'banana', + textInput: 'banana', + }); + }); + + test('throw an error when autodetection fails', () => { + mockCreate.mockReturnValue({ + root: { type: View, children: [], props: {} }, + }); + + expect(() => getHostComponentNames()).toThrowErrorMatchingInlineSnapshot(` + "Trying to detect host component names triggered the following error: + + Unable to find an element with testID: text + + There seems to be an issue with your configuration that prevents React Native Testing Library from working correctly. + Please check if you are using compatible versions of React Native and React Native Testing Library. + " + `); + }); +}); diff --git a/src/__tests__/render-debug.test.tsx b/src/__tests__/render-debug.test.tsx index 6697de3da..bd6652430 100644 --- a/src/__tests__/render-debug.test.tsx +++ b/src/__tests__/render-debug.test.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { View, Text, TextInput, Pressable } from 'react-native'; import stripAnsi from 'strip-ansi'; -import { render, fireEvent, resetToDefaults, configure } from '..'; +import { render, fireEvent, configure } from '..'; type ConsoleLogMock = jest.Mock>; @@ -18,7 +18,6 @@ const ignoreWarnings = ['Using debug("message") is deprecated']; const realConsoleWarn = console.warn; beforeEach(() => { - resetToDefaults(); jest.spyOn(console, 'log').mockImplementation(() => {}); jest.spyOn(console, 'warn').mockImplementation((message) => { if (!ignoreWarnings.some((warning) => message.includes(warning))) { diff --git a/src/__tests__/render.test.tsx b/src/__tests__/render.test.tsx index 5cd7e0f97..53a0185c8 100644 --- a/src/__tests__/render.test.tsx +++ b/src/__tests__/render.test.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import * as React from 'react'; import { View, Text, TextInput, Pressable, SafeAreaView } from 'react-native'; -import { render, fireEvent, RenderAPI, resetToDefaults } from '..'; +import { render, fireEvent, RenderAPI } from '..'; const PLACEHOLDER_FRESHNESS = 'Add custom freshness'; const PLACEHOLDER_CHEF = 'Who inspected freshness?'; @@ -10,10 +10,6 @@ const INPUT_CHEF = 'I inspected freshie'; const DEFAULT_INPUT_CHEF = 'What did you inspect?'; const DEFAULT_INPUT_CUSTOMER = 'What banana?'; -beforeEach(() => { - resetToDefaults(); -}); - class MyButton extends React.Component { render() { return ( diff --git a/src/__tests__/waitFor.test.tsx b/src/__tests__/waitFor.test.tsx index 913d76adb..15daf8ac7 100644 --- a/src/__tests__/waitFor.test.tsx +++ b/src/__tests__/waitFor.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Text, TouchableOpacity, View, Pressable } from 'react-native'; -import { fireEvent, render, waitFor, configure, resetToDefaults } from '..'; +import { fireEvent, render, waitFor, configure } from '..'; class Banana extends React.Component { changeFresh = () => { @@ -19,10 +19,6 @@ class Banana extends React.Component { } } -beforeEach(() => { - resetToDefaults(); -}); - class BananaContainer extends React.Component<{}, any> { state = { fresh: false }; diff --git a/src/config.ts b/src/config.ts index 64de13d13..732206e38 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,6 +3,7 @@ import { DebugOptions } from './helpers/debugDeep'; /** * Global configuration options for React Native Testing Library. */ + export type Config = { /** Default timeout, in ms, for `waitFor` and `findBy*` queries. */ asyncUtilTimeout: number; @@ -19,9 +20,17 @@ export type ConfigAliasOptions = { defaultHidden: boolean; }; +export type HostComponentNames = { + text: string; + textInput: string; +}; + export type InternalConfig = Config & { /** Whether to use breaking changes intended for next major version release. */ useBreakingChanges: boolean; + + /** Names for key React Native host components. */ + hostComponentNames?: HostComponentNames; }; const defaultConfig: InternalConfig = { diff --git a/src/fireEvent.ts b/src/fireEvent.ts index f5ec35f4f..c4e451dc3 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -3,6 +3,7 @@ import { TextInput } from 'react-native'; import act from './act'; import { isHostElement } from './helpers/component-tree'; import { filterNodeByType } from './helpers/filterNodeByType'; +import { getHostComponentNames } from './helpers/host-component-names'; type EventHandler = (...args: any) => unknown; @@ -14,12 +15,11 @@ const isTextInput = (element?: ReactTestInstance) => { // We have to test if the element type is either the TextInput component // (which would if it is a composite component) or the string // TextInput (which would be true if it is a host component) - // All queries but the one by testID return composite component and event - // if all queries returned host components, since fireEvent bubbles up + // All queries return host components but since fireEvent bubbles up // it would trigger the parent prop without the composite component check return ( filterNodeByType(element, TextInput) || - filterNodeByType(element, 'TextInput') + filterNodeByType(element, getHostComponentNames().textInput) ); }; diff --git a/src/helpers/host-component-names.tsx b/src/helpers/host-component-names.tsx new file mode 100644 index 000000000..d4611702f --- /dev/null +++ b/src/helpers/host-component-names.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Text, TextInput, View } from 'react-native'; +import TestRenderer from 'react-test-renderer'; +import { configureInternal, getConfig, HostComponentNames } from '../config'; +import { getQueriesForElement } from '../within'; + +const defaultErrorMessage = `There seems to be an issue with your configuration that prevents React Native Testing Library from working correctly. +Please check if you are using compatible versions of React Native and React Native Testing Library.`; + +export function getHostComponentNames(): HostComponentNames { + const configHostComponentNames = getConfig().hostComponentNames; + if (configHostComponentNames) { + return configHostComponentNames; + } + + try { + const renderer = TestRenderer.create( + + Hello + + + ); + const { getByTestId } = getQueriesForElement(renderer.root); + const textHostName = getByTestId('text').type; + const textInputHostName = getByTestId('textInput').type; + + // This code path should not happen as getByTestId always returns host elements. + if ( + typeof textHostName !== 'string' || + typeof textInputHostName !== 'string' + ) { + throw new Error(defaultErrorMessage); + } + + const hostComponentNames = { + text: textHostName, + textInput: textInputHostName, + }; + configureInternal({ hostComponentNames }); + return hostComponentNames; + } catch (error) { + const errorMessage = + error && typeof error === 'object' && 'message' in error + ? error.message + : null; + + throw new Error(`Trying to detect host component names triggered the following error:\n\n${errorMessage}\n\n${defaultErrorMessage} +`); + } +} diff --git a/src/helpers/matchers/matchTextContent.ts b/src/helpers/matchers/matchTextContent.ts index 8b011b916..cf7770e27 100644 --- a/src/helpers/matchers/matchTextContent.ts +++ b/src/helpers/matchers/matchTextContent.ts @@ -1,15 +1,20 @@ import { Text } from 'react-native'; import type { ReactTestInstance } from 'react-test-renderer'; +import { getConfig } from '../../config'; import { matches, TextMatch, TextMatchOptions } from '../../matches'; import { filterNodeByType } from '../filterNodeByType'; import { getTextContent } from '../getTextContent'; +import { getHostComponentNames } from '../host-component-names'; export function matchTextContent( node: ReactTestInstance, text: TextMatch, options: TextMatchOptions = {} ) { - if (!filterNodeByType(node, Text)) { + const textType = getConfig().useBreakingChanges + ? getHostComponentNames().text + : Text; + if (!filterNodeByType(node, textType)) { return false; } diff --git a/src/queries/__tests__/displayValue.breaking.test.tsx b/src/queries/__tests__/displayValue.breaking.test.tsx new file mode 100644 index 000000000..1bcb79b88 --- /dev/null +++ b/src/queries/__tests__/displayValue.breaking.test.tsx @@ -0,0 +1,136 @@ +import * as React from 'react'; +import { View, TextInput } from 'react-native'; + +import { render } from '../..'; +import { configureInternal } from '../../config'; + +const PLACEHOLDER_FRESHNESS = 'Add custom freshness'; +const PLACEHOLDER_CHEF = 'Who inspected freshness?'; +const INPUT_FRESHNESS = 'Custom Freshie'; +const INPUT_CHEF = 'I inspected freshie'; +const DEFAULT_INPUT_CHEF = 'What did you inspect?'; +const DEFAULT_INPUT_CUSTOMER = 'What banana?'; + +beforeEach(() => configureInternal({ useBreakingChanges: true })); + +const Banana = () => ( + + + + + + +); + +test('getByDisplayValue, queryByDisplayValue', () => { + const { getByDisplayValue, queryByDisplayValue } = render(); + const input = getByDisplayValue(/custom/i); + + expect(input.props.value).toBe(INPUT_FRESHNESS); + + const sameInput = getByDisplayValue(INPUT_FRESHNESS); + + expect(sameInput.props.value).toBe(INPUT_FRESHNESS); + expect(() => getByDisplayValue('no value')).toThrow( + 'Unable to find an element with displayValue: no value' + ); + + expect(queryByDisplayValue(/custom/i)).toBe(input); + expect(queryByDisplayValue('no value')).toBeNull(); + expect(() => queryByDisplayValue(/fresh/i)).toThrow( + 'Found multiple elements with display value: /fresh/i' + ); +}); + +test('getByDisplayValue, queryByDisplayValue get element by default value only when value is undefined', () => { + const { getByDisplayValue, queryByDisplayValue } = render(); + expect(() => + getByDisplayValue(DEFAULT_INPUT_CHEF) + ).toThrowErrorMatchingInlineSnapshot( + `"Unable to find an element with displayValue: What did you inspect?"` + ); + expect(queryByDisplayValue(DEFAULT_INPUT_CHEF)).toBeNull(); + + expect(() => getByDisplayValue('hello')).toThrowErrorMatchingInlineSnapshot( + `"Unable to find an element with displayValue: hello"` + ); + expect(queryByDisplayValue('hello')).toBeNull(); + + expect(getByDisplayValue(DEFAULT_INPUT_CUSTOMER)).toBeTruthy(); + expect(queryByDisplayValue(DEFAULT_INPUT_CUSTOMER)).toBeTruthy(); +}); + +test('getAllByDisplayValue, queryAllByDisplayValue', () => { + const { getAllByDisplayValue, queryAllByDisplayValue } = render(); + const inputs = getAllByDisplayValue(/fresh/i); + + expect(inputs).toHaveLength(2); + expect(() => getAllByDisplayValue('no value')).toThrow( + 'Unable to find an element with displayValue: no value' + ); + + expect(queryAllByDisplayValue(/fresh/i)).toEqual(inputs); + expect(queryAllByDisplayValue('no value')).toHaveLength(0); +}); + +test('findBy queries work asynchronously', async () => { + const options = { timeout: 10 }; // Short timeout so that this test runs quickly + const { rerender, findByDisplayValue, findAllByDisplayValue } = render( + + ); + + await expect( + findByDisplayValue('Display Value', {}, options) + ).rejects.toBeTruthy(); + await expect( + findAllByDisplayValue('Display Value', {}, options) + ).rejects.toBeTruthy(); + + setTimeout( + () => + rerender( + + + + ), + 20 + ); + + await expect(findByDisplayValue('Display Value')).resolves.toBeTruthy(); + await expect(findAllByDisplayValue('Display Value')).resolves.toHaveLength(1); +}, 20000); + +test('byDisplayValue queries support hidden option', () => { + const { getByDisplayValue, queryByDisplayValue } = render( + + ); + + expect(getByDisplayValue('hidden')).toBeTruthy(); + expect( + getByDisplayValue('hidden', { includeHiddenElements: true }) + ).toBeTruthy(); + + expect( + queryByDisplayValue('hidden', { includeHiddenElements: false }) + ).toBeFalsy(); + expect(() => + getByDisplayValue('hidden', { includeHiddenElements: false }) + ).toThrowErrorMatchingInlineSnapshot( + `"Unable to find an element with displayValue: hidden"` + ); +}); + +test('byDisplayValue should return host component', () => { + const { getByDisplayValue } = render(); + + expect(getByDisplayValue('value').type).toBe('TextInput'); +}); diff --git a/src/queries/__tests__/displayValue.test.tsx b/src/queries/__tests__/displayValue.test.tsx index 1ba94bff2..e8f351c2b 100644 --- a/src/queries/__tests__/displayValue.test.tsx +++ b/src/queries/__tests__/displayValue.test.tsx @@ -119,3 +119,9 @@ test('byDisplayValue queries support hidden option', () => { `"Unable to find an element with displayValue: hidden"` ); }); + +test('byDisplayValue should return composite TextInput', () => { + const { getByDisplayValue } = render(); + + expect(getByDisplayValue('value').type).toBe(TextInput); +}); diff --git a/src/queries/__tests__/placeholderText.breaking.test.tsx b/src/queries/__tests__/placeholderText.breaking.test.tsx new file mode 100644 index 000000000..c3a35fb0d --- /dev/null +++ b/src/queries/__tests__/placeholderText.breaking.test.tsx @@ -0,0 +1,93 @@ +import * as React from 'react'; +import { View, TextInput } from 'react-native'; +import { render } from '../..'; +import { configureInternal } from '../../config'; + +const PLACEHOLDER_FRESHNESS = 'Add custom freshness'; +const PLACEHOLDER_CHEF = 'Who inspected freshness?'; +const INPUT_FRESHNESS = 'Custom Freshie'; +const INPUT_CHEF = 'I inspected freshie'; +const DEFAULT_INPUT_CHEF = 'What did you inspect?'; + +beforeEach(() => { + configureInternal({ useBreakingChanges: true }); +}); + +const Banana = () => ( + + + + +); + +test('getByPlaceholderText, queryByPlaceholderText', () => { + const { getByPlaceholderText, queryByPlaceholderText } = render(); + const input = getByPlaceholderText(/custom/i); + + expect(input.props.placeholder).toBe(PLACEHOLDER_FRESHNESS); + + const sameInput = getByPlaceholderText(PLACEHOLDER_FRESHNESS); + + expect(sameInput.props.placeholder).toBe(PLACEHOLDER_FRESHNESS); + expect(() => getByPlaceholderText('no placeholder')).toThrow( + 'Unable to find an element with placeholder: no placeholder' + ); + + expect(queryByPlaceholderText(/add/i)).toBe(input); + expect(queryByPlaceholderText('no placeholder')).toBeNull(); + expect(() => queryByPlaceholderText(/fresh/)).toThrow( + 'Found multiple elements with placeholder: /fresh/ ' + ); +}); + +test('getAllByPlaceholderText, queryAllByPlaceholderText', () => { + const { getAllByPlaceholderText, queryAllByPlaceholderText } = render( + + ); + const inputs = getAllByPlaceholderText(/fresh/i); + + expect(inputs).toHaveLength(2); + expect(() => getAllByPlaceholderText('no placeholder')).toThrow( + 'Unable to find an element with placeholder: no placeholder' + ); + + expect(queryAllByPlaceholderText(/fresh/i)).toEqual(inputs); + expect(queryAllByPlaceholderText('no placeholder')).toHaveLength(0); +}); + +test('byPlaceholderText queries support hidden option', () => { + const { getByPlaceholderText, queryByPlaceholderText } = render( + + ); + + expect(getByPlaceholderText('hidden')).toBeTruthy(); + expect( + getByPlaceholderText('hidden', { includeHiddenElements: true }) + ).toBeTruthy(); + + expect( + queryByPlaceholderText('hidden', { includeHiddenElements: false }) + ).toBeFalsy(); + expect(() => + getByPlaceholderText('hidden', { includeHiddenElements: false }) + ).toThrowErrorMatchingInlineSnapshot( + `"Unable to find an element with placeholder: hidden"` + ); +}); + +test('byPlaceHolderText should return host component', () => { + const { getByPlaceholderText } = render( + + ); + + expect(getByPlaceholderText('placeholder').type).toBe('TextInput'); +}); diff --git a/src/queries/__tests__/placeholderText.test.tsx b/src/queries/__tests__/placeholderText.test.tsx index ef75bfe36..c16cf1f59 100644 --- a/src/queries/__tests__/placeholderText.test.tsx +++ b/src/queries/__tests__/placeholderText.test.tsx @@ -78,3 +78,11 @@ test('byPlaceholderText queries support hidden option', () => { `"Unable to find an element with placeholder: hidden"` ); }); + +test('byPlaceHolderText should return composite component', () => { + const { getByPlaceholderText } = render( + + ); + + expect(getByPlaceholderText('placeholder').type).toBe(TextInput); +}); diff --git a/src/queries/__tests__/text.breaking.test.tsx b/src/queries/__tests__/text.breaking.test.tsx new file mode 100644 index 000000000..322588ad7 --- /dev/null +++ b/src/queries/__tests__/text.breaking.test.tsx @@ -0,0 +1,515 @@ +import * as React from 'react'; +import { + View, + Text, + TouchableOpacity, + Image, + Button, + TextInput, +} from 'react-native'; +import { render, getDefaultNormalizer, within } from '../..'; +import { configureInternal } from '../../config'; + +beforeEach(() => { + configureInternal({ useBreakingChanges: true }); +}); + +test('byText matches simple text', () => { + const { getByText } = render(Hello World); + expect(getByText('Hello World').props.testID).toBe('text'); +}); + +test('byText matches inner nested text', () => { + const { getByText } = render( + + Hello World + + ); + expect(getByText('Hello World').props.testID).toBe('inner'); +}); + +test('byText matches accross multiple texts', () => { + const { getByText } = render( + + Hello World + + ); + expect(getByText('Hello World').props.testID).toBe('outer'); +}); + +type MyButtonProps = { + children: React.ReactNode; + onPress: () => void; +}; +const MyButton = ({ children, onPress }: MyButtonProps) => ( + + {children} + +); + +const Banana = () => { + const test = 0; + return ( + + Is the banana fresh? + not fresh + + Change freshness! + First Text + Second Text + {test} + + ); +}; + +type ChildrenProps = { children: React.ReactNode }; + +test('getByText, queryByText', () => { + const { getByText, queryByText } = render(); + const button = getByText(/change/i); + + expect(button.props.children).toBe('Change freshness!'); + + const sameButton = getByText('not fresh'); + + expect(sameButton.props.children).toBe('not fresh'); + expect(() => getByText('InExistent')).toThrow( + 'Unable to find an element with text: InExistent' + ); + + const zeroText = getByText('0'); + + expect(queryByText(/change/i)).toBe(button); + expect(queryByText('InExistent')).toBeNull(); + expect(() => queryByText(/fresh/)).toThrow( + 'Found multiple elements with text: /fresh/' + ); + expect(queryByText('0')).toBe(zeroText); +}); + +test('getByText, queryByText with children as Array', () => { + type BananaCounterProps = { numBananas: number }; + const BananaCounter = ({ numBananas }: BananaCounterProps) => ( + There are {numBananas} bananas in the bunch + ); + + const BananaStore = () => ( + + + + + + ); + + const { getByText } = render(); + + const threeBananaBunch = getByText('There are 3 bananas in the bunch'); + expect(threeBananaBunch.props.children).toEqual([ + 'There are ', + 3, + ' bananas in the bunch', + ]); +}); + +test('getAllByText, queryAllByText', () => { + const { getAllByText, queryAllByText } = render(); + const buttons = getAllByText(/fresh/i); + + expect(buttons).toHaveLength(3); + expect(() => getAllByText('InExistent')).toThrow( + 'Unable to find an element with text: InExistent' + ); + + expect(queryAllByText(/fresh/i)).toEqual(buttons); + expect(queryAllByText('InExistent')).toHaveLength(0); +}); + +test('findByText queries work asynchronously', async () => { + const options = { timeout: 10 }; // Short timeout so that this test runs quickly + const { rerender, findByText, findAllByText } = render(); + await expect(findByText('Some Text', {}, options)).rejects.toBeTruthy(); + await expect(findAllByText('Some Text', {}, options)).rejects.toBeTruthy(); + + setTimeout( + () => + rerender( + + Some Text + + ), + 20 + ); + + await expect(findByText('Some Text')).resolves.toBeTruthy(); + await expect(findAllByText('Some Text')).resolves.toHaveLength(1); +}, 20000); + +test('getByText works properly with custom text component', () => { + function BoldText({ children }: ChildrenProps) { + return {children}; + } + + expect( + render( + + Hello + + ).getByText('Hello') + ).toBeTruthy(); +}); + +test('getByText works properly with custom text container', () => { + function MyText({ children }: ChildrenProps) { + return {children}; + } + function BoldText({ children }: ChildrenProps) { + return {children}; + } + + expect( + render( + + Hello + + ).getByText('Hello') + ).toBeTruthy(); +}); + +test('queryByText nested in at start', () => { + expect( + render( + + + Hello + + ).queryByText('Hello') + ).toBeTruthy(); +}); + +test('queryByText nested in at end', () => { + expect( + render( + + Hello + + + ).queryByText('Hello') + ).toBeTruthy(); +}); + +test('queryByText nested in in middle', () => { + expect( + render( + + Hello + + World + + ).queryByText('HelloWorld') + ).toBeTruthy(); +}); + +test('queryByText not found', () => { + expect( + render( + + Hello + + + ).queryByText('SomethingElse') + ).toBeFalsy(); +}); + +test('*ByText matches text across multiple nested Text', () => { + const { getByText } = render( + + Hello{' '} + + World + !{true} + + + ); + + expect(getByText('Hello World!')).toBeTruthy(); +}); + +test('queryByText with nested Text components return the closest Text', () => { + const NestedTexts = () => ( + + My text + + ); + + const { queryByText } = render(); + expect(queryByText('My text', { exact: false })?.props.nativeID).toBe('2'); +}); + +test('queryByText with nested Text components each with text return the lowest one', () => { + const NestedTexts = () => ( + + bob + My text + + ); + + const { queryByText } = render(); + + expect(queryByText('My text', { exact: false })?.props.nativeID).toBe('2'); +}); + +test('queryByText nested deep in ', () => { + const CustomText = ({ children }: ChildrenProps) => { + return {children}; + }; + + expect( + render( + + Hello World! + + ).getByText('Hello World!') + ).toBeTruthy(); +}); + +test('queryByText with nested Text components: not-exact text match returns the most deeply nested common component', () => { + const { queryByText: queryByTextFirstCase } = render( + + bob + My + text + + ); + + const { queryByText: queryByTextSecondCase } = render( + + bob + My text for test + + ); + + expect(queryByTextFirstCase('My text')).toBe(null); + expect( + queryByTextSecondCase('My text', { exact: false })?.props.nativeID + ).toBe('2'); +}); + +test('queryAllByText does not match several times the same text', () => { + const allMatched = render( + + Start + This is a long text + + ).queryAllByText('long text', { exact: false }); + expect(allMatched.length).toBe(1); + expect(allMatched[0].props.nativeID).toBe('2'); +}); + +test('queryAllByText matches all the matching nodes', () => { + const allMatched = render( + + Start + This is a long text + This is another long text + + ).queryAllByText('long text', { exact: false }); + expect(allMatched.length).toBe(2); + expect(allMatched.map((node) => node.props.nativeID)).toEqual(['2', '3']); +}); + +describe('supports TextMatch options', () => { + test('getByText, getAllByText', () => { + const { getByText, getAllByText } = render( + + Text and details +