Move ReactNativeTestRule to OSS

Summary: And migrated ReactRootViewTestCase to use ReactNativeTestRule.

Reviewed By: mdvacca

Differential Revision: D9557362

fbshipit-source-id: 1469d0ad8c125b5ea729371d81956e61780c56cf
This commit is contained in:
Andrew Chen (Eng)
2018-08-29 12:24:09 -07:00
committed by Facebook Github Bot
parent bf8e1b4ffa
commit afe0843bee
7 changed files with 369 additions and 107 deletions
@@ -0,0 +1,35 @@
# BUILD FILE SYNTAX: SKYLARK
load(
"@xplat//ReactNative:DEFS.bzl",
"react_native_dep",
"react_native_integration_tests_target",
"react_native_target",
"rn_android_library",
)
rn_android_library(
name = "rule",
srcs = glob(["*.java"]),
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("java/com/facebook/testing/instrumentation:instrumentation"),
react_native_dep("java/com/facebook/testing/instrumentation/base:base"),
react_native_dep("third-party/java/espresso:espresso"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_dep("third-party/java/junit:junit"),
react_native_dep("third-party/java/testing-support-lib:testing-support-lib"),
react_native_dep("third-party/android/support/v4:lib-support-v4"),
react_native_dep("third-party/android/support/v7/appcompat-orig:appcompat"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_integration_tests_target("java/com/facebook/react/testing:testing"),
react_native_integration_tests_target("java/com/facebook/react/testing/idledetection:idledetection"),
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/modules/core:core"),
react_native_target("java/com/facebook/react/shell:shell"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
],
)
@@ -0,0 +1,177 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.testing.rule;
import android.app.Activity;
import android.os.Build;
import android.support.test.rule.ActivityTestRule;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.testing.ReactTestHelper;
import com.facebook.react.testing.idledetection.ReactBridgeIdleSignaler;
import com.facebook.react.testing.idledetection.ReactIdleDetectionUtil;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.UIImplementation;
import com.facebook.react.uimanager.UIManagerModule;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/** A test rule to simplify React Native rendering tests. */
public class ReactNativeTestRule implements TestRule {
// we need a bigger timeout for CI builds because they run on a slow emulator
private static final long IDLE_TIMEOUT_MS = 120000;
@Rule public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
private final String mBundleName;
private ReactPackage mReactPackage;
private ReactInstanceManager mReactInstanceManager;
private ReactBridgeIdleSignaler mBridgeIdleSignaler;
private ReactRootView mView;
private CountDownLatch mLatch;
public ReactNativeTestRule(String bundleName) {
this(bundleName, null);
}
public ReactNativeTestRule(String bundleName, ReactPackage reactPackage) {
mBundleName = bundleName;
mReactPackage = reactPackage;
}
@Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
setUp();
base.evaluate();
tearDown();
}
};
}
@SuppressWarnings("deprecation")
private void setUp() {
final Activity activity = mActivityRule.launchActivity(null);
mView = new ReactRootView(activity);
activity.runOnUiThread(
new Runnable() {
@Override
public void run() {
mBridgeIdleSignaler = new ReactBridgeIdleSignaler();
mReactInstanceManager =
ReactTestHelper.getReactTestFactory()
.getReactInstanceManagerBuilder()
.setApplication(activity.getApplication())
.setBundleAssetName(mBundleName)
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
.setBridgeIdleDebugListener(mBridgeIdleSignaler)
.addPackage(mReactPackage != null ? mReactPackage : new MainReactPackage())
.build();
mReactInstanceManager.onHostResume(activity);
// This threading garbage will be replaced by Surface
final AtomicBoolean isLayoutUpdated = new AtomicBoolean(false);
mReactInstanceManager.addReactInstanceEventListener(
new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
final UIManagerModule uiManagerModule =
reactContext.getCatalystInstance().getNativeModule(UIManagerModule.class);
uiManagerModule
.getUIImplementation()
.setLayoutUpdateListener(
new UIImplementation.LayoutUpdateListener() {
@Override
public void onLayoutUpdated(ReactShadowNode reactShadowNode) {
uiManagerModule.getUIImplementation().removeLayoutUpdateListener();
isLayoutUpdated.set(true);
}
});
}
});
mView
.getViewTreeObserver()
.addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (isLayoutUpdated.get()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
mView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
mLatch.countDown();
}
}
});
}
});
}
private void tearDown() {
final ReactRootView view = mView;
final ReactInstanceManager reactInstanceManager = mReactInstanceManager;
mView = null;
mReactInstanceManager = null;
mActivityRule
.getActivity()
.runOnUiThread(
new Runnable() {
@Override
public void run() {
view.unmountReactApplication();
reactInstanceManager.destroy();
}
});
}
/** Renders the react component and waits until the layout has completed before returning */
public void render(final String componentName) {
mLatch = new CountDownLatch(1);
final Activity activity = mActivityRule.getActivity();
activity.runOnUiThread(
new Runnable() {
@Override
public void run() {
ReactRootView view = getView();
view.startReactApplication(mReactInstanceManager, componentName);
activity.setContentView(view);
}
});
int timeoutSec = 10;
try {
mLatch.await(timeoutSec, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(
"Failed to render " + componentName + " after " + timeoutSec + " seconds");
}
}
public void waitForIdleSync() {
ReactIdleDetectionUtil.waitForBridgeAndUIIdle(
mBridgeIdleSignaler,
mReactInstanceManager.getCurrentReactContext(),
IDLE_TIMEOUT_MS);
}
/** Returns the react view */
public ReactRootView getView() {
return mView;
}
}