Move Fabric tests to OSS
Reviewed By: mdvacca Differential Revision: D7654802 fbshipit-source-id: a30c8cf062636243066a1fc73da0711442a29c76
This commit is contained in:
parent
4469952c9a
commit
c10c6dbf7c
|
@ -0,0 +1,41 @@
|
|||
load("//ReactNative:DEFS.bzl", "rn_robolectric_test", "YOGA_TARGET", "react_native_dep", "react_native_target", "react_native_tests_target")
|
||||
|
||||
rn_robolectric_test(
|
||||
name = "fabric",
|
||||
srcs = glob(["**/*.java"]),
|
||||
contacts = ["oncall+fbandroid_sheriff@xmail.facebook.com"],
|
||||
resources = glob([
|
||||
"**/*.txt",
|
||||
"**/*.json",
|
||||
]),
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
deps = [
|
||||
"xplat//yoga/java:java",
|
||||
react_native_dep("java/com/facebook/common/executors:executors"),
|
||||
react_native_dep("java/com/facebook/testing/robolectric/v3:v3"),
|
||||
react_native_dep("third-party/java/assertj:assertj-core"),
|
||||
react_native_dep("third-party/java/fest:fest"),
|
||||
react_native_dep("third-party/java/fest:fest_android"),
|
||||
react_native_dep("third-party/java/guava:guava"),
|
||||
react_native_dep("third-party/java/jackson:jackson"),
|
||||
react_native_dep("third-party/java/jsr-305:jsr-305"),
|
||||
react_native_dep("third-party/java/jsr-330:jsr-330"),
|
||||
react_native_dep("third-party/java/junit:junit"),
|
||||
react_native_dep("third-party/java/mockito:mockito"),
|
||||
react_native_dep("third-party/java/robolectric3/robolectric:robolectric"),
|
||||
react_native_dep("libraries/fbcore/src/test/java/com/facebook/powermock:powermock"),
|
||||
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
|
||||
react_native_dep("third-party/android/support/v4:lib-support-v4"),
|
||||
react_native_target("java/com/facebook/react:react"),
|
||||
react_native_target("java/com/facebook/react/bridge:bridge"),
|
||||
react_native_target("java/com/facebook/react/common:common"),
|
||||
react_native_target("java/com/facebook/react/fabric:fabric"),
|
||||
react_native_target("java/com/facebook/react/modules/core:core"),
|
||||
react_native_target("java/com/facebook/react/uimanager:uimanager"),
|
||||
react_native_target("java/com/facebook/react/views/text:text"),
|
||||
react_native_target("java/com/facebook/react/views/view:view"),
|
||||
react_native_tests_target("java/com/facebook/react/bridge:testhelpers"),
|
||||
],
|
||||
)
|
|
@ -0,0 +1,224 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
package com.facebook.react.fabric;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.powermock.api.mockito.PowerMockito.mockStatic;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.common.ClearableSynchronizedPool;
|
||||
import com.facebook.react.fabric.FabricReconciler;
|
||||
import com.facebook.react.modules.core.ReactChoreographer;
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.ReactShadowNodeImpl;
|
||||
import com.facebook.react.uimanager.ReactYogaConfigProvider;
|
||||
import com.facebook.react.uimanager.UIViewOperationQueue;
|
||||
import com.facebook.react.uimanager.ViewAtIndex;
|
||||
import com.facebook.react.uimanager.YogaNodePool;
|
||||
import com.facebook.testing.robolectric.v3.WithTestDefaultsRunner;
|
||||
import com.facebook.yoga.YogaConfig;
|
||||
import com.facebook.yoga.YogaNode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
/** Tests {@link FabricReconciler} */
|
||||
@PrepareForTest({
|
||||
ReactChoreographer.class,
|
||||
ReactYogaConfigProvider.class,
|
||||
YogaNodePool.class,
|
||||
})
|
||||
@RunWith(WithTestDefaultsRunner.class)
|
||||
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
|
||||
public class FabricReconcilerTest {
|
||||
|
||||
private FabricReconciler mFabricReconciler;
|
||||
private MockUIViewOperationQueue mMockUIViewOperationQueue;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ReactApplicationContext reactContext =
|
||||
new ReactApplicationContext(RuntimeEnvironment.application);
|
||||
mMockUIViewOperationQueue = new MockUIViewOperationQueue(reactContext);
|
||||
mFabricReconciler = new FabricReconciler(mMockUIViewOperationQueue);
|
||||
|
||||
setupHacks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleHierarchy() {
|
||||
ReactShadowNode parent = createNode(0);
|
||||
ReactShadowNode child1 = createNode(1);
|
||||
ReactShadowNode child2 = createNode(2);
|
||||
addChildren(parent, child1, child2);
|
||||
|
||||
ReactShadowNode parentCloned = createNode(0);
|
||||
ReactShadowNode child3 = createNode(3);
|
||||
addChildren(parentCloned, child3, child2);
|
||||
|
||||
mFabricReconciler.manageChildren(parent, parentCloned);
|
||||
|
||||
List<ManageChildrenOperation> expectedOperations = new ArrayList<>();
|
||||
expectedOperations.add(
|
||||
new ManageChildrenOperation(
|
||||
0,
|
||||
new int[] {0, 1},
|
||||
new ViewAtIndex[] {new ViewAtIndex(3, 0), new ViewAtIndex(2, 1)},
|
||||
new int[] {1}));
|
||||
assertThat(mMockUIViewOperationQueue.getOperations()).isEqualTo(expectedOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVirtualNodes() {
|
||||
ReactShadowNode parent = createNode(0);
|
||||
ReactShadowNode child1 = createVirtualNode(1);
|
||||
ReactShadowNode child2 = createVirtualNode(2);
|
||||
ReactShadowNode child3 = createVirtualNode(3);
|
||||
addChildren(parent, child1, child2, child3);
|
||||
|
||||
ReactShadowNode parentCloned = createNode(0);
|
||||
ReactShadowNode child4 = createVirtualNode(4);
|
||||
addChildren(parentCloned, child1, child4, child3);
|
||||
|
||||
mFabricReconciler.manageChildren(parent, parentCloned);
|
||||
|
||||
List<ManageChildrenOperation> expectedOperations = new ArrayList<>();
|
||||
assertThat(mMockUIViewOperationQueue.getOperations()).isEqualTo(expectedOperations);
|
||||
}
|
||||
|
||||
private static ReactShadowNode createNode(int tag) {
|
||||
return createNode(tag, false);
|
||||
}
|
||||
|
||||
private static ReactShadowNode createVirtualNode(int tag) {
|
||||
return createNode(tag, true);
|
||||
}
|
||||
|
||||
private static ReactShadowNode createNode(int tag, boolean virtual) {
|
||||
ReactShadowNode node;
|
||||
if (virtual) {
|
||||
node = new VirtualReactShadowNode();
|
||||
} else {
|
||||
node = new ReactShadowNodeImpl();
|
||||
}
|
||||
node.setReactTag(tag);
|
||||
return node;
|
||||
}
|
||||
|
||||
private static class VirtualReactShadowNode extends ReactShadowNodeImpl {
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addChildren(ReactShadowNode parent, ReactShadowNode... children) {
|
||||
for (ReactShadowNode child : children) {
|
||||
parent.addChildAt(child, parent.getChildCount());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ManageChildrenOperation {
|
||||
private int mTag;
|
||||
private int[] mIndicesToRemove;
|
||||
private ViewAtIndex[] mViewsToAdd;
|
||||
private int[] mTagsToRemove;
|
||||
|
||||
private ManageChildrenOperation(
|
||||
int tag, int[] indicesToRemove, ViewAtIndex[] viewsToAdd, int[] tagsToRemove) {
|
||||
mTag = tag;
|
||||
mIndicesToRemove = indicesToRemove;
|
||||
mViewsToAdd = viewsToAdd;
|
||||
mTagsToRemove = tagsToRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
ManageChildrenOperation op = (ManageChildrenOperation) obj;
|
||||
return mTag == op.mTag
|
||||
&& Arrays.equals(mIndicesToRemove, op.mIndicesToRemove)
|
||||
&& Arrays.equals(mViewsToAdd, op.mViewsToAdd)
|
||||
&& Arrays.equals(mTagsToRemove, op.mTagsToRemove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.deepHashCode(new Object[] {mTag, mIndicesToRemove, mViewsToAdd, mTagsToRemove});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ManageChildrenOperation \n\tindicesToRemove: "
|
||||
+ Arrays.toString(mIndicesToRemove)
|
||||
+ "\n\tviewsToAdd: "
|
||||
+ Arrays.toString(mViewsToAdd)
|
||||
+ "\n\ttagsToRemove: "
|
||||
+ Arrays.toString(mTagsToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MockUIViewOperationQueue extends UIViewOperationQueue {
|
||||
|
||||
private List<ManageChildrenOperation> mOperations;
|
||||
|
||||
private MockUIViewOperationQueue(ReactApplicationContext context) {
|
||||
super(context, mock(NativeViewHierarchyManager.class), 0);
|
||||
mOperations = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enqueueManageChildren(
|
||||
int reactTag, int[] indicesToRemove, ViewAtIndex[] viewsToAdd, int[] tagsToDelete) {
|
||||
mOperations.add(
|
||||
new ManageChildrenOperation(reactTag, indicesToRemove, viewsToAdd, tagsToDelete));
|
||||
}
|
||||
|
||||
public List<ManageChildrenOperation> getOperations() {
|
||||
return Collections.unmodifiableList(mOperations);
|
||||
}
|
||||
}
|
||||
|
||||
/** Hacks to get tests to start working end to end */
|
||||
private void setupHacks() {
|
||||
// Hack around Yoga by mocking it out until the UnsatisfiedLinkErrors are fixed t14964130
|
||||
mockStatic(YogaNodePool.class, ReactYogaConfigProvider.class);
|
||||
PowerMockito.when(YogaNodePool.get())
|
||||
.thenAnswer(
|
||||
new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Exception {
|
||||
ClearableSynchronizedPool<YogaNode> yogaPool =
|
||||
mock(ClearableSynchronizedPool.class);
|
||||
YogaNode yogaNode = mock(YogaNode.class);
|
||||
when(yogaNode.clone()).thenReturn(mock(YogaNode.class));
|
||||
when(yogaNode.isMeasureDefined()).thenReturn(true);
|
||||
when(yogaPool.acquire()).thenReturn(yogaNode);
|
||||
return yogaPool;
|
||||
}
|
||||
});
|
||||
PowerMockito.when(ReactYogaConfigProvider.get())
|
||||
.thenAnswer(
|
||||
new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) {
|
||||
return mock(YogaConfig.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,387 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
package com.facebook.react.fabric;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.bridge.CatalystInstance;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactTestHelper;
|
||||
import com.facebook.react.bridge.ReadableNativeMap;
|
||||
import com.facebook.react.common.ClearableSynchronizedPool;
|
||||
import com.facebook.react.fabric.FabricUIManager;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
import com.facebook.react.uimanager.ReactShadowNodeImpl;
|
||||
import com.facebook.react.uimanager.ReactYogaConfigProvider;
|
||||
import com.facebook.react.uimanager.Spacing;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.uimanager.ViewManagerRegistry;
|
||||
import com.facebook.react.uimanager.YogaNodePool;
|
||||
import com.facebook.react.views.text.ReactRawTextManager;
|
||||
import com.facebook.react.views.text.ReactRawTextShadowNode;
|
||||
import com.facebook.react.views.text.ReactTextViewManager;
|
||||
import com.facebook.react.views.view.ReactViewManager;
|
||||
import com.facebook.testing.robolectric.v3.WithTestDefaultsRunner;
|
||||
import com.facebook.yoga.YogaConfig;
|
||||
import com.facebook.yoga.YogaMeasureFunction;
|
||||
import com.facebook.yoga.YogaNode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
/** Tests {@link FabricUIManager} */
|
||||
@PrepareForTest({YogaNodePool.class, ReactYogaConfigProvider.class})
|
||||
@RunWith(WithTestDefaultsRunner.class)
|
||||
public class FabricUIManagerTest {
|
||||
|
||||
private FabricUIManager mFabricUIManager;
|
||||
private ThemedReactContext mThemedReactContext;
|
||||
private int mNextReactTag;
|
||||
private MockYogaNodePool mMockYogaNodePool;
|
||||
|
||||
private YogaMeasureFunction mLastYogaMeasureFunction;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mNextReactTag = 2;
|
||||
mThemedReactContext = mock(ThemedReactContext.class);
|
||||
CatalystInstance catalystInstance = ReactTestHelper.createMockCatalystInstance();
|
||||
ReactApplicationContext reactContext =
|
||||
new ReactApplicationContext(RuntimeEnvironment.application);
|
||||
reactContext.initializeWithInstance(catalystInstance);
|
||||
List<ViewManager> viewManagers =
|
||||
Arrays.<ViewManager>asList(
|
||||
new ReactViewManager(), new ReactTextViewManager(), new ReactRawTextManager());
|
||||
ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagers);
|
||||
|
||||
mFabricUIManager = new FabricUIManager(reactContext, viewManagerRegistry);
|
||||
|
||||
// Hack around Yoga until the UnsatisfiedLinkErrors are fixed t14964130
|
||||
PowerMockito.mockStatic(YogaNodePool.class, ReactYogaConfigProvider.class);
|
||||
mMockYogaNodePool = new MockYogaNodePool();
|
||||
PowerMockito.when(YogaNodePool.get()).thenReturn(mMockYogaNodePool);
|
||||
PowerMockito.when(ReactYogaConfigProvider.get())
|
||||
.thenAnswer(
|
||||
new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) {
|
||||
return mock(YogaConfig.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNode() {
|
||||
ReactRootView rootView =
|
||||
new ReactRootView(RuntimeEnvironment.application.getApplicationContext());
|
||||
int rootTag = mFabricUIManager.addRootView(rootView);
|
||||
int reactTag = mNextReactTag++;
|
||||
String viewClass = ReactViewManager.REACT_CLASS;
|
||||
ReactShadowNode node = mFabricUIManager.createNode(reactTag, viewClass, rootTag, null);
|
||||
|
||||
assertThat(reactTag).isEqualTo(node.getReactTag());
|
||||
assertThat(viewClass).isEqualTo(node.getViewClass());
|
||||
assertThat(rootTag).isEqualTo(rootTag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMultpleRootViews() {
|
||||
createAndRenderRootView();
|
||||
createAndRenderRootView();
|
||||
}
|
||||
|
||||
private int createAndRenderRootView() {
|
||||
ReactRootView rootView =
|
||||
new ReactRootView(RuntimeEnvironment.application.getApplicationContext());
|
||||
int rootTag = mFabricUIManager.addRootView(rootView);
|
||||
int reactTag = mNextReactTag++;
|
||||
String viewClass = ReactViewManager.REACT_CLASS;
|
||||
ReactShadowNode node = mFabricUIManager.createNode(reactTag, viewClass, rootTag, null);
|
||||
|
||||
List<ReactShadowNode> childSet = mFabricUIManager.createChildSet(rootTag);
|
||||
mFabricUIManager.appendChildToSet(childSet, node);
|
||||
mFabricUIManager.completeRoot(rootTag, childSet);
|
||||
|
||||
return rootTag;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloneNode() {
|
||||
ReactShadowNode node = createViewNode();
|
||||
ReactShadowNode child = createViewNode();
|
||||
node.addChildAt(child, 0);
|
||||
|
||||
ReactShadowNode clonedNode = mFabricUIManager.cloneNode(node);
|
||||
|
||||
assertThat(clonedNode).isNotSameAs(node);
|
||||
assertThat(clonedNode.getOriginalReactShadowNode()).isSameAs(node);
|
||||
assertSameFields(clonedNode, node);
|
||||
assertSameChildren(clonedNode, node);
|
||||
assertThat(clonedNode.getChildAt(0)).isEqualTo(child);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloneVirtualNode() {
|
||||
ReactRawTextShadowNode node = new ReactRawTextShadowNode();
|
||||
node.setText("test");
|
||||
assertThat(node.isVirtual()).isTrue();
|
||||
|
||||
ReactRawTextShadowNode clonedNode = (ReactRawTextShadowNode) node.mutableCopy();
|
||||
|
||||
assertThat(clonedNode.getText()).isEqualTo("test");
|
||||
assertThat(clonedNode).isNotEqualTo(node);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloneNodeWithNewChildren() {
|
||||
ReactShadowNode node = createViewNode();
|
||||
ReactShadowNode child = createViewNode();
|
||||
node.addChildAt(child, 0);
|
||||
|
||||
ReactShadowNode clonedNode = mFabricUIManager.cloneNodeWithNewChildren(node);
|
||||
|
||||
assertThat(clonedNode.getChildCount()).isZero();
|
||||
assertSameFields(clonedNode, node);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloneNodeWithNewProps() {
|
||||
ReactShadowNode node = createViewNode();
|
||||
ReadableNativeMap props = null; // TODO(ayc): Figure out how to create a Native map from tests.
|
||||
|
||||
ReactShadowNode clonedNode = mFabricUIManager.cloneNodeWithNewProps(node, props);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloneNodeWithNewChildrenAndProps() {
|
||||
ReactShadowNode node = createViewNode();
|
||||
ReadableNativeMap props = null;
|
||||
|
||||
ReactShadowNode clonedNode = mFabricUIManager.cloneNodeWithNewChildrenAndProps(node, props);
|
||||
|
||||
assertThat(clonedNode.getChildCount()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppendChild() {
|
||||
ReactShadowNode node = createViewNode();
|
||||
ReactShadowNode child = createViewNode();
|
||||
|
||||
mFabricUIManager.appendChild(node, child);
|
||||
|
||||
assertThat(node.getChildCount()).isEqualTo(1);
|
||||
assertThat(node.getChildAt(0)).isEqualTo(child);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateChildSet() {
|
||||
List<ReactShadowNode> childSet = mFabricUIManager.createChildSet(0);
|
||||
|
||||
assertThat(childSet).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppendChildToSet() {
|
||||
ReactShadowNode node = createViewNode();
|
||||
List<ReactShadowNode> childSet = mFabricUIManager.createChildSet(0);
|
||||
|
||||
mFabricUIManager.appendChildToSet(childSet, node);
|
||||
|
||||
assertThat(childSet).hasSize(1);
|
||||
assertThat(childSet).contains(node);
|
||||
}
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void testCompleteRootBeforeAddRoot() {
|
||||
mFabricUIManager.completeRoot(0, new ArrayList<ReactShadowNode>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteRoot() {
|
||||
ReactRootView rootView =
|
||||
new ReactRootView(RuntimeEnvironment.application.getApplicationContext());
|
||||
int rootTag = mFabricUIManager.addRootView(rootView);
|
||||
List<ReactShadowNode> children = mFabricUIManager.createChildSet(rootTag);
|
||||
|
||||
mFabricUIManager.completeRoot(rootTag, children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that cloned text nodes will reassign their yoga nodes' measure functions.
|
||||
*
|
||||
* <p>TODO(T26729515): Currently this tests the wrong implementation. It assumes that yoga nodes
|
||||
* are reused across clones and simply checks the most recently assigned measure functions of the
|
||||
* shared yoga node. When yoga node cloning is implemented, this needs to be changed to mock each
|
||||
* cloned shadow nodes' yoga nodes and make the same assertions on each of their measure
|
||||
* functions.
|
||||
*/
|
||||
@Test
|
||||
public void testTextMutableClone() {
|
||||
ReactRootView rootView =
|
||||
new ReactRootView(RuntimeEnvironment.application.getApplicationContext());
|
||||
int rootTag = mFabricUIManager.addRootView(rootView);
|
||||
|
||||
final YogaNode yogaNode = mock(YogaNode.class);
|
||||
|
||||
doAnswer(
|
||||
new Answer() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) {
|
||||
when(yogaNode.isMeasureDefined()).thenReturn(true);
|
||||
when(yogaNode.clone()).thenReturn(yogaNode);
|
||||
when(yogaNode.cloneWithNewChildren()).thenReturn(yogaNode);
|
||||
mLastYogaMeasureFunction = (YogaMeasureFunction) invocation.getArguments()[0];
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.when(yogaNode)
|
||||
.setMeasureFunction(any(YogaMeasureFunction.class));
|
||||
|
||||
mMockYogaNodePool.add(yogaNode);
|
||||
|
||||
ReactShadowNode text =
|
||||
mFabricUIManager.createNode(0, ReactTextViewManager.REACT_CLASS, rootTag, null);
|
||||
YogaMeasureFunction textMeasureFunction = mLastYogaMeasureFunction;
|
||||
assertThat(text.isMeasureDefined()).isTrue();
|
||||
|
||||
ReactShadowNode textCopy = text.mutableCopy();
|
||||
YogaMeasureFunction textCopyMeasureFunction = mLastYogaMeasureFunction;
|
||||
assertThat(textCopy.isMeasureDefined()).isTrue();
|
||||
assertThat(textCopyMeasureFunction).isNotSameAs(textMeasureFunction);
|
||||
|
||||
ReactShadowNode textCopyWithNewChildren = text.mutableCopyWithNewChildren();
|
||||
YogaMeasureFunction textCopyWithNewChildrenMeasureFunction = mLastYogaMeasureFunction;
|
||||
assertThat(textCopyWithNewChildren.isMeasureDefined()).isTrue();
|
||||
assertThat(textCopyWithNewChildrenMeasureFunction).isNotSameAs(textMeasureFunction);
|
||||
assertThat(textCopyWithNewChildrenMeasureFunction).isNotSameAs(textCopyMeasureFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the reconciliation phase will always set the originalNode field of every node in
|
||||
* the tree to null once completeRoot has finished to prevent memory leaks.
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveOriginalNodeReferences() {
|
||||
ReactRootView rootView =
|
||||
new ReactRootView(RuntimeEnvironment.application.getApplicationContext());
|
||||
int rootTag = mFabricUIManager.addRootView(rootView);
|
||||
String viewClass = ReactViewManager.REACT_CLASS;
|
||||
|
||||
ReactShadowNode aa = mFabricUIManager.createNode(2, viewClass, rootTag, null);
|
||||
ReactShadowNode a = mFabricUIManager.createNode(3, viewClass, rootTag, null);
|
||||
mFabricUIManager.appendChild(a, aa);
|
||||
ReactShadowNode bb = mFabricUIManager.createNode(4, viewClass, rootTag, null);
|
||||
ReactShadowNode b = mFabricUIManager.createNode(5, viewClass, rootTag, null);
|
||||
mFabricUIManager.appendChild(b, bb);
|
||||
ReactShadowNode container = mFabricUIManager.createNode(6, viewClass, rootTag, null);
|
||||
mFabricUIManager.appendChild(container, a);
|
||||
mFabricUIManager.appendChild(container, b);
|
||||
List<ReactShadowNode> childSet = mFabricUIManager.createChildSet(rootTag);
|
||||
mFabricUIManager.appendChildToSet(childSet, container);
|
||||
mFabricUIManager.completeRoot(rootTag, childSet);
|
||||
|
||||
ReactShadowNode aaClone = mFabricUIManager.cloneNodeWithNewProps(aa, null);
|
||||
ReactShadowNode aClone = mFabricUIManager.cloneNodeWithNewChildren(a);
|
||||
mFabricUIManager.appendChild(aClone, aaClone);
|
||||
ReactShadowNode containerClone = mFabricUIManager.cloneNodeWithNewChildren(container);
|
||||
mFabricUIManager.appendChild(containerClone, b);
|
||||
mFabricUIManager.appendChild(containerClone, aClone);
|
||||
List<ReactShadowNode> childSet2 = mFabricUIManager.createChildSet(rootTag);
|
||||
mFabricUIManager.appendChildToSet(childSet2, containerClone);
|
||||
mFabricUIManager.completeRoot(rootTag, childSet2);
|
||||
|
||||
ReactShadowNode[] nodes =
|
||||
new ReactShadowNode[] {aa, a, bb, b, container, aaClone, aClone, containerClone};
|
||||
|
||||
for (ReactShadowNode node : nodes) {
|
||||
assertThat(node.getOriginalReactShadowNode()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSameChildren(ReactShadowNode node1, ReactShadowNode node2) {
|
||||
assertThat(node1.getChildCount()).isEqualTo(node2.getChildCount());
|
||||
for (int i = 0; i < node1.getChildCount(); i++) {
|
||||
assertThat(node1.getChildAt(i)).isEqualTo(node2.getChildAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSameFields(ReactShadowNode node1, ReactShadowNode node2) {
|
||||
assertThat(node1.getReactTag()).isEqualTo(node2.getReactTag());
|
||||
assertThat(node1.getViewClass()).isEqualTo(node2.getViewClass());
|
||||
assertThat(node2.getParent()).isNull();
|
||||
assertThat(node1.getThemedContext()).isEqualTo(node2.getThemedContext());
|
||||
assertThat(node1.isVirtual()).isEqualTo(node2.isVirtual());
|
||||
assertThat(node1.getLayoutDirection()).isEqualTo(node2.getLayoutDirection());
|
||||
assertThat(node1.getLayoutHeight()).isEqualTo(node2.getLayoutHeight());
|
||||
assertThat(node1.getLayoutWidth()).isEqualTo(node2.getLayoutWidth());
|
||||
assertThat(node1.getLayoutX()).isEqualTo(node2.getLayoutX());
|
||||
assertThat(node1.getLayoutY()).isEqualTo(node2.getLayoutY());
|
||||
for (int spacingType = Spacing.LEFT; spacingType <= Spacing.ALL; spacingType++) {
|
||||
assertThat(node1.getPadding(spacingType)).isEqualTo(node2.getPadding(spacingType));
|
||||
assertThat(node1.getStylePadding(spacingType)).isEqualTo(node2.getStylePadding(spacingType));
|
||||
}
|
||||
assertThat(node1.getStyleWidth()).isEqualTo(node2.getStyleWidth());
|
||||
assertThat(node1.getStyleHeight()).isEqualTo(node2.getStyleHeight());
|
||||
}
|
||||
|
||||
private ReactShadowNode createViewNode() {
|
||||
ReactShadowNode node = new ReactShadowNodeImpl();
|
||||
node.setViewClassName(ReactViewManager.REACT_CLASS);
|
||||
node.setThemedContext(mThemedReactContext);
|
||||
return node;
|
||||
}
|
||||
|
||||
private static class MockYogaNodePool extends ClearableSynchronizedPool<YogaNode> {
|
||||
|
||||
private List<YogaNode> mMockYogaNodes;
|
||||
|
||||
public MockYogaNodePool() {
|
||||
super(1024);
|
||||
mMockYogaNodes = new LinkedList<>();
|
||||
}
|
||||
|
||||
public void add(YogaNode... nodes) {
|
||||
Collections.addAll(mMockYogaNodes, nodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized YogaNode acquire() {
|
||||
if (!mMockYogaNodes.isEmpty()) {
|
||||
return mMockYogaNodes.remove(0);
|
||||
}
|
||||
return createMockYogaNode();
|
||||
}
|
||||
|
||||
private static YogaNode createMockYogaNode() {
|
||||
final YogaNode yogaNode = mock(YogaNode.class);
|
||||
when(yogaNode.clone()).thenReturn(yogaNode);
|
||||
when(yogaNode.cloneWithNewChildren()).thenReturn(yogaNode);
|
||||
doAnswer(
|
||||
new Answer() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) {
|
||||
when(yogaNode.isMeasureDefined()).thenReturn(true);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.when(yogaNode)
|
||||
.setMeasureFunction(any(YogaMeasureFunction.class));
|
||||
return yogaNode;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue