diff --git a/packages/react-native-fantom/src/__tests__/Fantom-itest.js b/packages/react-native-fantom/src/__tests__/Fantom-itest.js index a304f9884d0..88a69ef4b02 100644 --- a/packages/react-native-fantom/src/__tests__/Fantom-itest.js +++ b/packages/react-native-fantom/src/__tests__/Fantom-itest.js @@ -473,4 +473,108 @@ describe('Fantom', () => { y: 2, }); }); + + describe('scrollTo', () => { + it('throws error if called on node that is not scroll view', () => { + const root = Fantom.createRoot(); + let maybeNode; + + Fantom.runTask(() => { + root.render( + { + maybeNode = node; + }} + />, + ); + }); + + const element = ensureInstance(maybeNode, ReactNativeElement); + + expect(() => { + Fantom.runOnUIThread(() => { + Fantom.scrollTo(element, { + x: 0, + y: 1, + }); + }); + }).toThrow( + 'Exception in HostFunction: scrollTo() can only be called on ', + ); + }); + + it('delivers onScroll event and affects position of elements on screen', () => { + const root = Fantom.createRoot(); + let maybeScrollViewNode; + let maybeNode; + const onScroll = jest.fn(); + + Fantom.runTask(() => { + root.render( + { + onScroll(event.nativeEvent); + }} + ref={node => { + maybeScrollViewNode = node; + }}> + { + maybeNode = node; + }} + /> + , + ); + }); + + const scrollViewElement = ensureInstance( + maybeScrollViewNode, + ReactNativeElement, + ); + + Fantom.runOnUIThread(() => { + Fantom.scrollTo(scrollViewElement, { + x: 0, + y: 1, + }); + }); + + Fantom.runWorkLoop(); + + expect(onScroll).toHaveBeenCalledTimes(1); + + const viewElement = ensureInstance(maybeNode, ReactNativeElement); + + let rect; + + viewElement.measure((x, y, width, height, pageX, pageY) => { + rect = { + x, + y, + width, + height, + pageX, + pageY, + }; + }); + + expect(rect).toEqual({ + x: 0, + y: 3, + width: 1, + height: 2, + pageY: 2, + pageX: 0, + }); + + const boundingClientRect = viewElement.getBoundingClientRect(); + expect(boundingClientRect.x).toBe(0); + expect(boundingClientRect.y).toBe(2); + expect(boundingClientRect.width).toBe(1); + expect(boundingClientRect.height).toBe(2); + + root.destroy(); + }); + }); }); diff --git a/packages/react-native-fantom/src/index.js b/packages/react-native-fantom/src/index.js index ae1280c41b6..640efa2c43d 100644 --- a/packages/react-native-fantom/src/index.js +++ b/packages/react-native-fantom/src/index.js @@ -167,6 +167,16 @@ function dispatchNativeEvent( ); } +function scrollTo( + node: ReactNativeElement, + options: {x: number, y: number, zoomScale?: number}, +) { + const shadowNode = getShadowNode(node); + NativeFantom.scrollTo(shadowNode, options); +} + +export const unstable_benchmark = Benchmark; + type FantomConstants = $ReadOnly<{ isRunningFromCI: boolean, }>; @@ -255,4 +265,5 @@ export default { createRoot, dispatchNativeEvent, unstable_benchmark: Benchmark, + scrollTo, }; diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index c09dbf37118..0a1a7fbbd59 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -10465,6 +10465,11 @@ export enum NativeEventCategory { Discrete = 3, Continuous = 4, } +export type ScrollOptions = { + x: number, + y: number, + zoomScale?: number, +}; interface Spec extends TurboModule { startSurface: ( surfaceId: number, @@ -10480,6 +10485,7 @@ interface Spec extends TurboModule { category?: NativeEventCategory, isUnique?: boolean ) => void; + scrollTo: (shadowNode: mixed, options: ScrollOptions) => void; getMountingManagerLogs: (surfaceId: number) => Array; flushMessageQueue: () => void; flushEventQueue: () => void; diff --git a/packages/react-native/src/private/specs/modules/NativeFantom.js b/packages/react-native/src/private/specs/modules/NativeFantom.js index 775d0bc243c..a7afa3a0dd3 100644 --- a/packages/react-native/src/private/specs/modules/NativeFantom.js +++ b/packages/react-native/src/private/specs/modules/NativeFantom.js @@ -51,6 +51,12 @@ export enum NativeEventCategory { Continuous = 4, } +export type ScrollOptions = { + x: number, + y: number, + zoomScale?: number, +}; + interface Spec extends TurboModule { startSurface: ( surfaceId: number, @@ -66,6 +72,10 @@ interface Spec extends TurboModule { category?: NativeEventCategory, isUnique?: boolean, ) => void; + scrollTo: ( + shadowNode: mixed /* ShadowNode */, + options: ScrollOptions, + ) => void; getMountingManagerLogs: (surfaceId: number) => Array; flushMessageQueue: () => void; flushEventQueue: () => void;