diff --git a/packages/react-native/ReactCommon/react/bridging/EventEmitter.h b/packages/react-native/ReactCommon/react/bridging/EventEmitter.h index acfb6a3152d..a635ce1bae1 100644 --- a/packages/react-native/ReactCommon/react/bridging/EventEmitter.h +++ b/packages/react-native/ReactCommon/react/bridging/EventEmitter.h @@ -28,14 +28,27 @@ class EventSubscription { EventSubscription(const EventSubscription&) = delete; EventSubscription& operator=(const EventSubscription&) = delete; + void remove() { + remove_(); + } + private: friend Bridging; - std::function remove_; }; template <> struct Bridging { + static EventSubscription fromJs( + jsi::Runtime& rt, + const jsi::Object& value, + const std::shared_ptr& jsInvoker) { + auto listener = bridging::fromJs>( + rt, value.getProperty(rt, "remove"), jsInvoker); + return EventSubscription( + [listener = std::move(listener)]() mutable { listener(); }); + } + static jsi::Object toJs( jsi::Runtime& rt, const EventSubscription& eventSubscription, diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h index 9ef16f4cc5c..478ba2d6226 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h @@ -91,6 +91,8 @@ class JSI_EXPORT TurboModule : public jsi::HostObject { size_t count); }; std::unordered_map methodMap_; + + friend class TurboModuleTestFixtureInternal; std::unordered_map> eventEmitterMap_; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/tests/TurboModuleTestFixture.h b/packages/react-native/ReactCommon/react/nativemodule/core/tests/TurboModuleTestFixture.h index 84e20686e69..c98f0595fb5 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/tests/TurboModuleTestFixture.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/tests/TurboModuleTestFixture.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -16,10 +17,25 @@ #include namespace facebook::react { -class TurboModule; + +class TurboModuleTestFixtureInternal { + public: + static bool containsEventEmitter( + TurboModule& turboModule, + const std::string& eventEmitterName) { + return turboModule.eventEmitterMap_.contains(eventEmitterName); + } + + static const std::shared_ptr getEventEmitter( + TurboModule& turboModule, + const std::string& eventEmitterName) { + return turboModule.eventEmitterMap_.at(eventEmitterName); + } +}; template -class TurboModuleTestFixture : public ::testing::Test { +class TurboModuleTestFixture : public TurboModuleTestFixtureInternal, + public ::testing::Test { static_assert( std::is_base_of::value, "T must be derived from TurboModule"); @@ -54,6 +70,29 @@ class TurboModuleTestFixture : public ::testing::Test { })); } + template + EventSubscription addEventEmitterListener( + jsi::Runtime& rt, + const std::string& eventEmitterName, + Listener&& listener) { + EXPECT_TRUE(containsEventEmitter(*module_, eventEmitterName)); + auto listenJs = bridging::toJs( + rt, + [listener = std::forward(listener)]( + const EventType&... event) { listener(event...); }, + jsInvoker_); + std::shared_ptr> eventEmitter = + std::static_pointer_cast>( + getEventEmitter(*module_, eventEmitterName)); + jsi::Object eventEmitterJs = bridging::toJs(rt, *eventEmitter, jsInvoker_); + auto eventSubscriptionJs = + jsi::Object(eventEmitterJs.asFunction(rt) + .callWithThis(rt, eventEmitterJs, listenJs) + .asObject(rt)); + return bridging::fromJs( + rt, eventSubscriptionJs, jsInvoker_); + } + void TearDown() override { module_ = nullptr; jsInvoker_ = nullptr; diff --git a/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp b/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp index c4655af63e9..1de45d805f8 100644 --- a/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp +++ b/packages/rn-tester/NativeCxxModuleExample/tests/NativeCxxModuleExampleTests.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -172,4 +171,25 @@ TEST_F( EXPECT_EQ(module_->getWithWithOptionalArgs(*runtime_, false), false); } +TEST_F(NativeCxxModuleExampleTests, VoidFunEmitsEvents) { + int onPressCalled = 0; + std::string onClickCalled; + auto onPressSubscription = addEventEmitterListener<>( + *runtime_, "onPress", [&]() { onPressCalled++; }); + addEventEmitterListener( + *runtime_, "onClick", [&](const std::string& event) { + onClickCalled = event; + }); + module_->voidFunc(*runtime_); + jsInvoker_->flushQueue(); + EXPECT_EQ(onPressCalled, 1); + EXPECT_EQ(onClickCalled, "value from callback on click!"); + + onPressSubscription.remove(); + jsInvoker_->flushQueue(); + + module_->voidFunc(*runtime_); + jsInvoker_->flushQueue(); + EXPECT_EQ(onPressCalled, 1); +} } // namespace facebook::react