-
Notifications
You must be signed in to change notification settings - Fork 24.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reviewed By: mdvacca Differential Revision: D7654802 fbshipit-source-id: a30c8cf062636243066a1fc73da0711442a29c76
- Loading branch information
1 parent
4469952
commit c10c6db
Showing
3 changed files
with
652 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"), | ||
], | ||
) |
224 changes: 224 additions & 0 deletions
224
ReactAndroid/src/test/java/com/facebook/react/fabric/FabricReconcilerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.