/* * 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. */ #include "ReactInstanceIntegrationTest.h" #include "FollyDynamicMatchers.h" #include "UniquePtrFactory.h" #include "prelude.js.h" #include #include #include #include #include #include using namespace ::testing; namespace facebook::react::jsinspector_modern { #pragma region ReactInstanceIntegrationTest ReactInstanceIntegrationTest::ReactInstanceIntegrationTest() : runtime(nullptr), instance(nullptr), messageQueueThread(std::make_shared()), errorHandler(std::make_shared()) {} void ReactInstanceIntegrationTest::SetUp() { auto mockRegistry = std::make_unique(); auto timerManager = std::make_shared(std::move(mockRegistry)); auto onJsError = [](jsi::Runtime& /*runtime*/, const JsErrorHandler::ParsedError& error) noexcept { LOG(INFO) << "[jsErrorHandlingFunc called]"; LOG(INFO) << error << std::endl; }; auto jsRuntimeFactory = std::make_unique(); std::unique_ptr runtime_ = jsRuntimeFactory->createJSRuntime( nullptr, nullptr, messageQueueThread, false); jsi::Runtime* jsiRuntime = &runtime_->getRuntime(); // Error handler: jsiRuntime->global().setProperty( *jsiRuntime, "ErrorUtils", jsi::Object::createFromHostObject(*jsiRuntime, errorHandler)); std::shared_ptr hostTargetIfModernCDP = nullptr; if (InspectorFlags::getInstance().getFuseboxEnabled()) { VoidExecutor inspectorExecutor = [this](auto callback) { immediateExecutor_.add(callback); }; MockHostTargetDelegate hostTargetDelegate; hostTargetIfModernCDP = HostTarget::create(hostTargetDelegate, inspectorExecutor); } instance = std::make_unique( std::move(runtime_), messageQueueThread, timerManager, std::move(onJsError), hostTargetIfModernCDP == nullptr ? nullptr : hostTargetIfModernCDP.get()); timerManager->setRuntimeExecutor(instance->getBufferedRuntimeExecutor()); // JS Environment: initializeRuntime(preludeJsCode); // Inspector: auto& inspector = getInspectorInstance(); if (hostTargetIfModernCDP != nullptr) { // Under modern CDP, the React host is responsible for adding itself as // the root target on startup. pageId_ = inspector.addPage( "mock-description", "mock-vm", [hostTargetIfModernCDP](std::unique_ptr remote) -> std::unique_ptr { auto localConnection = hostTargetIfModernCDP->connect(std::move(remote)); return localConnection; }, // TODO: Allow customisation of InspectorTargetCapabilities {}); } else { // Under legacy CDP, Hermes' DecoratedRuntime adds its page automatically // within ConnectionDemux.enableDebugging. auto pages = inspector.getPages(); ASSERT_GT(pages.size(), 0); pageId_ = pages.back().id; } clientToVM_ = inspector.connect(pageId_.value(), mockRemoteConnections_.make_unique()); ASSERT_NE(clientToVM_, nullptr); // Default to ignoring console messages originating inside the backend. EXPECT_CALL( getRemoteConnection(), onMessage(JsonParsed(AllOf( AtJsonPtr("/method", "Runtime.consoleAPICalled"), AtJsonPtr("/params/context", "main#InstanceAgent"))))) .Times(AnyNumber()); } void ReactInstanceIntegrationTest::TearDown() { clientToVM_->disconnect(); // Destroy the local connection. clientToVM_.reset(); if (pageId_.has_value() && InspectorFlags::getInstance().getFuseboxEnabled()) { // Under modern CDP, clean up the page we added in SetUp and destroy // resources owned by HostTarget. getInspectorInstance().removePage(pageId_.value()); } pageId_.reset(); // Expect the remote connection to have been destroyed. EXPECT_EQ(mockRemoteConnections_[0], nullptr); ReactNativeFeatureFlags::dangerouslyReset(); } void ReactInstanceIntegrationTest::initializeRuntime(std::string_view script) { react::ReactInstance::JSRuntimeFlags flags{ .isProfiling = false, }; instance->initializeRuntime(flags, [](jsi::Runtime& rt) { // NOTE: RN's console polyfill (included in prelude.js.h) depends on the // native logging hook being installed, even if it's a noop. facebook::react::bindNativeLogger(rt, [](auto, auto) {}); }); messageQueueThread->tick(); std::string init(script); // JS calls no longer buffered after calling loadScript instance->loadScript(std::make_unique(init), ""); } void ReactInstanceIntegrationTest::send( const std::string& method, const folly::dynamic& params) { folly::dynamic request = folly::dynamic::object(); request["method"] = method; request["id"] = id_++; request["params"] = params; sendJSONString(folly::toJson(request)); } void ReactInstanceIntegrationTest::sendJSONString(const std::string& message) { // The runtime must be initialized and connected to before messaging clientToVM_->sendMessage(message); } jsi::Value ReactInstanceIntegrationTest::run(const std::string& script) { auto runtimeExecutor = instance->getUnbufferedRuntimeExecutor(); auto ret = jsi::Value::undefined(); runtimeExecutor([script, &ret](jsi::Runtime& rt) { ret = rt.evaluateJavaScript( std::make_unique(script), ""); }); messageQueueThread->flush(); while (verbose_ && errorHandler->size() > 0) { LOG(INFO) << "Error: " << errorHandler->getLastError().getMessage(); } return ret; } bool ReactInstanceIntegrationTest::verbose(bool isVerbose) { const bool previous = verbose_; verbose_ = isVerbose; return previous; } #pragma endregion TEST_F(ReactInstanceIntegrationTest, RuntimeEvalTest) { auto val = run("1 + 2"); EXPECT_EQ(val.asNumber(), 3); } TEST_P(ReactInstanceIntegrationTestWithFlags, ConsoleLog) { EXPECT_CALL( getRemoteConnection(), onMessage(JsonParsed( AtJsonPtr("/method", Eq("Runtime.executionContextCreated"))))); EXPECT_CALL( getRemoteConnection(), onMessage(JsonParsed(AtJsonPtr("/id", Eq(1))))); InSequence s; EXPECT_CALL( getRemoteConnection(), onMessage(JsonParsed(AllOf( AtJsonPtr("/params/args/0/value", Eq("Hello, World!")), AtJsonPtr("/method", Eq("Runtime.consoleAPICalled")))))); EXPECT_CALL(getRemoteConnection(), onDisconnect()); send("Runtime.enable"); run("console.log('Hello, World!');"); } INSTANTIATE_TEST_SUITE_P( ReactInstanceVaryingInspectorFlags, ReactInstanceIntegrationTestWithFlags, ::testing::Values( InspectorFlagOverrides{.fuseboxEnabledDebug = false}, InspectorFlagOverrides{.fuseboxEnabledDebug = false}, InspectorFlagOverrides{.fuseboxEnabledDebug = true})); } // namespace facebook::react::jsinspector_modern