diff --git a/change/react-native-windows-013e8bca-757a-4c11-a0b6-12aa0aa23505.json b/change/react-native-windows-013e8bca-757a-4c11-a0b6-12aa0aa23505.json
new file mode 100644
index 00000000000..5453997ffef
--- /dev/null
+++ b/change/react-native-windows-013e8bca-757a-4c11-a0b6-12aa0aa23505.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Simplify Microsoft.ReactNative.IntegrationTests",
+ "packageName": "react-native-windows",
+ "email": "vmorozov@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/vnext/Desktop.ABITests/React.Windows.Desktop.ABITests.vcxproj b/vnext/Desktop.ABITests/React.Windows.Desktop.ABITests.vcxproj
index 45cf81f0faa..24cb5a0087a 100644
--- a/vnext/Desktop.ABITests/React.Windows.Desktop.ABITests.vcxproj
+++ b/vnext/Desktop.ABITests/React.Windows.Desktop.ABITests.vcxproj
@@ -128,6 +128,8 @@
+
+
@@ -151,13 +153,16 @@
+
+
+
-
+
diff --git a/vnext/Desktop.ABITests/React.Windows.Desktop.ABITests.vcxproj.filters b/vnext/Desktop.ABITests/React.Windows.Desktop.ABITests.vcxproj.filters
index 3f5b169e85f..f12a16e3251 100644
--- a/vnext/Desktop.ABITests/React.Windows.Desktop.ABITests.vcxproj.filters
+++ b/vnext/Desktop.ABITests/React.Windows.Desktop.ABITests.vcxproj.filters
@@ -1,90 +1,69 @@
-
- {93995380-89BD-4b04-88EB-625FBE52EBFB}
- h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
+
+ {dd007ef2-0241-4f4c-bac8-916c537375a3}
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+ {8af93322-e15d-4a04-af2f-d9012ef7b089}
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Utilities
+
+
+ Utilities
- Source Files
+ Utilities
-
- Source Files
+
+ Utilities
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
+ Utilities
- Header Files
+ Utilities
- Header Files
+ Utilities
+
+
+ Utilities
+
+
+ Utilities
-
+
+ Other files
+
-
+
+ Other files
+
+
\ No newline at end of file
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.cpp
new file mode 100644
index 00000000000..46e2b7c6fa5
--- /dev/null
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.cpp
@@ -0,0 +1,135 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "pch.h"
+#include
+#include
+#include "TestEventService.h"
+#include "TestReactNativeHostHolder.h"
+
+using namespace facebook::jsi;
+using namespace winrt;
+using namespace Microsoft::ReactNative;
+
+namespace ReactNativeIntegrationTests {
+
+// Use anonymous namespace to avoid any linking conflicts
+namespace {
+
+REACT_MODULE(TestExecuteJsiModule)
+struct TestExecuteJsiModule {
+ REACT_INIT(Initialize)
+ void Initialize(ReactContext const &reactContext) noexcept {
+ m_reactContext = reactContext;
+ TestEventService::LogEvent("initialize", nullptr);
+ }
+
+ REACT_METHOD(TestSimpleExecuteJsi, L"testSimpleExecuteJsi")
+ void TestSimpleExecuteJsi() noexcept {
+ TestEventService::LogEvent("testSimpleExecuteJsi started", nullptr);
+ ExecuteJsi(m_reactContext, [](Runtime &rt) {
+ auto eval = rt.global().getPropertyAsFunction(rt, "eval");
+ auto addFunc = eval.call(rt, "(function(x, y) { return x + y; })").getObject(rt).getFunction(rt);
+ TestCheckEqual(7, addFunc.call(rt, 3, 4).getNumber());
+ TestEventService::LogEvent("testSimpleExecuteJsi completed", nullptr);
+ });
+ }
+
+ REACT_METHOD(TestHostFunction, L"testHostFunction")
+ void TestHostFunction() noexcept {
+ TestEventService::LogEvent("testHostFunction started", nullptr);
+ ExecuteJsi(m_reactContext, [](Runtime &rt) {
+ Function hostGreeter = Function::createFromHostFunction(
+ rt,
+ PropNameID::forAscii(rt, "hostGreeter"),
+ 1,
+ [](Runtime &rt, const Value & /*thisVal*/, const Value *args, size_t count) {
+ TestCheckEqual(1, count);
+ return Value{rt, String::createFromUtf8(rt, "Hello " + args[0].getString(rt).utf8(rt))};
+ });
+ TestCheckEqual("Hello World", hostGreeter.call(rt, "World").getString(rt).utf8(rt));
+ TestCheck(hostGreeter.getHostFunction(rt) != nullptr);
+
+ Function hostGreater2 = hostGreeter.getFunction(rt);
+ TestCheck(hostGreater2.isHostFunction(rt));
+ TestCheckEqual("Hello People", hostGreater2.call(rt, "People").getString(rt).utf8(rt));
+ TestCheck(hostGreater2.getHostFunction(rt) != nullptr);
+
+ TestEventService::LogEvent("testHostFunction completed", nullptr);
+ });
+ }
+
+ REACT_METHOD(TestHostObject, L"testHostObject")
+ void TestHostObject() noexcept {
+ TestEventService::LogEvent("testHostObject started", nullptr);
+ ExecuteJsi(m_reactContext, [](Runtime &rt) {
+ class GreeterHostObject : public HostObject {
+ Value get(Runtime &rt, const PropNameID &) override {
+ return String::createFromAscii(rt, "Hello");
+ }
+ void set(Runtime &, const PropNameID &, const Value &) override {}
+ };
+
+ Object hostObjGreeter = Object::createFromHostObject(rt, std::make_shared());
+ TestCheckEqual(
+ "Hello", hostObjGreeter.getProperty(rt, PropNameID::forAscii(rt, "someProp")).getString(rt).utf8(rt));
+ TestCheck(hostObjGreeter.getHostObject(rt) != nullptr);
+
+ Object hostObjGreeter2 = Value{rt, hostObjGreeter}.getObject(rt);
+ TestCheck(hostObjGreeter2.isHostObject(rt));
+ TestCheckEqual(
+ "Hello", hostObjGreeter2.getProperty(rt, PropNameID::forAscii(rt, "someProp")).getString(rt).utf8(rt));
+ TestCheck(hostObjGreeter2.getHostObject(rt) != nullptr);
+
+ TestEventService::LogEvent("testHostObject completed", nullptr);
+ });
+ }
+
+ REACT_METHOD(TestSameJsiRuntime, L"testSameJsiRuntime")
+ void TestSameJsiRuntime() noexcept {
+ // Make sure that we use the same facebook::jsi::Runtime when we run second time.
+ // The JSI executed synchronously here because we are in JS thread.
+ TestEventService::LogEvent("testSameJsiRuntime started", nullptr);
+ Runtime *jsiRuntime{};
+ ExecuteJsi(m_reactContext, [&jsiRuntime](Runtime &rt) { jsiRuntime = &rt; });
+ ExecuteJsi(m_reactContext, [&jsiRuntime](Runtime &rt) {
+ TestCheckEqual(jsiRuntime, &rt);
+ TestEventService::LogEvent("testSameJsiRuntime completed", nullptr);
+ });
+ }
+
+ private:
+ ReactContext m_reactContext;
+};
+
+struct TestPackageProvider : winrt::implements {
+ void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept {
+ TryAddAttributedModule(packageBuilder, L"TestExecuteJsiModule");
+ }
+};
+
+} // namespace
+
+TEST_CLASS (ExecuteJsiTests) {
+ TEST_METHOD(Run_JSDrivenTests) {
+ TestEventService::Initialize();
+
+ auto reactNativeHost = TestReactNativeHostHolder(L"ExecuteJsiTests", [](ReactNativeHost const &host) noexcept {
+ host.PackageProviders().Append(winrt::make());
+ });
+
+ TestEventService::ObserveEvents({
+ TestEvent{"initialize", nullptr},
+ TestEvent{"testSimpleExecuteJsi started", nullptr},
+ TestEvent{"testSimpleExecuteJsi completed", nullptr},
+ TestEvent{"testHostFunction started", nullptr},
+ TestEvent{"testHostFunction completed", nullptr},
+ TestEvent{"testHostObject started", nullptr},
+ TestEvent{"testHostObject completed", nullptr},
+ TestEvent{"testSameJsiRuntime started", nullptr},
+ TestEvent{"testSameJsiRuntime completed", nullptr},
+ });
+ }
+};
+
+} // namespace ReactNativeIntegrationTests
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.js b/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.js
new file mode 100644
index 00000000000..f47882f5f42
--- /dev/null
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/ExecuteJsiTests.js
@@ -0,0 +1,8 @@
+import { NativeModules } from 'react-native';
+
+const { TestExecuteJsiModule } = NativeModules;
+
+TestExecuteJsiModule.testSimpleExecuteJsi();
+TestExecuteJsiModule.testHostFunction();
+TestExecuteJsiModule.testHostObject();
+TestExecuteJsiModule.testSameJsiRuntime();
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/JsiTurboModuleTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/JsiTurboModuleTests.cpp
index b82352c3e48..72acd9ca863 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/JsiTurboModuleTests.cpp
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/JsiTurboModuleTests.cpp
@@ -17,19 +17,16 @@
// See the details for the MySimpleTurboModulePackageProvider below.
#include "pch.h"
-#include
#include // This comes from the react-native package.
#include // This must come from the react-native package, but we use a local version to fix issues.
#include // It is RNW specific
-
-#include
#include
-#include
-#include
-#include
+#include "TestEventService.h"
+#include "TestReactNativeHostHolder.h"
-using namespace winrt::Microsoft::ReactNative;
-using namespace winrt::Windows::Foundation;
+using namespace facebook;
+using namespace winrt;
+using namespace Microsoft::ReactNative;
namespace ReactNativeIntegrationTests {
@@ -40,137 +37,131 @@ namespace {
// >>>> Start generated
// The spec from .h file
-struct MySimpleTurboModuleSpec : ::facebook::react::TurboModule {
- virtual void
- logAction(facebook::jsi::Runtime &rt, const facebook::jsi::String &actionName, const facebook::jsi::Value &value) = 0;
- virtual void voidFunc(::facebook::jsi::Runtime &rt) = 0;
- virtual bool getBool(::facebook::jsi::Runtime &rt, bool arg) = 0;
- virtual double getNumber(::facebook::jsi::Runtime &rt, double arg) = 0;
- virtual ::facebook::jsi::String getString(::facebook::jsi::Runtime &rt, const ::facebook::jsi::String &arg) = 0;
- virtual ::facebook::jsi::Array getArray(::facebook::jsi::Runtime &rt, const ::facebook::jsi::Array &arg) = 0;
- virtual ::facebook::jsi::Object getObject(::facebook::jsi::Runtime &rt, const ::facebook::jsi::Object &arg) = 0;
- virtual ::facebook::jsi::Object getValue(
- ::facebook::jsi::Runtime &rt,
- double x,
- const ::facebook::jsi::String &y,
- const ::facebook::jsi::Object &z) = 0;
- virtual void getValueWithCallback(::facebook::jsi::Runtime &rt, const ::facebook::jsi::Function &callback) = 0;
- virtual ::facebook::jsi::Value getValueWithPromise(::facebook::jsi::Runtime &rt, bool error) = 0;
- virtual ::facebook::jsi::Object getConstants(::facebook::jsi::Runtime &rt) = 0;
+struct MySimpleTurboModuleSpec : react::TurboModule {
+ virtual void logAction(jsi::Runtime &rt, const jsi::String &actionName, const jsi::Value &value) = 0;
+ virtual void voidFunc(jsi::Runtime &rt) = 0;
+ virtual bool getBool(jsi::Runtime &rt, bool arg) = 0;
+ virtual double getNumber(jsi::Runtime &rt, double arg) = 0;
+ virtual jsi::String getString(jsi::Runtime &rt, const jsi::String &arg) = 0;
+ virtual jsi::Array getArray(jsi::Runtime &rt, const jsi::Array &arg) = 0;
+ virtual jsi::Object getObject(jsi::Runtime &rt, const jsi::Object &arg) = 0;
+ virtual jsi::Object getValue(jsi::Runtime &rt, double x, const jsi::String &y, const jsi::Object &z) = 0;
+ virtual void getValueWithCallback(jsi::Runtime &rt, const jsi::Function &callback) = 0;
+ virtual jsi::Value getValueWithPromise(jsi::Runtime &rt, bool error) = 0;
+ virtual jsi::Object getConstants(jsi::Runtime &rt) = 0;
protected:
- MySimpleTurboModuleSpec(std::shared_ptr jsInvoker);
+ MySimpleTurboModuleSpec(std::shared_ptr jsInvoker);
};
// The spec from .cpp file
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_logAction(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_logAction(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 2);
static_cast(&turboModule)->logAction(rt, args[0].getString(rt), args[1]);
- return ::facebook::jsi::Value::undefined();
+ return jsi::Value::undefined();
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_voidFunc(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_voidFunc(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 0);
static_cast(&turboModule)->voidFunc(rt);
- return ::facebook::jsi::Value::undefined();
+ return jsi::Value::undefined();
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_getBool(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_getBool(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 1);
- return ::facebook::jsi::Value(static_cast(&turboModule)->getBool(rt, args[0].getBool()));
+ return jsi::Value(static_cast(&turboModule)->getBool(rt, args[0].getBool()));
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_getNumber(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_getNumber(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 1);
- return ::facebook::jsi::Value(
- static_cast(&turboModule)->getNumber(rt, args[0].getNumber()));
+ return jsi::Value(static_cast(&turboModule)->getNumber(rt, args[0].getNumber()));
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_getString(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_getString(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 1);
return static_cast(&turboModule)->getString(rt, args[0].getString(rt));
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_getArray(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_getArray(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 1);
return static_cast(&turboModule)->getArray(rt, args[0].getObject(rt).getArray(rt));
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_getObject(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_getObject(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 1);
return static_cast(&turboModule)->getObject(rt, args[0].getObject(rt));
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_getValue(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_getValue(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 3);
return static_cast(&turboModule)
->getValue(rt, args[0].getNumber(), args[1].getString(rt), args[2].getObject(rt));
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_getValueWithCallback(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_getValueWithCallback(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 1);
static_cast(&turboModule)
->getValueWithCallback(rt, std::move(args[0].getObject(rt).getFunction(rt)));
- return ::facebook::jsi::Value::undefined();
+ return jsi::Value::undefined();
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_getValueWithPromise(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_getValueWithPromise(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 1);
return static_cast(&turboModule)->getValueWithPromise(rt, args[0].getBool());
}
-static ::facebook::jsi::Value MySimpleTurboModuleSpec_getConstants(
- ::facebook::jsi::Runtime &rt,
- ::facebook::react::TurboModule &turboModule,
- [[maybe_unused]] const ::facebook::jsi::Value *args,
+static jsi::Value MySimpleTurboModuleSpec_getConstants(
+ jsi::Runtime &rt,
+ react::TurboModule &turboModule,
+ [[maybe_unused]] const jsi::Value *args,
[[maybe_unused]] size_t count) {
assert(count >= 0);
return static_cast(&turboModule)->getConstants(rt);
}
-MySimpleTurboModuleSpec::MySimpleTurboModuleSpec(std::shared_ptr<::facebook::react::CallInvoker> jsInvoker)
- : ::facebook::react::TurboModule("MySimpleTurboModule", std::move(jsInvoker)) {
+MySimpleTurboModuleSpec::MySimpleTurboModuleSpec(std::shared_ptr jsInvoker)
+ : react::TurboModule("MySimpleTurboModule", std::move(jsInvoker)) {
methodMap_.try_emplace("logAction", MethodMetadata{0, MySimpleTurboModuleSpec_logAction});
methodMap_.try_emplace("voidFunc", MethodMetadata{0, MySimpleTurboModuleSpec_voidFunc});
methodMap_.try_emplace("getBool", MethodMetadata{1, MySimpleTurboModuleSpec_getBool});
@@ -186,58 +177,26 @@ MySimpleTurboModuleSpec::MySimpleTurboModuleSpec(std::shared_ptr<::facebook::rea
// <<<< End generated
-struct TestAction {
- std::string ActionName;
- JSValue Value;
-};
-
struct MySimpleTurboModule : MySimpleTurboModuleSpec {
- MySimpleTurboModule(std::shared_ptr jsInvoker);
-
- void logAction(facebook::jsi::Runtime &rt, const facebook::jsi::String &actionName, const facebook::jsi::Value &value)
- override;
- void voidFunc(facebook::jsi::Runtime &rt) override;
- bool getBool(facebook::jsi::Runtime &rt, bool arg) override;
- double getNumber(facebook::jsi::Runtime &rt, double arg) override;
- facebook::jsi::String getString(facebook::jsi::Runtime &rt, const facebook::jsi::String &arg) override;
- facebook::jsi::Array getArray(facebook::jsi::Runtime &rt, const facebook::jsi::Array &arg) override;
- facebook::jsi::Object getObject(facebook::jsi::Runtime &rt, const facebook::jsi::Object &arg) override;
- facebook::jsi::Object getValue(
- facebook::jsi::Runtime &rt,
- double x,
- const facebook::jsi::String &y,
- const facebook::jsi::Object &z) override;
- void getValueWithCallback(facebook::jsi::Runtime &rt, const facebook::jsi::Function &callback) override;
- facebook::jsi::Value getValueWithPromise(facebook::jsi::Runtime &rt, bool error) override;
- facebook::jsi::Object getConstants(facebook::jsi::Runtime &rt) override;
-
- public: // Code to report test results
- static void LogAction(std::string actionName, JSValue value) {
- auto lock = std::scoped_lock{s_mutex};
- s_currentAction.ActionName = std::move(actionName);
- s_currentAction.Value = std::move(value);
- ++s_actionIndex;
- s_cv.notify_all();
- }
-
- static std::mutex s_mutex;
- static std::condition_variable s_cv;
- static TestAction s_currentAction;
- static int s_actionIndex;
+ MySimpleTurboModule(std::shared_ptr jsInvoker);
+
+ void logAction(jsi::Runtime &rt, const jsi::String &actionName, const jsi::Value &value) override;
+ void voidFunc(jsi::Runtime &rt) override;
+ bool getBool(jsi::Runtime &rt, bool arg) override;
+ double getNumber(jsi::Runtime &rt, double arg) override;
+ jsi::String getString(jsi::Runtime &rt, const jsi::String &arg) override;
+ jsi::Array getArray(jsi::Runtime &rt, const jsi::Array &arg) override;
+ jsi::Object getObject(jsi::Runtime &rt, const jsi::Object &arg) override;
+ jsi::Object getValue(jsi::Runtime &rt, double x, const jsi::String &y, const jsi::Object &z) override;
+ void getValueWithCallback(jsi::Runtime &rt, const jsi::Function &callback) override;
+ jsi::Value getValueWithPromise(jsi::Runtime &rt, bool error) override;
+ jsi::Object getConstants(jsi::Runtime &rt) override;
};
-/*static*/ std::mutex MySimpleTurboModule::s_mutex;
-/*static*/ std::condition_variable MySimpleTurboModule::s_cv;
-/*static*/ TestAction MySimpleTurboModule::s_currentAction;
-/*static*/ int MySimpleTurboModule::s_actionIndex{-1};
-
-MySimpleTurboModule::MySimpleTurboModule(std::shared_ptr jsInvoker)
+MySimpleTurboModule::MySimpleTurboModule(std::shared_ptr jsInvoker)
: MySimpleTurboModuleSpec(std::move(jsInvoker)) {}
-void MySimpleTurboModule::logAction(
- facebook::jsi::Runtime &rt,
- const facebook::jsi::String &actionName,
- const facebook::jsi::Value &value) {
+void MySimpleTurboModule::logAction(jsi::Runtime &rt, const jsi::String &actionName, const jsi::Value &value) {
JSValue jsValue{};
if (value.isBool()) {
jsValue = JSValue(value.getBool());
@@ -246,76 +205,71 @@ void MySimpleTurboModule::logAction(
} else if (value.isString()) {
jsValue = JSValue(value.getString(rt).utf8(rt));
}
- LogAction(actionName.utf8(rt), std::move(jsValue));
+ TestEventService::LogEvent(actionName.utf8(rt), std::move(jsValue));
}
-void MySimpleTurboModule::voidFunc(facebook::jsi::Runtime & /*rt*/) {
- LogAction("voidFunc called", nullptr);
+void MySimpleTurboModule::voidFunc(jsi::Runtime & /*rt*/) {
+ TestEventService::LogEvent("voidFunc called", nullptr);
}
-bool MySimpleTurboModule::getBool(facebook::jsi::Runtime & /*rt*/, bool arg) {
- LogAction("getBool called", arg);
+bool MySimpleTurboModule::getBool(jsi::Runtime & /*rt*/, bool arg) {
+ TestEventService::LogEvent("getBool called", arg);
return arg;
}
-double MySimpleTurboModule::getNumber(facebook::jsi::Runtime & /*rt*/, double arg) {
- LogAction("getNumber called", arg);
+double MySimpleTurboModule::getNumber(jsi::Runtime & /*rt*/, double arg) {
+ TestEventService::LogEvent("getNumber called", arg);
return arg;
}
-facebook::jsi::String MySimpleTurboModule::getString(facebook::jsi::Runtime &rt, const facebook::jsi::String &arg) {
- LogAction("getString called", arg.utf8(rt));
- return facebook::jsi::String::createFromUtf8(rt, arg.utf8(rt));
+jsi::String MySimpleTurboModule::getString(jsi::Runtime &rt, const jsi::String &arg) {
+ TestEventService::LogEvent("getString called", arg.utf8(rt));
+ return jsi::String::createFromUtf8(rt, arg.utf8(rt));
}
-facebook::jsi::Array MySimpleTurboModule::getArray(facebook::jsi::Runtime &rt, const facebook::jsi::Array &arg) {
- LogAction("getArray called", arg.length(rt));
- return facebook::react::deepCopyJSIArray(rt, arg);
+jsi::Array MySimpleTurboModule::getArray(jsi::Runtime &rt, const jsi::Array &arg) {
+ TestEventService::LogEvent("getArray called", arg.length(rt));
+ return react::deepCopyJSIArray(rt, arg);
}
-facebook::jsi::Object MySimpleTurboModule::getObject(facebook::jsi::Runtime &rt, const facebook::jsi::Object &arg) {
- LogAction("getObject called", "OK");
- return facebook::react::deepCopyJSIObject(rt, arg);
+jsi::Object MySimpleTurboModule::getObject(jsi::Runtime &rt, const jsi::Object &arg) {
+ TestEventService::LogEvent("getObject called", "OK");
+ return react::deepCopyJSIObject(rt, arg);
}
-facebook::jsi::Object MySimpleTurboModule::getValue(
- facebook::jsi::Runtime &rt,
- double x,
- const facebook::jsi::String &y,
- const facebook::jsi::Object &z) {
- LogAction("getValue called", "OK");
+jsi::Object MySimpleTurboModule::getValue(jsi::Runtime &rt, double x, const jsi::String &y, const jsi::Object &z) {
+ TestEventService::LogEvent("getValue called", "OK");
// Note: return type isn't type-safe.
- facebook::jsi::Object result(rt);
- result.setProperty(rt, "x", facebook::jsi::Value(x));
- result.setProperty(rt, "y", facebook::jsi::String::createFromUtf8(rt, y.utf8(rt)));
- result.setProperty(rt, "z", facebook::react::deepCopyJSIObject(rt, z));
+ jsi::Object result(rt);
+ result.setProperty(rt, "x", jsi::Value(x));
+ result.setProperty(rt, "y", jsi::String::createFromUtf8(rt, y.utf8(rt)));
+ result.setProperty(rt, "z", react::deepCopyJSIObject(rt, z));
return result;
}
-void MySimpleTurboModule::getValueWithCallback(facebook::jsi::Runtime &rt, const facebook::jsi::Function &callback) {
- LogAction("getValueWithCallback called", "OK");
- callback.call(rt, facebook::jsi::String::createFromUtf8(rt, "value from callback!"));
+void MySimpleTurboModule::getValueWithCallback(jsi::Runtime &rt, const jsi::Function &callback) {
+ TestEventService::LogEvent("getValueWithCallback called", "OK");
+ callback.call(rt, jsi::String::createFromUtf8(rt, "value from callback!"));
}
-facebook::jsi::Value MySimpleTurboModule::getValueWithPromise(facebook::jsi::Runtime &rt, bool error) {
- LogAction("getValueWithPromise called", error);
- return facebook::react::createPromiseAsJSIValue(
- rt, [error](facebook::jsi::Runtime &rt2, std::shared_ptr promise) {
- if (error) {
- promise->reject("intentional promise rejection");
- } else {
- promise->resolve(facebook::jsi::String::createFromUtf8(rt2, "result!"));
- }
- });
+jsi::Value MySimpleTurboModule::getValueWithPromise(jsi::Runtime &rt, bool error) {
+ TestEventService::LogEvent("getValueWithPromise called", error);
+ return react::createPromiseAsJSIValue(rt, [error](jsi::Runtime &rt2, std::shared_ptr promise) {
+ if (error) {
+ promise->reject("intentional promise rejection");
+ } else {
+ promise->resolve(jsi::String::createFromUtf8(rt2, "result!"));
+ }
+ });
}
-facebook::jsi::Object MySimpleTurboModule::getConstants(facebook::jsi::Runtime &rt) {
- LogAction("getConstants called", "OK");
+jsi::Object MySimpleTurboModule::getConstants(jsi::Runtime &rt) {
+ TestEventService::LogEvent("getConstants called", "OK");
// Note: return type isn't type-safe.
- facebook::jsi::Object result(rt);
- result.setProperty(rt, "const1", facebook::jsi::Value(true));
- result.setProperty(rt, "const2", facebook::jsi::Value(375));
- result.setProperty(rt, "const3", facebook::jsi::String::createFromUtf8(rt, "something"));
+ jsi::Object result(rt);
+ result.setProperty(rt, "const1", jsi::Value(true));
+ result.setProperty(rt, "const2", jsi::Value(375));
+ result.setProperty(rt, "const3", jsi::String::createFromUtf8(rt, "something"));
return result;
}
@@ -330,90 +284,42 @@ struct MySimpleTurboModulePackageProvider
TEST_CLASS (JsiTurboModuleTests) {
TEST_METHOD(ExecuteSampleTurboModule) {
- MySimpleTurboModule::s_actionIndex = -1;
- TestAction expectedActions[] = {
- TestAction{"voidFunc called", nullptr},
- TestAction{"getBool called", true},
- TestAction{"getBool result", true},
- TestAction{"getBool called", false},
- TestAction{"getBool result", false},
- TestAction{"getNumber called", 5.0},
- TestAction{"getNumber result", 5.0},
- TestAction{"getNumber called", std::numeric_limits::quiet_NaN()},
- TestAction{"getNumber result", std::numeric_limits::quiet_NaN()},
- TestAction{"getNumber called", std::numeric_limits::infinity()},
- TestAction{"getNumber result", std::numeric_limits::infinity()},
- TestAction{"getString called", "Hello"},
- TestAction{"getString result", "Hello"},
- TestAction{"getString called", ""},
- TestAction{"getString result", ""},
- TestAction{"getArray called", 3},
- TestAction{"getArray result", "OK"},
- TestAction{"getObject called", "OK"},
- TestAction{"getObject result", "OK"},
- TestAction{"getValue called", "OK"},
- TestAction{"getValue result", "OK"},
- TestAction{"getConstants called", "OK"},
- TestAction{"getConstants result", "OK"},
- TestAction{"getValueWithCallback called", "OK"},
- TestAction{"getValueWithCallback result", "value from callback!"},
- TestAction{"getValueWithPromise called", false},
- TestAction{"getValueWithPromise called", true},
- TestAction{"getValueWithPromise result resolve", "result!"},
- TestAction{"getValueWithPromise result reject", "intentional promise rejection"},
- };
-
- ReactNativeHost host{};
-
- auto queueController = winrt::Windows::System::DispatcherQueueController::CreateOnDedicatedThread();
- queueController.DispatcherQueue().TryEnqueue([&]() noexcept {
- host.PackageProviders().Append(winrt::make());
-
- // bundle is assumed to be co-located with the test binary
- wchar_t testBinaryPath[MAX_PATH];
- TestCheck(GetModuleFileNameW(NULL, testBinaryPath, MAX_PATH) < MAX_PATH);
- testBinaryPath[std::wstring_view{testBinaryPath}.rfind(L"\\")] = 0;
-
- host.InstanceSettings().BundleRootPath(testBinaryPath);
- host.InstanceSettings().JavaScriptBundleFile(L"JsiTurboModuleTests");
- host.InstanceSettings().UseDeveloperSupport(false);
- host.InstanceSettings().UseWebDebugger(false);
- host.InstanceSettings().UseFastRefresh(false);
- host.InstanceSettings().UseLiveReload(false);
- host.InstanceSettings().EnableDeveloperMenu(false);
-
- host.LoadInstance().Completed(
- [](IAsyncAction asyncInfo, AsyncStatus asyncStatus) { TestCheckEqual(AsyncStatus::Completed, asyncStatus); });
- });
+ TestEventService::Initialize();
- auto lock = std::unique_lock{MySimpleTurboModule::s_mutex};
- MySimpleTurboModule::s_cv.wait(lock, [&]() {
- if (MySimpleTurboModule::s_actionIndex >= 0) {
- auto const &expectedAction = expectedActions[MySimpleTurboModule::s_actionIndex];
- TestCheckEqual(expectedAction.ActionName, MySimpleTurboModule::s_currentAction.ActionName);
- if (auto d1 = expectedAction.Value.TryGetDouble(),
- d2 = MySimpleTurboModule::s_currentAction.Value.TryGetDouble();
- d1 && d2) {
- if (!isnan(*d1) && !isnan(*d2)) {
- TestCheckEqual(*d1, *d2);
- }
- } else if (expectedAction.Value != MySimpleTurboModule::s_currentAction.Value) {
- std::stringstream os;
- os << "Action index: " << MySimpleTurboModule::s_actionIndex << '\n'
- << "Expected: " << expectedAction.Value.ToString() << '\n'
- << "Actual: " << MySimpleTurboModule::s_currentAction.Value.ToString();
- TestCheckFail("%s", os.str().c_str());
- }
- }
- return MySimpleTurboModule::s_actionIndex >= static_cast(std::size(expectedActions)) - 1;
+ auto reactNativeHost = TestReactNativeHostHolder(L"JsiTurboModuleTests", [](ReactNativeHost const &host) noexcept {
+ host.PackageProviders().Append(winrt::make());
});
- // Make sure that we did all actions
- TestCheckEqual(MySimpleTurboModule::s_actionIndex, static_cast(std::size(expectedActions)) - 1);
- lock.unlock();
-
- host.UnloadInstance().get();
- queueController.ShutdownQueueAsync().get();
+ TestEventService::ObserveEvents(
+ {TestEvent{"voidFunc called", nullptr},
+ TestEvent{"getBool called", true},
+ TestEvent{"getBool result", true},
+ TestEvent{"getBool called", false},
+ TestEvent{"getBool result", false},
+ TestEvent{"getNumber called", 5.0},
+ TestEvent{"getNumber result", 5.0},
+ TestEvent{"getNumber called", std::numeric_limits::quiet_NaN()},
+ TestEvent{"getNumber result", std::numeric_limits::quiet_NaN()},
+ TestEvent{"getNumber called", std::numeric_limits::infinity()},
+ TestEvent{"getNumber result", std::numeric_limits::infinity()},
+ TestEvent{"getString called", "Hello"},
+ TestEvent{"getString result", "Hello"},
+ TestEvent{"getString called", ""},
+ TestEvent{"getString result", ""},
+ TestEvent{"getArray called", 3},
+ TestEvent{"getArray result", "OK"},
+ TestEvent{"getObject called", "OK"},
+ TestEvent{"getObject result", "OK"},
+ TestEvent{"getValue called", "OK"},
+ TestEvent{"getValue result", "OK"},
+ TestEvent{"getConstants called", "OK"},
+ TestEvent{"getConstants result", "OK"},
+ TestEvent{"getValueWithCallback called", "OK"},
+ TestEvent{"getValueWithCallback result", "value from callback!"},
+ TestEvent{"getValueWithPromise called", false},
+ TestEvent{"getValueWithPromise called", true},
+ TestEvent{"getValueWithPromise result resolve", "result!"},
+ TestEvent{"getValueWithPromise result reject", "intentional promise rejection"}});
}
};
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj
index c7332560777..14363a9a246 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj
@@ -107,6 +107,7 @@
+
@@ -114,6 +115,8 @@
+
+
@@ -134,15 +137,19 @@
+
+
-
+
+
-
+
+
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj.filters b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj.filters
index 789add985c2..4688dd42000 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj.filters
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj.filters
@@ -1,9 +1,7 @@
-
-
-
+
@@ -11,20 +9,56 @@
-
-
+
+ Utilities
+
+
+ Utilities
+
+
+ Utilities
+
+
+ Utilities
+
-
-
+
+ Utilities
+
+
+ Utilities
+
+
+ Utilities
+
+
+ Utilities
+
-
-
-
+
+
+
+
+ Other Files
+
+
+
+
+ {3141d738-5613-46d6-a29b-60993e27c870}
+
+
+ {7c09653b-8405-45c7-91cb-9c84b1d401cb}
+
+
+
+
+ Other Files
+
\ No newline at end of file
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp
index f53161abc59..dd1e10c9664 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.cpp
@@ -2,103 +2,51 @@
// Licensed under the MIT License.
#include "pch.h"
-#include
#include
-#include
#include "MockReactPackageProvider.h"
+#include "TestEventService.h"
+#include "TestReactNativeHostHolder.h"
-using namespace React;
+using namespace winrt;
+using namespace Microsoft::ReactNative;
namespace ReactNativeIntegrationTests {
+// Use anonymous namespace to avoid any linking conflicts
+namespace {
+
REACT_MODULE(TestHostModule)
struct TestHostModule {
REACT_INIT(Initialize)
- void Initialize(ReactContext const &reactContext) noexcept {
- using namespace facebook::jsi;
- TestHostModule::Instance.set_value(*this);
-
- Runtime *jsiRuntime{nullptr};
- bool jsiExecuted{false};
- // The JSI executed synchronously here because we are in JS thread.
- ExecuteJsi(reactContext, [&](Runtime &rt) {
- jsiRuntime = &rt;
- jsiExecuted = true;
- auto eval = rt.global().getPropertyAsFunction(rt, "eval");
- auto addFunc = eval.call(rt, "(function(x, y) { return x + y; })").getObject(rt).getFunction(rt);
- TestCheckEqual(7, addFunc.call(rt, 3, 4).getNumber());
-
- Function hostGreeter = Function::createFromHostFunction(
- rt,
- PropNameID::forAscii(rt, "multFunc"),
- 1,
- [](Runtime &rt, const Value & /*thisVal*/, const Value *args, size_t /*count*/) {
- return Value{rt, String::createFromUtf8(rt, "Hello " + args[0].getString(rt).utf8(rt))};
- });
- TestCheckEqual("Hello World", hostGreeter.call(rt, "World").getString(rt).utf8(rt));
- TestCheck(hostGreeter.getHostFunction(rt) != nullptr);
-
- Function hostGreater2 = hostGreeter.getFunction(rt);
- TestCheck(hostGreater2.isHostFunction(rt));
- TestCheckEqual("Hello World", hostGreater2.call(rt, "World").getString(rt).utf8(rt));
- TestCheck(hostGreater2.getHostFunction(rt) != nullptr);
-
- class GreeterHostObject : public HostObject {
- Value get(Runtime &rt, const PropNameID &) override {
- return String::createFromAscii(rt, "Hello");
- }
- void set(Runtime &, const PropNameID &, const Value &) override {}
- };
-
- Object hostObjGreeter = Object::createFromHostObject(rt, std::make_shared());
- TestCheckEqual(
- "Hello", hostObjGreeter.getProperty(rt, PropNameID::forAscii(rt, "someProp")).getString(rt).utf8(rt));
- TestCheck(hostObjGreeter.getHostObject(rt) != nullptr);
-
- Object hostObjGreeter2 = Value{rt, hostObjGreeter}.getObject(rt);
- TestCheck(hostObjGreeter2.isHostObject(rt));
- TestCheckEqual(
- "Hello", hostObjGreeter2.getProperty(rt, PropNameID::forAscii(rt, "someProp")).getString(rt).utf8(rt));
- TestCheck(hostObjGreeter2.getHostObject(rt) != nullptr);
- });
- TestCheck(jsiExecuted);
-
- jsiExecuted = false;
- // Make sure that we use the same facebook::jsi::Runtime when we run second time.
- ExecuteJsi(reactContext, [&](Runtime &rt) {
- jsiExecuted = true;
- TestCheckEqual(jsiRuntime, &rt);
- });
- TestCheck(jsiExecuted);
+ void Initialize(ReactContext const & /*reactContext*/) noexcept {
+ TestEventService::LogEvent("initialize", nullptr);
}
REACT_FUNCTION(addValues, L"addValues", L"TestHostModuleFunctions")
std::function addValues;
- REACT_METHOD(Start, L"start")
- void Start() noexcept {
- // Native modules are created on-demand.
- // This method is used to start loading the module from JavaScript.
- }
+ REACT_METHOD(StartTests, L"startTests")
+ void StartTests() noexcept {
+ TestEventService::LogEvent("start tests", nullptr);
- REACT_METHOD(ReturnInt, L"returnInt")
- void ReturnInt(int value) noexcept {
- TestHostModule::IntReturnValue.set_value(value);
+ TestEventService::LogEvent("call addValues", JSValueArray{4, 7});
+ addValues(4, 7);
}
- static std::promise Instance;
- static std::promise IntReturnValue;
+ REACT_METHOD(ReturnResult, L"returnResult")
+ void ReturnResult(JSValue value) noexcept {
+ TestEventService::LogEvent("return result", std::move(value));
+ }
};
-std::promise TestHostModule::Instance;
-std::promise TestHostModule::IntReturnValue;
-
struct TestPackageProvider : winrt::implements {
void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept {
TryAddAttributedModule(packageBuilder, L"TestHostModule");
}
};
+} // namespace
+
TEST_CLASS (ReactNativeHostTests) {
TEST_METHOD(Activation_Succeeds) {
TestCheckNoThrow(winrt::Microsoft::ReactNative::ReactNativeHost{});
@@ -125,42 +73,21 @@ TEST_CLASS (ReactNativeHostTests) {
const wchar_t *path = L"a/b/c";
ReactNativeHost host{};
host.InstanceSettings().BundleRootPath(path);
- TestCheckEqual(std::wstring_view{path}, (std::wstring_view)host.InstanceSettings().BundleRootPath());
+ TestCheckEqual(path, host.InstanceSettings().BundleRootPath());
}
- TEST_METHOD(JsFunctionCall_Succeeds) {
- std::future testHostModule = TestHostModule::Instance.get_future();
- std::future returnValue = TestHostModule::IntReturnValue.get_future();
-
- winrt::Microsoft::ReactNative::ReactNativeHost host{};
+ TEST_METHOD(Run_JSDrivenTests) {
+ TestEventService::Initialize();
- auto queueController = winrt::Windows::System::DispatcherQueueController::CreateOnDedicatedThread();
- queueController.DispatcherQueue().TryEnqueue([&]() noexcept {
+ auto reactNativeHost = TestReactNativeHostHolder(L"ReactNativeHostTests", [](ReactNativeHost const &host) noexcept {
host.PackageProviders().Append(winrt::make());
-
- // bundle is assumed to be co-located with the test binary
- wchar_t testBinaryPath[MAX_PATH];
- TestCheck(GetModuleFileNameW(NULL, testBinaryPath, MAX_PATH) < MAX_PATH);
- testBinaryPath[std::wstring_view{testBinaryPath}.rfind(L"\\")] = 0;
-
- host.InstanceSettings().BundleRootPath(testBinaryPath);
- host.InstanceSettings().JavaScriptBundleFile(L"AddValues");
- host.InstanceSettings().UseDeveloperSupport(false);
- host.InstanceSettings().UseWebDebugger(false);
- host.InstanceSettings().UseFastRefresh(false);
- host.InstanceSettings().UseLiveReload(false);
- host.InstanceSettings().EnableDeveloperMenu(false);
-
- host.InstanceSettings().UseDirectDebugger(true);
-
- host.LoadInstance();
});
- testHostModule.get().addValues(12, 23);
- TestCheckEqual(35, returnValue.get());
-
- host.UnloadInstance().get();
- queueController.ShutdownQueueAsync().get();
+ TestEventService::ObserveEvents(
+ {TestEvent{"initialize", nullptr},
+ TestEvent{"start tests", nullptr},
+ TestEvent{"call addValues", JSValueArray{4, 7}},
+ TestEvent{"return result", 11}});
}
};
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/AddValues.js b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.js
similarity index 69%
rename from vnext/Microsoft.ReactNative.IntegrationTests/AddValues.js
rename to vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.js
index ff0f1c1d1ba..6bd0c5009d4 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/AddValues.js
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactNativeHostTests.js
@@ -2,7 +2,7 @@ import { NativeModules } from 'react-native';
class TestHostModuleFunctions {
addValues(a, b) {
- NativeModules.TestHostModule.returnInt(a + b);
+ NativeModules.TestHostModule.returnResult(a + b);
}
}
@@ -10,6 +10,6 @@ class TestHostModuleFunctions {
if (NativeModules.TestHostModule) {
global.__fbBatchedBridge.registerLazyCallableModule('TestHostModuleFunctions', () => new TestHostModuleFunctions());
- // Native modules are created on demand from JavaScript code.
- NativeModules.TestHostModule.start();
+ // Start running tests.
+ NativeModules.TestHostModule.startTests();
}
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.cpp
new file mode 100644
index 00000000000..effab76188d
--- /dev/null
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.cpp
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "pch.h"
+#include "TestEventService.h"
+#include
+#include
+#include
+#include
+
+namespace ReactNativeIntegrationTests {
+
+using namespace std::literals::chrono_literals;
+
+/*static*/ std::mutex TestEventService::s_mutex;
+/*static*/ std::condition_variable TestEventService::s_cv;
+/*static*/ TestEvent TestEventService::s_loggedEvent{};
+/*static*/ bool TestEventService::s_previousEventIsObserved{true}; // true to allow new event to be logged
+/*static*/ uint32_t TestEventService::s_observeEventIndex{0};
+
+/*static*/ void TestEventService::Initialize() noexcept {
+ auto lock = std::scoped_lock{s_mutex};
+ s_previousEventIsObserved = true;
+ s_observeEventIndex = 0;
+}
+
+/*static*/ void TestEventService::LogEvent(std::string_view eventName, JSValue &&value) noexcept {
+ // Blocks the thread until the previous logged event is observed.
+ while (true) {
+ {
+ // Do not hold the lock while the thread sleeps.
+ auto lock = std::scoped_lock{s_mutex};
+ if (s_previousEventIsObserved) {
+ s_loggedEvent.EventName = eventName;
+ s_loggedEvent.Value = std::move(value);
+ s_previousEventIsObserved = false;
+ s_cv.notify_all();
+ break;
+ }
+ }
+
+ std::this_thread::sleep_for(1ms);
+ }
+}
+
+/*static*/ void TestEventService::ObserveEvents(winrt::array_view expectedEvents) noexcept {
+ auto lock = std::unique_lock{s_mutex};
+ s_cv.wait(lock, [&]() {
+ if (!s_previousEventIsObserved) {
+ TestCheck(s_observeEventIndex < expectedEvents.size());
+ auto const &expectedEvent = expectedEvents[s_observeEventIndex];
+
+ // Check the event name and value
+ TestCheckEqual(expectedEvent.EventName, s_loggedEvent.EventName);
+ if (auto d1 = expectedEvent.Value.TryGetDouble(), d2 = s_loggedEvent.Value.TryGetDouble(); d1 && d2) {
+ // Comparison of doubles has special logic because NaN != NaN.
+ if (!isnan(*d1) && !isnan(*d2)) {
+ TestCheckEqual(*d1, *d2);
+ }
+ } else if (expectedEvent.Value != s_loggedEvent.Value) { // Use JSValue strict compare
+ std::stringstream os;
+ os << "Event index: " << s_observeEventIndex << '\n'
+ << "Expected: " << expectedEvent.Value.ToString() << '\n'
+ << "Actual: " << s_loggedEvent.Value.ToString();
+ TestCheckFail("%s", os.str().c_str());
+ }
+ s_previousEventIsObserved = true;
+ ++s_observeEventIndex;
+ }
+
+ // Finish observing after we observed the last event.
+ return s_observeEventIndex >= expectedEvents.size();
+ });
+}
+
+} // namespace ReactNativeIntegrationTests
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.h b/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.h
new file mode 100644
index 00000000000..c3312be3e53
--- /dev/null
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/TestEventService.h
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+// The TestEventService allows to verify sequence of test events.
+// The events are logged by the TestEventService::LogEvent method.
+// They are observed by the TestEventService::ObserveEvents method which blocks current thread.
+// Each test must call TestEventService::Initialize to reset the service state.
+
+#include
+#include
+#include
+#include
+
+namespace ReactNativeIntegrationTests {
+
+using namespace winrt::Microsoft::ReactNative;
+
+struct TestEvent {
+ std::string EventName;
+ JSValue Value;
+};
+
+struct TestEventService {
+ // Sets to the service to the initial state.
+ static void Initialize() noexcept;
+
+ // Logs new event and notifies the observer to check it.
+ // It blocks current thread until the previous event is observed.
+ //
+ // The expectation is that this method is always called on a thread
+ // different to the one that runs the ObserveEvents method.
+ // We will have a deadlock if this expectation is not met.
+ static void LogEvent(std::string_view eventName, JSValue &&value) noexcept;
+
+ // Blocks current thread and observes all incoming events until we see them all.
+ static void ObserveEvents(winrt::array_view expectedEvents) noexcept;
+
+ private:
+ static std::mutex s_mutex; // to synchronize access to the fields below
+ static std::condition_variable s_cv; // to notify about new event
+ static TestEvent s_loggedEvent; // last logged event
+ static bool s_previousEventIsObserved; // did we observe the last logged event?
+ static uint32_t s_observeEventIndex; // the event index to observe
+};
+
+} // namespace ReactNativeIntegrationTests
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TestReactNativeHostHolder.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/TestReactNativeHostHolder.cpp
new file mode 100644
index 00000000000..1918bf63b34
--- /dev/null
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/TestReactNativeHostHolder.cpp
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "pch.h"
+#include "TestReactNativeHostHolder.h"
+
+namespace ReactNativeIntegrationTests {
+
+using namespace winrt;
+using namespace Microsoft::ReactNative;
+using namespace Windows::System;
+
+TestReactNativeHostHolder::TestReactNativeHostHolder(
+ std::wstring_view jsBundle,
+ Mso::Functor &&hostInitializer) noexcept {
+ m_host = ReactNativeHost{};
+ m_queueController = DispatcherQueueController::CreateOnDedicatedThread();
+ m_queueController.DispatcherQueue().TryEnqueue(
+ [this, jsBundle = std::wstring{jsBundle}, hostInitializer = std::move(hostInitializer)]() noexcept {
+ // bundle is assumed to be co-located with the test binary
+ wchar_t testBinaryPath[MAX_PATH];
+ TestCheck(GetModuleFileNameW(NULL, testBinaryPath, MAX_PATH) < MAX_PATH);
+ testBinaryPath[std::wstring_view{testBinaryPath}.rfind(L"\\")] = 0;
+
+ m_host.InstanceSettings().BundleRootPath(testBinaryPath);
+ m_host.InstanceSettings().JavaScriptBundleFile(jsBundle);
+ m_host.InstanceSettings().UseDeveloperSupport(false);
+ m_host.InstanceSettings().UseWebDebugger(false);
+ m_host.InstanceSettings().UseFastRefresh(false);
+ m_host.InstanceSettings().UseLiveReload(false);
+ m_host.InstanceSettings().EnableDeveloperMenu(false);
+
+ hostInitializer(m_host);
+
+ m_host.LoadInstance();
+ });
+}
+
+TestReactNativeHostHolder::~TestReactNativeHostHolder() noexcept {
+ m_host.UnloadInstance().get();
+ m_queueController.ShutdownQueueAsync().get();
+}
+
+} // namespace ReactNativeIntegrationTests
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TestReactNativeHostHolder.h b/vnext/Microsoft.ReactNative.IntegrationTests/TestReactNativeHostHolder.h
new file mode 100644
index 00000000000..505b3fe26da
--- /dev/null
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/TestReactNativeHostHolder.h
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace ReactNativeIntegrationTests {
+
+struct TestReactNativeHostHolder {
+ TestReactNativeHostHolder(
+ std::wstring_view jsBundle,
+ Mso::Functor &&hostInitializer) noexcept;
+ ~TestReactNativeHostHolder() noexcept;
+
+ private:
+ winrt::Microsoft::ReactNative::ReactNativeHost m_host{nullptr};
+ winrt::Windows::System::DispatcherQueueController m_queueController{nullptr};
+};
+
+} // namespace ReactNativeIntegrationTests
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp
index 167617b29e0..be72c5a9021 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/TurboModuleTests.cpp
@@ -5,12 +5,16 @@
#include
#include
#include
+#include "TestEventService.h"
+#include "TestReactNativeHostHolder.h"
-using namespace React;
-using namespace winrt::Microsoft::ReactNative;
+using namespace winrt;
+using namespace Microsoft::ReactNative;
namespace ReactNativeIntegrationTests {
+namespace {
+
REACT_MODULE(SampleTurboModule)
struct SampleTurboModule {
REACT_INIT(Initialize)
@@ -30,13 +34,13 @@ struct SampleTurboModule {
REACT_METHOD(Succeeded, L"succeeded")
void Succeeded() noexcept {
- succeededSignal.set_value(true);
+ TestEventService::LogEvent("succeededSignal", true);
}
REACT_METHOD(OnError, L"onError")
void OnError(std::string errorMessage) noexcept {
// intended to keep the parameter name for debug purpose
- succeededSignal.set_value(false);
+ TestEventService::LogEvent("succeededSignal", false);
}
REACT_METHOD(PromiseFunction, L"promiseFunction")
@@ -52,7 +56,7 @@ struct SampleTurboModule {
REACT_METHOD(PromiseFunctionResult, L"promiseFunctionResult")
void PromiseFunctionResult(std::string a) noexcept {
- promiseFunctionSignal.set_value(a);
+ TestEventService::LogEvent("promiseFunctionSignal", std::move(a));
}
REACT_SYNC_METHOD(SyncFunction, L"syncFunction")
@@ -63,13 +67,13 @@ struct SampleTurboModule {
REACT_METHOD(SyncFunctionResult, L"syncFunctionResult")
void SyncFunctionResult(std::string a) noexcept {
- syncFunctionSignal.set_value(a);
+ TestEventService::LogEvent("syncFunctionSignal", std::move(a));
}
REACT_METHOD(Constants, L"constants")
void Constants(std::string a, int b, std::string c, int d) noexcept {
auto resultString = (std::stringstream() << a << ", " << b << ", " << c << ", " << d).str();
- constantsSignal.set_value(resultString);
+ TestEventService::LogEvent("constantsSignal", std::move(resultString));
}
REACT_METHOD(OneCallback, L"oneCallback")
@@ -79,7 +83,7 @@ struct SampleTurboModule {
REACT_METHOD(OneCallbackResult, L"oneCallbackResult")
void OneCallbackResult(int r) noexcept {
- oneCallbackSignal.set_value(r);
+ TestEventService::LogEvent("oneCallbackSignal", r);
}
REACT_METHOD(TwoCallbacks, L"twoCallbacks")
@@ -98,35 +102,19 @@ struct SampleTurboModule {
REACT_METHOD(TwoCallbacksResolved, L"twoCallbacksResolved")
void TwoCallbacksResolved(int r) noexcept {
- twoCallbacksResolvedSignal.set_value(r);
+ TestEventService::LogEvent("twoCallbacksResolvedSignal", r);
}
REACT_METHOD(TwoCallbacksRejected, L"twoCallbacksRejected")
void TwoCallbacksRejected(std::string r) noexcept {
- twoCallbacksRejectedSignal.set_value(r);
+ TestEventService::LogEvent("twoCallbacksResolvedSignal", std::move(r));
}
-
- static std::promise succeededSignal;
- static std::promise promiseFunctionSignal;
- static std::promise syncFunctionSignal;
- static std::promise constantsSignal;
- static std::promise oneCallbackSignal;
- static std::promise twoCallbacksResolvedSignal;
- static std::promise twoCallbacksRejectedSignal;
};
-std::promise SampleTurboModule::succeededSignal;
-std::promise SampleTurboModule::promiseFunctionSignal;
-std::promise SampleTurboModule::syncFunctionSignal;
-std::promise SampleTurboModule::constantsSignal;
-std::promise SampleTurboModule::oneCallbackSignal;
-std::promise SampleTurboModule::twoCallbacksResolvedSignal;
-std::promise SampleTurboModule::twoCallbacksRejectedSignal;
-
struct SampleTurboModuleSpec : TurboModuleSpec {
static constexpr auto methods = std::tuple{
Method{0, L"succeeded"},
- Method{0, L"onError"},
+ Method{1, L"onError"},
Method) noexcept>{2, L"promiseFunction"},
Method{3, L"promiseFunctionResult"},
SyncMethod{4, L"syncFunction"},
@@ -171,40 +159,25 @@ struct SampleTurboModulePackageProvider : winrt::implements());
-
- // bundle is assumed to be co-located with the test binary
- wchar_t testBinaryPath[MAX_PATH];
- TestCheck(GetModuleFileNameW(NULL, testBinaryPath, MAX_PATH) < MAX_PATH);
- testBinaryPath[std::wstring_view{testBinaryPath}.rfind(L"\\")] = 0;
-
- host.InstanceSettings().BundleRootPath(testBinaryPath);
- host.InstanceSettings().JavaScriptBundleFile(L"TurboModuleTests");
- host.InstanceSettings().UseDeveloperSupport(false);
- host.InstanceSettings().UseWebDebugger(false);
- host.InstanceSettings().UseFastRefresh(false);
- host.InstanceSettings().UseLiveReload(false);
- host.InstanceSettings().EnableDeveloperMenu(false);
-
- host.LoadInstance();
});
- TestCheckEqual(true, SampleTurboModule::succeededSignal.get_future().get());
- TestCheckEqual("something, 1, true", SampleTurboModule::promiseFunctionSignal.get_future().get());
- TestCheckEqual("something, 2, false", SampleTurboModule::syncFunctionSignal.get_future().get());
- TestCheckEqual("constantString, 3, Hello, 10", SampleTurboModule::constantsSignal.get_future().get());
- TestCheckEqual(3, SampleTurboModule::oneCallbackSignal.get_future().get());
- TestCheckEqual(123, SampleTurboModule::twoCallbacksResolvedSignal.get_future().get());
- TestCheckEqual("Failed", SampleTurboModule::twoCallbacksRejectedSignal.get_future().get());
-
- host.UnloadInstance().get();
- queueController.ShutdownQueueAsync().get();
+ TestEventService::ObserveEvents({
+ TestEvent{"promiseFunctionSignal", "something, 1, true"},
+ TestEvent{"oneCallbackSignal", 3},
+ TestEvent{"twoCallbacksResolvedSignal", 123},
+ TestEvent{"twoCallbacksResolvedSignal", "Failed"},
+ TestEvent{"syncFunctionSignal", "something, 2, false"},
+ TestEvent{"constantsSignal", "constantString, 3, Hello, 10"},
+ TestEvent{"succeededSignal", true},
+ });
}
};
diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/pch.h b/vnext/Microsoft.ReactNative.IntegrationTests/pch.h
index e42d31260b7..bd79f6fa6c3 100644
--- a/vnext/Microsoft.ReactNative.IntegrationTests/pch.h
+++ b/vnext/Microsoft.ReactNative.IntegrationTests/pch.h
@@ -4,7 +4,6 @@
#include
#include
-#include
#undef GetCurrentTime
#include
diff --git a/vnext/Microsoft.ReactNative/DynamicReader.cpp b/vnext/Microsoft.ReactNative/DynamicReader.cpp
index 34e424ff41f..6a63add04ef 100644
--- a/vnext/Microsoft.ReactNative/DynamicReader.cpp
+++ b/vnext/Microsoft.ReactNative/DynamicReader.cpp
@@ -39,7 +39,11 @@ JSValueType DynamicReader::ValueType() noexcept {
case folly::dynamic::Type::BOOL:
return JSValueType::Boolean;
case folly::dynamic::Type::DOUBLE:
- return JSValueType::Double;
+ if (double value = m_current->getDouble(); static_cast(value) == value) {
+ return JSValueType::Int64;
+ } else {
+ return JSValueType::Double;
+ }
case folly::dynamic::Type::INT64:
return JSValueType::Int64;
case folly::dynamic::Type::OBJECT:
@@ -136,7 +140,9 @@ bool DynamicReader::GetBoolean() noexcept {
}
int64_t DynamicReader::GetInt64() noexcept {
- return (m_current->type() == folly::dynamic::Type::INT64) ? m_current->getInt() : 0;
+ return (m_current->type() == folly::dynamic::Type::INT64)
+ ? m_current->getInt()
+ : (m_current->type() == folly::dynamic::Type::DOUBLE) ? static_cast(m_current->getDouble()) : 0;
}
double DynamicReader::GetDouble() noexcept {