Skip to content

Commit 13bc78a

Browse files
refactor(v13): remove detect host component names (#1697)
1 parent 7905bb5 commit 13bc78a

10 files changed

+105
-314
lines changed

src/__tests__/config.test.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getConfig, configure, resetToDefaults, configureInternal } from '../config';
1+
import { getConfig, configure, resetToDefaults } from '../config';
22

33
beforeEach(() => {
44
resetToDefaults();
@@ -34,27 +34,11 @@ test('resetToDefaults() resets config to defaults', () => {
3434
});
3535

3636
test('resetToDefaults() resets internal config to defaults', () => {
37-
configureInternal({
38-
hostComponentNames: {
39-
text: 'A',
40-
textInput: 'A',
41-
image: 'A',
42-
switch: 'A',
43-
scrollView: 'A',
44-
modal: 'A',
45-
},
46-
});
47-
expect(getConfig().hostComponentNames).toEqual({
48-
text: 'A',
49-
textInput: 'A',
50-
image: 'A',
51-
switch: 'A',
52-
scrollView: 'A',
53-
modal: 'A',
54-
});
37+
configure({ asyncUtilTimeout: 2000 });
38+
expect(getConfig().asyncUtilTimeout).toBe(2000);
5539

5640
resetToDefaults();
57-
expect(getConfig().hostComponentNames).toBe(undefined);
41+
expect(getConfig().asyncUtilTimeout).toBe(1000);
5842
});
5943

6044
test('configure handles alias option defaultHidden', () => {
Lines changed: 36 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,48 @@
11
import * as React from 'react';
2-
import { View } from 'react-native';
3-
import TestRenderer from 'react-test-renderer';
4-
import { configureInternal, getConfig } from '../config';
2+
import { Image, Modal, ScrollView, Switch, Text, TextInput } from 'react-native';
53
import {
6-
getHostComponentNames,
7-
configureHostComponentNamesIfNeeded,
4+
isHostImage,
5+
isHostModal,
6+
isHostScrollView,
7+
isHostSwitch,
8+
isHostText,
9+
isHostTextInput,
810
} from '../helpers/host-component-names';
9-
import { act, render } from '..';
11+
import { render, screen } from '..';
1012

11-
describe('getHostComponentNames', () => {
12-
test('returns host component names from internal config', () => {
13-
configureInternal({
14-
hostComponentNames: {
15-
text: 'banana',
16-
textInput: 'banana',
17-
image: 'banana',
18-
switch: 'banana',
19-
scrollView: 'banana',
20-
modal: 'banana',
21-
},
22-
});
23-
24-
expect(getHostComponentNames()).toEqual({
25-
text: 'banana',
26-
textInput: 'banana',
27-
image: 'banana',
28-
switch: 'banana',
29-
scrollView: 'banana',
30-
modal: 'banana',
31-
});
32-
});
33-
34-
test('detects host component names if not present in internal config', () => {
35-
expect(getConfig().hostComponentNames).toBeUndefined();
36-
37-
const hostComponentNames = getHostComponentNames();
38-
39-
expect(hostComponentNames).toEqual({
40-
text: 'Text',
41-
textInput: 'TextInput',
42-
image: 'Image',
43-
switch: 'RCTSwitch',
44-
scrollView: 'RCTScrollView',
45-
modal: 'Modal',
46-
});
47-
expect(getConfig().hostComponentNames).toBe(hostComponentNames);
48-
});
49-
50-
// Repro test for case when user indirectly triggers `getHostComponentNames` calls from
51-
// explicit `act` wrapper.
52-
// See: https://github.com/callstack/react-native-testing-library/issues/1302
53-
// and https://github.com/callstack/react-native-testing-library/issues/1305
54-
test('does not throw when wrapped in act after render has been called', () => {
55-
render(<View />);
56-
expect(() =>
57-
act(() => {
58-
getHostComponentNames();
59-
}),
60-
).not.toThrow();
61-
});
13+
test('detects host Text component', () => {
14+
render(<Text>Hello</Text>);
15+
expect(isHostText(screen.root)).toBe(true);
6216
});
6317

64-
describe('configureHostComponentNamesIfNeeded', () => {
65-
test('updates internal config with host component names when they are not defined', () => {
66-
expect(getConfig().hostComponentNames).toBeUndefined();
67-
68-
configureHostComponentNamesIfNeeded();
69-
70-
expect(getConfig().hostComponentNames).toEqual({
71-
text: 'Text',
72-
textInput: 'TextInput',
73-
image: 'Image',
74-
switch: 'RCTSwitch',
75-
scrollView: 'RCTScrollView',
76-
modal: 'Modal',
77-
});
78-
});
79-
80-
test('does not update internal config when host component names are already configured', () => {
81-
configureInternal({
82-
hostComponentNames: {
83-
text: 'banana',
84-
textInput: 'banana',
85-
image: 'banana',
86-
switch: 'banana',
87-
scrollView: 'banana',
88-
modal: 'banana',
89-
},
90-
});
91-
92-
configureHostComponentNamesIfNeeded();
93-
94-
expect(getConfig().hostComponentNames).toEqual({
95-
text: 'banana',
96-
textInput: 'banana',
97-
image: 'banana',
98-
switch: 'banana',
99-
scrollView: 'banana',
100-
modal: 'banana',
101-
});
102-
});
103-
104-
test('throw an error when auto-detection fails', () => {
105-
const mockCreate = jest.spyOn(TestRenderer, 'create') as jest.Mock;
106-
const renderer = TestRenderer.create(<View />);
18+
// Some users might use the raw RCTText component directly for performance reasons.
19+
// See: https://blog.theodo.com/2023/10/native-views-rn-performance/
20+
test('detects raw RCTText component', () => {
21+
render(React.createElement('RCTText', { testID: 'text' }, 'Hello'));
22+
expect(isHostText(screen.root)).toBe(true);
23+
});
10724

108-
mockCreate.mockReturnValue({
109-
root: renderer.root,
110-
});
25+
test('detects host TextInput component', () => {
26+
render(<TextInput />);
27+
expect(isHostTextInput(screen.root)).toBe(true);
28+
});
11129

112-
expect(() => configureHostComponentNamesIfNeeded()).toThrowErrorMatchingInlineSnapshot(`
113-
"Trying to detect host component names triggered the following error:
30+
test('detects host Image component', () => {
31+
render(<Image />);
32+
expect(isHostImage(screen.root)).toBe(true);
33+
});
11434

115-
Unable to find an element with testID: text
35+
test('detects host Switch component', () => {
36+
render(<Switch />);
37+
expect(isHostSwitch(screen.root)).toBe(true);
38+
});
11639

117-
There seems to be an issue with your configuration that prevents React Native Testing Library from working correctly.
118-
Please check if you are using compatible versions of React Native and React Native Testing Library."
119-
`);
40+
test('detects host ScrollView component', () => {
41+
render(<ScrollView />);
42+
expect(isHostScrollView(screen.root)).toBe(true);
43+
});
12044

121-
mockCreate.mockReset();
122-
});
45+
test('detects host Modal component', () => {
46+
render(<Modal />);
47+
expect(isHostModal(screen.root)).toBe(true);
12348
});

src/__tests__/render.test.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable no-console */
22
import * as React from 'react';
33
import { Pressable, Text, TextInput, View } from 'react-native';
4-
import { getConfig, resetToDefaults } from '../config';
54
import { fireEvent, render, RenderAPI, screen } from '..';
65

76
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';
@@ -234,17 +233,9 @@ test('returned output can be spread using rest operator', () => {
234233
expect(rest).toBeTruthy();
235234
});
236235

237-
test('render calls detects host component names', () => {
238-
resetToDefaults();
239-
expect(getConfig().hostComponentNames).toBeUndefined();
240-
241-
render(<View testID="test" />);
242-
expect(getConfig().hostComponentNames).not.toBeUndefined();
243-
});
244-
245236
test('supports legacy rendering', () => {
246237
render(<View testID="test" />, { concurrentRoot: false });
247-
expect(screen.root).toBeDefined();
238+
expect(screen.root).toBeOnTheScreen();
248239
});
249240

250241
test('supports concurrent rendering', () => {

src/config.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,7 @@ export type ConfigAliasOptions = {
2626
defaultHidden: boolean;
2727
};
2828

29-
export type HostComponentNames = {
30-
text: string;
31-
textInput: string;
32-
image: string;
33-
switch: string;
34-
scrollView: string;
35-
modal: string;
36-
};
37-
38-
export type InternalConfig = Config & {
39-
/** Names for key React Native host components. */
40-
hostComponentNames?: HostComponentNames;
41-
};
42-
43-
const defaultConfig: InternalConfig = {
29+
const defaultConfig: Config = {
4430
asyncUtilTimeout: 1000,
4531
defaultIncludeHiddenElements: false,
4632
concurrentRoot: true,
@@ -66,13 +52,6 @@ export function configure(options: Partial<Config & ConfigAliasOptions>) {
6652
};
6753
}
6854

69-
export function configureInternal(option: Partial<InternalConfig>) {
70-
config = {
71-
...config,
72-
...option,
73-
};
74-
}
75-
7655
export function resetToDefaults() {
7756
config = { ...defaultConfig };
7857
}

src/fire-event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function isEventEnabled(
5252
eventName: string,
5353
nearestTouchResponder?: ReactTestInstance,
5454
) {
55-
if (isHostTextInput(nearestTouchResponder)) {
55+
if (nearestTouchResponder != null && isHostTextInput(nearestTouchResponder)) {
5656
return (
5757
isTextInputEditable(nearestTouchResponder) ||
5858
textInputEventsIgnoringEditableProp.has(eventName)

src/helpers/accessibility.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,7 @@ import {
77
} from 'react-native';
88
import { ReactTestInstance } from 'react-test-renderer';
99
import { getHostSiblings, getUnsafeRootElement } from './component-tree';
10-
import {
11-
getHostComponentNames,
12-
isHostImage,
13-
isHostSwitch,
14-
isHostText,
15-
isHostTextInput,
16-
} from './host-component-names';
10+
import { isHostImage, isHostSwitch, isHostText, isHostTextInput } from './host-component-names';
1711
import { getTextContent } from './text-content';
1812
import { isTextInputEditable } from './text-input';
1913

@@ -112,12 +106,7 @@ export function isAccessibilityElement(element: ReactTestInstance | null): boole
112106
return element.props.accessible;
113107
}
114108

115-
const hostComponentNames = getHostComponentNames();
116-
return (
117-
element?.type === hostComponentNames?.text ||
118-
element?.type === hostComponentNames?.textInput ||
119-
element?.type === hostComponentNames?.switch
120-
);
109+
return isHostText(element) || isHostTextInput(element) || isHostSwitch(element);
121110
}
122111

123112
/**

src/helpers/host-component-names.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { ReactTestInstance } from 'react-test-renderer';
2+
import { HostTestInstance } from './component-tree';
3+
4+
const HOST_TEXT_NAMES = ['Text', 'RCTText'];
5+
const HOST_TEXT_INPUT_NAMES = ['TextInput'];
6+
const HOST_IMAGE_NAMES = ['Image'];
7+
const HOST_SWITCH_NAMES = ['RCTSwitch'];
8+
const HOST_SCROLL_VIEW_NAMES = ['RCTScrollView'];
9+
const HOST_MODAL_NAMES = ['Modal'];
10+
11+
/**
12+
* Checks if the given element is a host Text element.
13+
* @param element The element to check.
14+
*/
15+
export function isHostText(element: ReactTestInstance): element is HostTestInstance {
16+
return typeof element?.type === 'string' && HOST_TEXT_NAMES.includes(element.type);
17+
}
18+
19+
/**
20+
* Checks if the given element is a host TextInput element.
21+
* @param element The element to check.
22+
*/
23+
export function isHostTextInput(element: ReactTestInstance): element is HostTestInstance {
24+
return typeof element?.type === 'string' && HOST_TEXT_INPUT_NAMES.includes(element.type);
25+
}
26+
27+
/**
28+
* Checks if the given element is a host Image element.
29+
* @param element The element to check.
30+
*/
31+
export function isHostImage(element: ReactTestInstance): element is HostTestInstance {
32+
return typeof element?.type === 'string' && HOST_IMAGE_NAMES.includes(element.type);
33+
}
34+
35+
/**
36+
* Checks if the given element is a host Switch element.
37+
* @param element The element to check.
38+
*/
39+
export function isHostSwitch(element: ReactTestInstance): element is HostTestInstance {
40+
return typeof element?.type === 'string' && HOST_SWITCH_NAMES.includes(element.type);
41+
}
42+
43+
/**
44+
* Checks if the given element is a host ScrollView element.
45+
* @param element The element to check.
46+
*/
47+
export function isHostScrollView(element: ReactTestInstance): element is HostTestInstance {
48+
return typeof element?.type === 'string' && HOST_SCROLL_VIEW_NAMES.includes(element.type);
49+
}
50+
51+
/**
52+
* Checks if the given element is a host Modal element.
53+
* @param element The element to check.
54+
*/
55+
export function isHostModal(element: ReactTestInstance): element is HostTestInstance {
56+
return typeof element?.type === 'string' && HOST_MODAL_NAMES.includes(element.type);
57+
}

0 commit comments

Comments
 (0)