mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-02 22:41:18 +08:00
Summary: Closes https://github.com/facebook/react-native/pull/8151 Differential Revision: D3444091 fbshipit-source-id: a8a43a56332c66b0af84695aa54fb1e4c5d659f6
210 lines
6.4 KiB
Java
210 lines
6.4 KiB
Java
/**
|
|
* Copyright (c) 2014-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
package com.facebook.react.testing;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.Semaphore;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import android.app.Application;
|
|
import android.support.test.InstrumentationRegistry;
|
|
import android.test.AndroidTestCase;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
|
|
import com.facebook.infer.annotation.Assertions;
|
|
import com.facebook.react.bridge.BaseJavaModule;
|
|
import com.facebook.react.bridge.ReactContext;
|
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
import com.facebook.react.bridge.CatalystInstance;
|
|
import com.facebook.react.bridge.LifecycleEventListener;
|
|
import com.facebook.react.bridge.SoftAssertions;
|
|
import com.facebook.react.bridge.UiThreadUtil;
|
|
import com.facebook.react.common.ApplicationHolder;
|
|
import com.facebook.react.common.futures.SimpleSettableFuture;
|
|
import com.facebook.react.devsupport.DevSupportManager;
|
|
import com.facebook.react.modules.core.Timing;
|
|
|
|
import com.facebook.soloader.SoLoader;
|
|
|
|
import static org.mockito.Mockito.mock;
|
|
|
|
/**
|
|
* Use this class for writing integration tests of catalyst. This class will run all JNI call
|
|
* within separate android looper, thus you don't need to care about starting your own looper.
|
|
*
|
|
* Keep in mind that all JS remote method calls and script load calls are asynchronous and you
|
|
* should not expect them to return results immediately.
|
|
*
|
|
* In order to write catalyst integration:
|
|
* 1) Make {@link ReactIntegrationTestCase} a base class of your test case
|
|
* 2) Use {@link ReactTestHelper#catalystInstanceBuilder()}
|
|
* instead of {@link com.facebook.react.bridge.CatalystInstanceImpl.Builder} to build catalyst
|
|
* instance for testing purposes
|
|
*
|
|
*/
|
|
public abstract class ReactIntegrationTestCase extends AndroidTestCase {
|
|
|
|
private static final long IDLE_TIMEOUT_MS = 15000;
|
|
|
|
private @Nullable CatalystInstance mInstance;
|
|
private @Nullable ReactBridgeIdleSignaler mBridgeIdleSignaler;
|
|
private @Nullable ReactApplicationContext mReactContext;
|
|
|
|
@Override
|
|
public ReactApplicationContext getContext() {
|
|
if (mReactContext == null) {
|
|
mReactContext = new ReactApplicationContext(super.getContext());
|
|
Assertions.assertNotNull(mReactContext);
|
|
}
|
|
|
|
return mReactContext;
|
|
}
|
|
|
|
public void shutDownContext() {
|
|
if (mInstance != null) {
|
|
final ReactContext contextToDestroy = mReactContext;
|
|
mReactContext = null;
|
|
mInstance = null;
|
|
|
|
final SimpleSettableFuture<Void> semaphore = new SimpleSettableFuture<>();
|
|
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (contextToDestroy != null) {
|
|
contextToDestroy.destroy();
|
|
}
|
|
semaphore.set(null);
|
|
}
|
|
});
|
|
semaphore.getOrThrow();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method isn't safe since it doesn't factor in layout-only view removal. Use
|
|
* {@link #getViewByTestId} instead.
|
|
*/
|
|
@Deprecated
|
|
public <T extends View> T getViewAtPath(ViewGroup rootView, int... path) {
|
|
return ReactTestHelper.getViewAtPath(rootView, path);
|
|
}
|
|
|
|
public <T extends View> T getViewByTestId(ViewGroup rootView, String testID) {
|
|
return (T) ReactTestHelper.getViewWithReactTestId(rootView, testID);
|
|
}
|
|
|
|
public static class Event {
|
|
private final CountDownLatch mLatch;
|
|
|
|
public Event() {
|
|
this(1);
|
|
}
|
|
|
|
public Event(int counter) {
|
|
mLatch = new CountDownLatch(counter);
|
|
}
|
|
|
|
public void occur() {
|
|
mLatch.countDown();
|
|
}
|
|
|
|
public boolean didOccur() {
|
|
return mLatch.getCount() == 0;
|
|
}
|
|
|
|
public boolean await(long millis) {
|
|
try {
|
|
return mLatch.await(millis, TimeUnit.MILLISECONDS);
|
|
} catch (InterruptedException ignore) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Timing module needs to be created on the main thread so that it gets the correct Choreographer.
|
|
*/
|
|
protected Timing createTimingModule() {
|
|
final SimpleSettableFuture<Timing> simpleSettableFuture = new SimpleSettableFuture<Timing>();
|
|
UiThreadUtil.runOnUiThread(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
Timing timing = new Timing(getContext(), mock(DevSupportManager.class));
|
|
simpleSettableFuture.set(timing);
|
|
}
|
|
});
|
|
try {
|
|
return simpleSettableFuture.get(5000, TimeUnit.MILLISECONDS);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
public void initializeWithInstance(CatalystInstance instance) {
|
|
mInstance = instance;
|
|
mBridgeIdleSignaler = new ReactBridgeIdleSignaler();
|
|
mInstance.addBridgeIdleDebugListener(mBridgeIdleSignaler);
|
|
getContext().initializeWithInstance(mInstance);
|
|
}
|
|
|
|
public boolean waitForBridgeIdle(long millis) {
|
|
return Assertions.assertNotNull(mBridgeIdleSignaler).waitForIdle(millis);
|
|
}
|
|
|
|
public void waitForIdleSync() {
|
|
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
|
}
|
|
|
|
public void waitForBridgeAndUIIdle() {
|
|
ReactIdleDetectionUtil.waitForBridgeAndUIIdle(
|
|
Assertions.assertNotNull(mBridgeIdleSignaler),
|
|
getContext(),
|
|
IDLE_TIMEOUT_MS);
|
|
}
|
|
|
|
@Override
|
|
protected void setUp() throws Exception {
|
|
super.setUp();
|
|
SoLoader.init(getContext(), /* native exopackage */ false);
|
|
ApplicationHolder.setApplication((Application) getContext().getApplicationContext());
|
|
}
|
|
|
|
@Override
|
|
protected void tearDown() throws Exception {
|
|
super.tearDown();
|
|
shutDownContext();
|
|
}
|
|
|
|
protected static void initializeJavaModule(final BaseJavaModule javaModule) {
|
|
final Semaphore semaphore = new Semaphore(0);
|
|
UiThreadUtil.runOnUiThread(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
javaModule.initialize();
|
|
if (javaModule instanceof LifecycleEventListener) {
|
|
((LifecycleEventListener) javaModule).onHostResume();
|
|
}
|
|
semaphore.release();
|
|
}
|
|
});
|
|
try {
|
|
SoftAssertions.assertCondition(
|
|
semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS),
|
|
"Timed out initializing timing module");
|
|
} catch (InterruptedException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
}
|