diff --git a/packages/react-native-fantom/src/index.js b/packages/react-native-fantom/src/index.js index 70e98f25709..30b61051efe 100644 --- a/packages/react-native-fantom/src/index.js +++ b/packages/react-native-fantom/src/index.js @@ -207,6 +207,24 @@ export function unstable_produceFramesForDuration(milliseconds: number) { NativeFantom.produceFramesForDuration(milliseconds); } +/** + * Returns props appplied via direct manipulation to a view represented by shadow node. + * Direct manipulation is used by C++ Animated to change view properties on UI tick + * while the animation is in progress. Once animation finishes, the final state is committed + * to the shadow tree and result is observable through other JavaScript APIs, like `measure`. + * + * @param node - The node for which to retrieve direct manipulation props. + * @returns Mixed type data containing the direct manipulation properties + * + * Note: This API is marked as unstable and may change in future versions. + */ +export function unstable_getDirectManipulationProps( + node: ReactNativeElement, +): mixed { + const shadowNode = getNativeNodeReference(node); + return NativeFantom.getDirectManipulationProps(shadowNode); +} + /** * Simulates running a task on the UI thread and forces side effect to drain * the event queue, scheduling events to be dispatched to JavaScript. diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js index dfe5aac78ab..6978f3494f5 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js @@ -49,9 +49,7 @@ test('moving box by 100 points', () => { const viewElement = ensureInstance(viewRef.current, ReactNativeElement); - let boundingClientRect = viewElement.getBoundingClientRect(); - - expect(boundingClientRect.x).toBe(0); + expect(viewElement.getBoundingClientRect().x).toBe(0); Fantom.runTask(() => { Animated.timing(_translateX, { @@ -61,13 +59,26 @@ test('moving box by 100 points', () => { }).start(); }); - Fantom.unstable_produceFramesForDuration(1000); - boundingClientRect = viewElement.getBoundingClientRect(); - expect(boundingClientRect.x).toBe(100); + Fantom.unstable_produceFramesForDuration(500); + + // shadow tree is not synchronised yet, position X is still 0. + expect(viewElement.getBoundingClientRect().x).toBe(0); + + const transform = + // $FlowFixMe[incompatible-use] + Fantom.unstable_getDirectManipulationProps(viewElement).transform[0]; + + // direct manipulation has been applied. 50% through the animation + // and with linear animation, that is position X = 50. + expect(transform.translateX).toBeCloseTo(50, 0.001); + + Fantom.unstable_produceFramesForDuration(500); + + // Animation is completed now. C++ Animated will commit the final position to the shadow tree. + expect(viewElement.getBoundingClientRect().x).toBe(100); // TODO: this shouldn't be needed but C++ Animated still schedules a React state update // for synchronisation, even though it doesn't need to. Fantom.runWorkLoop(); - boundingClientRect = viewElement.getBoundingClientRect(); - expect(boundingClientRect.x).toBe(100); + expect(viewElement.getBoundingClientRect().x).toBe(100); }); diff --git a/packages/react-native/src/private/testing/fantom/specs/NativeFantom.js b/packages/react-native/src/private/testing/fantom/specs/NativeFantom.js index a4a827b7c4a..b696691f0da 100644 --- a/packages/react-native/src/private/testing/fantom/specs/NativeFantom.js +++ b/packages/react-native/src/private/testing/fantom/specs/NativeFantom.js @@ -88,6 +88,7 @@ interface Spec extends TurboModule { width: number, ) => void; takeMountingManagerLogs: (surfaceId: number) => Array; + getDirectManipulationProps: (shadowNode: mixed /* ShadowNode */) => mixed; flushMessageQueue: () => void; flushEventQueue: () => void; produceFramesForDuration: (miliseconds: number) => void;