Files
react-native/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp
T
Joshua Gross 92091b8b31 New Flattening Differ
Summary:
# What is this?

For a very long time, we've discussed the possibility of detecting Node Reparenting in the Fabric Differ. Practically, from the developer perspective, ReactJS and React Native do not allow reparenting: nodes cannot be reparented, only deleted and then recreated with entirely new tags.

However, Fabric introduced the idea of View Flattening where views deemed unnecessary would be removed from the View hierarchy entirely. This is great and improves memory usage, except for one issue: if a View becomes unflattened, or becomes flattened, the entire tree underneath it must be rebuilt.

In a past diff we introduced a mechanism to detect sibling reordering cleverly, and produce a minimal instruction set. This diff is very similar: we know the invariants around flattening and unflattening of views and we take advantage of them to produce an optimal set of instructions efficiently.

# What's different from previous attempts?

No global maps! Those are slow!

This seems to work and (hopefully) might even improve performance, since way less work is being done on the UI thread in cases when views are (un)flattened.

This *only* does extra work when flattening/unflattening happens, which gives product engineers a little more control over perf.

# So, how's it work?

This algorithm is intuitively simple (I think) but tricky to pull off, because there are lots of edge-cases.

In short: In the past, that information was hidden from the Differ: the differ didn't know if views were being reparented, it would see them
as entirely new views or as views being deleted if a View was flattened or unflattened. We very subtly change the information given to the differ:
all nodes are visible to the differ, but marked as Flattened or Unflattened. Thus, when the differ compares two nodes in the "old" and "new" tree,
it can tell not just if there are updates to the node but if it has been unflattened or flattened as well.

For example, take this tree, where * indicates that a View is flattened:

```
         A
         +
    +----+---+
    B*       X
    +        +
    |        |
+---+--+     +
E      F     Y
```

When the Differ asks for the children of A, in the past it would get a list `[E, F, X]`. That is, B* and X are both its children, but since B is flattened, it is omitted entirely from the list and
its children are substituted.

Now, when the Differ asks for the children of A, we give it this list instead: `[B*, E, F, X]`. That is: we give it a list which includes B, but B is marked as flattened.

Another wrinkle: A node `X` could have its children flattened, but still be a concrete view: so flattening/unflattening is a different operation from making a view "concrete" or "unconcrete", which can change independently of flattening.

There is one additional wrinkle: because of zIndex/stacking order, the children of `B` might not actually appear after `B` in the list. Depending on zIndex, a tree that looks like this:

```
          A
          +
   +------+------+
   B*            C*
   +             +
   |             |
+--+--+       +--+--+
D     E       F     G
```

Could actually be linearized as: `[D G B* F C* E]` (as an extreme example; but basically all permutations as possible).

This is the reason, and the *only* reason that the inner Flattener/Unflattener

## The cases we need to handle

There are 7 cases/edge-cases of flattening and unflattening that we need to handle. Practically, all cases of reordering + flattening/unflattening, and taking recursive cases into account:

1. View A and A' (A in the old tree, A' in the new tree) are matched in the differ, and A* has been flattened or unflattened. These two cases are the easiest to handle.
2. View A' has been reordered with its siblings, and has been flattened or unflattened. These cases are slightly trickier to handle.
3. While flattening or unflattening, we encounter a child that has also been unflattened or flattened. So we need to handle four cases here in total: Flatten-Flatten, Flatten-Unflatten, Unflatten-Flatten, and Unflatten-Unflatten.

Other things to think about, also covered above:

1. Ordering. Views can be reordered and flattened/unflattened at the same time.
2. zIndex ordering: children in a certain order from the ShadowNode perspective may be stacked differently from a View perspective. We use the zIndex ordering for everything in the differ, and this prevents us from performing certain optimizations (see above: we cannot assume that children come after their parent in a list; they may come before, may be interwoven with children from other parents, etc).

# Perf Implications?

Practically, there should be very little negative overhead. There is some overhead in actually performing a flattening/unflattening operation, but... not much more than before. We don't use global maps, so the cost of flattening/unflattening is basically `O(number of nodes reparented)` - note that that's direct nodes reparented, *not* descendants.

tl;dr the perf hit should be similar to reordering, which is non-zero, but close to zero, and zero-cost for any diff operations on parts of the tree that don't involve flattening/unflattening. AFAICT this is very close to an ideal solution for that reason (but I wish it was simpler overall).

# In Summary?

I hope this works out and I think it could improve a number of things downstream: perf, LayoutAnimations, Bindings, certain crashes because of platform assumptions about mutations, etc.

Is it worth it? This new implementation is substantially harder to reason about, harder to read, and harder to understand. This is an important consideration. All I can say there is that I trust the test suite I've been using, but
the decreased readability is a big negative. Hopefully we can improve this in the future.

The rest is fiddly implementation details that I sincerely hope can be improved and simplified in the future.

# Followups?

The part that makes this algorithm the most expensive is that because of zIndex ordering, we cannot assume that children are linearized after their parents and so we rely more heavily on maps for the flattening/unflattening. Our TinyMap implementation should make these `find` operations fast enough unless trees' children are constantly being reordered, but it's still worth thinking of ways to make this even faster.

Changelog: [Internal]

Reviewed By: shergin, mdvacca

Differential Revision: D23259341

fbshipit-source-id: 35d9b90caf262d601a31996ea2cb37e329c61ffc
2020-08-24 13:09:12 -07:00

697 lines
34 KiB
C++

/*
* 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 <memory>
#include <react/renderer/components/root/RootComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/mounting/Differentiator.h>
#include <react/renderer/mounting/stubs.h>
#include "shadowTreeGeneration.h"
#include <glog/logging.h>
#include <gtest/gtest.h>
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<ViewProps const>(
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<SharedShadowNodeList>(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<ContextContainer>();
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<RootShadowNode>(
std::static_pointer_cast<RootShadowNode const>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{RootShadowNode::defaultSharedProps()},
rootFamily)));
// Applying size constraints.
emptyRootNode = emptyRootNode->clone(
LayoutConstraints{Size{512, 0},
Size{512, std::numeric_limits<Float>::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>(
SharedShadowNodeList{childB, childC, childD})},
family);
auto shadowNodeV2 = shadowNodeV1->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childA, childB, childC, childD})});
auto shadowNodeV3 = shadowNodeV2->clone(
ShadowNodeFragment{generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childB, childC, childD})});
auto shadowNodeV4 = shadowNodeV3->clone(
ShadowNodeFragment{generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childB, childD, childE})});
auto shadowNodeV5 = shadowNodeV4->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childB, childA, childE, childC})});
auto shadowNodeV6 = shadowNodeV5->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(SharedShadowNodeList{
childB, childA, childD, childF, childE, childC})});
auto shadowNodeV7 = shadowNodeV6->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(SharedShadowNodeList{childF,
childE,
childC,
childD,
childG,
childH,
childI,
childJ,
childK})});
// Injecting a tree into the root node.
auto rootNodeV1 = std::static_pointer_cast<RootShadowNode const>(
emptyRootNode->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV1})}));
auto rootNodeV2 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV1->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV2})}));
auto rootNodeV3 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV2->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV3})}));
auto rootNodeV4 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV3->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV4})}));
auto rootNodeV5 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV4->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV5})}));
auto rootNodeV6 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV5->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV6})}));
auto rootNodeV7 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV6->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV7})}));
// Layout
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV1{};
affectedLayoutableNodesV1.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV1)
->layoutIfNeeded(&affectedLayoutableNodesV1);
rootNodeV1->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV2{};
affectedLayoutableNodesV2.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV2)
->layoutIfNeeded(&affectedLayoutableNodesV2);
rootNodeV2->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV3{};
affectedLayoutableNodesV3.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV3)
->layoutIfNeeded(&affectedLayoutableNodesV3);
rootNodeV3->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV4{};
affectedLayoutableNodesV4.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV4)
->layoutIfNeeded(&affectedLayoutableNodesV4);
rootNodeV4->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV5{};
affectedLayoutableNodesV5.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV5)
->layoutIfNeeded(&affectedLayoutableNodesV5);
rootNodeV5->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV6{};
affectedLayoutableNodesV6.reserve(1024);
std::const_pointer_cast<RootShadowNode>(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);
}
}
}
/**
* 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<ContextContainer>();
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<RootShadowNode>(
std::static_pointer_cast<RootShadowNode const>(
rootComponentDescriptor.createShadowNode(
ShadowNodeFragment{RootShadowNode::defaultSharedProps()},
rootFamily)));
// Applying size constraints.
emptyRootNode = emptyRootNode->clone(
LayoutConstraints{Size{512, 0},
Size{512, std::numeric_limits<Float>::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);
auto reparentedViewA = makeNode(
viewComponentDescriptor,
1000,
SharedShadowNodeList{
childC->clone({}), childA->clone({}), childB->clone({})});
auto reparentedViewB = makeNode(
viewComponentDescriptor,
2000,
SharedShadowNodeList{
childF->clone({}), childE->clone({}), childD->clone({})});
// Root -> G* -> H -> I -> J -> A* [nodes with * are _not_ flattened]
auto shadowNodeV1 = viewComponentDescriptor.createShadowNode(
ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childG->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childH->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<
SharedShadowNodeList>(SharedShadowNodeList{
childI->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{
childJ->clone(ShadowNodeFragment{
generateDefaultProps(
viewComponentDescriptor),
std::make_shared<
SharedShadowNodeList>(
SharedShadowNodeList{
reparentedViewA->clone(
{})})})})})})})})})})},
family);
// Root -> G* -> H* -> I -> J -> A* [nodes with * are _not_ flattened]
auto shadowNodeV2 = shadowNodeV1->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childG->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childH->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childI->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<
SharedShadowNodeList>(SharedShadowNodeList{
childJ->clone(ShadowNodeFragment{
generateDefaultProps(
viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{
reparentedViewA->clone(
{})})})})})})})})})})});
// Root -> G* -> H -> I -> J -> A* [nodes with * are _not_ flattened]
auto shadowNodeV3 = shadowNodeV2->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childG->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childH->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childI->clone(ShadowNodeFragment{
generateDefaultProps(viewComponentDescriptor),
std::make_shared<
SharedShadowNodeList>(SharedShadowNodeList{
childJ->clone(ShadowNodeFragment{
generateDefaultProps(
viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{
reparentedViewA->clone(
{})})})})})})})})})})});
// 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),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childG->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childH->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childI->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<
SharedShadowNodeList>(SharedShadowNodeList{
childJ->clone(ShadowNodeFragment{
generateDefaultProps(
viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{
reparentedViewB->clone({}),
reparentedViewA->clone(
{})})})})})})})})})})});
// 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),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childG->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childH->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{childI->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(viewComponentDescriptor),
std::make_shared<
SharedShadowNodeList>(SharedShadowNodeList{
childJ->clone(ShadowNodeFragment{
nonFlattenedDefaultProps(
viewComponentDescriptor),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{
reparentedViewA->clone({}),
reparentedViewB->clone(
{})})})})})})})})})})});
// Injecting a tree into the root node.
auto rootNodeV1 = std::static_pointer_cast<RootShadowNode const>(
emptyRootNode->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV1})}));
auto rootNodeV2 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV1->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV2})}));
auto rootNodeV3 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV2->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV3})}));
auto rootNodeV4 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV3->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV4})}));
auto rootNodeV5 = std::static_pointer_cast<RootShadowNode const>(
rootNodeV4->ShadowNode::clone(
ShadowNodeFragment{ShadowNodeFragment::propsPlaceholder(),
std::make_shared<SharedShadowNodeList>(
SharedShadowNodeList{shadowNodeV5})}));
// Layout
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV1{};
affectedLayoutableNodesV1.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV1)
->layoutIfNeeded(&affectedLayoutableNodesV1);
rootNodeV1->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV2{};
affectedLayoutableNodesV2.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV2)
->layoutIfNeeded(&affectedLayoutableNodesV2);
rootNodeV2->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV3{};
affectedLayoutableNodesV3.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV3)
->layoutIfNeeded(&affectedLayoutableNodesV3);
rootNodeV3->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV4{};
affectedLayoutableNodesV4.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV4)
->layoutIfNeeded(&affectedLayoutableNodesV4);
rootNodeV4->sealRecursive();
std::vector<LayoutableShadowNode const *> affectedLayoutableNodesV5{};
affectedLayoutableNodesV5.reserve(1024);
std::const_pointer_cast<RootShadowNode>(rootNodeV5)
->layoutIfNeeded(&affectedLayoutableNodesV5);
rootNodeV5->sealRecursive();
// Calculating mutations.
auto mutations1 =
calculateShadowViewMutations(*rootNodeV1, *rootNodeV2, true);
EXPECT_EQ(mutations1.size(), 5);
EXPECT_EQ(mutations1[0].type, ShadowViewMutation::Update);
EXPECT_EQ(mutations1[0].oldChildShadowView.tag, 106);
EXPECT_EQ(mutations1[1].type, ShadowViewMutation::Remove);
EXPECT_EQ(mutations1[1].oldChildShadowView.tag, 1000);
EXPECT_EQ(mutations1[2].type, ShadowViewMutation::Create);
EXPECT_EQ(mutations1[2].newChildShadowView.tag, 107);
EXPECT_EQ(mutations1[3].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations1[3].newChildShadowView.tag, 107);
EXPECT_EQ(mutations1[4].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations1[4].newChildShadowView.tag, 1000);
auto mutations2 =
calculateShadowViewMutations(*rootNodeV2, *rootNodeV3, true);
EXPECT_EQ(mutations2.size(), 5);
EXPECT_EQ(mutations2[0].type, ShadowViewMutation::Update);
EXPECT_EQ(mutations2[0].oldChildShadowView.tag, 106);
EXPECT_EQ(mutations2[1].type, ShadowViewMutation::Remove);
EXPECT_EQ(mutations2[1].oldChildShadowView.tag, 1000);
EXPECT_EQ(mutations2[2].type, ShadowViewMutation::Remove);
EXPECT_EQ(mutations2[2].oldChildShadowView.tag, 107);
EXPECT_EQ(
mutations2[3].type,
ShadowViewMutation::Delete); // correct, 107 is removed from tree entirely
EXPECT_EQ(mutations2[3].oldChildShadowView.tag, 107);
EXPECT_EQ(mutations2[4].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations2[4].newChildShadowView.tag, 1000);
auto mutations3 =
calculateShadowViewMutations(*rootNodeV3, *rootNodeV4, true);
// 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, 106);
EXPECT_EQ(mutations3[1].type, ShadowViewMutation::Remove);
EXPECT_EQ(mutations3[1].oldChildShadowView.tag, 1000);
EXPECT_EQ(mutations3[2].type, ShadowViewMutation::Create);
EXPECT_EQ(mutations3[2].newChildShadowView.tag, 107);
EXPECT_EQ(mutations3[3].type, ShadowViewMutation::Create);
EXPECT_EQ(mutations3[3].newChildShadowView.tag, 2000);
EXPECT_EQ(mutations3[4].type, ShadowViewMutation::Create);
EXPECT_EQ(mutations3[4].newChildShadowView.tag, 108);
EXPECT_EQ(mutations3[5].type, ShadowViewMutation::Create);
EXPECT_EQ(mutations3[5].newChildShadowView.tag, 105);
EXPECT_EQ(mutations3[6].type, ShadowViewMutation::Create);
EXPECT_EQ(mutations3[6].newChildShadowView.tag, 104);
EXPECT_EQ(mutations3[7].type, ShadowViewMutation::Create);
EXPECT_EQ(mutations3[7].newChildShadowView.tag, 103);
EXPECT_EQ(mutations3[8].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations3[8].newChildShadowView.tag, 105);
EXPECT_EQ(mutations3[9].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations3[9].newChildShadowView.tag, 104);
EXPECT_EQ(mutations3[10].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations3[10].newChildShadowView.tag, 103);
EXPECT_EQ(mutations3[11].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations3[11].newChildShadowView.tag, 107);
EXPECT_EQ(mutations3[12].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations3[12].newChildShadowView.tag, 108);
EXPECT_EQ(mutations3[13].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations3[13].newChildShadowView.tag, 2000);
EXPECT_EQ(mutations3[14].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations3[14].newChildShadowView.tag, 1000);
auto mutations4 =
calculateShadowViewMutations(*rootNodeV4, *rootNodeV5, true);
EXPECT_EQ(mutations4.size(), 9);
EXPECT_EQ(mutations4[0].type, ShadowViewMutation::Update);
EXPECT_EQ(mutations4[0].oldChildShadowView.tag, 106);
EXPECT_EQ(mutations4[1].type, ShadowViewMutation::Update);
EXPECT_EQ(mutations4[1].oldChildShadowView.tag, 107);
EXPECT_EQ(mutations4[2].type, ShadowViewMutation::Update);
EXPECT_EQ(mutations4[2].oldChildShadowView.tag, 108);
EXPECT_EQ(mutations4[3].type, ShadowViewMutation::Remove);
EXPECT_EQ(mutations4[3].oldChildShadowView.tag, 1000);
EXPECT_EQ(mutations4[4].type, ShadowViewMutation::Remove);
EXPECT_EQ(mutations4[4].oldChildShadowView.tag, 2000);
EXPECT_EQ(mutations4[5].type, ShadowViewMutation::Create);
EXPECT_EQ(mutations4[5].newChildShadowView.tag, 109);
EXPECT_EQ(mutations4[6].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations4[6].newChildShadowView.tag, 109);
EXPECT_EQ(mutations4[7].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations4[7].newChildShadowView.tag, 1000);
EXPECT_EQ(mutations4[8].type, ShadowViewMutation::Insert);
EXPECT_EQ(mutations4[8].newChildShadowView.tag, 2000);
}
} // namespace react
} // namespace facebook