diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts
index d20f91707..803cfd621 100644
--- a/src/__tests__/config.test.ts
+++ b/src/__tests__/config.test.ts
@@ -1,4 +1,4 @@
-import { getConfig, configure, resetToDefaults, configureInternal } from '../config';
+import { getConfig, configure, resetToDefaults } from '../config';
beforeEach(() => {
resetToDefaults();
@@ -34,27 +34,11 @@ test('resetToDefaults() resets config to defaults', () => {
});
test('resetToDefaults() resets internal config to defaults', () => {
- configureInternal({
- hostComponentNames: {
- text: 'A',
- textInput: 'A',
- image: 'A',
- switch: 'A',
- scrollView: 'A',
- modal: 'A',
- },
- });
- expect(getConfig().hostComponentNames).toEqual({
- text: 'A',
- textInput: 'A',
- image: 'A',
- switch: 'A',
- scrollView: 'A',
- modal: 'A',
- });
+ configure({ asyncUtilTimeout: 2000 });
+ expect(getConfig().asyncUtilTimeout).toBe(2000);
resetToDefaults();
- expect(getConfig().hostComponentNames).toBe(undefined);
+ expect(getConfig().asyncUtilTimeout).toBe(1000);
});
test('configure handles alias option defaultHidden', () => {
diff --git a/src/__tests__/host-component-names.test.tsx b/src/__tests__/host-component-names.test.tsx
index 0e55f1a82..d3050c8ec 100644
--- a/src/__tests__/host-component-names.test.tsx
+++ b/src/__tests__/host-component-names.test.tsx
@@ -1,123 +1,48 @@
import * as React from 'react';
-import { View } from 'react-native';
-import TestRenderer from 'react-test-renderer';
-import { configureInternal, getConfig } from '../config';
+import { Image, Modal, ScrollView, Switch, Text, TextInput } from 'react-native';
import {
- getHostComponentNames,
- configureHostComponentNamesIfNeeded,
+ isHostImage,
+ isHostModal,
+ isHostScrollView,
+ isHostSwitch,
+ isHostText,
+ isHostTextInput,
} from '../helpers/host-component-names';
-import { act, render } from '..';
+import { render, screen } from '..';
-describe('getHostComponentNames', () => {
- test('returns host component names from internal config', () => {
- configureInternal({
- hostComponentNames: {
- text: 'banana',
- textInput: 'banana',
- image: 'banana',
- switch: 'banana',
- scrollView: 'banana',
- modal: 'banana',
- },
- });
-
- expect(getHostComponentNames()).toEqual({
- text: 'banana',
- textInput: 'banana',
- image: 'banana',
- switch: 'banana',
- scrollView: 'banana',
- modal: 'banana',
- });
- });
-
- test('detects host component names if not present in internal config', () => {
- expect(getConfig().hostComponentNames).toBeUndefined();
-
- const hostComponentNames = getHostComponentNames();
-
- expect(hostComponentNames).toEqual({
- text: 'Text',
- textInput: 'TextInput',
- image: 'Image',
- switch: 'RCTSwitch',
- scrollView: 'RCTScrollView',
- modal: 'Modal',
- });
- expect(getConfig().hostComponentNames).toBe(hostComponentNames);
- });
-
- // Repro test for case when user indirectly triggers `getHostComponentNames` calls from
- // explicit `act` wrapper.
- // See: https://github.com/callstack/react-native-testing-library/issues/1302
- // and https://github.com/callstack/react-native-testing-library/issues/1305
- test('does not throw when wrapped in act after render has been called', () => {
- render();
- expect(() =>
- act(() => {
- getHostComponentNames();
- }),
- ).not.toThrow();
- });
+test('detects host Text component', () => {
+ render(Hello);
+ expect(isHostText(screen.root)).toBe(true);
});
-describe('configureHostComponentNamesIfNeeded', () => {
- test('updates internal config with host component names when they are not defined', () => {
- expect(getConfig().hostComponentNames).toBeUndefined();
-
- configureHostComponentNamesIfNeeded();
-
- expect(getConfig().hostComponentNames).toEqual({
- text: 'Text',
- textInput: 'TextInput',
- image: 'Image',
- switch: 'RCTSwitch',
- scrollView: 'RCTScrollView',
- modal: 'Modal',
- });
- });
-
- test('does not update internal config when host component names are already configured', () => {
- configureInternal({
- hostComponentNames: {
- text: 'banana',
- textInput: 'banana',
- image: 'banana',
- switch: 'banana',
- scrollView: 'banana',
- modal: 'banana',
- },
- });
-
- configureHostComponentNamesIfNeeded();
-
- expect(getConfig().hostComponentNames).toEqual({
- text: 'banana',
- textInput: 'banana',
- image: 'banana',
- switch: 'banana',
- scrollView: 'banana',
- modal: 'banana',
- });
- });
-
- test('throw an error when auto-detection fails', () => {
- const mockCreate = jest.spyOn(TestRenderer, 'create') as jest.Mock;
- const renderer = TestRenderer.create();
+// Some users might use the raw RCTText component directly for performance reasons.
+// See: https://blog.theodo.com/2023/10/native-views-rn-performance/
+test('detects raw RCTText component', () => {
+ render(React.createElement('RCTText', { testID: 'text' }, 'Hello'));
+ expect(isHostText(screen.root)).toBe(true);
+});
- mockCreate.mockReturnValue({
- root: renderer.root,
- });
+test('detects host TextInput component', () => {
+ render();
+ expect(isHostTextInput(screen.root)).toBe(true);
+});
- expect(() => configureHostComponentNamesIfNeeded()).toThrowErrorMatchingInlineSnapshot(`
- "Trying to detect host component names triggered the following error:
+test('detects host Image component', () => {
+ render();
+ expect(isHostImage(screen.root)).toBe(true);
+});
- Unable to find an element with testID: text
+test('detects host Switch component', () => {
+ render();
+ expect(isHostSwitch(screen.root)).toBe(true);
+});
- 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."
- `);
+test('detects host ScrollView component', () => {
+ render();
+ expect(isHostScrollView(screen.root)).toBe(true);
+});
- mockCreate.mockReset();
- });
+test('detects host Modal component', () => {
+ render();
+ expect(isHostModal(screen.root)).toBe(true);
});
diff --git a/src/__tests__/render.test.tsx b/src/__tests__/render.test.tsx
index fae79012b..58acb4535 100644
--- a/src/__tests__/render.test.tsx
+++ b/src/__tests__/render.test.tsx
@@ -1,7 +1,6 @@
/* eslint-disable no-console */
import * as React from 'react';
import { Pressable, Text, TextInput, View } from 'react-native';
-import { getConfig, resetToDefaults } from '../config';
import { fireEvent, render, RenderAPI, screen } from '..';
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
@@ -234,17 +233,9 @@ test('returned output can be spread using rest operator', () => {
expect(rest).toBeTruthy();
});
-test('render calls detects host component names', () => {
- resetToDefaults();
- expect(getConfig().hostComponentNames).toBeUndefined();
-
- render();
- expect(getConfig().hostComponentNames).not.toBeUndefined();
-});
-
test('supports legacy rendering', () => {
render(, { concurrentRoot: false });
- expect(screen.root).toBeDefined();
+ expect(screen.root).toBeOnTheScreen();
});
test('supports concurrent rendering', () => {
diff --git a/src/config.ts b/src/config.ts
index 742963376..7d5d74617 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -26,21 +26,7 @@ export type ConfigAliasOptions = {
defaultHidden: boolean;
};
-export type HostComponentNames = {
- text: string;
- textInput: string;
- image: string;
- switch: string;
- scrollView: string;
- modal: string;
-};
-
-export type InternalConfig = Config & {
- /** Names for key React Native host components. */
- hostComponentNames?: HostComponentNames;
-};
-
-const defaultConfig: InternalConfig = {
+const defaultConfig: Config = {
asyncUtilTimeout: 1000,
defaultIncludeHiddenElements: false,
concurrentRoot: true,
@@ -66,13 +52,6 @@ export function configure(options: Partial) {
};
}
-export function configureInternal(option: Partial) {
- config = {
- ...config,
- ...option,
- };
-}
-
export function resetToDefaults() {
config = { ...defaultConfig };
}
diff --git a/src/fire-event.ts b/src/fire-event.ts
index 0f0287f5e..fe3fe6d79 100644
--- a/src/fire-event.ts
+++ b/src/fire-event.ts
@@ -52,7 +52,7 @@ export function isEventEnabled(
eventName: string,
nearestTouchResponder?: ReactTestInstance,
) {
- if (isHostTextInput(nearestTouchResponder)) {
+ if (nearestTouchResponder != null && isHostTextInput(nearestTouchResponder)) {
return (
isTextInputEditable(nearestTouchResponder) ||
textInputEventsIgnoringEditableProp.has(eventName)
diff --git a/src/helpers/accessibility.ts b/src/helpers/accessibility.ts
index 5eee9401a..062ea8fb2 100644
--- a/src/helpers/accessibility.ts
+++ b/src/helpers/accessibility.ts
@@ -7,13 +7,7 @@ import {
} from 'react-native';
import { ReactTestInstance } from 'react-test-renderer';
import { getHostSiblings, getUnsafeRootElement } from './component-tree';
-import {
- getHostComponentNames,
- isHostImage,
- isHostSwitch,
- isHostText,
- isHostTextInput,
-} from './host-component-names';
+import { isHostImage, isHostSwitch, isHostText, isHostTextInput } from './host-component-names';
import { getTextContent } from './text-content';
import { isTextInputEditable } from './text-input';
@@ -112,12 +106,7 @@ export function isAccessibilityElement(element: ReactTestInstance | null): boole
return element.props.accessible;
}
- const hostComponentNames = getHostComponentNames();
- return (
- element?.type === hostComponentNames?.text ||
- element?.type === hostComponentNames?.textInput ||
- element?.type === hostComponentNames?.switch
- );
+ return isHostText(element) || isHostTextInput(element) || isHostSwitch(element);
}
/**
diff --git a/src/helpers/host-component-names.ts b/src/helpers/host-component-names.ts
new file mode 100644
index 000000000..c960bf264
--- /dev/null
+++ b/src/helpers/host-component-names.ts
@@ -0,0 +1,57 @@
+import { ReactTestInstance } from 'react-test-renderer';
+import { HostTestInstance } from './component-tree';
+
+const HOST_TEXT_NAMES = ['Text', 'RCTText'];
+const HOST_TEXT_INPUT_NAMES = ['TextInput'];
+const HOST_IMAGE_NAMES = ['Image'];
+const HOST_SWITCH_NAMES = ['RCTSwitch'];
+const HOST_SCROLL_VIEW_NAMES = ['RCTScrollView'];
+const HOST_MODAL_NAMES = ['Modal'];
+
+/**
+ * Checks if the given element is a host Text element.
+ * @param element The element to check.
+ */
+export function isHostText(element: ReactTestInstance): element is HostTestInstance {
+ return typeof element?.type === 'string' && HOST_TEXT_NAMES.includes(element.type);
+}
+
+/**
+ * Checks if the given element is a host TextInput element.
+ * @param element The element to check.
+ */
+export function isHostTextInput(element: ReactTestInstance): element is HostTestInstance {
+ return typeof element?.type === 'string' && HOST_TEXT_INPUT_NAMES.includes(element.type);
+}
+
+/**
+ * Checks if the given element is a host Image element.
+ * @param element The element to check.
+ */
+export function isHostImage(element: ReactTestInstance): element is HostTestInstance {
+ return typeof element?.type === 'string' && HOST_IMAGE_NAMES.includes(element.type);
+}
+
+/**
+ * Checks if the given element is a host Switch element.
+ * @param element The element to check.
+ */
+export function isHostSwitch(element: ReactTestInstance): element is HostTestInstance {
+ return typeof element?.type === 'string' && HOST_SWITCH_NAMES.includes(element.type);
+}
+
+/**
+ * Checks if the given element is a host ScrollView element.
+ * @param element The element to check.
+ */
+export function isHostScrollView(element: ReactTestInstance): element is HostTestInstance {
+ return typeof element?.type === 'string' && HOST_SCROLL_VIEW_NAMES.includes(element.type);
+}
+
+/**
+ * Checks if the given element is a host Modal element.
+ * @param element The element to check.
+ */
+export function isHostModal(element: ReactTestInstance): element is HostTestInstance {
+ return typeof element?.type === 'string' && HOST_MODAL_NAMES.includes(element.type);
+}
diff --git a/src/helpers/host-component-names.tsx b/src/helpers/host-component-names.tsx
deleted file mode 100644
index b450c930b..000000000
--- a/src/helpers/host-component-names.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import * as React from 'react';
-import { ReactTestInstance } from 'react-test-renderer';
-import { Image, Modal, ScrollView, 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.`;
-
-export function getHostComponentNames(): HostComponentNames {
- let hostComponentNames = getConfig().hostComponentNames;
- if (!hostComponentNames) {
- hostComponentNames = detectHostComponentNames();
- configureInternal({ hostComponentNames });
- }
-
- return hostComponentNames;
-}
-
-export function configureHostComponentNamesIfNeeded() {
- const configHostComponentNames = getConfig().hostComponentNames;
- if (configHostComponentNames) {
- return;
- }
-
- const hostComponentNames = detectHostComponentNames();
- configureInternal({ hostComponentNames });
-}
-
-function detectHostComponentNames(): HostComponentNames {
- try {
- const renderer = renderWithAct(
-
- Hello
-
-
-
-
-
- ,
- );
-
- return {
- text: getByTestId(renderer.root, 'text').type as string,
- textInput: getByTestId(renderer.root, 'textInput').type as string,
- image: getByTestId(renderer.root, 'image').type as string,
- switch: getByTestId(renderer.root, 'switch').type as string,
- scrollView: getByTestId(renderer.root, 'scrollView').type as string,
- modal: getByTestId(renderer.root, 'modal').type as string,
- };
- } 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${userConfigErrorMessage}`,
- );
- }
-}
-
-function getByTestId(instance: ReactTestInstance, testID: string) {
- const nodes = instance.findAll(
- (node) => typeof node.type === 'string' && node.props.testID === testID,
- );
-
- if (nodes.length === 0) {
- throw new Error(`Unable to find an element with testID: ${testID}`);
- }
-
- return nodes[0];
-}
-
-/**
- * Checks if the given element is a host Text element.
- * @param element The element to check.
- */
-export function isHostText(element?: ReactTestInstance | null): element is HostTestInstance {
- return element?.type === getHostComponentNames().text;
-}
-
-/**
- * Checks if the given element is a host TextInput element.
- * @param element The element to check.
- */
-export function isHostTextInput(element?: ReactTestInstance | null): element is HostTestInstance {
- return element?.type === getHostComponentNames().textInput;
-}
-
-/**
- * Checks if the given element is a host Image element.
- * @param element The element to check.
- */
-export function isHostImage(element?: ReactTestInstance | null): element is HostTestInstance {
- return element?.type === getHostComponentNames().image;
-}
-
-/**
- * Checks if the given element is a host Switch element.
- * @param element The element to check.
- */
-export function isHostSwitch(element?: ReactTestInstance | null): element is HostTestInstance {
- return element?.type === getHostComponentNames().switch;
-}
-
-/**
- * Checks if the given element is a host ScrollView element.
- * @param element The element to check.
- */
-export function isHostScrollView(element?: ReactTestInstance | null): element is HostTestInstance {
- return element?.type === getHostComponentNames().scrollView;
-}
-
-/**
- * Checks if the given element is a host Modal element.
- * @param element The element to check.
- */
-export function isHostModal(element?: ReactTestInstance | null): element is HostTestInstance {
- return element?.type === getHostComponentNames().modal;
-}
diff --git a/src/render-hook.tsx b/src/render-hook.tsx
index f6e7cf08b..ba30077b0 100644
--- a/src/render-hook.tsx
+++ b/src/render-hook.tsx
@@ -37,7 +37,6 @@ export function renderHook(
,
{
wrapper,
- detectHostComponentNames: false,
},
);
diff --git a/src/render.tsx b/src/render.tsx
index 7727130c2..dfbec3155 100644
--- a/src/render.tsx
+++ b/src/render.tsx
@@ -8,9 +8,8 @@ import { Profiler } from 'react';
import act from './act';
import { addToCleanupQueue } from './cleanup';
import { getConfig } from './config';
-import { getHostChildren } from './helpers/component-tree';
+import { getHostSelves } from './helpers/component-tree';
import { debug, DebugOptions } from './helpers/debug';
-import { configureHostComponentNamesIfNeeded } from './helpers/host-component-names';
import { validateStringsRenderedWithinText } from './helpers/string-validation';
import { renderWithAct } from './render-act';
import { setRenderResult } from './screen';
@@ -43,18 +42,10 @@ export default function render(component: React.ReactElement, options: Ren
return renderInternal(component, options);
}
-export interface RenderInternalOptions extends RenderOptions {
- detectHostComponentNames?: boolean;
-}
-
-export function renderInternal(
- component: React.ReactElement,
- options?: RenderInternalOptions,
-) {
+export function renderInternal(component: React.ReactElement, options?: RenderOptions) {
const {
wrapper: Wrapper,
concurrentRoot,
- detectHostComponentNames = true,
unstable_validateStringsRenderedWithinText,
...rest
} = options || {};
@@ -65,10 +56,6 @@ export function renderInternal(
unstable_isConcurrent: concurrentRoot ?? getConfig().concurrentRoot,
};
- if (detectHostComponentNames) {
- configureHostComponentNamesIfNeeded();
- }
-
if (unstable_validateStringsRenderedWithinText) {
return renderWithStringValidation(component, {
wrapper: Wrapper,
@@ -130,7 +117,7 @@ function buildRenderResult(
toJSON: renderer.toJSON,
debug: makeDebug(instance, renderer),
get root(): ReactTestInstance {
- return getHostChildren(instance)[0];
+ return getHostSelves(instance)[0];
},
UNSAFE_root: instance,
};