/* * 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 "shadowTreeGeneration.h" #include #include namespace facebook { namespace react { static SharedViewProps nonFlattenedDefaultProps( ComponentDescriptor const &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; return std::static_pointer_cast( componentDescriptor.cloneProps(nullptr, RawProps{dynamic})); } static ShadowNode::Shared makeNode( ComponentDescriptor const &componentDescriptor, int tag, ShadowNode::ListOfShared children, bool flattened = false) { auto props = flattened ? generateDefaultProps(componentDescriptor) : nonFlattenedDefaultProps(componentDescriptor); return componentDescriptor.createShadowNode( ShadowNodeFragment{props, std::make_shared(children)}, componentDescriptor.createFamily({tag, SurfaceId(1), nullptr}, nullptr)); } /** * 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}, nullptr); // Creating an initial root shadow node. auto emptyRootNode = std::const_pointer_cast( std::static_pointer_cast( rootComponentDescriptor.createShadowNode( ShadowNodeFragment{RootShadowNode::defaultSharedProps()}, rootFamily))); // Applying size constraints. emptyRootNode = emptyRootNode->clone( 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}, nullptr); // Construct "identical" shadow nodes: they differ only in children. auto shadowNodeV1 = viewComponentDescriptor.createShadowNode( ShadowNodeFragment{generateDefaultProps(viewComponentDescriptor), std::make_shared( SharedShadowNodeList{childB, childC, childD})}, family); auto shadowNodeV2 = shadowNodeV1->clone(ShadowNodeFragment{ generateDefaultProps(viewComponentDescriptor), std::make_shared( SharedShadowNodeList{childA, childB, childC, childD})}); auto shadowNodeV3 = shadowNodeV2->clone( ShadowNodeFragment{generateDefaultProps(viewComponentDescriptor), std::make_shared( SharedShadowNodeList{childB, childC, childD})}); auto shadowNodeV4 = shadowNodeV3->clone( ShadowNodeFragment{generateDefaultProps(viewComponentDescriptor), std::make_shared( SharedShadowNodeList{childB, childD, childE})}); auto shadowNodeV5 = shadowNodeV4->clone(ShadowNodeFragment{ generateDefaultProps(viewComponentDescriptor), std::make_shared( SharedShadowNodeList{childB, childA, childE, childC})}); auto shadowNodeV6 = shadowNodeV5->clone(ShadowNodeFragment{ generateDefaultProps(viewComponentDescriptor), std::make_shared(SharedShadowNodeList{ childB, childA, childD, childF, childE, childC})}); auto shadowNodeV7 = shadowNodeV6->clone(ShadowNodeFragment{ generateDefaultProps(viewComponentDescriptor), std::make_shared(SharedShadowNodeList{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(), std::make_shared( SharedShadowNodeList{shadowNodeV1})})); auto rootNodeV2 = std::static_pointer_cast( rootNodeV1->ShadowNode::clone( ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(), std::make_shared( SharedShadowNodeList{shadowNodeV2})})); auto rootNodeV3 = std::static_pointer_cast( rootNodeV2->ShadowNode::clone( ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(), std::make_shared( SharedShadowNodeList{shadowNodeV3})})); auto rootNodeV4 = std::static_pointer_cast( rootNodeV3->ShadowNode::clone( ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(), std::make_shared( SharedShadowNodeList{shadowNodeV4})})); auto rootNodeV5 = std::static_pointer_cast( rootNodeV4->ShadowNode::clone( ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(), std::make_shared( SharedShadowNodeList{shadowNodeV5})})); auto rootNodeV6 = std::static_pointer_cast( rootNodeV5->ShadowNode::clone( ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(), std::make_shared( SharedShadowNodeList{shadowNodeV6})})); auto rootNodeV7 = std::static_pointer_cast( rootNodeV6->ShadowNode::clone( ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(), std::make_shared( SharedShadowNodeList{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, false); // 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, false); // 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, false); LOG(ERROR) << "Num mutations IN OLD TEST mutations3: " << mutations3.size(); // 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, false); // 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, false); // 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, false); // 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 (int i = 0; i < mutations6.size(); i++) { if (mutations6[i].type == ShadowViewMutation::Create) { EXPECT_TRUE(mutations6[i].newChildShadowView.tag > 105); } } } } // namespace react } // namespace facebook