Skip to content

Commit 8e521ff

Browse files
committed
refactor: migrate to universal-text-renderer
1 parent ab06cf5 commit 8e521ff

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+304
-1385
lines changed

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
"peerDependencies": {
5555
"jest": ">=28.0.0",
5656
"react": ">=18.0.0",
57-
"react-native": ">=0.59"
57+
"react-native": ">=0.59",
58+
"universal-test-renderer": "0.1.2"
5859
},
5960
"peerDependenciesMeta": {
6061
"jest": {
@@ -87,7 +88,8 @@
8788
"react-native": "0.76.0-rc.6",
8889
"release-it": "^17.6.0",
8990
"strip-ansi": "^6.0.1",
90-
"typescript": "^5.5.4"
91+
"typescript": "^5.5.4",
92+
"universal-test-renderer": "0.1.2"
9193
},
9294
"publishConfig": {
9395
"registry": "https://registry.npmjs.org"

src/__tests__/host-component-names.test.tsx

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
configureHostComponentNamesIfNeeded,
77
} from '../helpers/host-component-names';
88
import { act, render } from '..';
9-
import * as rendererModule from '../renderer/renderer';
109

1110
describe('getHostComponentNames', () => {
1211
test('returns host component names from internal config', () => {
@@ -100,26 +99,4 @@ describe('configureHostComponentNamesIfNeeded', () => {
10099
modal: 'banana',
101100
});
102101
});
103-
104-
test('throw an error when auto-detection fails', () => {
105-
const renderer = rendererModule.createRenderer();
106-
renderer.render(<View />);
107-
108-
const mockCreateRenderer = jest
109-
.spyOn(rendererModule, 'createRenderer')
110-
.mockReturnValue(renderer);
111-
// @ts-expect-error
112-
jest.spyOn(renderer, 'render').mockReturnValue(renderer.root);
113-
114-
expect(() => configureHostComponentNamesIfNeeded()).toThrowErrorMatchingInlineSnapshot(`
115-
"Trying to detect host component names triggered the following error:
116-
117-
Unable to find an element with testID: text
118-
119-
There seems to be an issue with your configuration that prevents React Native Testing Library from working correctly.
120-
Please check if you are using compatible versions of React Native and React Native Testing Library."
121-
`);
122-
123-
mockCreateRenderer.mockReset();
124-
});
125102
});

src/__tests__/render-hook.test.tsx

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-disable jest/no-conditional-expect */
22
import React, { ReactNode } from 'react';
3-
import * as rendererModule from '../renderer/renderer';
43
import { renderHook } from '../pure';
54

65
test('gives committed result', () => {
@@ -86,22 +85,3 @@ test('props type is inferred correctly when initial props is explicitly undefine
8685

8786
expect(result.current).toBe(6);
8887
});
89-
90-
/**
91-
* This test makes sure that calling renderHook does
92-
* not try to detect host component names in any form.
93-
* But since there are numerous methods that could trigger that
94-
* we check the count of renders using React Test Renderers.
95-
*/
96-
test('does render only once', () => {
97-
const renderer = rendererModule.createRenderer();
98-
const renderSpy = jest.spyOn(renderer, 'render');
99-
jest.spyOn(rendererModule, 'createRenderer').mockReturnValue(renderer);
100-
101-
renderHook(() => {
102-
const [state, setState] = React.useState(1);
103-
return [state, setState];
104-
});
105-
106-
expect(renderSpy).toHaveBeenCalledTimes(1);
107-
});

src/__tests__/render.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* eslint-disable no-console */
22
import * as React from 'react';
33
import { Pressable, Text, TextInput, View } from 'react-native';
4+
import { CONTAINER_TYPE } from 'universal-test-renderer';
45
import { getConfig } from '../config';
5-
import { CONTAINER_TYPE } from '../renderer/contants';
66
import { fireEvent, render, type RenderAPI, screen, resetToDefaults } from '..';
77

88
const PLACEHOLDER_FRESHNESS = 'Add custom freshness';

src/fire-event.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@ import {
55
PressableProps,
66
ScrollViewProps,
77
} from 'react-native';
8+
import { HostComponent } from 'universal-test-renderer';
89
import act from './act';
910
import { isValidElement } from './helpers/component-tree';
1011
import { isHostScrollView, isHostTextInput } from './helpers/host-component-names';
1112
import { isPointerEventEnabled } from './helpers/pointer-events';
1213
import { isTextInputEditable } from './helpers/text-input';
1314
import { Point, StringWithAutocomplete } from './types';
1415
import { nativeState } from './native-state';
15-
import { HostElement } from './renderer/host-element';
1616
import { EventBuilder } from './user-event/event-builder';
1717

1818
type EventHandler = (...args: unknown[]) => unknown;
1919

20-
export function isTouchResponder(element: HostElement) {
20+
export function isTouchResponder(element: HostComponent) {
2121
if (!isValidElement(element)) {
2222
return false;
2323
}
@@ -57,9 +57,9 @@ const textInputEventsIgnoringEditableProp = new Set([
5757
]);
5858

5959
export function isEventEnabled(
60-
element: HostElement,
60+
element: HostComponent,
6161
eventName: string,
62-
nearestTouchResponder?: HostElement,
62+
nearestTouchResponder?: HostComponent,
6363
) {
6464
if (isHostTextInput(nearestTouchResponder)) {
6565
return (
@@ -82,9 +82,9 @@ export function isEventEnabled(
8282
}
8383

8484
function findEventHandler(
85-
element: HostElement,
85+
element: HostComponent,
8686
eventName: string,
87-
nearestTouchResponder?: HostElement,
87+
nearestTouchResponder?: HostComponent,
8888
): EventHandler | null {
8989
const touchResponder = isTouchResponder(element) ? element : nearestTouchResponder;
9090

@@ -101,7 +101,7 @@ function findEventHandler(
101101
return findEventHandler(element.parent, eventName, touchResponder);
102102
}
103103

104-
function getEventHandler(element: HostElement, eventName: string) {
104+
function getEventHandler(element: HostComponent, eventName: string) {
105105
const eventHandlerName = getEventHandlerName(eventName);
106106
if (typeof element.props[eventHandlerName] === 'function') {
107107
return element.props[eventHandlerName];
@@ -131,7 +131,7 @@ type EventName = StringWithAutocomplete<
131131
| EventNameExtractor<ScrollViewProps>
132132
>;
133133

134-
function fireEvent(element: HostElement, eventName: EventName, ...data: unknown[]) {
134+
function fireEvent(element: HostComponent, eventName: EventName, ...data: unknown[]) {
135135
setNativeStateIfNeeded(element, eventName, data[0]);
136136

137137
const handler = findEventHandler(element, eventName);
@@ -147,7 +147,7 @@ function fireEvent(element: HostElement, eventName: EventName, ...data: unknown[
147147
return returnValue;
148148
}
149149

150-
fireEvent.press = (element: HostElement, ...data: unknown[]) => {
150+
fireEvent.press = (element: HostComponent, ...data: unknown[]) => {
151151
const nativeData =
152152
data.length === 1 &&
153153
typeof data[0] === 'object' &&
@@ -178,10 +178,10 @@ fireEvent.press = (element: HostElement, ...data: unknown[]) => {
178178
fireEvent(element, 'responderRelease', responderReleaseEvent);
179179
};
180180

181-
fireEvent.changeText = (element: HostElement, ...data: unknown[]) =>
181+
fireEvent.changeText = (element: HostComponent, ...data: unknown[]) =>
182182
fireEvent(element, 'changeText', ...data);
183183

184-
fireEvent.scroll = (element: HostElement, ...data: unknown[]) =>
184+
fireEvent.scroll = (element: HostComponent, ...data: unknown[]) =>
185185
fireEvent(element, 'scroll', ...data);
186186

187187
export default fireEvent;
@@ -194,7 +194,7 @@ const scrollEventNames = new Set([
194194
'momentumScrollEnd',
195195
]);
196196

197-
function setNativeStateIfNeeded(element: HostElement, eventName: string, value: unknown) {
197+
function setNativeStateIfNeeded(element: HostComponent, eventName: string, value: unknown) {
198198
if (
199199
eventName === 'changeText' &&
200200
typeof value === 'string' &&

src/helpers/accessibility.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
Role,
66
StyleSheet,
77
} from 'react-native';
8-
import { HostElement } from '../renderer/host-element';
8+
import { HostComponent } from 'universal-test-renderer';
99
import { getHostSiblings, getRootElement } from './component-tree';
1010
import {
1111
getHostComponentNames,
@@ -19,7 +19,7 @@ import { isTextInputEditable } from './text-input';
1919
import { findAllByProps } from './find-all';
2020

2121
type IsInaccessibleOptions = {
22-
cache?: WeakMap<HostElement, boolean>;
22+
cache?: WeakMap<HostComponent, boolean>;
2323
};
2424

2525
export const accessibilityStateKeys: (keyof AccessibilityState)[] = [
@@ -33,14 +33,14 @@ export const accessibilityStateKeys: (keyof AccessibilityState)[] = [
3333
export const accessibilityValueKeys: (keyof AccessibilityValue)[] = ['min', 'max', 'now', 'text'];
3434

3535
export function isHiddenFromAccessibility(
36-
element: HostElement | null,
36+
element: HostComponent | null,
3737
{ cache }: IsInaccessibleOptions = {},
3838
): boolean {
3939
if (element == null) {
4040
return true;
4141
}
4242

43-
let current: HostElement | null = element;
43+
let current: HostComponent | null = element;
4444
while (current) {
4545
let isCurrentSubtreeInaccessible = cache?.get(current);
4646

@@ -62,7 +62,7 @@ export function isHiddenFromAccessibility(
6262
/** RTL-compatibility alias for `isHiddenFromAccessibility` */
6363
export const isInaccessible = isHiddenFromAccessibility;
6464

65-
function isSubtreeInaccessible(element: HostElement): boolean {
65+
function isSubtreeInaccessible(element: HostComponent): boolean {
6666
// Null props can happen for React.Fragments
6767
if (element.props == null) {
6868
return false;
@@ -99,7 +99,7 @@ function isSubtreeInaccessible(element: HostElement): boolean {
9999
return false;
100100
}
101101

102-
export function isAccessibilityElement(element: HostElement | null): boolean {
102+
export function isAccessibilityElement(element: HostComponent | null): boolean {
103103
if (element == null) {
104104
return false;
105105
}
@@ -134,7 +134,7 @@ export function isAccessibilityElement(element: HostElement | null): boolean {
134134
* @param element
135135
* @returns
136136
*/
137-
export function getRole(element: HostElement): Role | AccessibilityRole {
137+
export function getRole(element: HostComponent): Role | AccessibilityRole {
138138
const explicitRole = element.props.role ?? element.props.accessibilityRole;
139139
if (explicitRole) {
140140
return normalizeRole(explicitRole);
@@ -165,11 +165,11 @@ export function normalizeRole(role: string): Role | AccessibilityRole {
165165
return role as Role | AccessibilityRole;
166166
}
167167

168-
export function computeAriaModal(element: HostElement): boolean | undefined {
168+
export function computeAriaModal(element: HostComponent): boolean | undefined {
169169
return element.props['aria-modal'] ?? element.props.accessibilityViewIsModal;
170170
}
171171

172-
export function computeAriaLabel(element: HostElement): string | undefined {
172+
export function computeAriaLabel(element: HostComponent): string | undefined {
173173
const explicitLabel = element.props['aria-label'] ?? element.props.accessibilityLabel;
174174
if (explicitLabel) {
175175
return explicitLabel;
@@ -183,17 +183,17 @@ export function computeAriaLabel(element: HostElement): string | undefined {
183183
return undefined;
184184
}
185185

186-
export function computeAriaLabelledBy(element: HostElement): string | undefined {
186+
export function computeAriaLabelledBy(element: HostComponent): string | undefined {
187187
return element.props['aria-labelledby'] ?? element.props.accessibilityLabelledBy;
188188
}
189189

190190
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#busy-state
191-
export function computeAriaBusy({ props }: HostElement): boolean {
191+
export function computeAriaBusy({ props }: HostComponent): boolean {
192192
return props['aria-busy'] ?? props.accessibilityState?.busy ?? false;
193193
}
194194

195195
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#checked-state
196-
export function computeAriaChecked(element: HostElement): AccessibilityState['checked'] {
196+
export function computeAriaChecked(element: HostComponent): AccessibilityState['checked'] {
197197
const { props } = element;
198198

199199
if (isHostSwitch(element)) {
@@ -209,7 +209,7 @@ export function computeAriaChecked(element: HostElement): AccessibilityState['ch
209209
}
210210

211211
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#disabled-state
212-
export function computeAriaDisabled(element: HostElement): boolean {
212+
export function computeAriaDisabled(element: HostComponent): boolean {
213213
if (isHostTextInput(element) && !isTextInputEditable(element)) {
214214
return true;
215215
}
@@ -219,16 +219,16 @@ export function computeAriaDisabled(element: HostElement): boolean {
219219
}
220220

221221
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#expanded-state
222-
export function computeAriaExpanded({ props }: HostElement): boolean | undefined {
222+
export function computeAriaExpanded({ props }: HostComponent): boolean | undefined {
223223
return props['aria-expanded'] ?? props.accessibilityState?.expanded;
224224
}
225225

226226
// See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#selected-state
227-
export function computeAriaSelected({ props }: HostElement): boolean {
227+
export function computeAriaSelected({ props }: HostComponent): boolean {
228228
return props['aria-selected'] ?? props.accessibilityState?.selected ?? false;
229229
}
230230

231-
export function computeAriaValue(element: HostElement): AccessibilityValue {
231+
export function computeAriaValue(element: HostComponent): AccessibilityValue {
232232
const {
233233
accessibilityValue,
234234
'aria-valuemax': ariaValueMax,
@@ -245,7 +245,7 @@ export function computeAriaValue(element: HostElement): AccessibilityValue {
245245
};
246246
}
247247

248-
export function computeAccessibleName(element: HostElement): string | undefined {
248+
export function computeAccessibleName(element: HostComponent): string | undefined {
249249
const label = computeAriaLabel(element);
250250
if (label) {
251251
return label;

src/helpers/component-tree.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { CONTAINER_TYPE } from '../renderer/contants';
2-
import { HostElement } from '../renderer/host-element';
1+
import { CONTAINER_TYPE, HostComponent } from 'universal-test-renderer';
32

43
/**
54
* Checks if the given element is a host element.
65
* @param element The element to check.
76
*/
8-
export function isValidElement(element?: HostElement | null): element is HostElement {
7+
export function isValidElement(element?: HostComponent | null): element is HostComponent {
98
return typeof element?.type === 'string' && element.type !== CONTAINER_TYPE;
109
}
1110

@@ -15,8 +14,8 @@ export function isValidElement(element?: HostElement | null): element is HostEle
1514
* @param element The element start traversing from.
1615
* @returns The root element of the tree (host or composite).
1716
*/
18-
export function getRootElement(element: HostElement) {
19-
let current: HostElement | null = element;
17+
export function getRootElement(element: HostComponent) {
18+
let current: HostComponent | null = element;
2019
while (current?.parent) {
2120
current = current.parent;
2221
}
@@ -28,11 +27,11 @@ export function getRootElement(element: HostElement) {
2827
* Returns host siblings for given element.
2928
* @param element The element start traversing from.
3029
*/
31-
export function getHostSiblings(element: HostElement | null): HostElement[] {
30+
export function getHostSiblings(element: HostComponent | null): HostComponent[] {
3231
const hostParent = element?.parent ?? null;
3332
return (
3433
hostParent?.children.filter(
35-
(sibling): sibling is HostElement => typeof sibling === 'object' && sibling !== element,
34+
(sibling): sibling is HostComponent => typeof sibling === 'object' && sibling !== element,
3635
) ?? []
3736
);
3837
}

src/helpers/debug-deep.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { JsonNode } from '../renderer/render-to-json';
1+
import type { JsonNode } from 'universal-test-renderer/dist/index.d.ts';
22
import format, { FormatOptions } from './format';
33

44
export type DebugOptions = {

0 commit comments

Comments
 (0)