Skip to content

Commit 5e64e90

Browse files
rubennortemeta-codesync[bot]
authored andcommitted
Remove ReactNativeElement from Fantom tests (nullthrows/HostInstance/HTMLElement) (#57468)
Summary: Pull Request resolved: #57468 Removes the `ensureInstance(ref.current, ReactNativeElement)` pattern from Fantom tests. Host refs are typed as `HostInstance` and read with `nullthrows(ref.current)`; component-specific instance types (e.g. `React.ElementRef<typeof TextInput>`, `React.ElementRef<typeof ScrollView>`) are kept only where a test actually uses a component's imperative API (`focus`/`blur`/`clear`, `getInnerViewRef`, etc.). This drops the `ReactNativeElement`/`ensureInstance` imports from tests that only needed a non-null host instance. Where a test genuinely needs a runtime type check, `instanceof ReactNativeElement` / `toBeInstanceOf(ReactNativeElement)` is replaced with the standard `instanceof HTMLElement` (a global), since React Native elements are instances of `HTMLElement` at runtime. This also covers the internal `-itest.fb.js` tests and the shared ShadowNode test utilities (`ShadowNodeReferenceCounter`/`ShadowNodeRevisionGetter`), which now type their refs as `HostInstance`. Known follow-up: the RN Flow global type definitions still need to be fixed. At the type level `HTMLElement` does not match `HostInstance` yet, even though the runtime values do. Because of this, `HTMLElement` cannot be used where a narrowed value flows into RN-internal APIs typed as `HostInstance`/`ReadOnlyNode` (e.g. `VirtualView`, which instead types its helper params as `HostInstance` directly and passes them through without a narrowing check). Aligning the `HTMLElement` global definition with `HostInstance` would let those remaining spots use `HTMLElement` too. This is a test-only change with no runtime or user-facing impact. Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D110888634 fbshipit-source-id: c570700ed82fd8333093ee01816e872c486f3e8f
1 parent b67b7ba commit 5e64e90

30 files changed

Lines changed: 166 additions & 264 deletions

File tree

packages/react-native/Libraries/Animated/__tests__/Animated-itest.js

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
1313

1414
import type {HostInstance} from 'react-native';
1515

16-
import ensureInstance from '../../../src/private/__tests__/utilities/ensureInstance';
1716
import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
1817
import * as Fantom from '@react-native/fantom';
18+
import nullthrows from 'nullthrows';
19+
import * as React from 'react';
1920
import {createRef} from 'react';
2021
import {Animated, Easing, View, useAnimatedValue} from 'react-native';
2122
import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';
22-
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
2323

2424
// Deferred start outputs the initial value on the first animation frame and
2525
// re-anchors timing on the second. This delays animation progress by one
@@ -54,7 +54,7 @@ test('moving box by 100 points', () => {
5454
root.render(<MyApp />);
5555
});
5656

57-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
57+
const viewElement = nullthrows(viewRef.current);
5858

5959
expect(viewElement.getBoundingClientRect().x).toBe(0);
6060

@@ -119,7 +119,7 @@ test('native-driven interpolation honors custom easing', () => {
119119
root.render(<MyApp />);
120120
});
121121

122-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
122+
const viewElement = nullthrows(viewRef.current);
123123

124124
Fantom.runTask(() => {
125125
Animated.timing(_progress, {
@@ -179,7 +179,7 @@ test('native-driven interpolation preserves easing overshoot under clamp', () =>
179179
root.render(<MyApp />);
180180
});
181181

182-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
182+
const viewElement = nullthrows(viewRef.current);
183183

184184
Fantom.runTask(() => {
185185
Animated.timing(_progress, {
@@ -231,7 +231,7 @@ function startTimingAnimationAndGetTranslateXAfterFirstFrame(): number {
231231
root.render(<MyApp />);
232232
});
233233

234-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
234+
const viewElement = nullthrows(viewRef.current);
235235

236236
Fantom.runTask(() => {
237237
Animated.timing(_translateX, {
@@ -326,11 +326,8 @@ test('animation driven by onScroll event', () => {
326326
root.render(<PressableWithNativeDriver />);
327327
});
328328

329-
const scrollViewelement = ensureInstance(
330-
scrollViewRef.current,
331-
ReactNativeElement,
332-
);
333-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
329+
const scrollViewelement = nullthrows(scrollViewRef.current);
330+
const viewElement = nullthrows(viewRef.current);
334331

335332
Fantom.scrollTo(scrollViewelement, {
336333
x: 0,
@@ -397,10 +394,7 @@ test('animation driven by onScroll event when animated view is unmounted', () =>
397394
root.render(<PressableWithNativeDriver mountAnimatedView={false} />);
398395
});
399396

400-
const scrollViewelement = ensureInstance(
401-
scrollViewRef.current,
402-
ReactNativeElement,
403-
);
397+
const scrollViewelement = nullthrows(scrollViewRef.current);
404398

405399
Fantom.scrollTo(scrollViewelement, {
406400
x: 0,
@@ -438,7 +432,7 @@ test('animated opacity', () => {
438432
root.render(<MyApp />);
439433
});
440434

441-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
435+
const viewElement = nullthrows(viewRef.current);
442436

443437
expect(viewElement.getBoundingClientRect().x).toBe(0);
444438

@@ -492,7 +486,7 @@ test('moving box by 50 points with offset 10', () => {
492486
root.render(<MyApp />);
493487
});
494488

495-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
489+
const viewElement = nullthrows(viewRef.current);
496490

497491
expect(viewElement.getBoundingClientRect().x).toBe(0);
498492

@@ -592,11 +586,8 @@ describe('Value.flattenOffset', () => {
592586
_onScroll.addListener(fn);
593587
});
594588

595-
const scrollViewelement = ensureInstance(
596-
scrollViewRef.current,
597-
ReactNativeElement,
598-
);
599-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
589+
const scrollViewelement = nullthrows(scrollViewRef.current);
590+
const viewElement = nullthrows(viewRef.current);
600591

601592
Fantom.scrollTo(scrollViewelement, {
602593
x: 0,
@@ -676,11 +667,8 @@ describe('Value.extractOffset', () => {
676667
_onScroll.addListener(fn);
677668
});
678669

679-
const scrollViewelement = ensureInstance(
680-
scrollViewRef.current,
681-
ReactNativeElement,
682-
);
683-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
670+
const scrollViewelement = nullthrows(scrollViewRef.current);
671+
const viewElement = nullthrows(viewRef.current);
684672

685673
Fantom.scrollTo(scrollViewelement, {
686674
x: 0,
@@ -751,7 +739,7 @@ test('animate layout props', () => {
751739
root.render(<MyApp />);
752740
});
753741

754-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
742+
const viewElement = nullthrows(viewRef.current);
755743

756744
Fantom.runTask(() => {
757745
_heightAnimation = Animated.timing(_animatedHeight, {
@@ -819,7 +807,7 @@ test('AnimatedValue.interpolate', () => {
819807
root.render(<MyApp outputRangeX={1} />);
820808
});
821809

822-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
810+
const viewElement = nullthrows(viewRef.current);
823811

824812
expect(_valueX?.__getValue()).toBe(0.5);
825813
expect(_interpolatedValueX?.__getValue()).toBe(50);
@@ -891,7 +879,7 @@ test('Animated.sequence', () => {
891879
root.render(<MyApp />);
892880
});
893881

894-
const element = ensureInstance(elementRef.current, ReactNativeElement);
882+
const element = nullthrows(elementRef.current);
895883

896884
expect(element.getBoundingClientRect().y).toBe(0);
897885

packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
1313

1414
import type {HostInstance} from 'react-native';
1515

16-
import ensureInstance from '../../../src/private/__tests__/utilities/ensureInstance';
1716
import * as Fantom from '@react-native/fantom';
17+
import nullthrows from 'nullthrows';
1818
import * as React from 'react';
1919
import {Component, createRef, memo, useEffect, useMemo, useState} from 'react';
2020
import {Animated, View, useAnimatedValue} from 'react-native';
2121
import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';
22-
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
2322

2423
// marginLeft (and the other margin props) are only on the native animated
2524
// allowlist when the shared backend is enabled. This test deliberately does NOT
@@ -108,7 +107,7 @@ test('animated opacity', () => {
108107
root.render(<MyApp />);
109108
});
110109

111-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
110+
const viewElement = nullthrows(viewRef.current);
112111

113112
expect(viewElement.getBoundingClientRect().x).toBe(0);
114113

@@ -414,7 +413,7 @@ test('animate non-layout props and rerender', () => {
414413
root.render(<MyApp />);
415414
});
416415

417-
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
416+
const viewElement = nullthrows(viewRef.current);
418417

419418
Fantom.runTask(() => {
420419
_opacityAnimation = Animated.timing(_animatedOpacity, {

packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010

1111
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
1212

13-
import ensureInstance from '../../../../src/private/__tests__/utilities/ensureInstance';
13+
import type {HostInstance} from 'react-native';
14+
1415
import * as Fantom from '@react-native/fantom';
16+
import nullthrows from 'nullthrows';
1517
import * as React from 'react';
1618
import {createRef} from 'react';
1719
import {ActivityIndicator} from 'react-native';
18-
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
1920

2021
describe('<ActivityIndicator>', () => {
2122
it('sets displayName', () => {
@@ -167,28 +168,26 @@ describe('<ActivityIndicator>', () => {
167168
});
168169

169170
describe('ref', () => {
170-
it('provides a valid ReactNativeElement instance', () => {
171-
const elementRef =
172-
createRef<React.ElementRef<typeof ActivityIndicator>>();
171+
it('provides a valid HTMLElement instance', () => {
172+
const elementRef = createRef<HostInstance>();
173173
const root = Fantom.createRoot();
174174

175175
Fantom.runTask(() => {
176176
root.render(<ActivityIndicator ref={elementRef} />);
177177
});
178178

179-
expect(elementRef.current).toBeInstanceOf(ReactNativeElement);
179+
expect(elementRef.current).toBeInstanceOf(HTMLElement);
180180
});
181181

182182
it('has the correct tag name', () => {
183-
const elementRef =
184-
createRef<React.ElementRef<typeof ActivityIndicator>>();
183+
const elementRef = createRef<HostInstance>();
185184
const root = Fantom.createRoot();
186185

187186
Fantom.runTask(() => {
188187
root.render(<ActivityIndicator ref={elementRef} />);
189188
});
190189

191-
const element = ensureInstance(elementRef.current, ReactNativeElement);
190+
const element = nullthrows(elementRef.current);
192191
expect(element.tagName).toBe('RN:AndroidProgressBar');
193192
});
194193
});

packages/react-native/Libraries/Components/Pressable/__tests__/Pressable-itest.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@
1111

1212
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
1313

14-
import type {AccessibilityProps, HostInstance} from 'react-native';
14+
import type {HostInstance} from 'react-native';
15+
import type {AccessibilityProps} from 'react-native';
1516

1617
import * as Fantom from '@react-native/fantom';
18+
import nullthrows from 'nullthrows';
1719
import * as React from 'react';
1820
import {createRef} from 'react';
1921
import {Pressable} from 'react-native';
2022
import {PlatformColor, Text} from 'react-native';
2123
import accessibilityPropsSuite from 'react-native/src/private/__tests__/utilities/accessibilityPropsSuite';
22-
import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance';
23-
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
2424

2525
describe('<Pressable>', () => {
2626
describe('props', () => {
@@ -94,7 +94,7 @@ describe('<Pressable>', () => {
9494
);
9595
});
9696

97-
const element = ensureInstance(elementRef.current, ReactNativeElement);
97+
const element = nullthrows(elementRef.current);
9898
Fantom.dispatchNativeEvent(element, 'click');
9999

100100
expect(onPressCallback).toHaveBeenCalledTimes(1);
@@ -118,7 +118,7 @@ describe('<Pressable>', () => {
118118
);
119119
});
120120

121-
const element = ensureInstance(elementRef.current, ReactNativeElement);
121+
const element = nullthrows(elementRef.current);
122122
Fantom.dispatchNativeEvent(element, 'change', {value: true});
123123

124124
expect(onPressCallback).toHaveBeenCalledTimes(0);
@@ -263,7 +263,7 @@ describe('<Pressable>', () => {
263263
</Pressable>,
264264
);
265265
});
266-
const element = ensureInstance(elementRef.current, ReactNativeElement);
266+
const element = nullthrows(elementRef.current);
267267

268268
expect(element.childNodes.length).toBe(1);
269269

@@ -302,7 +302,7 @@ describe('<Pressable>', () => {
302302
root.render(<Pressable ref={elementRef} />);
303303
});
304304

305-
expect(elementRef.current).toBeInstanceOf(ReactNativeElement);
305+
expect(elementRef.current).toBeInstanceOf(HTMLElement);
306306
});
307307

308308
it('uses the "RN:View" tag name', () => {
@@ -314,7 +314,7 @@ describe('<Pressable>', () => {
314314
root.render(<Pressable ref={elementRef} />);
315315
});
316316

317-
const element = ensureInstance(elementRef.current, ReactNativeElement);
317+
const element = nullthrows(elementRef.current);
318318
// Pressable is implemented with a <View> under the hood
319319
expect(element.tagName).toBe('RN:View');
320320
});

packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-itest.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ import nullthrows from 'nullthrows';
1717
import * as React from 'react';
1818
import {createRef} from 'react';
1919
import {ScrollView, Text, View} from 'react-native';
20-
import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance';
21-
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
2220

2321
describe('<ScrollView>', () => {
2422
describe('rendering', () => {
@@ -93,7 +91,7 @@ describe('<ScrollView>', () => {
9391
root.render(<ScrollView innerViewRef={ref} />);
9492
});
9593

96-
expect(ref.mock.lastCall[0]).toBeInstanceOf(ReactNativeElement);
94+
expect(ref.mock.lastCall[0]).toBeInstanceOf(HTMLElement);
9795

9896
Fantom.runTask(() => {
9997
root.render(<></>);
@@ -111,14 +109,14 @@ describe('<ScrollView>', () => {
111109
root.render(<ScrollView innerViewRef={refA} />);
112110
});
113111

114-
expect(refA.mock.lastCall[0]).toBeInstanceOf(ReactNativeElement);
112+
expect(refA.mock.lastCall[0]).toBeInstanceOf(HTMLElement);
115113

116114
Fantom.runTask(() => {
117115
root.render(<ScrollView innerViewRef={refB} />);
118116
});
119117

120118
expect(refA.mock.lastCall[0]).toBe(null);
121-
expect(refB.mock.lastCall[0]).toBeInstanceOf(ReactNativeElement);
119+
expect(refB.mock.lastCall[0]).toBeInstanceOf(HTMLElement);
122120
});
123121
});
124122

@@ -131,10 +129,7 @@ describe('<ScrollView>', () => {
131129
root.render(<ScrollView ref={ref} />);
132130
});
133131

134-
const innerView = ensureInstance(
135-
nullthrows(ref.current).getInnerViewRef(),
136-
ReactNativeElement,
137-
);
132+
const innerView = nullthrows(nullthrows(ref.current).getInnerViewRef());
138133
expect(innerView.tagName).toBe('RN:View');
139134
});
140135
});

packages/react-native/Libraries/Components/Switch/__tests__/Switch-itest.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
*/
1010

1111
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
12+
1213
import type {HostInstance} from 'react-native';
1314

1415
import * as Fantom from '@react-native/fantom';
16+
import nullthrows from 'nullthrows';
1517
import * as React from 'react';
1618
import {createRef} from 'react';
1719
import {Switch} from 'react-native';
18-
import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance';
19-
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
2020

2121
describe('<ExampleComponent>', () => {
2222
describe('props', () => {
@@ -38,8 +38,8 @@ describe('<ExampleComponent>', () => {
3838
root.render(<Switch ref={elementRef} />);
3939
});
4040

41-
expect(elementRef.current).toBeInstanceOf(ReactNativeElement);
42-
const element = ensureInstance(elementRef.current, ReactNativeElement);
41+
expect(elementRef.current).toBeInstanceOf(HTMLElement);
42+
const element = nullthrows(elementRef.current);
4343
expect(element.tagName).toBe('RN:Switch');
4444
});
4545
});

0 commit comments

Comments
 (0)