Skip to content

Commit df30038

Browse files
lunaleapsfacebook-github-bot
authored andcommitted
Create IntersectionObserver benchmark (#50023)
Summary: Pull Request resolved: #50023 Changelog: [Internal] - Add benchmarks for Intersection Observer Reviewed By: rubennorte Differential Revision: D66847629 fbshipit-source-id: 01cb7994ddb007ecce29593b6ab4b1d359d68bd0
1 parent ff4537c commit df30038

File tree

1 file changed

+294
-0
lines changed

1 file changed

+294
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
* @fantom_flags enableAccessToHostTreeInFabric:true
11+
*/
12+
13+
import type IntersectionObserverType from '../IntersectionObserver';
14+
import type {Root} from '@react-native/fantom';
15+
16+
import * as Fantom from '@react-native/fantom';
17+
import * as React from 'react';
18+
import ScrollView from 'react-native/Libraries/Components/ScrollView/ScrollView';
19+
import View from 'react-native/Libraries/Components/View/View';
20+
import setUpIntersectionObserver from 'react-native/src/private/setup/setUpIntersectionObserver';
21+
import ensureInstance from 'react-native/src/private/utilities/ensureInstance';
22+
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
23+
24+
import 'react-native/Libraries/Core/InitializeCore';
25+
26+
declare const IntersectionObserver: Class<IntersectionObserverType>;
27+
28+
setUpIntersectionObserver();
29+
30+
let maybeNode;
31+
let node: ReactNativeElement;
32+
33+
let maybeScrollViewNode;
34+
let scrollViewNode: ReactNativeElement;
35+
let observer: IntersectionObserverType;
36+
const VIEWPORT_HEIGHT = 100;
37+
const VIEWPORT_WIDTH = 100;
38+
const root = Fantom.createRoot({
39+
viewportHeight: VIEWPORT_HEIGHT,
40+
viewportWidth: VIEWPORT_WIDTH,
41+
});
42+
let mockCallback = jest.fn();
43+
44+
function cleanup(renderedRoot: Root, testObserver: ?IntersectionObserverType) {
45+
Fantom.runTask(() => {
46+
if (testObserver != null) {
47+
testObserver.disconnect();
48+
}
49+
root.render(<></>);
50+
});
51+
}
52+
53+
// Scroll yOffset 1px at a time
54+
function scrollBy1(scrollNode: ReactNativeElement, yOffset: number) {
55+
for (let i = 1; i <= yOffset; i++) {
56+
Fantom.scrollTo(scrollNode, {
57+
x: 0,
58+
y: i,
59+
});
60+
}
61+
}
62+
63+
// Render element such that it appears at `Fantom.scrollTo({x, y: yOffset})`
64+
// A negative value `yOffset` means the element is already onscreen
65+
function renderElementAtYScrollPosition(yOffset: number, element: React.Node) {
66+
return (
67+
<>
68+
<View
69+
style={{
70+
width: VIEWPORT_WIDTH,
71+
height: VIEWPORT_HEIGHT + yOffset,
72+
}}
73+
/>
74+
{element}
75+
</>
76+
);
77+
}
78+
79+
Fantom.unstable_benchmark
80+
.suite('IntersectionObserver')
81+
.test(
82+
'Create IntersectionObserver',
83+
() => {
84+
Fantom.runTask(() => {
85+
observer = new IntersectionObserver(mockCallback, {});
86+
});
87+
},
88+
{
89+
beforeEach: () => {
90+
mockCallback = jest.fn();
91+
},
92+
afterEach: () => {
93+
expect(mockCallback).not.toHaveBeenCalled();
94+
cleanup(root, observer);
95+
},
96+
},
97+
)
98+
.test(
99+
'Observe a mounted view',
100+
() => {
101+
Fantom.runTask(() => {
102+
observer.observe(node);
103+
});
104+
},
105+
{
106+
beforeEach: () => {
107+
mockCallback = jest.fn();
108+
Fantom.runTask(() => {
109+
root.render(
110+
<View
111+
style={{width: 100, height: 10}}
112+
ref={receivedNode => {
113+
maybeNode = receivedNode;
114+
}}
115+
/>,
116+
);
117+
observer = new IntersectionObserver(mockCallback);
118+
});
119+
node = ensureInstance(maybeNode, ReactNativeElement);
120+
},
121+
afterEach: () => {
122+
expect(mockCallback).toHaveBeenCalledTimes(1);
123+
const [entries] = mockCallback.mock.lastCall;
124+
expect(entries.length).toBe(1);
125+
expect(entries[0].isIntersecting).toBe(true);
126+
127+
cleanup(root, observer);
128+
},
129+
},
130+
)
131+
.test(
132+
'ScrollView no intersection, no observation',
133+
() => {
134+
scrollBy1(scrollViewNode, VIEWPORT_HEIGHT);
135+
},
136+
{
137+
beforeEach: () => {
138+
Fantom.runTask(() => {
139+
root.render(
140+
<ScrollView
141+
ref={receivedNode => {
142+
maybeScrollViewNode = receivedNode;
143+
}}>
144+
{renderElementAtYScrollPosition(
145+
VIEWPORT_HEIGHT + 50,
146+
<View style={{width: 100, height: 10}} />,
147+
)}
148+
</ScrollView>,
149+
);
150+
});
151+
scrollViewNode = ensureInstance(
152+
maybeScrollViewNode,
153+
ReactNativeElement,
154+
);
155+
},
156+
afterEach: () => {
157+
cleanup(root, observer);
158+
},
159+
},
160+
)
161+
.test(
162+
'ScrollView intersection, no observation',
163+
() => {
164+
scrollBy1(scrollViewNode, VIEWPORT_HEIGHT);
165+
},
166+
{
167+
beforeEach: () => {
168+
Fantom.runTask(() => {
169+
root.render(
170+
<ScrollView
171+
ref={receivedNode => {
172+
maybeScrollViewNode = receivedNode;
173+
}}>
174+
{renderElementAtYScrollPosition(
175+
-5,
176+
<View style={{width: 100, height: 10}} />,
177+
)}
178+
</ScrollView>,
179+
);
180+
});
181+
scrollViewNode = ensureInstance(
182+
maybeScrollViewNode,
183+
ReactNativeElement,
184+
);
185+
},
186+
afterEach: () => {
187+
cleanup(root);
188+
},
189+
},
190+
)
191+
.test(
192+
'ScrollView no intersection, observation',
193+
() => {
194+
scrollBy1(scrollViewNode, VIEWPORT_HEIGHT);
195+
},
196+
{
197+
beforeEach: () => {
198+
mockCallback = jest.fn();
199+
200+
Fantom.runTask(() => {
201+
root.render(
202+
<ScrollView
203+
ref={receivedNode => {
204+
maybeScrollViewNode = receivedNode;
205+
}}>
206+
{renderElementAtYScrollPosition(
207+
VIEWPORT_HEIGHT + 50,
208+
<View
209+
ref={receivedNode => {
210+
maybeNode = receivedNode;
211+
}}
212+
style={{width: 100, height: 10}}
213+
/>,
214+
)}
215+
</ScrollView>,
216+
);
217+
});
218+
scrollViewNode = ensureInstance(
219+
maybeScrollViewNode,
220+
ReactNativeElement,
221+
);
222+
node = ensureInstance(maybeNode, ReactNativeElement);
223+
Fantom.runTask(() => {
224+
observer = new IntersectionObserver(mockCallback, {});
225+
observer.observe(node);
226+
});
227+
},
228+
afterEach: () => {
229+
expect(mockCallback).toHaveBeenCalledTimes(1);
230+
231+
const [entries] = mockCallback.mock.lastCall;
232+
expect(entries.length).toBe(1);
233+
expect(entries[0].isIntersecting).toBe(false);
234+
235+
cleanup(root, observer);
236+
},
237+
},
238+
)
239+
.test(
240+
'ScrollView intersection, observation',
241+
() => {
242+
scrollBy1(scrollViewNode, VIEWPORT_HEIGHT);
243+
},
244+
{
245+
beforeEach: () => {
246+
mockCallback = jest.fn();
247+
248+
Fantom.runTask(() => {
249+
root.render(
250+
<ScrollView
251+
ref={receivedNode => {
252+
maybeScrollViewNode = receivedNode;
253+
}}>
254+
{renderElementAtYScrollPosition(
255+
-5,
256+
<View
257+
ref={receivedNode => {
258+
maybeNode = receivedNode;
259+
}}
260+
style={{width: 100, height: 10}}
261+
/>,
262+
)}
263+
</ScrollView>,
264+
);
265+
});
266+
scrollViewNode = ensureInstance(
267+
maybeScrollViewNode,
268+
ReactNativeElement,
269+
);
270+
node = ensureInstance(maybeNode, ReactNativeElement);
271+
Fantom.runTask(() => {
272+
observer = new IntersectionObserver(mockCallback, {threshold: 1});
273+
observer.observe(node);
274+
});
275+
},
276+
afterEach: () => {
277+
expect(mockCallback).toHaveBeenCalledTimes(3);
278+
279+
const [entries1] = mockCallback.mock.calls[0];
280+
expect(entries1.length).toBe(1);
281+
expect(entries1[0].isIntersecting).toBe(false);
282+
283+
const [entries2] = mockCallback.mock.calls[1];
284+
expect(entries2.length).toBe(1);
285+
expect(entries2[0].isIntersecting).toBe(true);
286+
287+
const [entries3] = mockCallback.mock.calls[2];
288+
expect(entries3.length).toBe(1);
289+
expect(entries3[0].isIntersecting).toBe(false);
290+
291+
cleanup(root, observer);
292+
},
293+
},
294+
);

0 commit comments

Comments
 (0)