mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add support for Events on the Fabric Interop Layer (#37059)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37059 This diff introduces InteropEventEmitter, a re-implementation of RCTEventEmitter that works with Fabric and allows to support events on the Fabric Interop for Android. Thanks to this, users can keep on calling `getJSModule(RCTEventEmitter.class).receiveEvent(...)` in their legacy ViewManagers and they will be using the EventDispatcher under the hood to dispatch events. The logic is enabled only if the `unstable_useFabricInterop` flag is turned on. I've turned this on for the template setup and for RN Tester. On top of this, this diff takes care also of event name "normalization". On Fabric, all the events needs to be registered with a "top" prefix. With this diff, we'll be adding the "top" prefix at registration time, if the user hasn't added them. This allows to use legacy ViewManagers on Fabric without having to ask users to change their event name. Changelog: [Android] [Added] - Add support for Events on the Fabric Interop Layer Reviewed By: mdvacca Differential Revision: D45144246 fbshipit-source-id: 63d4060153907c05977c976379b90574d1f69866
This commit is contained in:
committed by
Lorenzo Sciandra
parent
2fcda359ba
commit
fed80ad8c3
+29
@@ -21,6 +21,7 @@ import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.ThreadConfined;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.interop.InteropModuleRegistry;
|
||||
import com.facebook.react.bridge.queue.MessageQueueThread;
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
@@ -69,6 +70,8 @@ public class ReactContext extends ContextWrapper {
|
||||
private @Nullable JSExceptionHandler mJSExceptionHandler;
|
||||
private @Nullable JSExceptionHandler mExceptionHandlerWrapper;
|
||||
private @Nullable WeakReference<Activity> mCurrentActivity;
|
||||
|
||||
private @Nullable InteropModuleRegistry mInteropModuleRegistry;
|
||||
private boolean mIsInitialized = false;
|
||||
|
||||
public ReactContext(Context base) {
|
||||
@@ -93,6 +96,7 @@ public class ReactContext extends ContextWrapper {
|
||||
|
||||
ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration();
|
||||
initializeMessageQueueThreads(queueConfig);
|
||||
initializeInteropModules();
|
||||
}
|
||||
|
||||
/** Initialize message queue threads using a ReactQueueConfiguration. */
|
||||
@@ -120,6 +124,14 @@ public class ReactContext extends ContextWrapper {
|
||||
mIsInitialized = true;
|
||||
}
|
||||
|
||||
protected void initializeInteropModules() {
|
||||
mInteropModuleRegistry = new InteropModuleRegistry();
|
||||
}
|
||||
|
||||
protected void initializeInteropModules(ReactContext reactContext) {
|
||||
mInteropModuleRegistry = reactContext.mInteropModuleRegistry;
|
||||
}
|
||||
|
||||
public void resetPerfStats() {
|
||||
if (mNativeModulesMessageQueueThread != null) {
|
||||
mNativeModulesMessageQueueThread.resetPerfStats();
|
||||
@@ -163,6 +175,10 @@ public class ReactContext extends ContextWrapper {
|
||||
}
|
||||
throw new IllegalStateException(EARLY_JS_ACCESS_EXCEPTION_MESSAGE);
|
||||
}
|
||||
if (mInteropModuleRegistry != null
|
||||
&& mInteropModuleRegistry.shouldReturnInteropModule(jsInterface)) {
|
||||
return mInteropModuleRegistry.getInteropModule(jsInterface);
|
||||
}
|
||||
return mCatalystInstance.getJSModule(jsInterface);
|
||||
}
|
||||
|
||||
@@ -546,4 +562,17 @@ public class ReactContext extends ContextWrapper {
|
||||
Assertions.assertNotNull(mCatalystInstance).registerSegment(segmentId, path);
|
||||
Assertions.assertNotNull(callback).invoke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link JavaScriptModule} within the Interop Layer so that can be consumed whenever
|
||||
* getJSModule is invoked.
|
||||
*
|
||||
* <p>This method is internal to React Native and should not be used externally.
|
||||
*/
|
||||
public <T extends JavaScriptModule> void internal_registerInteropModule(
|
||||
Class<T> interopModuleInterface, Object interopModule) {
|
||||
if (mInteropModuleRegistry != null) {
|
||||
mInteropModuleRegistry.registerInteropModule(interopModuleInterface, interopModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.interop;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* A utility class that takes care of returning {@link JavaScriptModule} which are used for the
|
||||
* Fabric Interop Layer. This allows us to override the returned classes once the user is invoking
|
||||
* `ReactContext.getJsModule()`.
|
||||
*
|
||||
* <p>Currently we only support a `RCTEventEmitter` re-implementation, being `InteropEventEmitter`
|
||||
* but this class can support other re-implementation in the future.
|
||||
*/
|
||||
public class InteropModuleRegistry {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final HashMap<Class, Object> supportedModules;
|
||||
|
||||
public InteropModuleRegistry() {
|
||||
this.supportedModules = new HashMap<>();
|
||||
}
|
||||
|
||||
public <T extends JavaScriptModule> boolean shouldReturnInteropModule(Class<T> requestedModule) {
|
||||
return checkReactFeatureFlagsConditions() && supportedModules.containsKey(requestedModule);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T extends JavaScriptModule> T getInteropModule(Class<T> requestedModule) {
|
||||
if (checkReactFeatureFlagsConditions()) {
|
||||
//noinspection unchecked
|
||||
return (T) supportedModules.get(requestedModule);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends JavaScriptModule> void registerInteropModule(
|
||||
Class<T> interopModuleInterface, Object interopModule) {
|
||||
if (checkReactFeatureFlagsConditions()) {
|
||||
supportedModules.put(interopModuleInterface, interopModule);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkReactFeatureFlagsConditions() {
|
||||
return ReactFeatureFlags.enableFabricRenderer && ReactFeatureFlags.unstable_useFabricInterop;
|
||||
}
|
||||
}
|
||||
+8
@@ -34,6 +34,14 @@ public class ReactFeatureFlags {
|
||||
*/
|
||||
public static volatile boolean enableFabricRenderer = false;
|
||||
|
||||
/**
|
||||
* Should this application enable the Fabric Interop Layer for Android? If yes, the application
|
||||
* will behave so that it can accept non-Fabric components and render them on Fabric. This toggle
|
||||
* is controlling extra logic such as custom event dispatching that are needed for the Fabric
|
||||
* Interop Layer to work correctly.
|
||||
*/
|
||||
public static volatile boolean unstable_useFabricInterop = false;
|
||||
|
||||
/**
|
||||
* Feature flag to enable the new bridgeless architecture. Note: Enabling this will force enable
|
||||
* the following flags: `useTurboModules` & `enableFabricRenderer`.
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ object DefaultNewArchitectureEntryPoint {
|
||||
) {
|
||||
ReactFeatureFlags.useTurboModules = turboModulesEnabled
|
||||
ReactFeatureFlags.enableFabricRenderer = fabricEnabled
|
||||
ReactFeatureFlags.unstable_useFabricInterop = fabricEnabled
|
||||
|
||||
this.privateFabricEnabled = fabricEnabled
|
||||
this.privateTurboModulesEnabled = turboModulesEnabled
|
||||
|
||||
+7
@@ -56,6 +56,7 @@ import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.react.fabric.events.EventBeatManager;
|
||||
import com.facebook.react.fabric.events.EventEmitterWrapper;
|
||||
import com.facebook.react.fabric.events.FabricEventEmitter;
|
||||
import com.facebook.react.fabric.interop.InteropEventEmitter;
|
||||
import com.facebook.react.fabric.mounting.MountItemDispatcher;
|
||||
import com.facebook.react.fabric.mounting.MountingManager;
|
||||
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
|
||||
@@ -81,6 +82,7 @@ import com.facebook.react.uimanager.ViewManagerRegistry;
|
||||
import com.facebook.react.uimanager.events.EventCategoryDef;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.uimanager.events.EventDispatcherImpl;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.views.text.TextLayoutManager;
|
||||
import com.facebook.react.views.text.TextLayoutManagerMapBuffer;
|
||||
import java.util.HashMap;
|
||||
@@ -390,6 +392,11 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
||||
|
||||
ReactMarker.addFabricListener(mDevToolsReactPerfLogger);
|
||||
}
|
||||
if (ReactFeatureFlags.unstable_useFabricInterop) {
|
||||
InteropEventEmitter interopEventEmitter = new InteropEventEmitter(mReactApplicationContext);
|
||||
mReactApplicationContext.internal_registerInteropModule(
|
||||
RCTEventEmitter.class, interopEventEmitter);
|
||||
}
|
||||
}
|
||||
|
||||
// This is called on the JS thread (see CatalystInstanceImpl).
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.interop;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
|
||||
/**
|
||||
* An {@link Event} class used by the {@link InteropEventEmitter}. This class is just holding the
|
||||
* event name and the data which is received by the `receiveEvent` method and will be passed over
|
||||
* the the {@link com.facebook.react.uimanager.events.EventDispatcher}
|
||||
*/
|
||||
class InteropEvent extends Event<InteropEvent> {
|
||||
|
||||
private final String mName;
|
||||
private final WritableMap mEventData;
|
||||
|
||||
InteropEvent(String name, @Nullable WritableMap eventData, int surfaceId, int viewTag) {
|
||||
super(surfaceId, viewTag);
|
||||
mName = name;
|
||||
mEventData = eventData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
public WritableMap getEventData() {
|
||||
return mEventData;
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.interop;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.uimanager.UIManagerHelper;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
/**
|
||||
* A reimplementation of {@link RCTEventEmitter} which is using a {@link EventDispatcher} under the
|
||||
* hood.
|
||||
*
|
||||
* <p>On Fabric, you're supposed to use {@link EventDispatcher} to dispatch events. However, we
|
||||
* provide an interop layer for non-Fabric migrated components.
|
||||
*
|
||||
* <p>This instance will be returned if the user is invoking `context.getJsModule(RCTEventEmitter)
|
||||
* and is providing support for the `receiveEvent` method, so that non-Fabric ViewManagers can
|
||||
* continue to deliver events also when Fabric is turned on.
|
||||
*/
|
||||
public class InteropEventEmitter implements RCTEventEmitter {
|
||||
|
||||
private final ReactContext mReactContext;
|
||||
|
||||
private @Nullable EventDispatcher mEventDispatcherOverride;
|
||||
|
||||
public InteropEventEmitter(ReactContext reactContext) {
|
||||
mReactContext = reactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveEvent(int targetReactTag, String eventName, @Nullable WritableMap eventData) {
|
||||
EventDispatcher dispatcher;
|
||||
if (mEventDispatcherOverride != null) {
|
||||
dispatcher = mEventDispatcherOverride;
|
||||
} else {
|
||||
dispatcher = UIManagerHelper.getEventDispatcherForReactTag(mReactContext, targetReactTag);
|
||||
}
|
||||
int surfaceId = UIManagerHelper.getSurfaceId(mReactContext);
|
||||
if (dispatcher != null) {
|
||||
dispatcher.dispatchEvent(new InteropEvent(eventName, eventData, surfaceId, targetReactTag));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveTouches(
|
||||
String eventName, WritableArray touches, WritableArray changedIndices) {
|
||||
throw new UnsupportedOperationException(
|
||||
"EventEmitter#receiveTouches is not supported by the Fabric Interop Layer");
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void overrideEventDispatcher(EventDispatcher eventDispatcherOverride) {
|
||||
mEventDispatcherOverride = eventDispatcherOverride;
|
||||
}
|
||||
}
|
||||
+1
@@ -50,6 +50,7 @@ public class ThemedReactContext extends ReactContext {
|
||||
if (reactApplicationContext.hasCatalystInstance()) {
|
||||
initializeWithInstance(reactApplicationContext.getCatalystInstance());
|
||||
}
|
||||
initializeInteropModules(reactApplicationContext);
|
||||
mReactApplicationContext = reactApplicationContext;
|
||||
mModuleName = moduleName;
|
||||
mSurfaceId = surfaceId;
|
||||
|
||||
+32
@@ -10,11 +10,15 @@ package com.facebook.react.uimanager;
|
||||
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.systrace.SystraceMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helps generate constants map for {@link UIManagerModule} by collecting and merging constants from
|
||||
@@ -113,6 +117,13 @@ import java.util.Map;
|
||||
|
||||
Map viewManagerBubblingEvents = viewManager.getExportedCustomBubblingEventTypeConstants();
|
||||
if (viewManagerBubblingEvents != null) {
|
||||
|
||||
if (ReactFeatureFlags.enableFabricRenderer && ReactFeatureFlags.unstable_useFabricInterop) {
|
||||
// For Fabric, events needs to be fired with a "top" prefix.
|
||||
// For the sake of Fabric Interop, here we normalize events adding "top" in their
|
||||
// name if the user hasn't provided it.
|
||||
normalizeEventTypes(viewManagerBubblingEvents);
|
||||
}
|
||||
recursiveMerge(cumulativeBubblingEventTypes, viewManagerBubblingEvents);
|
||||
recursiveMerge(viewManagerBubblingEvents, defaultBubblingEvents);
|
||||
viewManagerConstants.put(BUBBLING_EVENTS_KEY, viewManagerBubblingEvents);
|
||||
@@ -145,6 +156,27 @@ import java.util.Map;
|
||||
return viewManagerConstants;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
/* package */ static void normalizeEventTypes(Map events) {
|
||||
if (events == null) {
|
||||
return;
|
||||
}
|
||||
Set<String> keysToNormalize = new HashSet<>();
|
||||
for (Object key : events.keySet()) {
|
||||
if (key instanceof String) {
|
||||
String keyString = (String) key;
|
||||
if (!keyString.startsWith("top")) {
|
||||
keysToNormalize.add(keyString);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String oldKey : keysToNormalize) {
|
||||
Object value = events.get(oldKey);
|
||||
String newKey = "top" + oldKey.substring(0, 1).toUpperCase() + oldKey.substring(1);
|
||||
events.put(newKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges {@param source} map into {@param dest} map recursively */
|
||||
private static void recursiveMerge(@Nullable Map dest, @Nullable Map source) {
|
||||
if (dest == null || source == null || source.isEmpty()) {
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.interop;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
public class FakeRCTEventEmitter implements RCTEventEmitter {
|
||||
|
||||
@Override
|
||||
public void receiveEvent(int targetReactTag, String eventName, @Nullable WritableMap event) {}
|
||||
|
||||
@Override
|
||||
public void receiveTouches(
|
||||
String eventName, WritableArray touches, WritableArray changedIndices) {}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge.interop;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.react.modules.core.JSTimers;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class InteropModuleRegistryTest {
|
||||
|
||||
InteropModuleRegistry underTest;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
underTest = new InteropModuleRegistry();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnInteropModule_withFabricDisabled_returnsFalse() {
|
||||
ReactFeatureFlags.enableFabricRenderer = false;
|
||||
|
||||
assertFalse(underTest.shouldReturnInteropModule(RCTEventEmitter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnInteropModule_withFabricInteropDisabled_returnsFalse() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = false;
|
||||
|
||||
assertFalse(underTest.shouldReturnInteropModule(RCTEventEmitter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnInteropModule_withUnregisteredClass_returnsFalse() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = true;
|
||||
|
||||
assertFalse(underTest.shouldReturnInteropModule(JSTimers.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnInteropModule_withRegisteredClass_returnsTrue() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = true;
|
||||
|
||||
underTest.registerInteropModule(RCTEventEmitter.class, new FakeRCTEventEmitter());
|
||||
|
||||
assertTrue(underTest.shouldReturnInteropModule(RCTEventEmitter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInteropModule_withRegisteredClassAndInvalidFlags_returnsNull() {
|
||||
ReactFeatureFlags.enableFabricRenderer = false;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = false;
|
||||
underTest.registerInteropModule(RCTEventEmitter.class, new FakeRCTEventEmitter());
|
||||
|
||||
RCTEventEmitter interopModule = underTest.getInteropModule(RCTEventEmitter.class);
|
||||
|
||||
assertNull(interopModule);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInteropModule_withRegisteredClassAndValidFlags_returnsInteropModule() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = true;
|
||||
underTest.registerInteropModule(RCTEventEmitter.class, new FakeRCTEventEmitter());
|
||||
|
||||
RCTEventEmitter interopModule = underTest.getInteropModule(RCTEventEmitter.class);
|
||||
|
||||
assertTrue(interopModule instanceof FakeRCTEventEmitter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInteropModule_withUnregisteredClass_returnsNull() {
|
||||
ReactFeatureFlags.enableFabricRenderer = true;
|
||||
ReactFeatureFlags.unstable_useFabricInterop = true;
|
||||
JSTimers missingModule = underTest.getInteropModule(JSTimers.class);
|
||||
|
||||
assertNull(missingModule);
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.interop;
|
||||
|
||||
import com.facebook.react.uimanager.events.BatchEventDispatchedListener;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
import com.facebook.react.uimanager.events.EventDispatcherListener;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.uimanager.events.RCTModernEventEmitter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class FakeEventDispatcher implements EventDispatcher {
|
||||
|
||||
List<Event> recordedDispatchedEvents = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void dispatchEvent(Event event) {
|
||||
recordedDispatchedEvents.add(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchAllEvents() {}
|
||||
|
||||
@Override
|
||||
public void addListener(EventDispatcherListener listener) {}
|
||||
|
||||
@Override
|
||||
public void removeListener(EventDispatcherListener listener) {}
|
||||
|
||||
@Override
|
||||
public void addBatchEventDispatchedListener(BatchEventDispatchedListener listener) {}
|
||||
|
||||
@Override
|
||||
public void removeBatchEventDispatchedListener(BatchEventDispatchedListener listener) {}
|
||||
|
||||
@Override
|
||||
public void registerEventEmitter(int uiManagerType, RCTEventEmitter eventEmitter) {}
|
||||
|
||||
@Override
|
||||
public void registerEventEmitter(int uiManagerType, RCTModernEventEmitter eventEmitter) {}
|
||||
|
||||
@Override
|
||||
public void unregisterEventEmitter(int uiManagerType) {}
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroyed() {}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.interop;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import com.facebook.react.bridge.JavaOnlyMap;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class InteropEventEmitterTest {
|
||||
|
||||
ReactContext mReactContext;
|
||||
FakeEventDispatcher mEventDispatcher;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
|
||||
mEventDispatcher = new FakeEventDispatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveEvent_dispatchesCorrectly() {
|
||||
InteropEventEmitter eventEmitter = new InteropEventEmitter(mReactContext);
|
||||
eventEmitter.overrideEventDispatcher(mEventDispatcher);
|
||||
|
||||
eventEmitter.receiveEvent(42, "onTest", null);
|
||||
|
||||
assertEquals(1, mEventDispatcher.recordedDispatchedEvents.size());
|
||||
assertEquals("onTest", mEventDispatcher.recordedDispatchedEvents.get(0).getEventName());
|
||||
assertEquals(InteropEvent.class, mEventDispatcher.recordedDispatchedEvents.get(0).getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void receiveEvent_dataIsPreserved() {
|
||||
InteropEventEmitter eventEmitter = new InteropEventEmitter(mReactContext);
|
||||
eventEmitter.overrideEventDispatcher(mEventDispatcher);
|
||||
WritableMap eventData = JavaOnlyMap.of("color", "indigo");
|
||||
|
||||
eventEmitter.receiveEvent(42, "onTest", eventData);
|
||||
|
||||
InteropEvent event = (InteropEvent) mEventDispatcher.recordedDispatchedEvents.get(0);
|
||||
WritableMap dispatchedEventData = event.getEventData();
|
||||
assertNotNull(dispatchedEventData);
|
||||
assertEquals("indigo", dispatchedEventData.getString("color"));
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void receiveTouches_isNotSupported() {
|
||||
InteropEventEmitter eventEmitter = new InteropEventEmitter(mReactContext);
|
||||
eventEmitter.receiveTouches("a touch", null, null);
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import java.util.Map;
|
||||
import org.junit.Test;
|
||||
|
||||
public class UIManagerModuleConstantsHelperTest {
|
||||
|
||||
@Test
|
||||
public void normalizeEventTypes_withNull_doesNothing() {
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizeEventTypes_withEmptyMap_doesNothing() {
|
||||
Map<Object, Object> emptyMap = MapBuilder.builder().build();
|
||||
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(emptyMap);
|
||||
|
||||
assertTrue(emptyMap.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizeEventTypes_withOnEvent_doesNormalize() {
|
||||
Map<Object, Object> onClickMap = MapBuilder.builder().put("onClick", "¯\\_(ツ)_/¯").build();
|
||||
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap);
|
||||
|
||||
assertTrue(onClickMap.containsKey("topOnClick"));
|
||||
assertTrue(onClickMap.containsKey("onClick"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizeEventTypes_withTopEvent_doesNormalize() {
|
||||
Map<Object, Object> onClickMap = MapBuilder.builder().put("topOnClick", "¯\\_(ツ)_/¯").build();
|
||||
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap);
|
||||
|
||||
assertTrue(onClickMap.containsKey("topOnClick"));
|
||||
assertFalse(onClickMap.containsKey("onClick"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void normalizeEventTypes_withNestedObjects_doesNotLoseThem() {
|
||||
Map<String, Object> nestedObjects =
|
||||
MapBuilder.<String, Object>builder()
|
||||
.put(
|
||||
"onColorChanged",
|
||||
MapBuilder.of(
|
||||
"phasedRegistrationNames",
|
||||
MapBuilder.of(
|
||||
"bubbled", "onColorChanged", "captured", "onColorChangedCapture")))
|
||||
.build();
|
||||
|
||||
UIManagerModuleConstantsHelper.normalizeEventTypes(nestedObjects);
|
||||
|
||||
assertTrue(nestedObjects.containsKey("topOnColorChanged"));
|
||||
Map<String, Object> innerMap = (Map<String, Object>) nestedObjects.get("topOnColorChanged");
|
||||
assertNotNull(innerMap);
|
||||
assertTrue(innerMap.containsKey("phasedRegistrationNames"));
|
||||
Map<String, Object> innerInnerMap =
|
||||
(Map<String, Object>) innerMap.get("phasedRegistrationNames");
|
||||
assertNotNull(innerInnerMap);
|
||||
assertEquals("onColorChanged", innerInnerMap.get("bubbled"));
|
||||
assertEquals("onColorChangedCapture", innerInnerMap.get("captured"));
|
||||
|
||||
assertTrue(nestedObjects.containsKey("onColorChanged"));
|
||||
innerMap = (Map<String, Object>) nestedObjects.get("topOnColorChanged");
|
||||
assertNotNull(innerMap);
|
||||
assertTrue(innerMap.containsKey("phasedRegistrationNames"));
|
||||
innerInnerMap = (Map<String, Object>) innerMap.get("phasedRegistrationNames");
|
||||
assertNotNull(innerInnerMap);
|
||||
assertEquals("onColorChanged", innerInnerMap.get("bubbled"));
|
||||
assertEquals("onColorChangedCapture", innerInnerMap.get("captured"));
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,6 @@ android.enableJetifier=true
|
||||
FLIPPER_VERSION=0.182.0
|
||||
|
||||
# RN-Tester is building with NewArch always enabled
|
||||
newArchEnabled=true
|
||||
newArchEnabled=false
|
||||
# RN-Tester is running with Hermes enabled and filtering variants with enableHermesOnlyInVariants
|
||||
hermesEnabled=true
|
||||
|
||||
Reference in New Issue
Block a user