diff --git a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/ReJest/TestRunner/NotificationRegistry.ts b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/ReJest/TestRunner/NotificationRegistry.ts index 6a97b0670ea..a75f80a24a7 100644 --- a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/ReJest/TestRunner/NotificationRegistry.ts +++ b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/ReJest/TestRunner/NotificationRegistry.ts @@ -1,6 +1,6 @@ import { runOnJS } from 'react-native-reanimated'; -const notificationRegistry: Record = {}; +let notificationRegistry: Record = {}; function notifyJS(name: string) { notificationRegistry[name] = true; } @@ -25,4 +25,8 @@ export class NotificationRegistry { }, 10); }); } + + public resetRegistry() { + notificationRegistry = {}; + } } diff --git a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/ReJest/TestRunner/TestRunner.ts b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/ReJest/TestRunner/TestRunner.ts index e16ff623c6d..659e568a871 100644 --- a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/ReJest/TestRunner/TestRunner.ts +++ b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/ReJest/TestRunner/TestRunner.ts @@ -127,6 +127,7 @@ export class TestRunner { private async runTestCase(testSuite: TestSuite, testCase: TestCase) { this._callTrackerRegistry.resetRegistry(); + this._notificationRegistry.resetRegistry(); this._currentTestCase = testCase; if (testSuite.beforeEach) { diff --git a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx index c18c0cfca7b..d402e34d1e0 100644 --- a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx +++ b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx @@ -51,6 +51,8 @@ export default function RuntimeTestsExample() { require('./tests/core/useDerivedValue/chain.test'); require('./tests/core/useSharedValue/animationsCompilerApi.test'); + + require('./tests/core/onLayout.test'); }, }, { diff --git a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/core/onLayout.test.tsx b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/core/onLayout.test.tsx new file mode 100644 index 00000000000..a22860ae769 --- /dev/null +++ b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/core/onLayout.test.tsx @@ -0,0 +1,71 @@ +import { useEffect } from 'react'; +import type { LayoutChangeEvent } from 'react-native'; +import { StyleSheet, View } from 'react-native'; +import Animated, { runOnJS, runOnUI, useAnimatedStyle, useEvent, useSharedValue } from 'react-native-reanimated'; +import { describe, expect, notify, render, test, wait, waitForNotify } from '../../ReJest/RuntimeTestsApi'; + +interface TestResult { + height: number; + animatedHandlerCalled: boolean; +} + +const TestComponent = ({ result }: { result: TestResult }) => { + const height = useSharedValue(styles.smallBox.height); + + const onLayout = (event: LayoutChangeEvent) => { + result.height = event.nativeEvent.layout.height; + if (result.height === 200) { + notify('onLayout'); + } + }; + + const animatedStyle = useAnimatedStyle(() => { + return { height: height.value }; + }); + + useEffect(() => { + runOnUI(() => { + height.value += 100; + })(); + }, [height]); + + const setAnimatedHandlerCalled = () => { + result.animatedHandlerCalled = true; + notify('animatedOnLayout'); + }; + + const animatedOnLayout = useEvent(() => { + 'worklet'; + runOnJS(setAnimatedHandlerCalled)(); + }, ['onLayout']); + + return ( + + + + ); +}; + +describe('onLayout', () => { + test('is not intercepted when there are no registered event handlers', async () => { + const result = {} as TestResult; + await render(); + await Promise.race([waitForNotify('onLayout'), wait(1000)]); + expect(result.height).toBe(200); + }); + + test('is dispatched to the registered event handler', async () => { + const result = {} as TestResult; + await render(); + await Promise.race([waitForNotify('animatedOnLayout'), wait(1000)]); + expect(result.animatedHandlerCalled).toBe(true); + }); +}); + +const styles = StyleSheet.create({ + smallBox: { + width: 100, + height: 100, + backgroundColor: 'pink', + }, +}); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp index 88e27896f01..299d8d36e47 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp @@ -743,6 +743,11 @@ bool ReanimatedModuleProxy::handleRawEvent( if (eventType.rfind("top", 0) == 0) { eventType = "on" + eventType.substr(3); } + + if (!isAnyHandlerWaitingForEvent(eventType, tag)) { + return false; + } + jsi::Runtime &rt = workletsModuleProxy_->getUIWorkletRuntime()->getJSIRuntime(); const auto &eventPayload = rawEvent.eventPayload;