Update and expand bytecode spec

Summary:
* Adds parent tag as param for createNode in place of explicit appendChild commands.
* Adds version info to bytecode
* Adds native conditional support:

Conditionals are represented in product code with the new `NativeConditional` React
component. It takes params necessary to construct a native function call, and takes
a render prop as a child that passes the value of the native call as an arg. In
prod, the component would actually call the native module and render with that value,
but in jest we render for *both* true and false and set them as children
of a new jest-only primitive/host component which we special-case and generate a
special command with `OP_CODE.conditional`, generate the appropriate bytecode commands
for each branch, and embed them as args in the conditional OP_CODE command. When
evaluating the bytecode, only one set of commands is executed, based on the native
module value (which is evaluated with another new opcode which computes the value
and stuffs it in a "register").

Obviously generating this bytecode is kind of a cludge compared to prepack, but
when I asked @[501709947:Dominic] about it, he said they had no bytecode spec right
now, so I'm running ahead with this prototype. The main thing I'm focused on is
the C++/RN bytecode interpretter - this jest stuff is just a way to generate bytecode
for it to consume which could be replaced or augmented with many other approaches,
such as prepack, server rendering, etc.

Also piggybacked a bunch of other cleanup.

Reviewed By: shergin

Differential Revision: D10277121

fbshipit-source-id: 15d3217a59ef481b574c742d17d8a7dc893cba90
This commit is contained in:
Spencer Ahrens 2018-11-05 15:32:46 -08:00 committed by Facebook Github Bot
parent 636e146c4a
commit aab01608ba
10 changed files with 405 additions and 113 deletions

View File

@ -10,6 +10,7 @@
#include <fabric/components/view/accessibilityPropsConversions.h> #include <fabric/components/view/accessibilityPropsConversions.h>
#include <fabric/components/view/propsConversions.h> #include <fabric/components/view/propsConversions.h>
#include <fabric/core/propsConversions.h> #include <fabric/core/propsConversions.h>
#include <fabric/debug/debugStringConvertibleUtils.h>
namespace facebook { namespace facebook {
namespace react { namespace react {
@ -17,10 +18,8 @@ namespace react {
AccessibilityProps::AccessibilityProps( AccessibilityProps::AccessibilityProps(
const AccessibilityProps &sourceProps, const AccessibilityProps &sourceProps,
const RawProps &rawProps) const RawProps &rawProps)
: accessible(convertRawProp( : accessible(
rawProps, convertRawProp(rawProps, "accessible", sourceProps.accessible)),
"accessible",
sourceProps.accessible)),
accessibilityTraits(convertRawProp( accessibilityTraits(convertRawProp(
rawProps, rawProps,
"accessibilityTraits", "accessibilityTraits",
@ -48,7 +47,20 @@ AccessibilityProps::AccessibilityProps(
accessibilityIgnoresInvertColors(convertRawProp( accessibilityIgnoresInvertColors(convertRawProp(
rawProps, rawProps,
"accessibilityIgnoresInvertColors", "accessibilityIgnoresInvertColors",
sourceProps.accessibilityIgnoresInvertColors)) {} sourceProps.accessibilityIgnoresInvertColors)),
testId(convertRawProp(rawProps, "testId", sourceProps.testId)) {}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList AccessibilityProps::getDebugProps() const {
const auto &defaultProps = AccessibilityProps();
LOG(INFO) << "Call AccessibilityProps::getDebugProps with testId " << testId;
return SharedDebugStringConvertibleList{
debugStringConvertibleItem("testId", testId, defaultProps.testId),
};
}
#endif // RN_DEBUG_STRING_CONVERTIBLE
} // namespace react } // namespace react
} // namespace facebook } // namespace facebook

View File

@ -37,6 +37,12 @@ class AccessibilityProps : public virtual DebugStringConvertible {
const bool accessibilityElementsHidden{false}; const bool accessibilityElementsHidden{false};
const bool accessibilityIgnoresInvertColors{false}; const bool accessibilityIgnoresInvertColors{false};
const std::string testId{""}; const std::string testId{""};
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
}; };
} // namespace react } // namespace react

View File

@ -39,7 +39,7 @@ rn_xplat_cxx_library(
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(), fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(),
fbobjc_tests = [ fbobjc_tests = [
":tests", ":uimanagertests",
], ],
force_static = True, force_static = True,
macosx_tests_override = [], macosx_tests_override = [],
@ -67,7 +67,7 @@ rn_xplat_cxx_library(
) )
fb_xplat_cxx_test( fb_xplat_cxx_test(
name = "tests", name = "uimanagertests",
srcs = glob(["tests/**/*.cpp"]), srcs = glob(["tests/**/*.cpp"]),
headers = glob(["tests/**/*.h"]), headers = glob(["tests/**/*.h"]),
compiler_flags = [ compiler_flags = [
@ -82,5 +82,11 @@ fb_xplat_cxx_test(
"xplat//folly:molly", "xplat//folly:molly",
"xplat//third-party/gmock:gtest", "xplat//third-party/gmock:gtest",
":uimanager", ":uimanager",
react_native_xplat_target("fabric/components/activityindicator:activityindicator"),
react_native_xplat_target("fabric/components/image:image"),
react_native_xplat_target("fabric/components/root:root"),
react_native_xplat_target("fabric/components/scrollview:scrollview"),
react_native_xplat_target("fabric/components/text:text"),
react_native_xplat_target("fabric/components/view:view"),
], ],
) )

View File

@ -30,7 +30,11 @@ class ComponentDescriptorRegistry {
const SharedComponentDescriptor operator[]( const SharedComponentDescriptor operator[](
const ComponentName &componentName) const; const ComponentName &componentName) const;
SharedShadowNode createNode( SharedShadowNode createNode(
Tag tag, const std::string &viewName, Tag rootTag, const folly::dynamic &props, const SharedEventTarget &eventTarget) const; Tag tag,
const std::string &viewName,
Tag rootTag,
const folly::dynamic &props,
const SharedEventTarget &eventTarget) const;
private: private:
std::unordered_map<ComponentHandle, SharedComponentDescriptor> std::unordered_map<ComponentHandle, SharedComponentDescriptor>

View File

@ -0,0 +1,137 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ReactBytecodeInterpreter.h"
#include <glog/logging.h>
#include <fabric/components/view/ViewComponentDescriptor.h>
#include <fabric/components/view/ViewProps.h>
#include <fabric/components/view/ViewShadowNode.h>
#include <fabric/core/LayoutContext.h>
#include <fabric/core/ShadowNodeFragment.h>
#include <fabric/core/componentDescriptor.h>
#include <fabric/debug/DebugStringConvertible.h>
#include <fabric/debug/DebugStringConvertibleItem.h>
#include <folly/json.h>
namespace facebook {
namespace react {
struct RBCContext {
const Tag rootTag;
const std::vector<SharedShadowNode> &nodes;
const std::vector<folly::dynamic> &registers;
const ComponentDescriptorRegistry &componentDescriptorRegistry;
const NativeModuleRegistry &nativeModuleRegistry;
};
// TODO: use RBCContext instead of all the separate arguments.
SharedShadowNode ReactBytecodeInterpreter::runCommand(
const folly::dynamic &command,
Tag rootTag,
std::vector<SharedShadowNode> &nodes,
std::vector<folly::dynamic> &registers,
const ComponentDescriptorRegistry &componentDescriptorRegistry,
const NativeModuleRegistry &nativeModuleRegistry) {
const std::string &opcode = command[0].asString();
const int tagOffset = 420000;
if (opcode == "createNode") {
int tag = command[1].asInt();
const auto &type = command[2].asString();
const auto parentTag = command[3].asInt();
const auto &props = command[4];
nodes[tag] = componentDescriptorRegistry.createNode(
tag + tagOffset, type, rootTag, props, nullptr);
if (parentTag > -1) { // parentTag == -1 indicates root node
auto parentShadowNode = nodes[parentTag];
const SharedComponentDescriptor &componentDescriptor =
componentDescriptorRegistry[parentShadowNode];
componentDescriptor->appendChild(parentShadowNode, nodes[tag]);
}
} else if (opcode == "childSetNode") {
LOG(INFO)
<< "(stop) ReactBytecodeInterpreter inject serialized 'server rendered' view tree";
return nodes[command[1].asInt()];
} else if (opcode == "loadNativeBool") {
int registerNumber = command[1].asInt();
const folly::dynamic &value = nativeModuleRegistry.call(
command[2].asString(), command[3].asString(), command[4]);
registers[registerNumber] = value.asBool();
} else if (opcode == "conditional") {
int registerNumber = command[1].asInt();
auto conditionDynamic = registers[registerNumber];
if (conditionDynamic.isNull()) {
// TODO: provide original command or command line?
auto err = std::runtime_error(
"register " + command[1].asString() +
" wasn't loaded before access");
throw err;
} else if (conditionDynamic.type() != folly::dynamic::BOOL) {
// TODO: provide original command or command line?
auto err = std::runtime_error(
"register " + command[1].asString() + " had type '" +
conditionDynamic.typeName() +
"' but needs to be 'boolean' for conditionals");
throw err;
}
const auto &nextCommands =
conditionDynamic.asBool() ? command[2] : command[3];
for (const auto &nextCommand : nextCommands) {
runCommand(
nextCommand,
rootTag,
nodes,
registers,
componentDescriptorRegistry,
nativeModuleRegistry);
}
} else {
throw std::runtime_error("Unsupported opcode: " + command[0].asString());
}
return nullptr;
}
SharedShadowNode ReactBytecodeInterpreter::buildShadowTree(
const std::string &jsonStr,
Tag rootTag,
const folly::dynamic &params,
const ComponentDescriptorRegistry &componentDescriptorRegistry,
const NativeModuleRegistry &nativeModuleRegistry) {
LOG(INFO)
<< "(strt) ReactBytecodeInterpreter inject hardcoded 'server rendered' view tree";
std::string content = jsonStr;
for (const auto &param : params.items()) {
const auto &key = param.first.asString();
size_t start_pos = content.find(key);
if (start_pos != std::string::npos) {
content.replace(start_pos, key.length(), param.second.asString());
}
}
auto parsed = folly::parseJson(content);
auto commands = parsed["commands"];
std::vector<SharedShadowNode> nodes(commands.size() * 2);
std::vector<folly::dynamic> registers(32);
for (const auto &command : commands) {
auto ret = runCommand(
command,
rootTag,
nodes,
registers,
componentDescriptorRegistry,
nativeModuleRegistry);
if (ret != nullptr) {
return ret;
}
}
throw std::runtime_error(
"Missing childSetNode command in template content:\n" + content);
return SharedShadowNode{};
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,63 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <folly/dynamic.h>
#include <fabric/core/ShadowNode.h>
#include <fabric/uimanager/ComponentDescriptorRegistry.h>
#include <fabric/uimanager/UIManagerDelegate.h>
namespace facebook {
namespace react {
// Temporary NativeModuleRegistry definition
using NativeModuleCallFn =
std::function<folly::dynamic(const std::string &, const folly::dynamic &)>;
class NativeModuleRegistry {
public:
void registerModule(
const std::string &moduleName,
NativeModuleCallFn callFn) {
modules_.emplace(moduleName, callFn);
}
folly::dynamic call(
const std::string &moduleName,
const std::string &methodName,
const folly::dynamic &args) const {
return modules_.at(moduleName)(methodName, args);
}
private:
std::unordered_map<std::string, NativeModuleCallFn> modules_;
};
class ReactBytecodeInterpreter {
public:
static SharedShadowNode buildShadowTree(
const std::string &jsonStr,
int rootTag,
const folly::dynamic &params,
const ComponentDescriptorRegistry &componentDescriptorRegistry,
const NativeModuleRegistry &nativeModuleRegistry);
private:
static SharedShadowNode runCommand(
const folly::dynamic &command,
Tag rootTag,
std::vector<SharedShadowNode> &nodes,
std::vector<folly::dynamic> &registers,
const ComponentDescriptorRegistry &componentDescriptorRegistry,
const NativeModuleRegistry &nativeModuleRegistry);
};
} // namespace react
} // namespace facebook

View File

@ -11,7 +11,7 @@
#include <fabric/uimanager/ComponentDescriptorRegistry.h> #include <fabric/uimanager/ComponentDescriptorRegistry.h>
#include <fabric/uimanager/FabricUIManager.h> #include <fabric/uimanager/FabricUIManager.h>
#include <fabric/uimanager/JSIFabricUIManager.h> #include <fabric/uimanager/JSIFabricUIManager.h>
#include <fabric/uimanager/TemplateRenderer.h> #include <fabric/uimanager/ReactBytecodeInterpreter.h>
#include "ComponentDescriptorFactory.h" #include "ComponentDescriptorFactory.h"
#include "Differentiator.h" #include "Differentiator.h"
@ -73,21 +73,27 @@ void Scheduler::startSurface(
auto shadowTree = auto shadowTree =
std::make_unique<ShadowTree>(surfaceId, layoutConstraints, layoutContext); std::make_unique<ShadowTree>(surfaceId, layoutConstraints, layoutContext);
shadowTree->setDelegate(this); shadowTree->setDelegate(this);
shadowTreeRegistry_.emplace(surfaceId, std::move(shadowTree));
#ifndef ANDROID LOG(INFO) << "initialProps in Scheduler::startSurface - type: " << initialProps.type() << initialProps.typeName() << initialProps;
// TODO: Is this an ok place to do this? // TODO: Is this an ok place to do this?
if (initialProps.type() == folly::dynamic::OBJECT) {
auto serializedCommands = initialProps.find("serializedCommands"); auto serializedCommands = initialProps.find("serializedCommands");
if (serializedCommands != initialProps.items().end()) { if (serializedCommands != initialProps.items().end()) {
auto tree = TemplateRenderer::buildShadowTree(serializedCommands->second.asString(), surfaceId, folly::dynamic::object(), *componentDescriptorRegistry_); NativeModuleRegistry nMR;
auto tree = ReactBytecodeInterpreter::buildShadowTree(serializedCommands->second.asString(), surfaceId, folly::dynamic::object(), *componentDescriptorRegistry_, nMR);
uiManagerDidFinishTransactionWithoutLock(surfaceId, std::make_shared<SharedShadowNodeList>(SharedShadowNodeList {tree})); shadowTree->complete(std::make_shared<SharedShadowNodeList>(SharedShadowNodeList {tree}));
shadowTreeRegistry_.emplace(surfaceId, std::move(shadowTree));
// TODO: hydrate rather than replace // TODO: hydrate rather than replace
#ifndef ANDROID
uiManager_->startSurface(surfaceId, moduleName, initialProps); uiManager_->startSurface(surfaceId, moduleName, initialProps);
} else { #endif
uiManager_->startSurface(surfaceId, moduleName, initialProps); return;
} }
}
shadowTreeRegistry_.emplace(surfaceId, std::move(shadowTree));
#ifndef ANDROID
uiManager_->startSurface(surfaceId, moduleName, initialProps);
#endif #endif
} }
@ -130,16 +136,6 @@ void Scheduler::constraintSurfaceLayout(
}); });
} }
void Scheduler::uiManagerDidFinishTransactionWithoutLock(Tag rootTag, const SharedShadowNodeUnsharedList &rootChildNodes) {
const auto iterator = shadowTreeRegistry_.find(rootTag);
if (iterator == shadowTreeRegistry_.end()) {
// This might happen during surface unmounting/deallocation process
// due to the asynchronous nature of JS calls.
return;
}
iterator->second->complete(rootChildNodes);
}
#pragma mark - Delegate #pragma mark - Delegate
void Scheduler::setDelegate(SchedulerDelegate *delegate) { void Scheduler::setDelegate(SchedulerDelegate *delegate) {
@ -167,7 +163,13 @@ void Scheduler::uiManagerDidFinishTransaction(
Tag rootTag, Tag rootTag,
const SharedShadowNodeUnsharedList &rootChildNodes) { const SharedShadowNodeUnsharedList &rootChildNodes) {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
uiManagerDidFinishTransactionWithoutLock(rootTag, rootChildNodes); const auto iterator = shadowTreeRegistry_.find(rootTag);
if (iterator == shadowTreeRegistry_.end()) {
// This might happen during surface unmounting/deallocation process
// due to the asynchronous nature of JS calls.
return;
}
iterator->second->complete(rootChildNodes);
} }
void Scheduler::uiManagerDidCreateShadowNode( void Scheduler::uiManagerDidCreateShadowNode(

View File

@ -1,57 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TemplateRenderer.h"
#include <glog/logging.h>
#include <fabric/components/view/ViewComponentDescriptor.h>
#include <fabric/components/view/ViewProps.h>
#include <fabric/components/view/ViewShadowNode.h>
#include <fabric/core/componentDescriptor.h>
#include <fabric/core/LayoutContext.h>
#include <fabric/core/ShadowNodeFragment.h>
#include <fabric/debug/DebugStringConvertible.h>
#include <fabric/debug/DebugStringConvertibleItem.h>
#include <folly/json.h>
namespace facebook {
namespace react {
SharedShadowNode TemplateRenderer::buildShadowTree(const std::string &jsonStr, int rootTag, const folly::dynamic &params, const ComponentDescriptorRegistry &componentDescriptorRegistry) {
LOG(INFO) << "(strt) TemplateRenderer inject hardcoded 'server rendered' view tree";
std::string content = jsonStr;
for (const auto& param : params.items()) {
const auto& key = param.first.asString();
size_t start_pos = content.find(key);
if(start_pos != std::string::npos) {
content.replace(start_pos, key.length(), param.second.asString());
}
}
auto json = folly::parseJson(content);
std::vector<SharedShadowNode> nodes;
nodes.resize(json.size() * 2);
int tagOffset = 4560; // MAYBE TODO: use number of existing tags so they don't collide rather than random value
for (const auto& command : json) {
if (command[0] == "createNode") {
int tag = command[1].asInt();
const auto& type = command[2].asString();
const auto& props = command[3];
nodes[tag] = componentDescriptorRegistry.createNode(tag + tagOffset, type, rootTag, props, nullptr);
} else if (command[0] == "appendChild") {
auto parentShadowNode = nodes[command[1].asInt()];
const SharedComponentDescriptor &componentDescriptor = componentDescriptorRegistry[parentShadowNode];
componentDescriptor->appendChild(parentShadowNode, nodes[command[2].asInt()]);
} else if (command[0] == "childSetNode") {
LOG(INFO) << "(stop) TemplateView inject serialized 'server rendered' view tree";
return nodes[command[1].asInt()];
}
}
throw std::runtime_error("Missing childSetNode command in template content:\n" + content);
return SharedShadowNode {};
}
}
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <folly/dynamic.h>
#include <fabric/core/ShadowNode.h>
#include <fabric/uimanager/ComponentDescriptorRegistry.h>
#include <fabric/uimanager/UIManagerDelegate.h>
namespace facebook {
namespace react {
class TemplateRenderer {
public:
static SharedShadowNode buildShadowTree(const std::string &jsonStr, int rootTag, const folly::dynamic &params, const ComponentDescriptorRegistry &componentDescriptorRegistry);
};
} // react
} // facebook

View File

@ -0,0 +1,144 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <exception>
#include <fabric/uimanager/ComponentDescriptorFactory.h>
#include <fabric/uimanager/ReactBytecodeInterpreter.h>
#include <gtest/gtest.h>
using namespace facebook::react;
#include <fabric/components/activityindicator/ActivityIndicatorViewComponentDescriptor.h>
#include <fabric/components/image/ImageComponentDescriptor.h>
#include <fabric/components/scrollview/ScrollViewComponentDescriptor.h>
#include <fabric/components/text/ParagraphComponentDescriptor.h>
#include <fabric/components/text/RawTextComponentDescriptor.h>
#include <fabric/components/text/TextComponentDescriptor.h>
#include <fabric/components/view/ViewComponentDescriptor.h>
#include <fabric/uimanager/ComponentDescriptorRegistry.h>
namespace facebook {
namespace react {
SharedComponentDescriptorRegistry ComponentDescriptorFactory::buildRegistry(
const SharedEventDispatcher &eventDispatcher,
const SharedContextContainer &contextContainer) {
auto registry = std::make_shared<ComponentDescriptorRegistry>();
registry->registerComponentDescriptor(
std::make_shared<ViewComponentDescriptor>(eventDispatcher));
registry->registerComponentDescriptor(
std::make_shared<ImageComponentDescriptor>(
eventDispatcher, contextContainer));
registry->registerComponentDescriptor(
std::make_shared<ScrollViewComponentDescriptor>(eventDispatcher));
registry->registerComponentDescriptor(
std::make_shared<ParagraphComponentDescriptor>(
eventDispatcher, contextContainer));
registry->registerComponentDescriptor(
std::make_shared<TextComponentDescriptor>(eventDispatcher));
registry->registerComponentDescriptor(
std::make_shared<RawTextComponentDescriptor>(eventDispatcher));
registry->registerComponentDescriptor(
std::make_shared<ActivityIndicatorViewComponentDescriptor>(
eventDispatcher));
return registry;
}
bool mockSimpleTestValue_;
NativeModuleRegistry buildNativeModuleRegistry();
NativeModuleRegistry buildNativeModuleRegistry() {
NativeModuleRegistry nMR;
nMR.registerModule(
"MobileConfig",
[&](const std::string &methodName, const folly::dynamic &args) {
return mockSimpleTestValue_;
});
return nMR;
}
} // namespace react
} // namespace facebook
TEST(ReactBytecodeInterpreterTest, testSimpleBytecode) {
auto surfaceId = 11;
auto componentDescriptorRegistry =
ComponentDescriptorFactory::buildRegistry(nullptr, nullptr);
auto nativeModuleRegistry = buildNativeModuleRegistry();
auto bytecode = R"delim({"version":0.1,"commands":[
["createNode",2,"RCTView",-1,{"opacity": 0.5, "testId": "root"}],
["createNode",4,"RCTView",2,{"testId": "child"}],
["childSetNode",2]
]})delim";
mockSimpleTestValue_ = true;
auto root1 = ReactBytecodeInterpreter::buildShadowTree(
bytecode,
surfaceId,
folly::dynamic::object(),
*componentDescriptorRegistry,
nativeModuleRegistry);
LOG(INFO) << std::endl << root1->getDebugDescription();
auto props1 = std::dynamic_pointer_cast<const ViewProps>(root1->getProps());
ASSERT_NEAR(props1->opacity, 0.5, 0.001);
ASSERT_STREQ(props1->testId.c_str(), "root");
auto children1 = root1->getChildren();
ASSERT_EQ(children1.size(), 1);
auto child_props1 =
std::dynamic_pointer_cast<const ViewProps>(children1.at(0)->getProps());
ASSERT_STREQ(child_props1->testId.c_str(), "child");
}
TEST(ReactBytecodeInterpreterTest, testConditionalBytecode) {
auto surfaceId = 11;
auto componentDescriptorRegistry =
ComponentDescriptorFactory::buildRegistry(nullptr, nullptr);
auto nativeModuleRegistry = buildNativeModuleRegistry();
auto bytecode = R"delim({"version":0.1,"commands":[
["createNode",2,"RCTView",-1,{"testId": "root"}],
["loadNativeBool",1,"MobileConfig","getBool",["qe:simple_test"]],
["conditional",1,
[["createNode",4,"RCTView",2,{"testId": "cond_true"}]],
[["createNode",4,"RCTView",2,{"testId": "cond_false"}]]
],
["childSetNode",2]
]})delim";
mockSimpleTestValue_ = true;
auto root1 = ReactBytecodeInterpreter::buildShadowTree(
bytecode,
surfaceId,
folly::dynamic::object(),
*componentDescriptorRegistry,
nativeModuleRegistry);
LOG(INFO) << std::endl << root1->getDebugDescription();
auto props1 = std::dynamic_pointer_cast<const ViewProps>(root1->getProps());
ASSERT_STREQ(props1->testId.c_str(), "root");
auto children1 = root1->getChildren();
ASSERT_EQ(children1.size(), 1);
auto child_props1 =
std::dynamic_pointer_cast<const ViewProps>(children1.at(0)->getProps());
ASSERT_STREQ(child_props1->testId.c_str(), "cond_true");
mockSimpleTestValue_ = false;
auto root2 = ReactBytecodeInterpreter::buildShadowTree(
bytecode,
surfaceId,
folly::dynamic::object(),
*componentDescriptorRegistry,
nativeModuleRegistry);
auto child_props2 = std::dynamic_pointer_cast<const ViewProps>(
root2->getChildren().at(0)->getProps());
ASSERT_STREQ(child_props2->testId.c_str(), "cond_false");
}