diff --git a/src/fireEvent.ts b/src/fireEvent.ts index bdc6a5276..68746e300 100644 --- a/src/fireEvent.ts +++ b/src/fireEvent.ts @@ -7,9 +7,9 @@ import { ScrollViewProps, } from 'react-native'; import act from './act'; -import { isPointerEventEnabled } from './helpers/pointer-events'; import { isHostElement } from './helpers/component-tree'; import { isHostTextInput } from './helpers/host-component-names'; +import { isPointerEventEnabled } from './helpers/pointer-events'; type EventHandler = (...args: unknown[]) => unknown; diff --git a/src/helpers/component-tree.ts b/src/helpers/component-tree.ts index 3d3ea03eb..46993e956 100644 --- a/src/helpers/component-tree.ts +++ b/src/helpers/component-tree.ts @@ -1,10 +1,17 @@ import { ReactTestInstance } from 'react-test-renderer'; +/** + * ReactTestInstance referring to host element. + */ +export type HostTestInstance = ReactTestInstance & { type: string }; + /** * Checks if the given element is a host element. * @param element The element to check. */ -export function isHostElement(element?: ReactTestInstance | null) { +export function isHostElement( + element?: ReactTestInstance | null +): element is HostTestInstance { return typeof element?.type === 'string'; } @@ -14,7 +21,7 @@ export function isHostElement(element?: ReactTestInstance | null) { */ export function getHostParent( element: ReactTestInstance | null -): ReactTestInstance | null { +): HostTestInstance | null { if (element == null) { return null; } @@ -37,12 +44,12 @@ export function getHostParent( */ export function getHostChildren( element: ReactTestInstance | null -): ReactTestInstance[] { +): HostTestInstance[] { if (element == null) { return []; } - const hostChildren: ReactTestInstance[] = []; + const hostChildren: HostTestInstance[] = []; element.children.forEach((child) => { if (typeof child !== 'object') { @@ -68,10 +75,8 @@ export function getHostChildren( */ export function getHostSelves( element: ReactTestInstance | null -): ReactTestInstance[] { - return typeof element?.type === 'string' - ? [element] - : getHostChildren(element); +): HostTestInstance[] { + return isHostElement(element) ? [element] : getHostChildren(element); } /** @@ -80,7 +85,7 @@ export function getHostSelves( */ export function getHostSiblings( element: ReactTestInstance | null -): ReactTestInstance[] { +): HostTestInstance[] { const hostParent = getHostParent(element); const hostSelves = getHostSelves(element); return getHostChildren(hostParent).filter( diff --git a/src/helpers/filterNodeByType.ts b/src/helpers/filterNodeByType.ts deleted file mode 100644 index 1330d41c5..000000000 --- a/src/helpers/filterNodeByType.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { ReactTestInstance } from 'react-test-renderer'; -import * as React from 'react'; - -export const filterNodeByType = ( - node: ReactTestInstance | React.ReactElement, - type: React.ElementType | string -) => node.type === type; diff --git a/src/helpers/findAll.ts b/src/helpers/findAll.ts index 4204a9096..20c7c08a1 100644 --- a/src/helpers/findAll.ts +++ b/src/helpers/findAll.ts @@ -1,6 +1,7 @@ import { ReactTestInstance } from 'react-test-renderer'; import { getConfig } from '../config'; import { isHiddenFromAccessibility } from './accessiblity'; +import { HostTestInstance, isHostElement } from './component-tree'; interface FindAllOptions { /** Match elements hidden from accessibility */ @@ -17,7 +18,7 @@ export function findAll( root: ReactTestInstance, predicate: (element: ReactTestInstance) => boolean, options?: FindAllOptions -) { +): HostTestInstance[] { const results = findAllInternal(root, predicate, options); const includeHiddenElements = @@ -41,11 +42,11 @@ function findAllInternal( root: ReactTestInstance, predicate: (element: ReactTestInstance) => boolean, options?: FindAllOptions -): Array { - const results: ReactTestInstance[] = []; +): HostTestInstance[] { + const results: HostTestInstance[] = []; // Match descendants first but do not add them to results yet. - const matchingDescendants: ReactTestInstance[] = []; + const matchingDescendants: HostTestInstance[] = []; root.children.forEach((child) => { if (typeof child === 'string') { return; @@ -56,6 +57,7 @@ function findAllInternal( if ( // When matchDeepestOnly = true: add current element only if no descendants match (!options?.matchDeepestOnly || matchingDescendants.length === 0) && + isHostElement(root) && predicate(root) ) { results.push(root); diff --git a/src/helpers/host-component-names.tsx b/src/helpers/host-component-names.tsx index 3fd339b0d..d378424c3 100644 --- a/src/helpers/host-component-names.tsx +++ b/src/helpers/host-component-names.tsx @@ -3,6 +3,7 @@ import { ReactTestInstance } from 'react-test-renderer'; import { Switch, Text, TextInput, View } from 'react-native'; import { configureInternal, getConfig, HostComponentNames } from '../config'; import { renderWithAct } from '../render-act'; +import { HostTestInstance } from './component-tree'; const userConfigErrorMessage = `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.`; @@ -66,10 +67,22 @@ function getByTestId(instance: ReactTestInstance, testID: string) { return nodes[0]; } -export function isHostText(element?: ReactTestInstance) { +/** + * Checks if the given element is a host Text. + * @param element The element to check. + */ +export function isHostText( + element?: ReactTestInstance | null +): element is HostTestInstance { return element?.type === getHostComponentNames().text; } -export function isHostTextInput(element?: ReactTestInstance) { +/** + * Checks if the given element is a host TextInput. + * @param element The element to check. + */ +export function isHostTextInput( + element?: ReactTestInstance | null +): element is HostTestInstance { return element?.type === getHostComponentNames().textInput; } diff --git a/src/helpers/matchers/matchLabelText.ts b/src/helpers/matchers/matchLabelText.ts index 8058f2904..19c21669c 100644 --- a/src/helpers/matchers/matchLabelText.ts +++ b/src/helpers/matchers/matchLabelText.ts @@ -43,7 +43,6 @@ function matchAccessibilityLabelledBy( findAll( root, (node) => - typeof node.type === 'string' && node.props.nativeID === nativeId && matchTextContent(node, text, options) ).length > 0 diff --git a/src/queries/a11yState.ts b/src/queries/a11yState.ts index f9a61d164..80a759035 100644 --- a/src/queries/a11yState.ts +++ b/src/queries/a11yState.ts @@ -19,15 +19,11 @@ import { CommonQueryOptions } from './options'; const queryAllByA11yState = ( instance: ReactTestInstance -): (( - matcher: AccessibilityStateMatcher, - queryOptions?: CommonQueryOptions -) => Array) => +): QueryAllByQuery => function queryAllByA11yStateFn(matcher, queryOptions) { return findAll( instance, - (node) => - typeof node.type === 'string' && matchAccessibilityState(node, matcher), + (node) => matchAccessibilityState(node, matcher), queryOptions ); }; diff --git a/src/queries/a11yValue.ts b/src/queries/a11yValue.ts index f77881cff..753778548 100644 --- a/src/queries/a11yValue.ts +++ b/src/queries/a11yValue.ts @@ -19,15 +19,11 @@ import { CommonQueryOptions } from './options'; const queryAllByA11yValue = ( instance: ReactTestInstance -): (( - value: AccessibilityValueMatcher, - queryOptions?: CommonQueryOptions -) => Array) => +): QueryAllByQuery => function queryAllByA11yValueFn(value, queryOptions) { return findAll( instance, - (node) => - typeof node.type === 'string' && matchAccessibilityValue(node, value), + (node) => matchAccessibilityValue(node, value), queryOptions ); }; diff --git a/src/queries/displayValue.ts b/src/queries/displayValue.ts index 7ceef802f..d521dbf36 100644 --- a/src/queries/displayValue.ts +++ b/src/queries/displayValue.ts @@ -1,8 +1,7 @@ import type { ReactTestInstance } from 'react-test-renderer'; -import { filterNodeByType } from '../helpers/filterNodeByType'; import { findAll } from '../helpers/findAll'; +import { isHostTextInput } from '../helpers/host-component-names'; import { matches, TextMatch, TextMatchOptions } from '../matches'; -import { getHostComponentNames } from '../helpers/host-component-names'; import { makeQueries } from './makeQueries'; import type { FindAllByQuery, @@ -16,32 +15,26 @@ import type { CommonQueryOptions } from './options'; type ByDisplayValueOptions = CommonQueryOptions & TextMatchOptions; -const getTextInputNodeByDisplayValue = ( +const matchDisplayValue = ( node: ReactTestInstance, value: TextMatch, options: TextMatchOptions = {} ) => { const { exact, normalizer } = options; - const nodeValue = - node.props.value !== undefined ? node.props.value : node.props.defaultValue; + const nodeValue = node.props.value ?? node.props.defaultValue; - return ( - filterNodeByType(node, getHostComponentNames().textInput) && - matches(value, nodeValue, normalizer, exact) - ); + return matches(value, nodeValue, normalizer, exact); }; const queryAllByDisplayValue = ( instance: ReactTestInstance -): (( - displayValue: TextMatch, - queryOptions?: ByDisplayValueOptions -) => Array) => +): QueryAllByQuery => function queryAllByDisplayValueFn(displayValue, queryOptions) { return findAll( instance, (node) => - getTextInputNodeByDisplayValue(node, displayValue, queryOptions), + isHostTextInput(node) && + matchDisplayValue(node, displayValue, queryOptions), queryOptions ); }; diff --git a/src/queries/hintText.ts b/src/queries/hintText.ts index bcc55a4e8..d59d5133d 100644 --- a/src/queries/hintText.ts +++ b/src/queries/hintText.ts @@ -25,16 +25,11 @@ const getNodeByHintText = ( const queryAllByHintText = ( instance: ReactTestInstance -): (( - hint: TextMatch, - queryOptions?: ByHintTextOptions -) => Array) => +): QueryAllByQuery => function queryAllByA11yHintFn(hint, queryOptions) { return findAll( instance, - (node) => - typeof node.type === 'string' && - getNodeByHintText(node, hint, queryOptions), + (node) => getNodeByHintText(node, hint, queryOptions), queryOptions ); }; diff --git a/src/queries/labelText.ts b/src/queries/labelText.ts index 7b5fba156..9def35a9c 100644 --- a/src/queries/labelText.ts +++ b/src/queries/labelText.ts @@ -19,9 +19,7 @@ function queryAllByLabelText(instance: ReactTestInstance) { return (text: TextMatch, queryOptions?: ByLabelTextOptions) => { return findAll( instance, - (node) => - typeof node.type === 'string' && - matchLabelText(instance, node, text, queryOptions), + (node) => matchLabelText(instance, node, text, queryOptions), queryOptions ); }; diff --git a/src/queries/placeholderText.ts b/src/queries/placeholderText.ts index 613dc0e84..5967f5ccd 100644 --- a/src/queries/placeholderText.ts +++ b/src/queries/placeholderText.ts @@ -1,8 +1,7 @@ import type { ReactTestInstance } from 'react-test-renderer'; import { findAll } from '../helpers/findAll'; import { matches, TextMatch, TextMatchOptions } from '../matches'; -import { filterNodeByType } from '../helpers/filterNodeByType'; -import { getHostComponentNames } from '../helpers/host-component-names'; +import { isHostTextInput } from '../helpers/host-component-names'; import { makeQueries } from './makeQueries'; import type { FindAllByQuery, @@ -16,30 +15,24 @@ import type { CommonQueryOptions } from './options'; type ByPlaceholderTextOptions = CommonQueryOptions & TextMatchOptions; -const getTextInputNodeByPlaceholderText = ( +const matchPlaceholderText = ( node: ReactTestInstance, placeholder: TextMatch, options: TextMatchOptions = {} ) => { const { exact, normalizer } = options; - - return ( - filterNodeByType(node, getHostComponentNames().textInput) && - matches(placeholder, node.props.placeholder, normalizer, exact) - ); + return matches(placeholder, node.props.placeholder, normalizer, exact); }; const queryAllByPlaceholderText = ( instance: ReactTestInstance -): (( - placeholder: TextMatch, - queryOptions?: ByPlaceholderTextOptions -) => Array) => +): QueryAllByQuery => function queryAllByPlaceholderFn(placeholder, queryOptions) { return findAll( instance, (node) => - getTextInputNodeByPlaceholderText(node, placeholder, queryOptions), + isHostTextInput(node) && + matchPlaceholderText(node, placeholder, queryOptions), queryOptions ); }; diff --git a/src/queries/role.ts b/src/queries/role.ts index 7baa326fe..8f45508f0 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -61,13 +61,12 @@ const matchAccessibilityValueIfNeeded = ( const queryAllByRole = ( instance: ReactTestInstance -): ((role: TextMatch, options?: ByRoleOptions) => Array) => +): QueryAllByQuery => function queryAllByRoleFn(role, options) { return findAll( instance, (node) => // run the cheapest checks first, and early exit to avoid unneeded computations - typeof node.type === 'string' && isAccessibilityElement(node) && matchStringProp(node.props.accessibilityRole, role) && matchAccessibleStateIfNeeded(node, options) && diff --git a/src/queries/testId.ts b/src/queries/testId.ts index c33122b91..d5a0af7fc 100644 --- a/src/queries/testId.ts +++ b/src/queries/testId.ts @@ -14,27 +14,22 @@ import type { CommonQueryOptions } from './options'; type ByTestIdOptions = CommonQueryOptions & TextMatchOptions; -const getNodeByTestId = ( +const matchTestId = ( node: ReactTestInstance, - testID: TextMatch, + testId: TextMatch, options: TextMatchOptions = {} ) => { const { exact, normalizer } = options; - return matches(testID, node.props.testID, normalizer, exact); + return matches(testId, node.props.testID, normalizer, exact); }; const queryAllByTestId = ( instance: ReactTestInstance -): (( - testId: TextMatch, - queryOptions?: ByTestIdOptions -) => Array) => +): QueryAllByQuery => function queryAllByTestIdFn(testId, queryOptions) { return findAll( instance, - (node) => - typeof node.type === 'string' && - getNodeByTestId(node, testId, queryOptions), + (node) => matchTestId(node, testId, queryOptions), queryOptions ); }; diff --git a/src/queries/text.ts b/src/queries/text.ts index 425f0153c..0cb9ede3a 100644 --- a/src/queries/text.ts +++ b/src/queries/text.ts @@ -1,7 +1,6 @@ import type { ReactTestInstance } from 'react-test-renderer'; -import { filterNodeByType } from '../helpers/filterNodeByType'; import { findAll } from '../helpers/findAll'; -import { getHostComponentNames } from '../helpers/host-component-names'; +import { isHostText } from '../helpers/host-component-names'; import { matchTextContent } from '../helpers/matchers/matchTextContent'; import { TextMatch, TextMatchOptions } from '../matches'; import { makeQueries } from './makeQueries'; @@ -19,13 +18,11 @@ type ByTextOptions = CommonQueryOptions & TextMatchOptions; const queryAllByText = ( instance: ReactTestInstance -): ((text: TextMatch, options?: ByTextOptions) => Array) => +): QueryAllByQuery => function queryAllByTextFn(text, options = {}) { return findAll( instance, - (node) => - filterNodeByType(node, getHostComponentNames().text) && - matchTextContent(node, text, options), + (node) => isHostText(node) && matchTextContent(node, text, options), { ...options, matchDeepestOnly: true, diff --git a/src/render.tsx b/src/render.tsx index 05c6e0671..abe150f06 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -117,7 +117,7 @@ function buildRenderResult( rerender: update, // alias for `update` toJSON: renderer.toJSON, debug: debug(instance, renderer), - get root() { + get root(): ReactTestInstance { return getHostChildren(instance)[0]; }, UNSAFE_root: instance,