From aad99607de99bd3ecdd474850147736b1cac72a9 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Fri, 24 Apr 2020 21:36:56 -0700 Subject: [PATCH] Fabric: Test for State Reconciliation mechanism Summary: It's not immediately obvious from the UI/UX when/if this mechanism breaks, so it's good to have a test. Changelog: [Internal] Fabric-specific internal change. Reviewed By: JoshuaGross Differential Revision: D21184718 fbshipit-source-id: 25432a1398cff3ce61f62cf433e3cb73d7a7a93f --- ReactCommon/fabric/mounting/BUCK | 4 + .../tests/StateReconciliationTest.cpp | 176 ++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 ReactCommon/fabric/mounting/tests/StateReconciliationTest.cpp diff --git a/ReactCommon/fabric/mounting/BUCK b/ReactCommon/fabric/mounting/BUCK index 7bc30293aa9..9d6ad218c1d 100644 --- a/ReactCommon/fabric/mounting/BUCK +++ b/ReactCommon/fabric/mounting/BUCK @@ -81,5 +81,9 @@ fb_xplat_cxx_test( ":mounting", "//xplat/folly:molly", "//xplat/third-party/gmock:gtest", + react_native_xplat_target("fabric/element:element"), + react_native_xplat_target("fabric/components/root:root"), + react_native_xplat_target("fabric/components/view:view"), + react_native_xplat_target("fabric/components/scrollview:scrollview"), ], ) diff --git a/ReactCommon/fabric/mounting/tests/StateReconciliationTest.cpp b/ReactCommon/fabric/mounting/tests/StateReconciliationTest.cpp new file mode 100644 index 00000000000..19ba086ca2e --- /dev/null +++ b/ReactCommon/fabric/mounting/tests/StateReconciliationTest.cpp @@ -0,0 +1,176 @@ +/* + * 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 + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace facebook::react; + +class DummyShadowTreeDelegate : public ShadowTreeDelegate { + public: + virtual void shadowTreeDidFinishTransaction( + ShadowTree const &shadowTree, + MountingCoordinator::Shared const &mountingCoordinator) const override{}; +}; + +inline ShadowNode const *findDescendantNode( + ShadowNode const &shadowNode, + ShadowNodeFamily const &family) { + auto result = (ShadowNode const *){nullptr}; + shadowNode.cloneTree(family, [&](ShadowNode const &oldShadowNode) { + result = &oldShadowNode; + return oldShadowNode.clone({}); + }); + return result; +} + +inline ShadowNode const *findDescendantNode( + ShadowTree const &shadowTree, + ShadowNodeFamily const &family) { + auto result = (ShadowNode const *){nullptr}; + + shadowTree.tryCommit( + [&](RootShadowNode::Shared const &oldRootShadowNode) { + result = findDescendantNode(*oldRootShadowNode, family); + return nullptr; + }, + false); + + return result; +} + +TEST(StateReconciliationTest, testStateReconciliation) { + auto builder = simpleComponentBuilder(); + + auto shadowNodeA = std::shared_ptr{}; + auto shadowNodeAA = std::shared_ptr{}; + auto shadowNodeAB = std::shared_ptr{}; + auto shadowNodeABA = std::shared_ptr{}; + auto shadowNodeABB = std::shared_ptr{}; + auto shadowNodeABC = std::shared_ptr{}; + + // clang-format off + auto element = + Element() + .reference(shadowNodeA) + .finalize([](RootShadowNode &shadowNode){ + shadowNode.sealRecursive(); + }) + .children({ + Element() + .reference(shadowNodeAA), + Element() + .reference(shadowNodeAB) + .children({ + Element() + .reference(shadowNodeABA), + Element() + .reference(shadowNodeABB), + Element() + .reference(shadowNodeABC) + }) + }); + // clang-format on + + auto shadowNode = builder.build(element); + + auto rootShadowNodeState1 = shadowNode->ShadowNode::clone({}); + + auto &scrollViewComponentDescriptor = shadowNodeAB->getComponentDescriptor(); + auto &family = shadowNodeAB->getFamily(); + auto state1 = shadowNodeAB->getState(); + auto shadowTreeDelegate = DummyShadowTreeDelegate{}; + auto eventDispatcher = EventDispatcher::Shared{}; + auto rootComponentDescriptor = + ComponentDescriptorParameters{eventDispatcher, nullptr, nullptr}; + ShadowTree shadowTree{SurfaceId{11}, + LayoutConstraints{}, + LayoutContext{}, + rootComponentDescriptor, + shadowTreeDelegate}; + + shadowTree.commit( + [&](RootShadowNode::Shared const &oldRootShadowNode) { + return std::static_pointer_cast(rootShadowNodeState1); + }, + true); + + EXPECT_EQ(state1->getMostRecentState(), state1); + + EXPECT_EQ( + findDescendantNode(*rootShadowNodeState1, family)->getState(), state1); + + auto state2 = scrollViewComponentDescriptor.createState( + family, std::make_shared()); + + auto rootShadowNodeState2 = + shadowNode->cloneTree(family, [&](ShadowNode const &oldShadowNode) { + return oldShadowNode.clone({ShadowNodeFragment::propsPlaceholder(), + ShadowNodeFragment::childrenPlaceholder(), + state2}); + }); + + EXPECT_EQ( + findDescendantNode(*rootShadowNodeState2, family)->getState(), state2); + + shadowTree.commit( + [&](RootShadowNode::Shared const &oldRootShadowNode) { + return std::static_pointer_cast(rootShadowNodeState2); + }, + true); + + EXPECT_EQ(state1->getMostRecentState(), state2); + EXPECT_EQ(state2->getMostRecentState(), state2); + + auto state3 = scrollViewComponentDescriptor.createState( + family, std::make_shared()); + + auto rootShadowNodeState3 = rootShadowNodeState2->cloneTree( + family, [&](ShadowNode const &oldShadowNode) { + return oldShadowNode.clone({ShadowNodeFragment::propsPlaceholder(), + ShadowNodeFragment::childrenPlaceholder(), + state3}); + }); + + EXPECT_EQ( + findDescendantNode(*rootShadowNodeState3, family)->getState(), state3); + + shadowTree.commit( + [&](RootShadowNode::Shared const &oldRootShadowNode) { + return std::static_pointer_cast(rootShadowNodeState3); + }, + true); + + EXPECT_EQ(findDescendantNode(shadowTree, family)->getState(), state3); + + EXPECT_EQ(state1->getMostRecentState(), state3); + EXPECT_EQ(state2->getMostRecentState(), state3); + EXPECT_EQ(state3->getMostRecentState(), state3); + + // This is the core part of the whole test. + // Here we commit the old tree but we expect that the state associated with + // the node will stay the same (newer that the old tree has). + shadowTree.commit( + [&](RootShadowNode::Shared const &oldRootShadowNode) { + return std::static_pointer_cast(rootShadowNodeState2); + }, + true); + + EXPECT_EQ(findDescendantNode(shadowTree, family)->getState(), state3); +}