From 17c34295a18c707ea28dc7f6f8566bdb57472b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 29 Jan 2025 11:31:51 -0800 Subject: [PATCH] Migrate mounting tests to Fantom (#49018) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49018 Changelog: [internal] Migrates the mounting layer logs from C++ (640 lines of code) to Fantom (248 lines!!!). This is 1:1 translation of the test. Reviewed By: javache Differential Revision: D67549200 fbshipit-source-id: 735fa3203cd04dd5b3b4b5174e0c96fdc2354993 --- .../renderer/mounting/tests/MountingTest.cpp | 640 ------------------ .../mounting/__tests__/Mounting-itest.js | 230 +++++++ 2 files changed, 230 insertions(+), 640 deletions(-) delete mode 100644 packages/react-native/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp create mode 100644 packages/react-native/src/private/renderer/mounting/__tests__/Mounting-itest.js diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp deleted file mode 100644 index 161edf17f09ebf..00000000000000 --- a/packages/react-native/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp +++ /dev/null @@ -1,640 +0,0 @@ -/* - * 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 - -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace facebook::react { - -static SharedViewProps nonFlattenedDefaultProps( - const ComponentDescriptor& componentDescriptor) { - folly::dynamic dynamic = folly::dynamic::object(); - dynamic["position"] = "absolute"; - dynamic["top"] = 0; - dynamic["left"] = 0; - dynamic["width"] = 100; - dynamic["height"] = 100; - dynamic["nativeId"] = "NativeId"; - dynamic["accessible"] = true; - - ContextContainer contextContainer; - PropsParserContext parserContext{-1, contextContainer}; - - return std::static_pointer_cast( - componentDescriptor.cloneProps( - parserContext, nullptr, RawProps{dynamic})); -} - -static ShadowNode::Shared makeNode( - const ComponentDescriptor& componentDescriptor, - int tag, - std::shared_ptr children, - bool flattened = false) { - auto props = flattened ? generateDefaultProps(componentDescriptor) - : nonFlattenedDefaultProps(componentDescriptor); - - return componentDescriptor.createShadowNode( - ShadowNodeFragment{std::move(props), std::move(children)}, - componentDescriptor.createFamily({tag, SurfaceId(1), nullptr})); -} - -static std::shared_ptr listOfChildren( - std::initializer_list list) { - return std::make_shared( - ShadowNode::ListOfShared{list}); -} - -/** - * Test reordering of views with the same parent: - * - * For instance: - * A -> [B,C,D] ==> A -> [D,B,C] - * - * In the V1 of diffing this would produce 3 removes and 3 inserts, but with - * some cleverness we can reduce this to 1 remove and 1 insert. - */ -TEST(MountingTest, testReorderingInstructionGeneration) { - auto eventDispatcher = EventDispatcher::Shared{}; - - auto contextContainer = std::make_shared(); - auto componentDescriptorParameters = - ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr}; - auto viewComponentDescriptor = - ViewComponentDescriptor(componentDescriptorParameters); - auto rootComponentDescriptor = - RootComponentDescriptor(componentDescriptorParameters); - - auto rootFamily = - rootComponentDescriptor.createFamily({Tag(1), SurfaceId(1), nullptr}); - - // Creating an initial root shadow node. - auto emptyRootNode = std::const_pointer_cast( - std::static_pointer_cast( - rootComponentDescriptor.createShadowNode( - ShadowNodeFragment{RootShadowNode::defaultSharedProps()}, - rootFamily))); - - PropsParserContext parserContext{-1, *contextContainer}; - - // Applying size constraints. - emptyRootNode = emptyRootNode->clone( - parserContext, - LayoutConstraints{ - Size{512, 0}, Size{512, std::numeric_limits::infinity()}}, - LayoutContext{}); - - auto childA = makeNode(viewComponentDescriptor, 100, {}); - auto childB = makeNode(viewComponentDescriptor, 101, {}); - auto childC = makeNode(viewComponentDescriptor, 102, {}); - auto childD = makeNode(viewComponentDescriptor, 103, {}); - auto childE = makeNode(viewComponentDescriptor, 104, {}); - auto childF = makeNode(viewComponentDescriptor, 105, {}); - auto childG = makeNode(viewComponentDescriptor, 106, {}); - auto childH = makeNode(viewComponentDescriptor, 107, {}); - auto childI = makeNode(viewComponentDescriptor, 108, {}); - auto childJ = makeNode(viewComponentDescriptor, 109, {}); - auto childK = makeNode(viewComponentDescriptor, 110, {}); - - auto family = - viewComponentDescriptor.createFamily({10, SurfaceId(1), nullptr}); - - // Construct "identical" shadow nodes: they differ only in children. - auto shadowNodeV1 = viewComponentDescriptor.createShadowNode( - ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childB, childC, childD})}, - family); - auto shadowNodeV2 = shadowNodeV1->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childA, childB, childC, childD})}); - auto shadowNodeV3 = shadowNodeV2->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childB, childC, childD})}); - auto shadowNodeV4 = shadowNodeV3->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childB, childD, childE})}); - auto shadowNodeV5 = shadowNodeV4->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childB, childA, childE, childC})}); - auto shadowNodeV6 = shadowNodeV5->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - std::make_shared(ShadowNode::ListOfShared{ - childB, childA, childD, childF, childE, childC})}); - auto shadowNodeV7 = shadowNodeV6->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - std::make_shared(ShadowNode::ListOfShared{ - childF, - childE, - childC, - childD, - childG, - childH, - childI, - childJ, - childK})}); - - // Injecting a tree into the root node. - auto rootNodeV1 = std::static_pointer_cast( - emptyRootNode->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV1})})); - auto rootNodeV2 = std::static_pointer_cast( - rootNodeV1->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV2})})); - auto rootNodeV3 = std::static_pointer_cast( - rootNodeV2->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV3})})); - auto rootNodeV4 = std::static_pointer_cast( - rootNodeV3->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV4})})); - auto rootNodeV5 = std::static_pointer_cast( - rootNodeV4->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV5})})); - auto rootNodeV6 = std::static_pointer_cast( - rootNodeV5->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV6})})); - auto rootNodeV7 = std::static_pointer_cast( - rootNodeV6->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV7})})); - - // Layout - std::vector affectedLayoutableNodesV1{}; - affectedLayoutableNodesV1.reserve(1024); - std::const_pointer_cast(rootNodeV1) - ->layoutIfNeeded(&affectedLayoutableNodesV1); - rootNodeV1->sealRecursive(); - - std::vector affectedLayoutableNodesV2{}; - affectedLayoutableNodesV2.reserve(1024); - std::const_pointer_cast(rootNodeV2) - ->layoutIfNeeded(&affectedLayoutableNodesV2); - rootNodeV2->sealRecursive(); - - std::vector affectedLayoutableNodesV3{}; - affectedLayoutableNodesV3.reserve(1024); - std::const_pointer_cast(rootNodeV3) - ->layoutIfNeeded(&affectedLayoutableNodesV3); - rootNodeV3->sealRecursive(); - - std::vector affectedLayoutableNodesV4{}; - affectedLayoutableNodesV4.reserve(1024); - std::const_pointer_cast(rootNodeV4) - ->layoutIfNeeded(&affectedLayoutableNodesV4); - rootNodeV4->sealRecursive(); - - std::vector affectedLayoutableNodesV5{}; - affectedLayoutableNodesV5.reserve(1024); - std::const_pointer_cast(rootNodeV5) - ->layoutIfNeeded(&affectedLayoutableNodesV5); - rootNodeV5->sealRecursive(); - - std::vector affectedLayoutableNodesV6{}; - affectedLayoutableNodesV6.reserve(1024); - std::const_pointer_cast(rootNodeV6) - ->layoutIfNeeded(&affectedLayoutableNodesV6); - rootNodeV6->sealRecursive(); - - // This block displays all the mutations for debugging purposes. - /* - LOG(ERROR) << "Num mutations: " << mutations.size(); - for (auto const &mutation : mutations) { - switch (mutation.type) { - case ShadowViewMutation::Create: { - LOG(ERROR) << "CREATE " << mutation.newChildShadowView.tag; - break; - } - case ShadowViewMutation::Delete: { - LOG(ERROR) << "DELETE " << mutation.oldChildShadowView.tag; - break; - } - case ShadowViewMutation::Remove: { - LOG(ERROR) << "REMOVE " << mutation.oldChildShadowView.tag << " " << - mutation.index; break; - } - case ShadowViewMutation::Insert: { - LOG(ERROR) << "INSERT " << mutation.newChildShadowView.tag << " " << - mutation.index; break; - } - case ShadowViewMutation::Update: { - LOG(ERROR) << "UPDATE " << mutation.newChildShadowView.tag; - break; - } - } - }*/ - - // Calculating mutations. - auto mutations1 = calculateShadowViewMutations(*rootNodeV1, *rootNodeV2); - - // The order and exact mutation instructions here may change at any time. - // This test just ensures that any changes are intentional. - // This test, in particular, ensures that inserting a node at the beginning - // produces a single "Insert" instruction, and no remove/insert (move) - // operations. All these nodes are laid out with absolute positioning, so - // moving them around does not change layout. - EXPECT_TRUE(mutations1.size() == 2); - EXPECT_TRUE(mutations1[0].type == ShadowViewMutation::Create); - EXPECT_TRUE(mutations1[0].newChildShadowView.tag == 100); - EXPECT_TRUE(mutations1[1].type == ShadowViewMutation::Insert); - EXPECT_TRUE(mutations1[1].newChildShadowView.tag == 100); - EXPECT_TRUE(mutations1[1].index == 0); - - // Calculating mutations. - auto mutations2 = calculateShadowViewMutations(*rootNodeV2, *rootNodeV3); - - // The order and exact mutation instructions here may change at any time. - // This test just ensures that any changes are intentional. - // This test, in particular, ensures that removing a node at the beginning - // produces a single remove (and delete) instruction, and no remove/insert - // (move) operations. All these nodes are laid out with absolute positioning, - // so moving them around does not change layout. - EXPECT_TRUE(mutations2.size() == 2); - EXPECT_TRUE(mutations2[0].type == ShadowViewMutation::Remove); - EXPECT_TRUE(mutations2[0].oldChildShadowView.tag == 100); - EXPECT_TRUE(mutations2[0].index == 0); - EXPECT_TRUE(mutations2[1].type == ShadowViewMutation::Delete); - EXPECT_TRUE(mutations2[1].oldChildShadowView.tag == 100); - - // Calculating mutations. - auto mutations3 = calculateShadowViewMutations(*rootNodeV3, *rootNodeV4); - - // The order and exact mutation instructions here may change at any time. - // This test just ensures that any changes are intentional. - // This test, in particular, ensures that removing a node in the middle - // produces a single remove (and delete) instruction, and no remove/insert - // (move) operations; and that simultaneously, we can insert a node at the - // end. - EXPECT_TRUE(mutations3.size() == 4); - EXPECT_TRUE(mutations3[0].type == ShadowViewMutation::Remove); - EXPECT_TRUE(mutations3[0].oldChildShadowView.tag == 102); - EXPECT_TRUE(mutations3[0].index == 1); - EXPECT_TRUE(mutations3[1].type == ShadowViewMutation::Delete); - EXPECT_TRUE(mutations3[1].oldChildShadowView.tag == 102); - EXPECT_TRUE(mutations3[2].type == ShadowViewMutation::Create); - EXPECT_TRUE(mutations3[2].newChildShadowView.tag == 104); - EXPECT_TRUE(mutations3[3].type == ShadowViewMutation::Insert); - EXPECT_TRUE(mutations3[3].newChildShadowView.tag == 104); - EXPECT_TRUE(mutations3[3].index == 2); - - // Calculating mutations. - auto mutations4 = calculateShadowViewMutations(*rootNodeV4, *rootNodeV5); - - // The order and exact mutation instructions here may change at any time. - // This test just ensures that any changes are intentional. - // This test, in particular, ensures that inserting a child at the middle, and - // at the end, and removing a node in the middle, produces the minimal set of - // instructions. All these nodes are laid out with absolute positioning, so - // moving them around does not change layout. - EXPECT_TRUE(mutations4.size() == 6); - EXPECT_TRUE(mutations4[0].type == ShadowViewMutation::Remove); - EXPECT_TRUE(mutations4[0].oldChildShadowView.tag == 103); - EXPECT_TRUE(mutations4[0].index == 1); - EXPECT_TRUE(mutations4[1].type == ShadowViewMutation::Delete); - EXPECT_TRUE(mutations4[1].oldChildShadowView.tag == 103); - EXPECT_TRUE(mutations4[2].type == ShadowViewMutation::Create); - EXPECT_TRUE(mutations4[2].newChildShadowView.tag == 100); - EXPECT_TRUE(mutations4[3].type == ShadowViewMutation::Create); - EXPECT_TRUE(mutations4[3].newChildShadowView.tag == 102); - EXPECT_TRUE(mutations4[4].type == ShadowViewMutation::Insert); - EXPECT_TRUE(mutations4[4].newChildShadowView.tag == 100); - EXPECT_TRUE(mutations4[4].index == 1); - EXPECT_TRUE(mutations4[5].type == ShadowViewMutation::Insert); - EXPECT_TRUE(mutations4[5].newChildShadowView.tag == 102); - EXPECT_TRUE(mutations4[5].index == 3); - - auto mutations5 = calculateShadowViewMutations(*rootNodeV5, *rootNodeV6); - - // The order and exact mutation instructions here may change at any time. - // This test just ensures that any changes are intentional. - // This test, in particular, ensures that inserting TWO children in the middle - // produces the minimal set of instructions. All these nodes are laid out with - // absolute positioning, so moving them around does not change layout. - EXPECT_TRUE(mutations5.size() == 4); - EXPECT_TRUE(mutations5[0].type == ShadowViewMutation::Create); - EXPECT_TRUE(mutations5[0].newChildShadowView.tag == 103); - EXPECT_TRUE(mutations5[1].type == ShadowViewMutation::Create); - EXPECT_TRUE(mutations5[1].newChildShadowView.tag == 105); - EXPECT_TRUE(mutations5[2].type == ShadowViewMutation::Insert); - EXPECT_TRUE(mutations5[2].newChildShadowView.tag == 103); - EXPECT_TRUE(mutations5[2].index == 2); - EXPECT_TRUE(mutations5[3].type == ShadowViewMutation::Insert); - EXPECT_TRUE(mutations5[3].newChildShadowView.tag == 105); - EXPECT_TRUE(mutations5[3].index == 3); - - auto mutations6 = calculateShadowViewMutations(*rootNodeV6, *rootNodeV7); - - // The order and exact mutation instructions here may change at any time. - // This test just ensures that any changes are intentional. - // This test, in particular, ensures that a bug has been fixed: that with - // a particular sequence of inserts/removes/moves, we don't unintentionally - // create more "CREATE" mutations than necessary. - // The actual nodes that should be created in this transaction have a tag > - // 105. - EXPECT_TRUE(mutations6.size() == 25); - for (auto& i : mutations6) { - if (i.type == ShadowViewMutation::Create) { - EXPECT_TRUE(i.newChildShadowView.tag > 105); - } - } -} - -/** - * Test reparenting mutation instruction generation. - * We cannot practically handle all possible use-cases here. - * It would be helpful to do verification with randomized trees, but it's - * much easier to do that in JS. - */ -TEST(MountingTest, testViewReparentingInstructionGeneration) { - auto eventDispatcher = EventDispatcher::Shared{}; - auto contextContainer = std::make_shared(); - auto componentDescriptorParameters = - ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr}; - auto viewComponentDescriptor = - ViewComponentDescriptor(componentDescriptorParameters); - auto rootComponentDescriptor = - RootComponentDescriptor(componentDescriptorParameters); - - auto rootFamily = - rootComponentDescriptor.createFamily({Tag(1), SurfaceId(1), nullptr}); - - // Creating an initial root shadow node. - auto emptyRootNode = std::const_pointer_cast( - std::static_pointer_cast( - rootComponentDescriptor.createShadowNode( - ShadowNodeFragment{RootShadowNode::defaultSharedProps()}, - rootFamily))); - - PropsParserContext parserContext{-1, *contextContainer}; - - // Applying size constraints. - emptyRootNode = emptyRootNode->clone( - parserContext, - LayoutConstraints{ - Size{512, 0}, Size{512, std::numeric_limits::infinity()}}, - LayoutContext{}); - - auto childA = makeNode(viewComponentDescriptor, 100, {}); - auto childB = makeNode(viewComponentDescriptor, 101, {}); - auto childC = makeNode(viewComponentDescriptor, 102, {}); - auto childD = makeNode(viewComponentDescriptor, 103, {}); - auto childE = makeNode(viewComponentDescriptor, 104, {}); - auto childF = makeNode(viewComponentDescriptor, 105, {}); - - auto childG = makeNode(viewComponentDescriptor, 106, {}); - auto childH = makeNode(viewComponentDescriptor, 107, {}); - auto childI = makeNode(viewComponentDescriptor, 108, {}); - auto childJ = makeNode(viewComponentDescriptor, 109, {}); - auto childK = makeNode(viewComponentDescriptor, 110, {}); - - auto family = - viewComponentDescriptor.createFamily({10, SurfaceId(1), nullptr}); - - auto reparentedViewA_ = makeNode( - viewComponentDescriptor, 1000, listOfChildren({childC, childA, childB})); - auto reparentedViewA = reparentedViewA_->clone( - ShadowNodeFragment{nonFlattenedDefaultProps(viewComponentDescriptor)}); - auto reparentedViewB = makeNode( - viewComponentDescriptor, 2000, listOfChildren({childF, childE, childD})); - - // Root -> G* -> H -> I -> J -> A* [nodes with * are _not_ flattened] - auto shadowNodeV1 = viewComponentDescriptor.createShadowNode( - ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childG->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childH->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childI->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childJ->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({reparentedViewA_})})})})})})})})})}, - family); - - // Root -> G* -> H* -> I -> J -> A* [nodes with * are _not_ flattened] - // Force an update with A with new props - auto shadowNodeV2 = shadowNodeV1->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childG->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childH->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childI->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childJ->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({reparentedViewA})})})})})})})})})}); - - // Root -> G* -> H -> I -> J -> A* [nodes with * are _not_ flattened] - auto shadowNodeV3 = shadowNodeV2->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childG->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childH->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childI->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childJ->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({reparentedViewA})})})})})})})})})}); - - // The view is reparented 1 level down with a different sibling - // Root -> G* -> H* -> I* -> J -> [B*, A*] [nodes with * are _not_ flattened] - auto shadowNodeV4 = shadowNodeV3->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childG->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childH->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childI->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childJ->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren( - {reparentedViewB, reparentedViewA})})})})})})})})})}); - - // The view is reparented 1 level further down with its order with the sibling - // swapped - // Root -> G* -> H* -> I* -> J* -> [A*, B*] [nodes with * are _not_ flattened] - auto shadowNodeV5 = shadowNodeV4->clone(ShadowNodeFragment{ - generateDefaultProps(viewComponentDescriptor), - listOfChildren({childG->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childH->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childI->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren({childJ->clone(ShadowNodeFragment{ - nonFlattenedDefaultProps(viewComponentDescriptor), - listOfChildren( - {reparentedViewA, reparentedViewB})})})})})})})})})}); - - // Injecting a tree into the root node. - auto rootNodeV1 = std::static_pointer_cast( - emptyRootNode->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV1})})); - auto rootNodeV2 = std::static_pointer_cast( - rootNodeV1->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV2})})); - auto rootNodeV3 = std::static_pointer_cast( - rootNodeV2->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV3})})); - auto rootNodeV4 = std::static_pointer_cast( - rootNodeV3->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV4})})); - auto rootNodeV5 = std::static_pointer_cast( - rootNodeV4->ShadowNode::clone(ShadowNodeFragment{ - ShadowNodeFragment::propsPlaceholder(), - listOfChildren({shadowNodeV5})})); - - // Layout - std::vector affectedLayoutableNodesV1{}; - affectedLayoutableNodesV1.reserve(1024); - std::const_pointer_cast(rootNodeV1) - ->layoutIfNeeded(&affectedLayoutableNodesV1); - rootNodeV1->sealRecursive(); - - std::vector affectedLayoutableNodesV2{}; - affectedLayoutableNodesV2.reserve(1024); - std::const_pointer_cast(rootNodeV2) - ->layoutIfNeeded(&affectedLayoutableNodesV2); - rootNodeV2->sealRecursive(); - - std::vector affectedLayoutableNodesV3{}; - affectedLayoutableNodesV3.reserve(1024); - std::const_pointer_cast(rootNodeV3) - ->layoutIfNeeded(&affectedLayoutableNodesV3); - rootNodeV3->sealRecursive(); - - std::vector affectedLayoutableNodesV4{}; - affectedLayoutableNodesV4.reserve(1024); - std::const_pointer_cast(rootNodeV4) - ->layoutIfNeeded(&affectedLayoutableNodesV4); - rootNodeV4->sealRecursive(); - - std::vector affectedLayoutableNodesV5{}; - affectedLayoutableNodesV5.reserve(1024); - std::const_pointer_cast(rootNodeV5) - ->layoutIfNeeded(&affectedLayoutableNodesV5); - rootNodeV5->sealRecursive(); - - // Calculating mutations. - auto mutations1 = calculateShadowViewMutations(*rootNodeV1, *rootNodeV2); - - EXPECT_EQ(mutations1.size(), 6); - EXPECT_EQ(mutations1[0].type, ShadowViewMutation::Update); - EXPECT_EQ(mutations1[0].oldChildShadowView.tag, childG->getTag()); - EXPECT_EQ(mutations1[1].type, ShadowViewMutation::Update); - EXPECT_EQ(mutations1[1].oldChildShadowView.tag, reparentedViewA->getTag()); - EXPECT_EQ(mutations1[1].parentTag, childG->getTag()); - EXPECT_EQ(mutations1[2].type, ShadowViewMutation::Remove); - EXPECT_EQ(mutations1[2].oldChildShadowView.tag, reparentedViewA->getTag()); - EXPECT_EQ(mutations1[3].type, ShadowViewMutation::Create); - EXPECT_EQ(mutations1[3].newChildShadowView.tag, childH->getTag()); - EXPECT_EQ(mutations1[4].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations1[4].newChildShadowView.tag, childH->getTag()); - EXPECT_EQ(mutations1[5].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations1[5].newChildShadowView.tag, reparentedViewA->getTag()); - - auto mutations2 = calculateShadowViewMutations(*rootNodeV2, *rootNodeV3); - - EXPECT_EQ(mutations2.size(), 5); - EXPECT_EQ(mutations2[0].type, ShadowViewMutation::Update); - EXPECT_EQ(mutations2[0].oldChildShadowView.tag, childG->getTag()); - EXPECT_EQ(mutations2[0].parentTag, emptyRootNode->getTag()); - EXPECT_EQ(mutations2[1].type, ShadowViewMutation::Remove); - EXPECT_EQ(mutations2[1].oldChildShadowView.tag, reparentedViewA->getTag()); - EXPECT_EQ(mutations2[2].type, ShadowViewMutation::Remove); - EXPECT_EQ(mutations2[2].oldChildShadowView.tag, childH->getTag()); - EXPECT_EQ( - mutations2[3].type, - ShadowViewMutation::Delete); // correct, H is removed from tree entirely - EXPECT_EQ(mutations2[3].oldChildShadowView.tag, childH->getTag()); - EXPECT_EQ(mutations2[4].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations2[4].newChildShadowView.tag, reparentedViewA->getTag()); - - auto mutations3 = calculateShadowViewMutations(*rootNodeV3, *rootNodeV4); - - // between these two trees, lots of new nodes are created and inserted - this - // is all correct, and this is the minimal amount of mutations - - EXPECT_EQ(mutations3.size(), 15); - EXPECT_EQ(mutations3[0].type, ShadowViewMutation::Update); - EXPECT_EQ(mutations3[0].oldChildShadowView.tag, childG->getTag()); - EXPECT_EQ(mutations3[0].parentTag, emptyRootNode->getTag()); - EXPECT_EQ(mutations3[1].type, ShadowViewMutation::Remove); - EXPECT_EQ(mutations3[1].oldChildShadowView.tag, reparentedViewA->getTag()); - EXPECT_EQ(mutations3[2].type, ShadowViewMutation::Create); - EXPECT_EQ(mutations3[2].newChildShadowView.tag, childH->getTag()); - EXPECT_EQ(mutations3[3].type, ShadowViewMutation::Create); - EXPECT_EQ(mutations3[3].newChildShadowView.tag, reparentedViewB->getTag()); - EXPECT_EQ(mutations3[4].type, ShadowViewMutation::Create); - EXPECT_EQ(mutations3[4].newChildShadowView.tag, childI->getTag()); - EXPECT_EQ(mutations3[5].type, ShadowViewMutation::Create); - EXPECT_EQ(mutations3[5].newChildShadowView.tag, childF->getTag()); - EXPECT_EQ(mutations3[6].type, ShadowViewMutation::Create); - EXPECT_EQ(mutations3[6].newChildShadowView.tag, childE->getTag()); - EXPECT_EQ(mutations3[7].type, ShadowViewMutation::Create); - EXPECT_EQ(mutations3[7].newChildShadowView.tag, childD->getTag()); - EXPECT_EQ(mutations3[8].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations3[8].newChildShadowView.tag, childF->getTag()); - EXPECT_EQ(mutations3[9].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations3[9].newChildShadowView.tag, childE->getTag()); - EXPECT_EQ(mutations3[10].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations3[10].newChildShadowView.tag, childD->getTag()); - EXPECT_EQ(mutations3[11].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations3[11].newChildShadowView.tag, childH->getTag()); - EXPECT_EQ(mutations3[12].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations3[12].newChildShadowView.tag, childI->getTag()); - EXPECT_EQ(mutations3[13].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations3[13].newChildShadowView.tag, reparentedViewB->getTag()); - EXPECT_EQ(mutations3[14].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations3[14].newChildShadowView.tag, reparentedViewA->getTag()); - - auto mutations4 = calculateShadowViewMutations(*rootNodeV4, *rootNodeV5); - - EXPECT_EQ(mutations4.size(), 9); - EXPECT_EQ(mutations4[0].type, ShadowViewMutation::Update); - EXPECT_EQ(mutations4[0].oldChildShadowView.tag, childG->getTag()); - EXPECT_EQ(mutations4[1].type, ShadowViewMutation::Update); - EXPECT_EQ(mutations4[1].oldChildShadowView.tag, childH->getTag()); - EXPECT_EQ(mutations4[2].type, ShadowViewMutation::Update); - EXPECT_EQ(mutations4[2].oldChildShadowView.tag, childI->getTag()); - EXPECT_EQ(mutations4[3].type, ShadowViewMutation::Remove); - EXPECT_EQ(mutations4[3].oldChildShadowView.tag, reparentedViewA->getTag()); - EXPECT_EQ(mutations4[4].type, ShadowViewMutation::Remove); - EXPECT_EQ(mutations4[4].oldChildShadowView.tag, reparentedViewB->getTag()); - EXPECT_EQ(mutations4[5].type, ShadowViewMutation::Create); - EXPECT_EQ(mutations4[5].newChildShadowView.tag, childJ->getTag()); - EXPECT_EQ(mutations4[6].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations4[6].newChildShadowView.tag, childJ->getTag()); - EXPECT_EQ(mutations4[7].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations4[7].newChildShadowView.tag, reparentedViewA->getTag()); - EXPECT_EQ(mutations4[8].type, ShadowViewMutation::Insert); - EXPECT_EQ(mutations4[8].newChildShadowView.tag, reparentedViewB->getTag()); -} - -} // namespace facebook::react diff --git a/packages/react-native/src/private/renderer/mounting/__tests__/Mounting-itest.js b/packages/react-native/src/private/renderer/mounting/__tests__/Mounting-itest.js new file mode 100644 index 00000000000000..a3f96c15cf4cb0 --- /dev/null +++ b/packages/react-native/src/private/renderer/mounting/__tests__/Mounting-itest.js @@ -0,0 +1,230 @@ +/** + * 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. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +import View from '../../../../../Libraries/Components/View/View'; +import Fantom from '@react-native/fantom'; +import * as React from 'react'; + +import '../../../../../Libraries/Core/InitializeCore'; + +describe('ViewFlattening', () => { + /** + * Test reordering of views with the same parent: + * + * For instance: + * A -> [B,C,D] ==> A -> [D,B,C] + * + * In the V1 of diffing this would produce 3 removes and 3 inserts, but with + * some cleverness we can reduce this to 1 remove and 1 insert. + */ + test('reordering', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render( + + + + + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + + + + + , + ); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Update {type: "RootView", nativeID: (root)}', + 'Create {type: "View", nativeID: "A"}', + 'Create {type: "View", nativeID: "B"}', + 'Create {type: "View", nativeID: "C"}', + 'Create {type: "View", nativeID: "D"}', + 'Insert {type: "View", parentNativeID: "A", index: 0, nativeID: "B"}', + 'Insert {type: "View", parentNativeID: "A", index: 1, nativeID: "C"}', + 'Insert {type: "View", parentNativeID: "A", index: 2, nativeID: "D"}', + 'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: "A"}', + ]); + + Fantom.runTask(() => { + root.render( + + + + + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + + + + + , + ); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Remove {type: "View", parentNativeID: "A", index: 2, nativeID: "D"}', + 'Insert {type: "View", parentNativeID: "A", index: 0, nativeID: "D"}', + ]); + }); + + /** + * Test reparenting mutation instruction generation. + * We cannot practically handle all possible use-cases here. + */ + test('view reparenting', () => { + const root = Fantom.createRoot(); + + // Root -> G* -> H -> I -> J -> A* [nodes with * are _not_ flattened] + Fantom.runTask(() => { + root.render( + + + + + + + + + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + + + , + ); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Update {type: "RootView", nativeID: (root)}', + 'Create {type: "View", nativeID: "G"}', + 'Create {type: "View", nativeID: "A"}', + 'Insert {type: "View", parentNativeID: "G", index: 0, nativeID: "A"}', + 'Insert {type: "View", parentNativeID: (root), index: 0, nativeID: "G"}', + ]); + + // Root -> G* -> H* -> I -> J -> A* [nodes with * are _not_ flattened] + // Force an update with A with new props + Fantom.runTask(() => { + root.render( + + + + + + + + + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + + + + + , + ); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Update {type: "View", nativeID: "A"}', + 'Remove {type: "View", parentNativeID: "G", index: 0, nativeID: "A"}', + 'Create {type: "View", nativeID: "H"}', + 'Insert {type: "View", parentNativeID: "G", index: 0, nativeID: "H"}', + 'Insert {type: "View", parentNativeID: "H", index: 0, nativeID: "A"}', + ]); + + // The view is reparented 1 level down with a different sibling + // Root -> G* -> H* -> I* -> J -> [B*, A*] [nodes with * are _not_ flattened] + Fantom.runTask(() => { + root.render( + + + + + + + + + + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + + + + + + + + , + ); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Remove {type: "View", parentNativeID: "H", index: 0, nativeID: "A"}', + 'Create {type: "View", nativeID: "I"}', + 'Create {type: "View", nativeID: "B"}', + 'Insert {type: "View", parentNativeID: "H", index: 0, nativeID: "I"}', + 'Insert {type: "View", parentNativeID: "I", index: 0, nativeID: "B"}', + 'Insert {type: "View", parentNativeID: "I", index: 1, nativeID: "A"}', + ]); + + // The view is reparented 1 level further down with its order with the sibling + // swapped + // Root -> G* -> H* -> I* -> J* -> [A*, B*] [nodes with * are _not_ flattened] + Fantom.runTask(() => { + root.render( + + + + + + + + + + , + ); + }); + + expect(root.getRenderedOutput().toJSX()).toEqual( + + + + + + + + + + , + ); + + expect(root.takeMountingManagerLogs()).toEqual([ + 'Remove {type: "View", parentNativeID: "I", index: 1, nativeID: "A"}', + 'Remove {type: "View", parentNativeID: "I", index: 0, nativeID: "B"}', + 'Create {type: "View", nativeID: "J"}', + 'Insert {type: "View", parentNativeID: "I", index: 0, nativeID: "J"}', + 'Insert {type: "View", parentNativeID: "J", index: 0, nativeID: "A"}', + 'Insert {type: "View", parentNativeID: "J", index: 1, nativeID: "B"}', + ]); + }); +});