Authored by 张丽霞

add files

Showing 43 changed files with 4830 additions and 0 deletions

Too many changes to show.

To preserve performance only 43 of 43+ files are displayed.

# Make sure we never publish ReactAndroid/build (Gradle output) to npm.
# The folder is huge (> 100MB)!
build/
... ...
Here's how to test the whole dev experience end-to-end. This will be eventually merged into the [Getting Started guide](https://facebook.github.io/react-native/docs/getting-started.html).
Assuming you have the [Android SDK](https://developer.android.com/sdk/installing/index.html) installed, run `android` to open the Android SDK Manager.
Make sure you have the following installed:
- Android SDK version 23
- SDK build tools version 23
- Android Support Repository 17 (for Android Support Library)
Follow steps on https://github.com/facebook/react-native/blob/master/react-native-cli/CONTRIBUTING.md, but be sure to bump the version of react-native in package.json to some version > 0.9 (latest published npm version) or set up proxying properly for react-native
- From the react-native-android repo:
- `./gradlew :ReactAndroid:installArchives`
- *Assuming you already have android-jsc installed to local maven repo, no steps included here*
- `react-native init ProjectName`
- Open up your Android emulator (Genymotion is recommended)
- `cd ProjectName`
- `react-native run-android`
In case the app crashed:
- Run `adb logcat` and try to find a Java exception
... ...
# Building React Native for Android
See the [docs on the website](https://facebook.github.io/react-native/docs/android-building-from-source.html).
# Running tests
When you submit a pull request CircleCI will automatically run all tests. To run tests locally, see [Testing](https://facebook.github.io/react-native/docs/testing.html).
... ...
// Copyright 2015-present Facebook. All Rights Reserved.
apply plugin: 'com.android.library'
apply plugin: 'maven'
apply plugin: 'de.undercouch.download'
import de.undercouch.gradle.tasks.download.Download
import org.apache.tools.ant.taskdefs.condition.Os
import org.apache.tools.ant.filters.ReplaceTokens
// We download various C++ open-source dependencies into downloads.
// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk.
// After that we build native code from src/main/jni with module path pointing at third-party-ndk.
def downloadsDir = new File("$buildDir/downloads")
def thirdPartyNdkDir = new File("$buildDir/third-party-ndk")
// You need to have following folders in this directory:
// - boost_1_63_0
// - double-conversion-1.1.1
// - folly-deprecate-dynamic-initializer
// - glog-0.3.3
// - jsc-headers
def dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES")
// The Boost library is a very large download (>100MB).
// If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable
// and the build will use that.
def boostPath = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH")
task createNativeDepsDirectories {
downloadsDir.mkdirs()
thirdPartyNdkDir.mkdirs()
}
task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) {
src 'https://github.com/react-native-community/boost-for-react-native/releases/download/v1.63.0-0/boost_1_63_0.tar.gz'
onlyIfNewer true
overwrite false
dest new File(downloadsDir, 'boost_1_63_0.tar.gz')
}
task prepareBoost(dependsOn: boostPath ? [] : [downloadBoost], type: Copy) {
from boostPath ?: tarTree(resources.gzip(downloadBoost.dest))
from 'src/main/jni/third-party/boost/Android.mk'
include 'Android.mk', 'boost_1_63_0/boost/**/*.hpp', 'boost/boost/**/*.hpp'
includeEmptyDirs = false
into "$thirdPartyNdkDir/boost"
doLast {
file("$thirdPartyNdkDir/boost/boost").renameTo("$thirdPartyNdkDir/boost/boost_1_63_0")
}
}
task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) {
src 'https://github.com/google/double-conversion/archive/v1.1.1.tar.gz'
onlyIfNewer true
overwrite false
dest new File(downloadsDir, 'double-conversion-1.1.1.tar.gz')
}
task prepareDoubleConversion(dependsOn: dependenciesPath ? [] : [downloadDoubleConversion], type: Copy) {
from dependenciesPath ?: tarTree(downloadDoubleConversion.dest)
from 'src/main/jni/third-party/double-conversion/Android.mk'
include 'double-conversion-1.1.1/src/**/*', 'Android.mk'
filesMatching('*/src/**/*', {fname -> fname.path = "double-conversion/${fname.name}"})
includeEmptyDirs = false
into "$thirdPartyNdkDir/double-conversion"
}
task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) {
src 'https://github.com/facebook/folly/archive/v2016.09.26.00.tar.gz'
onlyIfNewer true
overwrite false
dest new File(downloadsDir, 'folly-2016.09.26.00.tar.gz');
}
task prepareFolly(dependsOn: dependenciesPath ? [] : [downloadFolly], type: Copy) {
from dependenciesPath ?: tarTree(downloadFolly.dest)
from 'src/main/jni/third-party/folly/Android.mk'
include 'folly-2016.09.26.00/folly/**/*', 'Android.mk'
eachFile {fname -> fname.path = (fname.path - "folly-2016.09.26.00/")}
includeEmptyDirs = false
into "$thirdPartyNdkDir/folly"
}
task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) {
src 'https://github.com/google/glog/archive/v0.3.3.tar.gz'
onlyIfNewer true
overwrite false
dest new File(downloadsDir, 'glog-0.3.3.tar.gz')
}
// Prepare glog sources to be compiled, this task will perform steps that normally should've been
// executed by automake. This way we can avoid dependencies on make/automake
task prepareGlog(dependsOn: dependenciesPath ? [] : [downloadGlog], type: Copy) {
from dependenciesPath ?: tarTree(downloadGlog.dest)
from 'src/main/jni/third-party/glog/'
include 'glog-0.3.3/src/**/*', 'Android.mk', 'config.h'
includeEmptyDirs = false
filesMatching('**/*.h.in') {
filter(ReplaceTokens, tokens: [
ac_cv_have_unistd_h: '1',
ac_cv_have_stdint_h: '1',
ac_cv_have_systypes_h: '1',
ac_cv_have_inttypes_h: '1',
ac_cv_have_libgflags: '0',
ac_google_start_namespace: 'namespace google {',
ac_cv_have_uint16_t: '1',
ac_cv_have_u_int16_t: '1',
ac_cv_have___uint16: '0',
ac_google_end_namespace: '}',
ac_cv_have___builtin_expect: '1',
ac_google_namespace: 'google',
ac_cv___attribute___noinline: '__attribute__ ((noinline))',
ac_cv___attribute___noreturn: '__attribute__ ((noreturn))',
ac_cv___attribute___printf_4_5: '__attribute__((__format__ (__printf__, 4, 5)))'
])
it.path = (it.name - '.in')
}
into "$thirdPartyNdkDir/glog"
}
task downloadJSCHeaders(type: Download) {
// in sync with webkit SVN revision 174650
def jscAPIBaseURL = 'https://raw.githubusercontent.com/WebKit/webkit/38b15a3ba3c1b0798f2036f7cea36ffdc096202e/Source/JavaScriptCore/API/'
def jscHeaderFiles = ['JavaScript.h', 'JSBase.h', 'JSContextRef.h', 'JSObjectRef.h', 'JSRetainPtr.h', 'JSStringRef.h', 'JSValueRef.h', 'WebKitAvailability.h']
def output = new File(downloadsDir, 'jsc')
output.mkdirs()
src(jscHeaderFiles.collect { headerName -> "$jscAPIBaseURL$headerName" })
onlyIfNewer true
overwrite false
dest output
}
// Create Android.mk library module based on so files from mvn + include headers fetched from webkit.org
task prepareJSC(dependsOn: dependenciesPath ? [] : [downloadJSCHeaders]) << {
copy {
from zipTree(configurations.compile.fileCollection { dep -> dep.name == 'android-jsc' }.singleFile)
from dependenciesPath ? "$dependenciesPath/jsc-headers" : {downloadJSCHeaders.dest}
from 'src/main/jni/third-party/jsc/Android.mk'
include 'jni/**/*.so', '*.h', 'Android.mk'
filesMatching('*.h', { fname -> fname.path = "JavaScriptCore/${fname.path}"})
into "$thirdPartyNdkDir/jsc";
}
}
def getNdkBuildName() {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
return "ndk-build.cmd"
} else {
return "ndk-build"
}
}
def findNdkBuildFullPath() {
// we allow to provide full path to ndk-build tool
if (hasProperty('ndk.command')) {
return property('ndk.command')
}
// or just a path to the containing directory
if (hasProperty('ndk.path')) {
def ndkDir = property('ndk.path')
return new File(ndkDir, getNdkBuildName()).getAbsolutePath()
}
if (System.getenv('ANDROID_NDK') != null) {
def ndkDir = System.getenv('ANDROID_NDK')
return new File(ndkDir, getNdkBuildName()).getAbsolutePath()
}
def ndkDir = android.hasProperty('plugin') ? android.plugin.ndkFolder :
plugins.getPlugin('com.android.library').hasProperty('sdkHandler') ?
plugins.getPlugin('com.android.library').sdkHandler.getNdkFolder() :
android.ndkDirectory.absolutePath
if (ndkDir) {
return new File(ndkDir, getNdkBuildName()).getAbsolutePath()
}
return null
}
def getNdkBuildFullPath() {
def ndkBuildFullPath = findNdkBuildFullPath()
if (ndkBuildFullPath == null) {
throw new GradleScriptException(
"ndk-build binary cannot be found, check if you've set " +
"\$ANDROID_NDK environment variable correctly or if ndk.dir is " +
"setup in local.properties",
null)
}
if (!new File(ndkBuildFullPath).canExecute()) {
throw new GradleScriptException(
"ndk-build binary " + ndkBuildFullPath + " doesn't exist or isn't executable.\n" +
"Check that the \$ANDROID_NDK environment variable, or ndk.dir in local.properties, is set correctly.\n" +
"(On Windows, make sure you escape backslashes in local.properties or use forward slashes, e.g. C:\\\\ndk or C:/ndk rather than C:\\ndk)",
null)
}
return ndkBuildFullPath
}
task buildReactNdkLib(dependsOn: [prepareJSC, prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog], type: Exec) {
inputs.file('src/main/jni/react')
outputs.dir("$buildDir/react-ndk/all")
commandLine getNdkBuildFullPath(),
'NDK_PROJECT_PATH=null',
"NDK_APPLICATION_MK=$projectDir/src/main/jni/Application.mk",
'NDK_OUT=' + temporaryDir,
"NDK_LIBS_OUT=$buildDir/react-ndk/all",
"THIRD_PARTY_NDK_DIR=$buildDir/third-party-ndk",
"REACT_COMMON_DIR=$projectDir/../ReactCommon",
'-C', file('src/main/jni/react/jni').absolutePath,
'--jobs', project.hasProperty("jobs") ? project.property("jobs") : Runtime.runtime.availableProcessors()
}
task cleanReactNdkLib(type: Exec) {
commandLine getNdkBuildFullPath(),
"NDK_APPLICATION_MK=$projectDir/src/main/jni/Application.mk",
"THIRD_PARTY_NDK_DIR=$buildDir/third-party-ndk",
"REACT_COMMON_DIR=$projectDir/../ReactCommon",
'-C', file('src/main/jni/react/jni').absolutePath,
'clean'
}
task packageReactNdkLibs(dependsOn: buildReactNdkLib, type: Copy) {
from "$buildDir/react-ndk/all"
exclude '**/libjsc.so'
into "$buildDir/react-ndk/exported"
}
task packageReactNdkLibsForBuck(dependsOn: packageReactNdkLibs, type: Copy) {
from "$buildDir/react-ndk/exported"
into "src/main/jni/prebuilt/lib"
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.0"
ndk {
moduleName "reactnativejni"
}
buildConfigField 'boolean', 'IS_INTERNAL_BUILD', 'false'
buildConfigField 'int', 'EXOPACKAGE_FLAGS', '0'
testApplicationId "com.facebook.react.tests.gradle"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
sourceSets.main {
jni.srcDirs = []
jniLibs.srcDir "$buildDir/react-ndk/exported"
res.srcDirs = ['src/main/res/devsupport', 'src/main/res/shell', 'src/main/res/views/modal', 'src/main/res/views/uimanager']
java {
srcDirs = ['src/main/java', 'src/main/libraries/soloader/java', 'src/main/jni/first-party/fb/jni/java']
exclude 'com/facebook/react/processing'
exclude 'com/facebook/react/module/processing'
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn packageReactNdkLibs
}
clean.dependsOn cleanReactNdkLib
lintOptions {
abortOnError false
}
packagingOptions {
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
}
}
dependencies {
compile fileTree(dir: 'src/main/third-party/java/infer-annotations/', include: ['*.jar'])
compile 'javax.inject:javax.inject:1'
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.facebook.fbui.textlayoutbuilder:textlayoutbuilder:1.0.0'
compile 'com.facebook.fresco:fresco:1.3.0'
compile 'com.facebook.fresco:imagepipeline-okhttp3:1.3.0'
compile 'com.facebook.soloader:soloader:0.1.0'
compile 'com.google.code.findbugs:jsr305:3.0.0'
compile 'com.squareup.okhttp3:okhttp:3.6.0'
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.6.0'
compile 'com.squareup.okio:okio:1.13.0'
compile 'org.webkit:android-jsc:r174650'
testCompile "junit:junit:${JUNIT_VERSION}"
testCompile "org.powermock:powermock-api-mockito:${POWERMOCK_VERSION}"
testCompile "org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}"
testCompile "org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}"
testCompile "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}"
testCompile "org.easytesting:fest-assert-core:${FEST_ASSERT_CORE_VERSION}"
testCompile "org.robolectric:robolectric:${ROBOLECTRIC_VERSION}"
androidTestCompile fileTree(dir: 'src/main/third-party/java/buck-android-support/', include: ['*.jar'])
androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}"
}
apply from: 'release.gradle'
... ...
VERSION_NAME=0.54.4
GROUP=com.facebook.react
POM_NAME=ReactNative
POM_ARTIFACT_ID=react-native
POM_PACKAGING=aar
android.useDeprecatedNdk=true
MOCKITO_CORE_VERSION=1.10.19
POWERMOCK_VERSION=1.6.2
ROBOLECTRIC_VERSION=3.0
JUNIT_VERSION=4.12
FEST_ASSERT_CORE_VERSION=2.0M10
... ...
android_prebuilt_aar(
name = "appcompat",
aar = ":appcompat-binary-aar",
visibility = ["//ReactAndroid/..."],
)
remote_file(
name = "appcompat-binary-aar",
sha1 = "7d659f671541394a8bc2b9f909950aa2a5ec87ff",
url = "mvn:com.android.support:appcompat-v7:aar:23.0.1",
)
android_prebuilt_aar(
name = "android-jsc",
aar = ":android-jsc-aar",
)
remote_file(
name = "android-jsc-aar",
sha1 = "880cedd93f43e0fc841f01f2fa185a63d9230f85",
url = "mvn:org.webkit:android-jsc:aar:r174650",
)
... ...
// Copyright 2015-present Facebook. All Rights Reserved.
apply plugin: 'maven'
apply plugin: 'signing'
// Gradle tasks for publishing to maven
// 1) To install in local maven repo use :installArchives task
// 2) To upload artifact to maven central use: :uploadArchives (you'd need to have the permission to do that)
def isReleaseBuild() {
return VERSION_NAME.contains('SNAPSHOT') == false
}
def getRepositoryUrl() {
return hasProperty('repositoryUrl') ? property('repositoryUrl') : 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
}
def getRepositoryUsername() {
return hasProperty('repositoryUsername') ? property('repositoryUsername') : ''
}
def getRepositoryPassword() {
return hasProperty('repositoryPassword') ? property('repositoryPassword') : ''
}
def configureReactNativePom(def pom) {
pom.project {
name POM_NAME
artifactId POM_ARTIFACT_ID
packaging POM_PACKAGING
description 'A framework for building native apps with React'
url 'https://github.com/facebook/react-native'
scm {
url 'https://github.com/facebook/react-native.git'
connection 'scm:git:https://github.com/facebook/react-native.git'
developerConnection 'scm:git:git@github.com:facebook/react-native.git'
}
licenses {
license {
name 'BSD License'
url 'https://github.com/facebook/react-native/blob/master/LICENSE'
distribution 'repo'
}
}
developers {
developer {
id 'facebook'
name 'Facebook'
}
}
}
}
if (JavaVersion.current().isJava8Compatible()) {
allprojects {
tasks.withType(Javadoc) {
options.addStringOption('Xdoclint:none', '-quiet')
}
}
}
afterEvaluate { project ->
task androidJavadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += files(android.bootClasspath)
classpath += files(project.getConfigurations().getByName('compile').asList())
include '**/*.java'
exclude '**/ReactBuildConfig.java'
}
task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) {
classifier = 'javadoc'
from androidJavadoc.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
include '**/*.java'
}
android.libraryVariants.all { variant ->
def name = variant.name.capitalize()
task "jar${name}"(type: Jar, dependsOn: variant.javaCompile) {
from variant.javaCompile.destinationDir
}
}
artifacts {
archives androidSourcesJar
archives androidJavadocJar
}
version = VERSION_NAME
group = GROUP
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask('uploadArchives') }
sign configurations.archives
}
uploadArchives {
configuration = configurations.archives
repositories.mavenDeployer {
beforeDeployment {
MavenDeployment deployment -> signing.signPom(deployment)
}
repository(url: getRepositoryUrl()) {
authentication(
userName: getRepositoryUsername(),
password: getRepositoryPassword())
}
configureReactNativePom pom
}
}
task installArchives(type: Upload) {
configuration = configurations.archives
repositories.mavenDeployer {
// Deploy to react-native/android, ready to publish to npm
repository url: "file://${projectDir}/../android"
configureReactNativePom pom
}
}
}
... ...
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.facebook.react.tests.gradle"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:targetSdkVersion="7" />
<supports-screens android:anyDensity="true" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- needed for screenshot tests -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:hardwareAccelerated="false">
<activity
android:name="com.facebook.react.testing.ReactAppTestActivity"
android:theme="@style/Theme.ReactNative.AppCompat.Light.NoActionBar.FullScreen"
/>
</application>
</manifest>
... ...
load("//ReactNative:DEFS.bzl", "rn_android_resource")
rn_android_resource(
name = "assets",
assets = ".",
visibility = ["PUBLIC"],
)
... ...
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.facebook.react.tests">
<supports-screens android:anyDensity="true" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- needed for screenshot tests -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:hardwareAccelerated="false">
<uses-library android:name="android.test.runner" />
<activity
android:name="com.facebook.react.testing.ReactAppTestActivity"
android:theme="@style/Theme.ReactNative.AppCompat.Light.NoActionBar.FullScreen">
</activity>
</application>
<instrumentation
android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.facebook.react.tests"
android:label="Buck runs instrumentation tests"/>
</manifest>
... ...
load("//ReactNative:DEFS.bzl", "rn_android_binary", "react_native_dep", "react_native_integration_tests_target", "react_native_target", "KEYSTORE_TARGET")
# We are running instrumentation tests in simple mode: app code and instrumentation are in the same APK
# Currently you need to run these commands to execute tests:
#
# node local-cli/cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/androidTest/js/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
# gradle :ReactAndroid:packageReactNdkLibsForBuck
# buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests
# ./scripts/run-android-instrumentation-tests.sh com.facebook.react.tests
rn_android_binary(
name = "instrumentation-tests",
keystore = KEYSTORE_TARGET,
manifest = "AndroidManifest.xml",
deps = [
react_native_dep("android_res/com/facebook/catalyst/appcompat:appcompat"),
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
react_native_dep("third-party/java/testing-support-lib:exposed-instrumentation-api"),
react_native_integration_tests_target("assets:assets"),
react_native_integration_tests_target("java/com/facebook/react/tests:tests"),
react_native_target("java/com/facebook/react/devsupport:devsupport"),
react_native_target("jni/prebuilt:android-jsc"),
react_native_target("jni/prebuilt:reactnative-libs"),
],
)
... ...
/**
* 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 java.util.ArrayList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
/**
* Shared by {@link ReactScrollViewTestCase} and {@link ReactHorizontalScrollViewTestCase}.
* See also ScrollViewTestModule.js
*/
public abstract class AbstractScrollViewTestCase extends ReactAppInstrumentationTestCase {
protected ScrollListenerModule mScrollListenerModule;
protected static interface ScrollViewTestModule extends JavaScriptModule {
public void scrollTo(float destX, float destY);
}
@Override
protected void tearDown() throws Exception {
waitForBridgeAndUIIdle(60000);
super.tearDown();
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
mScrollListenerModule = new ScrollListenerModule();
return super.createReactInstanceSpecForTest()
.addNativeModule(mScrollListenerModule);
}
// See ScrollViewListenerModule.js
protected static class ScrollListenerModule extends BaseJavaModule {
private final ArrayList<Double> mXOffsets = new ArrayList<Double>();
private final ArrayList<Double> mYOffsets = new ArrayList<Double>();
private final ArrayList<Integer> mItemsPressed = new ArrayList<Integer>();
private final Semaphore mScrollSignaler = new Semaphore(0);
private boolean mScrollBeginDragCalled;
private boolean mScrollEndDragCalled;
// Matches ScrollViewListenerModule.js
@Override
public String getName() {
return "ScrollListener";
}
@ReactMethod
public void onScroll(double x, double y) {
mXOffsets.add(x);
mYOffsets.add(y);
mScrollSignaler.release();
}
@ReactMethod
public void onItemPress(int itemNumber) {
mItemsPressed.add(itemNumber);
}
@ReactMethod
public void onScrollBeginDrag(double x, double y) {
mScrollBeginDragCalled = true;
}
@ReactMethod
public void onScrollEndDrag(double x, double y) {
mScrollEndDragCalled = true;
}
public void waitForScrollIdle() {
while (true) {
try {
boolean gotScrollSignal = mScrollSignaler.tryAcquire(1000, TimeUnit.MILLISECONDS);
if (!gotScrollSignal) {
return;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public ArrayList<Double> getXOffsets() {
return mXOffsets;
}
public ArrayList<Double> getYOffsets() {
return mYOffsets;
}
public ArrayList<Integer> getItemsPressed() {
return mItemsPressed;
}
public boolean dragEventsMatch() {
return mScrollBeginDragCalled && mScrollEndDragCalled;
}
}
}
... ...
/**
* 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 com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
/**
* NativeModule for tests that allows assertions from JS to propagate to Java.
*/
public class AssertModule extends BaseJavaModule {
private boolean mGotSuccess;
private boolean mGotFailure;
private @Nullable String mFirstFailureStackTrace;
@Override
public String getName() {
return "Assert";
}
@ReactMethod
public void fail(String stackTrace) {
if (!mGotFailure) {
mGotFailure = true;
mFirstFailureStackTrace = stackTrace;
}
}
@ReactMethod
public void success() {
mGotSuccess = true;
}
/**
* Allows the user of this module to verify that asserts are actually being called from JS and
* that none of them failed.
*/
public void verifyAssertsAndReset() {
assertFalse("First failure: " + mFirstFailureStackTrace, mGotFailure);
assertTrue("Received no assertions during the test!", mGotSuccess);
mGotFailure = false;
mGotSuccess = false;
mFirstFailureStackTrace = null;
}
}
... ...
load("//ReactNative:DEFS.bzl", "rn_android_library", "react_native_dep", "react_native_integration_tests_target", "react_native_target")
rn_android_library(
name = "testing",
srcs = glob(
["**/*.java"],
excludes = [
"idledetection/**/*.java",
"network/**/*.java",
],
),
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
react_native_dep("third-party/android/support/v4:lib-support-v4"),
react_native_dep("third-party/java/buck-android-support:buck-android-support"),
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_dep("third-party/java/junit:junit"),
react_native_dep("third-party/java/mockito:mockito"),
react_native_dep("third-party/java/testing-support-lib:runner"),
react_native_integration_tests_target("java/com/facebook/react/testing/idledetection:idledetection"),
react_native_integration_tests_target("java/com/facebook/react/testing/network:network"),
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/devsupport:interfaces"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/module/model:model"),
react_native_target("java/com/facebook/react/modules/core:core"),
react_native_target("java/com/facebook/react/modules/debug:interfaces"),
react_native_target("java/com/facebook/react/shell:shell"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
react_native_target("res:uimanager"),
],
)
... ...
/**
* 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 com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableMap;
/**
* Dummy implementation of storage module, used for testing
*/
public final class FakeAsyncLocalStorage extends BaseJavaModule {
private static WritableMap errorMessage;
static {
errorMessage = Arguments.createMap();
errorMessage.putString("message", "Fake Async Local Storage");
}
@Override
public String getName() {
return "AsyncSQLiteDBStorage";
}
@ReactMethod
public void multiGet(final ReadableArray keys, final Callback callback) {
callback.invoke(errorMessage, null);
}
@ReactMethod
public void multiSet(final ReadableArray keyValueArray, final Callback callback) {
callback.invoke(errorMessage);
}
@ReactMethod
public void multiRemove(final ReadableArray keys, final Callback callback) {
callback.invoke(errorMessage);
}
@ReactMethod
public void clear(Callback callback) {
callback.invoke(errorMessage);
}
@ReactMethod
public void getAllKeys(final Callback callback) {
callback.invoke(errorMessage, null);
}
}
... ...
/**
* 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 com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
/**
* Dummy implementation of storage module, used for testing
*/
public final class FakeWebSocketModule extends BaseJavaModule {
@Override
public String getName() {
return "WebSocketModule";
}
@Override
public boolean canOverrideExistingModule() {
return true;
}
@ReactMethod
public void connect(
final String url,
@Nullable final ReadableArray protocols,
@Nullable final ReadableMap headers,
final int id) {
}
@ReactMethod
public void close(int code, String reason, int id) {
}
@ReactMethod
public void send(String message, int id) {
}
@ReactMethod
public void sendBinary(String base64String, int id) {
}
}
... ...
/**
* 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 java.util.List;
import android.view.View;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.ReactPackage;
/**
* This class wraps {@class ReactInstanceSpecForTest} in {@class ReactPackage} interface.
* TODO(6788898): Refactor test code to use ReactPackage instead of SpecForTest
*/
public class InstanceSpecForTestPackage implements ReactPackage {
private final ReactInstanceSpecForTest mSpecForTest;
public InstanceSpecForTestPackage(ReactInstanceSpecForTest specForTest) {
mSpecForTest = specForTest;
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext catalystApplicationContext) {
return mSpecForTest.getExtraNativeModulesForTest();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return mSpecForTest.getExtraViewManagers();
}
}
... ...
/**
* 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 java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
/**
* Native module provides single method {@link #record} which records its single int argument
* in calls array
*/
public class IntRecordingModule extends BaseJavaModule {
private final List<Integer> mCalls = new ArrayList<>();
private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
@Override
public String getName() {
return "Recording";
}
@ReactMethod
public void record(int i) {
mCalls.add(i);
mCountDownLatch.countDown();
}
public void reset() {
mCalls.clear();
}
public List<Integer> getCalls() {
return mCalls;
}
public void waitForFirstCall() {
try {
if (!mCountDownLatch.await(15000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Timed out waiting for first call");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
... ...
/**
* 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 java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactMethod;
/**
* This class is used to verify that some JS integration tests have completed successfully.
* The JS integration tests can be started from a ReactIntegrationTestCase and upon
* finishing successfully the {@link JSIntegrationTestChecker#testDone()} method will be called.
* To verify if the test has completed successfully, call {#link JSIntegrationTestChecker#await()}
* to wait for the test to run, and {#link JSIntegrationTestChecker#isTestDone()} to check if it
* completed successfully.
*/
public class JSIntegrationTestChecker extends BaseJavaModule {
private final CountDownLatch mLatch;
public JSIntegrationTestChecker() {
mLatch = new CountDownLatch(1);
}
@Override
public String getName() {
return "TestModule";
}
@ReactMethod
public void markTestCompleted() {
mLatch.countDown();
}
@ReactMethod
public void verifySnapshot(Callback callback) {
}
public boolean await(long ms) {
try {
return mLatch.await(ms, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignore) {
return false;
}
}
public boolean isTestDone() {
return mLatch.getCount() == 0;
}
}
... ...
/**
* 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 java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
/**
* Custom implementation of {@link org.junit.runners.model.MultipleFailureException} that includes
* stack information of collected exception as a part of the message.
*/
public class MultipleFailureException extends org.junit.runners.model.MultipleFailureException {
public MultipleFailureException(List<Throwable> errors) {
super(errors);
}
@Override
public String getMessage() {
StringBuilder sb = new StringBuilder();
List<Throwable> errors = getFailures();
sb.append(String.format("There were %d errors:", errors.size()));
int i = 0;
for (Throwable e : errors) {
sb.append(String.format("%n---- Error #%d", i));
sb.append("\n" + getStackTraceAsString(e));
i++;
}
sb.append("\n");
return sb.toString();
}
private static String getStackTraceAsString(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
}
... ...
/**
* 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.TimeUnit;
import android.graphics.Bitmap;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.testing.idledetection.IdleWaiter;
/**
* Base class for instrumentation tests that runs React based react application in UI mode
*/
public abstract class ReactAppInstrumentationTestCase extends
ActivityInstrumentationTestCase2<ReactAppTestActivity> implements IdleWaiter {
public ReactAppInstrumentationTestCase() {
super(ReactAppTestActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
final ReactAppTestActivity activity = getActivity();
try {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
activity.loadApp(
getReactApplicationKeyUnderTest(),
createReactInstanceSpecForTest(),
getEnableDevSupport());
}
});
} catch (Throwable t) {
throw new Exception("Unable to load react app", t);
}
waitForBridgeAndUIIdle();
assertTrue("Layout never occurred!", activity.waitForLayout(5000));
waitForBridgeAndUIIdle();
}
@Override
protected void tearDown() throws Exception {
ReactAppTestActivity activity = getActivity();
super.tearDown();
activity.waitForDestroy(5000);
}
public ViewGroup getRootView() {
return (ViewGroup) getActivity().getRootView();
}
/**
* This method isn't safe since it doesn't factor in layout-only view removal. Use
* {@link #getViewByTestId(String)} instead.
*/
@Deprecated
public <T extends View> T getViewAtPath(int... path) {
return ReactTestHelper.getViewAtPath((ViewGroup) getRootView().getParent(), path);
}
public <T extends View> T getViewByTestId(String testID) {
return (T) ReactTestHelper
.getViewWithReactTestId((ViewGroup) getRootView().getParent(), testID);
}
public SingleTouchGestureGenerator createGestureGenerator() {
return new SingleTouchGestureGenerator(getRootView(), this);
}
public void waitForBridgeAndUIIdle() {
getActivity().waitForBridgeAndUIIdle();
}
public void waitForBridgeAndUIIdle(long timeoutMs) {
getActivity().waitForBridgeAndUIIdle(timeoutMs);
}
protected Bitmap getScreenshot() {
// Wait for the UI to settle. If the UI is doing animations, this may be unsafe!
getInstrumentation().waitForIdleSync();
final CountDownLatch latch = new CountDownLatch(1);
final BitmapHolder bitmapHolder = new BitmapHolder();
final Runnable getScreenshotRunnable = new Runnable() {
private static final int MAX_TRIES = 1000;
// This is the constant used in the support library for APIs that don't have Choreographer
private static final int FRAME_DELAY_MS = 10;
private int mNumRuns = 0;
@Override
public void run() {
mNumRuns++;
ReactAppTestActivity activity = getActivity();
if (!activity.isScreenshotReady()) {
if (mNumRuns > MAX_TRIES) {
throw new RuntimeException(
"Waited " + MAX_TRIES + " frames to get screenshot but it's still not ready!");
}
activity.postDelayed(this, FRAME_DELAY_MS);
return;
}
bitmapHolder.bitmap = getActivity().getCurrentScreenshot();
latch.countDown();
}
};
getActivity().runOnUiThread(getScreenshotRunnable);
try {
if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Timed out waiting for screenshot runnable to run!");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return Assertions.assertNotNull(bitmapHolder.bitmap);
}
/**
* Implement this method to provide application key to be launched. List of available
* application is located in TestBundle.js file
*/
protected abstract String getReactApplicationKeyUnderTest();
protected boolean getEnableDevSupport() {
return false;
}
/**
* Override this method to provide extra native modules to be loaded before the app starts
*/
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return new ReactInstanceSpecForTest();
}
protected ReactContext getReactContext() {
return getActivity().getReactContext();
}
/**
* Helper class to pass the bitmap between execution scopes in {@link #getScreenshot()}.
*/
private static class BitmapHolder {
public @Nullable volatile Bitmap bitmap;
}
}
... ...
/**
* 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 android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactInstanceManagerBuilder;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.testing.idledetection.ReactBridgeIdleSignaler;
import com.facebook.react.testing.idledetection.ReactIdleDetectionUtil;
import com.facebook.react.uimanager.UIImplementationProvider;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
public class ReactAppTestActivity extends FragmentActivity
implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private static final String DEFAULT_BUNDLE_NAME = "AndroidTestBundle.js";
private static final int ROOT_VIEW_ID = 8675309;
// we need a bigger timeout for CI builds because they run on a slow emulator
private static final long IDLE_TIMEOUT_MS = 120000;
private CountDownLatch mLayoutEvent = new CountDownLatch(1);
private @Nullable ReactBridgeIdleSignaler mBridgeIdleSignaler;
private ScreenshotingFrameLayout mScreenshotingFrameLayout;
private final CountDownLatch mDestroyCountDownLatch = new CountDownLatch(1);
private @Nullable ReactInstanceManager mReactInstanceManager;
private @Nullable ReactRootView mReactRootView;
private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
overridePendingTransition(0, 0);
// We wrap screenshot layout in another FrameLayout in order to handle custom dimensions of the
// screenshot view set through {@link #setScreenshotDimensions}
FrameLayout rootView = new FrameLayout(this);
setContentView(rootView);
mScreenshotingFrameLayout = new ScreenshotingFrameLayout(this);
mScreenshotingFrameLayout.setId(ROOT_VIEW_ID);
rootView.addView(mScreenshotingFrameLayout);
mReactRootView = new ReactRootView(this);
mScreenshotingFrameLayout.addView(mReactRootView);
}
@Override
protected void onPause() {
super.onPause();
mLifecycleState = LifecycleState.BEFORE_RESUME;
overridePendingTransition(0, 0);
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause();
}
}
@Override
protected void onResume() {
super.onResume();
mLifecycleState = LifecycleState.RESUMED;
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mDestroyCountDownLatch.countDown();
if (mReactInstanceManager != null) {
mReactInstanceManager.destroy();
mReactInstanceManager = null;
}
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
mReactRootView = null;
}
mScreenshotingFrameLayout.clean();
}
public void waitForDestroy(long timeoutMs) throws InterruptedException {
mDestroyCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
}
public void loadApp(String appKey, ReactInstanceSpecForTest spec, boolean enableDevSupport) {
loadApp(appKey, spec, null, DEFAULT_BUNDLE_NAME, enableDevSupport);
}
public void loadApp(String appKey, ReactInstanceSpecForTest spec, String bundleName) {
loadApp(appKey, spec, null, bundleName, false /* = useDevSupport */);
}
public void loadApp(
String appKey,
ReactInstanceSpecForTest spec,
String bundleName,
UIImplementationProvider uiImplementationProvider) {
loadApp(appKey, spec, null, bundleName, false /* = useDevSupport */, uiImplementationProvider);
}
public void resetRootViewForScreenshotTests() {
if (mReactInstanceManager != null) {
mReactInstanceManager.destroy();
mReactInstanceManager = null;
}
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
}
mReactRootView = new ReactRootView(this);
mScreenshotingFrameLayout.removeAllViews();
mScreenshotingFrameLayout.clean();
mScreenshotingFrameLayout.addView(mReactRootView);
}
public void loadApp(
String appKey,
ReactInstanceSpecForTest spec,
@Nullable Bundle initialProps,
String bundleName,
boolean useDevSupport) {
loadApp(appKey, spec, initialProps, bundleName, useDevSupport, null);
}
public void loadApp(
String appKey,
ReactInstanceSpecForTest spec,
@Nullable Bundle initialProps,
String bundleName,
boolean useDevSupport,
UIImplementationProvider uiImplementationProvider) {
final CountDownLatch currentLayoutEvent = mLayoutEvent = new CountDownLatch(1);
mBridgeIdleSignaler = new ReactBridgeIdleSignaler();
ReactInstanceManagerBuilder builder =
ReactTestHelper.getReactTestFactory()
.getReactInstanceManagerBuilder()
.setApplication(getApplication())
.setBundleAssetName(bundleName);
if (!spec.getAlternativeReactPackagesForTest().isEmpty()) {
builder.addPackages(spec.getAlternativeReactPackagesForTest());
} else {
builder.addPackage(new MainReactPackage());
}
builder
.addPackage(new InstanceSpecForTestPackage(spec))
// By not setting a JS module name, we force the bundle to be always loaded from
// assets, not the devserver, even if dev mode is enabled (such as when testing redboxes).
// This makes sense because we never run the devserver in tests.
//.setJSMainModuleName()
.setUseDeveloperSupport(useDevSupport)
.setBridgeIdleDebugListener(mBridgeIdleSignaler)
.setInitialLifecycleState(mLifecycleState)
.setUIImplementationProvider(uiImplementationProvider);
mReactInstanceManager = builder.build();
mReactInstanceManager.onHostResume(this, this);
Assertions.assertNotNull(mReactRootView).getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
currentLayoutEvent.countDown();
}
});
Assertions.assertNotNull(mReactRootView)
.startReactApplication(mReactInstanceManager, appKey, initialProps);
}
public boolean waitForLayout(long millis) throws InterruptedException {
return mLayoutEvent.await(millis, TimeUnit.MILLISECONDS);
}
public void waitForBridgeAndUIIdle() {
waitForBridgeAndUIIdle(IDLE_TIMEOUT_MS);
}
public void waitForBridgeAndUIIdle(long timeoutMs) {
ReactIdleDetectionUtil.waitForBridgeAndUIIdle(
Assertions.assertNotNull(mBridgeIdleSignaler),
getReactContext(),
timeoutMs);
}
public View getRootView() {
return Assertions.assertNotNull(mReactRootView);
}
public ReactContext getReactContext() {
return waitForReactContext();
}
// Because react context is created asynchronously, we may have to wait until it is available.
// It's simpler than exposing synchronosition mechanism to notify listener than react context
// creation has completed.
private ReactContext waitForReactContext() {
Assertions.assertNotNull(mReactInstanceManager);
try {
while (true) {
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
return reactContext;
}
Thread.sleep(100);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void postDelayed(Runnable r, int delayMS) {
getRootView().postDelayed(r, delayMS);
}
/**
* Does not ensure that this is run on the UI thread or that the UI Looper is idle like
* {@link ReactAppInstrumentationTestCase#getScreenshot()}. You probably want to use that
* instead.
*/
public Bitmap getCurrentScreenshot() {
return mScreenshotingFrameLayout.getLastDrawnBitmap();
}
public boolean isScreenshotReady() {
return mScreenshotingFrameLayout.isScreenshotReady();
}
public void setScreenshotDimensions(int width, int height) {
mScreenshotingFrameLayout.setLayoutParams(new FrameLayout.LayoutParams(width, height));
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
public void onRequestPermissionsResult(
int requestCode,
String[] permissions,
int[] grantResults) {
}
@Override
public void requestPermissions(
String[] permissions, int requestCode, PermissionListener listener) {}
}
... ...
/**
* 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 android.annotation.SuppressLint;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A spec that allows a test to add additional NativeModules/JS modules to the ReactInstance. This
* can also be used to stub out existing native modules by adding another module with the same name
* as a built-in module.
*/
@SuppressLint("JavatestsIncorrectFolder")
public class ReactInstanceSpecForTest {
private final List<NativeModule> mNativeModules =
new ArrayList<NativeModule>(Arrays.asList(new FakeWebSocketModule()));
private final List<Class<? extends JavaScriptModule>> mJSModuleSpecs = new ArrayList<>();
private final List<ViewManager> mViewManagers = new ArrayList<>();
private final ArrayList<ReactPackage> mReactPackages = new ArrayList<>();
public ReactInstanceSpecForTest addNativeModule(NativeModule module) {
mNativeModules.add(module);
return this;
}
public ReactInstanceSpecForTest setPackage(ReactPackage reactPackage) {
if (!mReactPackages.isEmpty()) {
throw new IllegalStateException(
"setPackage is not allowed after addPackages. " + reactPackage);
}
mReactPackages.add(reactPackage);
return this;
}
public ReactInstanceSpecForTest addPackages(List<ReactPackage> reactPackages) {
mReactPackages.addAll(reactPackages);
return this;
}
public ReactInstanceSpecForTest addViewManager(ViewManager viewManager) {
mViewManagers.add(viewManager);
return this;
}
public List<NativeModule> getExtraNativeModulesForTest() {
return mNativeModules;
}
public ReactPackage getAlternativeReactPackageForTest() {
if (mReactPackages.size() > 1) {
throw new IllegalStateException(
"Multiple packages were added - use getAlternativeReactPackagesForTest instead.");
}
return mReactPackages.get(0);
}
public List<ReactPackage> getAlternativeReactPackagesForTest() {
return mReactPackages;
}
public List<ViewManager> getExtraViewManagers() {
return mViewManagers;
}
}
... ...
/**
* 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.CatalystInstance;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.futures.SimpleSettableFuture;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.modules.core.Timing;
import com.facebook.react.testing.idledetection.ReactBridgeIdleSignaler;
import com.facebook.react.testing.idledetection.ReactIdleDetectionUtil;
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 {
// we need a bigger timeout for CI builds because they run on a slow emulator
private static final long IDLE_TIMEOUT_MS = 60000;
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);
}
@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);
}
}
}
... ...
/**
* 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 com.facebook.react.modules.debug.interfaces.DeveloperSettings;
public class ReactSettingsForTests implements DeveloperSettings {
@Override
public boolean isFpsDebugEnabled() {
return false;
}
@Override
public boolean isAnimationFpsDebugEnabled() {
return false;
}
@Override
public boolean isJSDevModeEnabled() {
return true;
}
@Override
public boolean isJSMinifyEnabled() {
return false;
}
@Override
public boolean isElementInspectorEnabled() {
return false;
}
@Override
public boolean isNuclideJSDebugEnabled() {
return false;
}
@Override
public boolean isRemoteJSDebugEnabled() {
return false;
}
@Override
public void setRemoteJSDebugEnabled(boolean remoteJSDebugEnabled) {
}
}
... ...
/**
* 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 com.facebook.buck.android.support.exopackage.ApplicationLike;
import com.facebook.buck.android.support.exopackage.ExopackageApplication;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.soloader.SoLoader;
/**
* Application class for the Catalyst Launcher to allow it to work as an exopackage.
*
* Any app-specific code that should run before secondary dex files are loaded
* (like setting up crash reporting) should go in onBaseContextAttached.
* Anything that should run after secondary dex should go in CatalystApplicationImpl.onCreate.
*/
public class ReactTestAppShell extends ExopackageApplication<ApplicationLike> {
public ReactTestAppShell() {
super("com.facebook.react.testing.ReactTestApplicationImpl", ReactBuildConfig.EXOPACKAGE_FLAGS);
}
@Override
protected void onBaseContextAttached() {
// This is a terrible hack. Don't copy it.
// It's unfortunate that Instagram does the same thing.
// We need to do this here because internal apps use SoLoader,
// and Open Source Buck uses ExopackageSoLoader.
// If you feel the need to copy this, we should refactor it
// into an FB-specific subclass of ExopackageApplication.
SoLoader.init(this, (ReactBuildConfig.EXOPACKAGE_FLAGS & 2) != 0);
}
}
... ...
/**
* 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 android.app.Application;
import com.facebook.buck.android.support.exopackage.DefaultApplicationLike;
public class ReactTestApplicationImpl extends DefaultApplicationLike {
public ReactTestApplicationImpl() {
super();
}
public ReactTestApplicationImpl(Application application) {
super(application);
}
}
... ...
/**
* 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 android.content.Context;
import com.facebook.react.ReactInstanceManagerBuilder;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.NativeModule;
public interface ReactTestFactory {
public static interface ReactInstanceEasyBuilder {
ReactInstanceEasyBuilder setContext(Context context);
ReactInstanceEasyBuilder addNativeModule(NativeModule module);
CatalystInstance build();
}
ReactInstanceEasyBuilder getCatalystInstanceBuilder();
ReactInstanceManagerBuilder getReactInstanceManagerBuilder();
}
... ...
/**
* 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 android.app.Instrumentation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.NativeModuleRegistryBuilder;
import com.facebook.react.R;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactInstanceManagerBuilder;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaScriptModuleRegistry;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
import com.facebook.react.bridge.CatalystInstanceImpl;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JSCJavaScriptExecutorFactory;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.modules.core.ReactChoreographer;
import com.android.internal.util.Predicate;
public class ReactTestHelper {
private static class DefaultReactTestFactory implements ReactTestFactory {
private static class ReactInstanceEasyBuilderImpl implements ReactInstanceEasyBuilder {
private NativeModuleRegistryBuilder mNativeModuleRegistryBuilder;
private @Nullable Context mContext;
@Override
public ReactInstanceEasyBuilder setContext(Context context) {
mContext = context;
return this;
}
@Override
public ReactInstanceEasyBuilder addNativeModule(NativeModule nativeModule) {
if (mNativeModuleRegistryBuilder == null) {
mNativeModuleRegistryBuilder = new NativeModuleRegistryBuilder(
(ReactApplicationContext) mContext,
null,
false);
}
Assertions.assertNotNull(nativeModule);
mNativeModuleRegistryBuilder.addNativeModule(nativeModule);
return this;
}
@Override
public CatalystInstance build() {
if (mNativeModuleRegistryBuilder == null) {
mNativeModuleRegistryBuilder = new NativeModuleRegistryBuilder(
(ReactApplicationContext) mContext,
null,
false);
}
JavaScriptExecutor executor = null;
try {
executor = new JSCJavaScriptExecutorFactory("ReactTestHelperApp", "ReactTestHelperDevice").create();
} catch (Exception e) {
throw new RuntimeException(e);
}
return new CatalystInstanceImpl.Builder()
.setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
.setJSExecutor(executor)
.setRegistry(mNativeModuleRegistryBuilder.build())
.setJSBundleLoader(JSBundleLoader.createAssetLoader(
mContext,
"assets://AndroidTestBundle.js",
false/* Asynchronous */))
.setNativeModuleCallExceptionHandler(
new NativeModuleCallExceptionHandler() {
@Override
public void handleException(Exception e) {
throw new RuntimeException(e);
}
})
.build();
}
}
@Override
public ReactInstanceEasyBuilder getCatalystInstanceBuilder() {
return new ReactInstanceEasyBuilderImpl();
}
@Override
public ReactInstanceManagerBuilder getReactInstanceManagerBuilder() {
return ReactInstanceManager.builder();
}
}
public static ReactTestFactory getReactTestFactory() {
Instrumentation inst = InstrumentationRegistry.getInstrumentation();
if (!(inst instanceof ReactTestFactory)) {
return new DefaultReactTestFactory();
}
return (ReactTestFactory) inst;
}
public static ReactTestFactory.ReactInstanceEasyBuilder catalystInstanceBuilder(
final ReactIntegrationTestCase testCase) {
final ReactTestFactory.ReactInstanceEasyBuilder builder =
getReactTestFactory().getCatalystInstanceBuilder();
ReactTestFactory.ReactInstanceEasyBuilder postBuilder =
new ReactTestFactory.ReactInstanceEasyBuilder() {
@Override
public ReactTestFactory.ReactInstanceEasyBuilder setContext(Context context) {
builder.setContext(context);
return this;
}
@Override
public ReactTestFactory.ReactInstanceEasyBuilder addNativeModule(NativeModule module) {
builder.addNativeModule(module);
return this;
}
@Override
public CatalystInstance build() {
final CatalystInstance instance = builder.build();
testCase.initializeWithInstance(instance);
instance.runJSBundle();
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
ReactChoreographer.initialize();
instance.initialize();
}
});
testCase.waitForBridgeAndUIIdle();
return instance;
}
};
postBuilder.setContext(testCase.getContext());
return postBuilder;
}
/**
* Gets the view at given path in the UI hierarchy, ignoring modals.
*/
public static <T extends View> T getViewAtPath(ViewGroup rootView, int... path) {
// The application root element is wrapped in a helper view in order
// to be able to display modals. See renderApplication.js.
ViewGroup appWrapperView = rootView;
View view = appWrapperView.getChildAt(0);
for (int i = 0; i < path.length; i++) {
view = ((ViewGroup) view).getChildAt(path[i]);
}
return (T) view;
}
/**
* Gets the view with a given react test ID in the UI hierarchy. React test ID is currently
* propagated into view content description.
*/
public static View getViewWithReactTestId(View rootView, String testId) {
return findChild(rootView, hasTagValue(testId));
}
public static String getTestId(View view) {
return view.getTag(R.id.react_test_id) instanceof String
? (String) view.getTag(R.id.react_test_id)
: null;
}
private static View findChild(View root, Predicate<View> predicate) {
if (predicate.apply(root)) {
return root;
}
if (root instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) root;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
View result = findChild(child, predicate);
if (result != null) {
return result;
}
}
}
return null;
}
private static Predicate<View> hasTagValue(final String tagValue) {
return new Predicate<View>() {
@Override
public boolean apply(View view) {
Object tag = getTestId(view);
return tag != null && tag.equals(tagValue);
}
};
}
}
... ...
/**
* 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 android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Looper;
import android.widget.FrameLayout;
/**
* A FrameLayout that allows you to access the result of the last time its hierarchy was drawn. It
* accomplishes this by drawing its hierarchy into a software Canvas, saving the resulting Bitmap
* and then drawing that Bitmap to the actual Canvas provided by the system.
*/
public class ScreenshotingFrameLayout extends FrameLayout {
private @Nullable Bitmap mBitmap;
private Canvas mCanvas;
public ScreenshotingFrameLayout(Context context) {
super(context);
mCanvas = new Canvas();
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mBitmap == null) {
mBitmap = createNewBitmap(canvas);
mCanvas.setBitmap(mBitmap);
} else if (mBitmap.getWidth() != canvas.getWidth() ||
mBitmap.getHeight() != canvas.getHeight()) {
mBitmap.recycle();
mBitmap = createNewBitmap(canvas);
mCanvas.setBitmap(mBitmap);
}
super.dispatchDraw(mCanvas);
canvas.drawBitmap(mBitmap, 0, 0, null);
}
public void clean() {
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
mCanvas.setBitmap(null);
}
private static Bitmap createNewBitmap(Canvas canvas) {
return Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
}
public Bitmap getLastDrawnBitmap() {
if (mBitmap == null) {
throw new RuntimeException("View has not been drawn yet!");
}
if (Looper.getMainLooper() != Looper.myLooper()) {
throw new RuntimeException(
"Must access screenshots from main thread or you may get partially drawn Bitmaps");
}
if (!isScreenshotReady()) {
throw new RuntimeException("Trying to get screenshot, but the view is dirty or needs layout");
}
return Bitmap.createBitmap(mBitmap);
}
public boolean isScreenshotReady() {
return !isDirty() && !isLayoutRequested();
}
}
... ...
/**
* 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 android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import com.facebook.react.testing.idledetection.IdleWaiter;
/**
* Provides methods for generating touch events and dispatching them directly to a given view.
* Events scenarios are based on {@link android.test.TouchUtils} but they get gets dispatched
* directly through the view hierarchy using {@link View#dispatchTouchEvent} method instead of
* using instrumentation API.
* <p>
* All the events for a gesture are dispatched immediately which makes tests run very fast.
* The eventTime for each event is still set correctly. Android's gesture recognizers check
* eventTime in order to figure out gesture speed, and therefore scroll vs fling is recognized.
*/
public class SingleTouchGestureGenerator {
private static final long DEFAULT_DELAY_MS = 20;
private View mDispatcherView;
private IdleWaiter mIdleWaiter;
private long mLastDownTime;
private long mEventTime;
private float mLastX;
private float mLastY;
private ViewConfiguration mViewConfig;
public SingleTouchGestureGenerator(View view, IdleWaiter idleWaiter) {
mDispatcherView = view;
mIdleWaiter = idleWaiter;
mViewConfig = ViewConfiguration.get(view.getContext());
}
private SingleTouchGestureGenerator dispatchEvent(
final int action,
final float x,
final float y,
long eventTime) {
mEventTime = eventTime;
if (action == MotionEvent.ACTION_DOWN) {
mLastDownTime = eventTime;
}
mLastX = x;
mLastY = y;
mDispatcherView.post(
new Runnable() {
@Override
public void run() {
MotionEvent event = MotionEvent.obtain(mLastDownTime, mEventTime, action, x, y, 0);
mDispatcherView.dispatchTouchEvent(event);
event.recycle();
}
});
mIdleWaiter.waitForBridgeAndUIIdle();
return this;
}
private float getViewCenterX(View view) {
int[] xy = new int[2];
view.getLocationOnScreen(xy);
int viewWidth = view.getWidth();
return xy[0] + (viewWidth / 2.0f);
}
private float getViewCenterY(View view) {
int[] xy = new int[2];
view.getLocationOnScreen(xy);
int viewHeight = view.getHeight();
return xy[1] + (viewHeight / 2.0f);
}
public SingleTouchGestureGenerator startGesture(float x, float y) {
return dispatchEvent(MotionEvent.ACTION_DOWN, x, y, SystemClock.uptimeMillis());
}
public SingleTouchGestureGenerator startGesture(View view) {
return startGesture(getViewCenterX(view), getViewCenterY(view));
}
private SingleTouchGestureGenerator dispatchDelayedEvent(
int action,
float x,
float y,
long delay) {
return dispatchEvent(action, x, y, mEventTime + delay);
}
public SingleTouchGestureGenerator endGesture(float x, float y, long delay) {
return dispatchDelayedEvent(MotionEvent.ACTION_UP, x, y, delay);
}
public SingleTouchGestureGenerator endGesture(float x, float y) {
return endGesture(x, y, DEFAULT_DELAY_MS);
}
public SingleTouchGestureGenerator endGesture() {
return endGesture(mLastX, mLastY);
}
public SingleTouchGestureGenerator moveGesture(float x, float y, long delay) {
return dispatchDelayedEvent(MotionEvent.ACTION_MOVE, x, y, delay);
}
public SingleTouchGestureGenerator moveBy(float dx, float dy, long delay) {
return moveGesture(mLastX + dx, mLastY + dy, delay);
}
public SingleTouchGestureGenerator moveBy(float dx, float dy) {
return moveBy(dx, dy, DEFAULT_DELAY_MS);
}
public SingleTouchGestureGenerator clickViewAt(float x, float y) {
float touchSlop = mViewConfig.getScaledTouchSlop();
return startGesture(x, y).moveBy(touchSlop / 2.0f, touchSlop / 2.0f).endGesture();
}
public SingleTouchGestureGenerator drag(
float fromX,
float fromY,
float toX,
float toY,
int stepCount,
long totalDelay) {
float xStep = (toX - fromX) / stepCount;
float yStep = (toY - fromY) / stepCount;
float x = fromX;
float y = fromY;
for (int i = 0; i < stepCount; i++) {
x += xStep;
y += yStep;
moveGesture(x, y, totalDelay / stepCount);
}
return this;
}
public SingleTouchGestureGenerator dragTo(float toX, float toY, int stepCount, long totalDelay) {
return drag(mLastX, mLastY, toX, toY, stepCount, totalDelay);
}
public SingleTouchGestureGenerator dragTo(View view, int stepCount, long totalDelay) {
return dragTo(getViewCenterX(view), getViewCenterY(view), stepCount, totalDelay);
}
public SingleTouchGestureGenerator dragTo(View view, int stepCount) {
return dragTo(view, stepCount, stepCount * DEFAULT_DELAY_MS);
}
}
... ...
/**
* 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 java.util.ArrayList;
import java.util.List;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
/**
* Native module provides single method {@link #record} which records its single string argument
* in calls array
*/
public class StringRecordingModule extends BaseJavaModule {
private final List<String> mCalls = new ArrayList<String>();
@Override
public String getName() {
return "Recording";
}
@ReactMethod
public void record(String text) {
mCalls.add(text);
}
public void reset() {
mCalls.clear();
}
public List<String> getCalls() {
return mCalls;
}
}
... ...
load("//ReactNative:DEFS.bzl", "rn_android_library", "react_native_dep", "react_native_target")
rn_android_library(
name = "idledetection",
srcs = glob(["**/*.java"]),
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("third-party/java/testing-support-lib:runner"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/modules/core:core"),
],
)
... ...
/**
* 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.idledetection;
/**
* Interface for something that knows how to wait for bridge and UI idle.
*/
public interface IdleWaiter {
void waitForBridgeAndUIIdle();
}
... ...
/**
* 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.idledetection;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
/**
* Utility class that uses {@link NotThreadSafeBridgeIdleDebugListener} interface to allow callers
* to wait for the bridge to be idle.
*/
public class ReactBridgeIdleSignaler implements NotThreadSafeBridgeIdleDebugListener {
// Starts at 1 since bridge starts idle. The logic here is that the semaphore is only acquirable
// if the bridge is idle.
private final Semaphore mBridgeIdleSemaphore = new Semaphore(1);
private volatile boolean mIsBridgeIdle = true;
@Override
public void onTransitionToBridgeIdle() {
mIsBridgeIdle = true;
mBridgeIdleSemaphore.release();
}
@Override
public void onTransitionToBridgeBusy() {
mIsBridgeIdle = false;
try {
if (!mBridgeIdleSemaphore.tryAcquire(15000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException(
"Timed out waiting to acquire the test idle listener semaphore. Deadlock?");
}
} catch (InterruptedException e) {
throw new RuntimeException("Got interrupted", e);
}
}
public boolean isBridgeIdle() {
return mIsBridgeIdle;
}
public boolean waitForIdle(long millis) {
try {
if (mBridgeIdleSemaphore.tryAcquire(millis, TimeUnit.MILLISECONDS)) {
mBridgeIdleSemaphore.release();
return true;
}
return false;
} catch (InterruptedException e) {
throw new RuntimeException("Got interrupted", e);
}
}
}
... ...
/**
* 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.idledetection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import android.app.Instrumentation;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.modules.core.ChoreographerCompat;
public class ReactIdleDetectionUtil {
/**
* Waits for both the UI thread and bridge to be idle. It determines this by waiting for the
* bridge to become idle, then waiting for the UI thread to become idle, then checking if the
* bridge is idle again (if the bridge was idle before and is still idle after running the UI
* thread to idle, then there are no more events to process in either place).
* <p/>
* Also waits for any Choreographer callbacks to run after the initial sync since things like UI
* events are initiated from Choreographer callbacks.
*/
public static void waitForBridgeAndUIIdle(
ReactBridgeIdleSignaler idleSignaler,
final ReactContext reactContext,
long timeoutMs) {
UiThreadUtil.assertNotOnUiThread();
long startTime = SystemClock.uptimeMillis();
waitInner(idleSignaler, timeoutMs);
long timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime));
waitForChoreographer(timeToWait);
waitForJSIdle(reactContext);
timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime));
waitInner(idleSignaler, timeToWait);
timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime));
waitForChoreographer(timeToWait);
}
private static void waitForChoreographer(long timeToWait) {
final int waitFrameCount = 2;
final CountDownLatch latch = new CountDownLatch(1);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
ChoreographerCompat.getInstance().postFrameCallback(
new ChoreographerCompat.FrameCallback() {
private int frameCount = 0;
@Override
public void doFrame(long frameTimeNanos) {
frameCount++;
if (frameCount == waitFrameCount) {
latch.countDown();
} else {
ChoreographerCompat.getInstance().postFrameCallback(this);
}
}
});
}
});
try {
if (!latch.await(timeToWait, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Timed out waiting for Choreographer");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void waitForJSIdle(ReactContext reactContext) {
if (!reactContext.hasActiveCatalystInstance()) {
return;
}
final CountDownLatch latch = new CountDownLatch(1);
reactContext.runOnJSQueueThread(
new Runnable() {
@Override
public void run() {
latch.countDown();
}
});
try {
if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Timed out waiting for JS thread");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void waitInner(ReactBridgeIdleSignaler idleSignaler, long timeToWait) {
// TODO gets broken in gradle, do we need it?
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
long startTime = SystemClock.uptimeMillis();
boolean bridgeWasIdle = false;
while (SystemClock.uptimeMillis() - startTime < timeToWait) {
boolean bridgeIsIdle = idleSignaler.isBridgeIdle();
if (bridgeIsIdle && bridgeWasIdle) {
return;
}
bridgeWasIdle = bridgeIsIdle;
long newTimeToWait = Math.max(1, timeToWait - (SystemClock.uptimeMillis() - startTime));
idleSignaler.waitForIdle(newTimeToWait);
instrumentation.waitForIdleSync();
}
throw new RuntimeException("Timed out waiting for bridge and UI idle!");
}
}
... ...
load("//ReactNative:DEFS.bzl", "rn_android_library", "react_native_dep", "react_native_target")
rn_android_library(
name = "network",
srcs = glob(["**/*.java"]),
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("third-party/android/support/v4:lib-support-v4"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/modules/core:core"),
],
)
... ...
/**
* 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.network;
import javax.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
/**
* Mock Networking module that records last request received by {@link #sendRequest} method and
* returns response code and body that should be set with {@link #setResponse}
*/
@ReactModule(name = "Networking", canOverrideExistingModule = true)
public class NetworkRecordingModuleMock extends ReactContextBaseJavaModule {
public int mRequestCount = 0;
public @Nullable String mRequestMethod;
public @Nullable String mRequestURL;
public @Nullable ReadableArray mRequestHeaders;
public @Nullable ReadableMap mRequestData;
public int mLastRequestId;
public boolean mAbortedRequest;
private boolean mCompleteRequest;
public NetworkRecordingModuleMock(ReactApplicationContext reactContext) {
this(reactContext, true);
}
public NetworkRecordingModuleMock(ReactApplicationContext reactContext, boolean completeRequest) {
super(reactContext);
mCompleteRequest = completeRequest;
}
public static interface RequestListener {
public void onRequest(String method, String url, ReadableArray header, ReadableMap data);
}
private int mResponseCode;
private @Nullable String mResponseBody;
private @Nullable RequestListener mRequestListener;
public void setResponse(int code, String body) {
mResponseCode = code;
mResponseBody = body;
}
public void setRequestListener(RequestListener requestListener) {
mRequestListener = requestListener;
}
@Override
public final String getName() {
return "Networking";
}
private void fireReactCallback(
Callback callback,
int status,
@Nullable String headers,
@Nullable String body) {
callback.invoke(status, headers, body);
}
@ReactMethod
public final void sendRequest(
String method,
String url,
int requestId,
ReadableArray headers,
ReadableMap data,
final String responseType,
boolean incrementalUpdates,
int timeout,
boolean withCredentials) {
mLastRequestId = requestId;
mRequestCount++;
mRequestMethod = method;
mRequestURL = url;
mRequestHeaders = headers;
mRequestData = data;
if (mRequestListener != null) {
mRequestListener.onRequest(method, url, headers, data);
}
if (mCompleteRequest) {
onResponseReceived(requestId, mResponseCode, null);
onDataReceived(requestId, mResponseBody);
onRequestComplete(requestId, null);
}
}
@ReactMethod
public void abortRequest(int requestId) {
mLastRequestId = requestId;
mAbortedRequest = true;
}
@Override
public boolean canOverrideExistingModule() {
return true;
}
public void setCompleteRequest(boolean completeRequest) {
mCompleteRequest = completeRequest;
}
private void onDataReceived(int requestId, String data) {
WritableArray args = Arguments.createArray();
args.pushInt(requestId);
args.pushString(data);
getEventEmitter().emit("didReceiveNetworkData", args);
}
private void onRequestComplete(int requestId, @Nullable String error) {
WritableArray args = Arguments.createArray();
args.pushInt(requestId);
args.pushString(error);
getEventEmitter().emit("didCompleteNetworkResponse", args);
}
private void onResponseReceived(int requestId, int code, WritableMap headers) {
WritableArray args = Arguments.createArray();
args.pushInt(requestId);
args.pushInt(code);
args.pushMap(headers);
getEventEmitter().emit("didReceiveNetworkResponse", args);
}
private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter() {
return getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
}
}
... ...
load("//ReactNative:DEFS.bzl", "rn_android_library", "react_native_dep", "react_native_integration_tests_target", "react_native_target")
rn_android_library(
name = "tests",
srcs = glob(["**/*.java"]),
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("third-party/android/support/v4:lib-support-v4"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_dep("third-party/java/junit:junit"),
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/module/annotations:annotations"),
react_native_target("java/com/facebook/react/modules/appstate:appstate"),
react_native_target("java/com/facebook/react/modules/core:core"),
react_native_target("java/com/facebook/react/modules/datepicker:datepicker"),
react_native_target("java/com/facebook/react/modules/deviceinfo:deviceinfo"),
react_native_target("java/com/facebook/react/modules/share:share"),
react_native_target("java/com/facebook/react/modules/systeminfo:systeminfo"),
react_native_target("java/com/facebook/react/modules/timepicker:timepicker"),
react_native_target("java/com/facebook/react/touch:touch"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
react_native_target("java/com/facebook/react/uimanager/util:util"),
react_native_target("java/com/facebook/react/views/picker:picker"),
react_native_target("java/com/facebook/react/views/progressbar:progressbar"),
react_native_target("java/com/facebook/react/views/scroll:scroll"),
react_native_target("java/com/facebook/react/views/slider:slider"),
react_native_target("java/com/facebook/react/views/swiperefresh:swiperefresh"),
react_native_target("java/com/facebook/react/views/text:text"),
react_native_target("java/com/facebook/react/views/textinput:textinput"),
react_native_target("java/com/facebook/react/views/view:view"),
],
)
... ...
/**
* 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.tests;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.testing.AssertModule;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
/**
* Tests for {@link UIManagerModule#measure}, {@link UIManagerModule#measureLayout}, and
* {@link UIManagerModule#measureLayoutRelativeToParent}. Tests measurement for views in the
* following hierarchy:
*
* +---------------------------------------------+
* | A |
* | |
* | +-----------+ +---------+ |
* | | B | | D | |
* | | +---+ | | | |
* | | | C | | | | |
* | | | | | +---------+ |
* | | +---+ | |
* | +-----------+ |
* | |
* | |
* | |
* +---------------------------------------------+
*
* View locations and dimensions:
* A - (0,0) to (500, 500) (500x500)
* B - (50,80) to (250, 380) (200x300)
* C - (150,150) to (200, 300) (50x150)
* D - (400,100) to (450, 300) (50x200)
*/
public class CatalystMeasureLayoutTest extends ReactAppInstrumentationTestCase {
private static interface MeasureLayoutTestModule extends JavaScriptModule {
public void verifyMeasureOnViewA();
public void verifyMeasureOnViewC();
public void verifyMeasureLayoutCRelativeToA();
public void verifyMeasureLayoutCRelativeToB();
public void verifyMeasureLayoutCRelativeToSelf();
public void verifyMeasureLayoutRelativeToParentOnViewA();
public void verifyMeasureLayoutRelativeToParentOnViewB();
public void verifyMeasureLayoutRelativeToParentOnViewC();
public void verifyMeasureLayoutDRelativeToB();
public void verifyMeasureLayoutNonExistentTag();
public void verifyMeasureLayoutNonExistentAncestor();
public void verifyMeasureLayoutRelativeToParentNonExistentTag();
}
private MeasureLayoutTestModule mTestJSModule;
private AssertModule mAssertModule;
@Override
protected void setUp() throws Exception {
super.setUp();
mTestJSModule = getReactContext().getJSModule(MeasureLayoutTestModule.class);
}
@Override
protected String getReactApplicationKeyUnderTest() {
return "MeasureLayoutTestApp";
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
mAssertModule = new AssertModule();
return super.createReactInstanceSpecForTest()
.addNativeModule(mAssertModule);
}
private void waitForBridgeIdleAndVerifyAsserts() {
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testMeasure() {
mTestJSModule.verifyMeasureOnViewA();
waitForBridgeIdleAndVerifyAsserts();
mTestJSModule.verifyMeasureOnViewC();
waitForBridgeIdleAndVerifyAsserts();
}
public void testMeasureLayout() {
mTestJSModule.verifyMeasureLayoutCRelativeToA();
waitForBridgeIdleAndVerifyAsserts();
mTestJSModule.verifyMeasureLayoutCRelativeToB();
waitForBridgeIdleAndVerifyAsserts();
mTestJSModule.verifyMeasureLayoutCRelativeToSelf();
waitForBridgeIdleAndVerifyAsserts();
}
public void testMeasureLayoutRelativeToParent() {
mTestJSModule.verifyMeasureLayoutRelativeToParentOnViewA();
waitForBridgeIdleAndVerifyAsserts();
mTestJSModule.verifyMeasureLayoutRelativeToParentOnViewB();
waitForBridgeIdleAndVerifyAsserts();
mTestJSModule.verifyMeasureLayoutRelativeToParentOnViewC();
waitForBridgeIdleAndVerifyAsserts();
}
public void testMeasureLayoutCallsErrorCallbackWhenViewIsNotChildOfAncestor() {
mTestJSModule.verifyMeasureLayoutDRelativeToB();
waitForBridgeIdleAndVerifyAsserts();
}
public void testMeasureLayoutCallsErrorCallbackWhenViewDoesNotExist() {
mTestJSModule.verifyMeasureLayoutNonExistentTag();
waitForBridgeIdleAndVerifyAsserts();
}
public void testMeasureLayoutCallsErrorCallbackWhenAncestorDoesNotExist() {
mTestJSModule.verifyMeasureLayoutNonExistentAncestor();
waitForBridgeIdleAndVerifyAsserts();
}
public void testMeasureLayoutRelativeToParentCallsErrorCallbackWhenViewDoesNotExist() {
mTestJSModule.verifyMeasureLayoutRelativeToParentNonExistentTag();
waitForBridgeIdleAndVerifyAsserts();
}
}
... ...
/**
* 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.tests;
import java.util.List;
import android.view.MotionEvent;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.StringRecordingModule;
/**
* Test case for verifying that multitouch events are directed to the React's view touch handlers
* properly
*/
public class CatalystMultitouchHandlingTestCase extends ReactAppInstrumentationTestCase {
private final StringRecordingModule mRecordingModule = new StringRecordingModule();
@Override
protected String getReactApplicationKeyUnderTest() {
return "MultitouchHandlingTestAppModule";
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return new ReactInstanceSpecForTest()
.addNativeModule(mRecordingModule);
}
/**
* In this test case we send pre-recorded stream of pinch out gesture and verify that we have
* recorded important touch events in JS module
*/
public void testMultitouchEvents() throws InterruptedException {
generateRecordedPinchTouchEvents();
waitForBridgeAndUIIdle();
// Expect to receive at least 5 events (DOWN for each pointer, UP for each pointer and at least
// one MOVE event with both pointers down)
List<String> calls = mRecordingModule.getCalls();
int moveWithBothPointersEventIndex = -1;
int startEventIndex = -1;
int startExtraPointerEventIndex = -1;
int endEventIndex = -1;
int endExtraPointerEventIndex = -1;
for (int i = 0; i < calls.size(); i++) {
String call = calls.get(i);
if (call.equals("start;ExtraPointer")) {
assertEquals(-1, startExtraPointerEventIndex);
startExtraPointerEventIndex = i;
} else if (call.equals("end;ExtraPointer")) {
assertEquals(-1, endExtraPointerEventIndex);
endExtraPointerEventIndex = i;
} else if (call.equals("start;1")) {
assertEquals(-1, startEventIndex);
startEventIndex = i;
} else if (call.equals("end;0")) {
assertEquals(-1, endEventIndex);
endEventIndex = i;
} else if (call.equals("move;2")) {
// this will happen more than once, let's just capture the last occurrence
moveWithBothPointersEventIndex = i;
}
}
assertEquals(0, startEventIndex);
assertTrue(-1 != startExtraPointerEventIndex);
assertTrue(-1 != moveWithBothPointersEventIndex);
assertTrue(-1 != endExtraPointerEventIndex);
assertTrue(startExtraPointerEventIndex < moveWithBothPointersEventIndex);
assertTrue(endExtraPointerEventIndex > moveWithBothPointersEventIndex);
assertEquals(calls.size() - 1, endEventIndex);
}
private MotionEvent.PointerProperties createPointerProps(int id, int toolType) {
MotionEvent.PointerProperties pointerProps = new MotionEvent.PointerProperties();
pointerProps.id = id;
pointerProps.toolType = toolType;
return pointerProps;
}
private MotionEvent.PointerCoords createPointerCoords(float x, float y) {
MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
pointerCoords.x = x;
pointerCoords.y = y;
return pointerCoords;
}
private void dispatchEvent(
final int action,
final long start,
final long when,
final int pointerCount,
final MotionEvent.PointerProperties[] pointerProps,
final MotionEvent.PointerCoords[] pointerCoords) {
getRootView().post(
new Runnable() {
@Override
public void run() {
MotionEvent event =
MotionEvent.obtain(start, when, action, pointerCount, pointerProps, pointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0);
getRootView().dispatchTouchEvent(event);
event.recycle();
}
});
getInstrumentation().waitForIdleSync();
}
/**
* This method "replay" multi-touch gesture recorded with modified TouchesHelper class that
* generated this piece of code (see https://phabricator.fb.com/P19756940).
* This is not intended to be copied/reused and once we need to have more multitouch gestures
* in instrumentation tests we should either:
* - implement nice generator similar to {@link SingleTouchGestureGenerator}
* - implement gesture recorded that will record touch data using arbitrary format and then read
* this recorded touch sequence during tests instead of generating code like this
*/
private void generateRecordedPinchTouchEvents() {
// START OF GENERATED CODE
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[1];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(268.0f, 347.0f);
dispatchEvent(MotionEvent.ACTION_DOWN, 446560605, 446560605, 1, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[1];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(267.0f, 346.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560630, 1, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(267.0f, 346.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(225.0f, 542.0f);
dispatchEvent(MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT), 446560605, 446560630, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(266.0f, 345.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(225.0f, 542.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560647, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(265.0f, 344.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(224.0f, 541.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560664, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(264.0f, 342.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(223.0f, 540.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560681, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(263.0f, 340.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(222.0f, 539.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560698, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(262.0f, 337.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(221.0f, 538.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560714, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(262.0f, 333.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(220.0f, 537.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560731, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(261.0f, 328.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(219.0f, 536.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560748, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(260.0f, 321.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(218.0f, 536.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560765, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(260.0f, 313.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(216.0f, 536.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560781, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(260.0f, 304.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(214.0f, 537.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560798, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(260.0f, 295.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(211.0f, 539.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560815, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(261.0f, 285.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(208.0f, 542.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560832, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(264.0f, 274.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(203.0f, 547.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560849, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(266.0f, 264.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(199.0f, 551.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560865, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(269.0f, 254.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(194.0f, 556.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560882, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(273.0f, 245.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(190.0f, 561.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560899, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(276.0f, 236.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(186.0f, 567.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560916, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(280.0f, 227.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(183.0f, 573.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560933, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(283.0f, 219.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(181.0f, 579.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560949, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(287.0f, 211.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(179.0f, 584.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560966, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(291.0f, 202.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(177.0f, 589.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560983, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(296.0f, 193.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(175.0f, 593.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561000, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(301.0f, 184.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(174.0f, 598.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561016, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(307.0f, 176.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(173.0f, 603.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561033, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(313.0f, 168.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(172.0f, 608.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561050, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(317.0f, 160.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(171.0f, 613.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561067, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(320.0f, 154.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(170.0f, 619.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561084, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(323.0f, 149.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(169.0f, 624.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561100, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(325.0f, 145.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(168.0f, 628.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561117, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(328.0f, 141.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(167.0f, 632.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561134, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(331.0f, 137.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(166.0f, 636.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561151, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(334.0f, 134.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(165.0f, 639.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561167, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(337.0f, 131.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(164.0f, 643.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561184, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(338.0f, 128.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(164.0f, 646.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561201, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(340.0f, 126.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(164.0f, 649.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561218, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(341.0f, 124.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(163.0f, 652.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561234, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(342.0f, 122.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(163.0f, 655.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561251, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(343.0f, 120.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(162.0f, 659.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561268, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(344.0f, 118.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(161.0f, 664.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561285, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(345.0f, 116.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(160.0f, 667.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561302, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(346.0f, 115.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(158.0f, 670.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561318, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(347.0f, 114.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(157.0f, 673.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561335, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(348.0f, 113.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(156.0f, 676.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561352, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(348.0f, 112.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(155.0f, 677.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561369, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(349.0f, 111.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(154.0f, 678.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561386, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(349.0f, 110.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(153.0f, 679.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561402, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(349.0f, 109.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(152.0f, 680.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561419, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(349.0f, 110.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(151.0f, 680.0f);
dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561435, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(349.0f, 110.0f);
pointerProps[1] = createPointerProps(1, 1);
pointerCoords[1] = createPointerCoords(151.0f, 680.0f);
dispatchEvent(MotionEvent.ACTION_POINTER_UP | (0 << MotionEvent.ACTION_POINTER_INDEX_SHIFT), 446560605, 446561443, 2, pointerProps, pointerCoords);
}
{
MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[1];
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1];
pointerProps[0] = createPointerProps(0, 1);
pointerCoords[0] = createPointerCoords(151.0f, 680.0f);
dispatchEvent(MotionEvent.ACTION_UP, 446560605, 446561451, 1, pointerProps, pointerCoords);
}
// END OF GENERATED CODE
}
}
... ...
/**
* 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.tests;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.InvalidIteratorException;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NoSuchKeyException;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReadableNativeMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.UnexpectedNativeTypeException;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.appstate.AppStateModule;
import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
import com.facebook.react.modules.systeminfo.AndroidInfoModule;
import com.facebook.react.testing.FakeWebSocketModule;
import com.facebook.react.testing.ReactIntegrationTestCase;
import com.facebook.react.testing.ReactTestHelper;
import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.view.ReactViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Ignore;
/**
* Integration test to verify passing various types of parameters from JS to Java works
*
* TODO: we should run these tests with isBlockingSynchronousMethod = true as well,
* since they currently use a completely different codepath
*/
@Ignore("Fix prop types and view managers.")
public class CatalystNativeJSToJavaParametersTestCase extends ReactIntegrationTestCase {
private interface TestJSToJavaParametersModule extends JavaScriptModule {
void returnBasicTypes();
void returnBoxedTypes();
void returnDynamicTypes();
void returnArrayWithBasicTypes();
void returnNestedArray();
void returnArrayWithMaps();
void returnMapWithBasicTypes();
void returnNestedMap();
void returnMapWithArrays();
void returnArrayWithStringDoubleIntMapArrayBooleanNull();
void returnMapWithStringDoubleIntMapArrayBooleanNull();
void returnMapForMerge1();
void returnMapForMerge2();
void returnMapWithMultibyteUTF8CharacterString();
void returnArrayWithMultibyteUTF8CharacterString();
void returnArrayWithLargeInts();
void returnMapWithLargeInts();
}
private RecordingTestModule mRecordingTestModule;
private CatalystInstance mCatalystInstance;
@Override
protected void setUp() throws Exception {
super.setUp();
List<ViewManager> viewManagers = Arrays.<ViewManager>asList(
new ReactViewManager());
final UIManagerModule mUIManager =
new UIManagerModule(getContext(), viewManagers, new UIImplementationProvider(), 0);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
mUIManager.onHostResume();
}
});
waitForIdleSync();
mRecordingTestModule = new RecordingTestModule();
mCatalystInstance = ReactTestHelper.catalystInstanceBuilder(this)
.addNativeModule(mRecordingTestModule)
.addNativeModule(new AndroidInfoModule())
.addNativeModule(new DeviceInfoModule(getContext()))
.addNativeModule(new AppStateModule(getContext()))
.addNativeModule(new FakeWebSocketModule())
.addNativeModule(mUIManager)
.build();
}
public void testBasicTypes() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnBasicTypes();
waitForBridgeAndUIIdle();
List<Object[]> basicTypesCalls = mRecordingTestModule.getBasicTypesCalls();
assertEquals(1, basicTypesCalls.size());
Object[] args = basicTypesCalls.get(0);
assertEquals("foo", args[0]);
assertEquals(3.14, args[1]);
assertEquals(true, args[2]);
assertNull(args[3]);
}
public void testBoxedTypes() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnBoxedTypes();
waitForBridgeAndUIIdle();
List<Object[]> boxedTypesCalls = mRecordingTestModule.getBoxedTypesCalls();
assertEquals(1, boxedTypesCalls.size());
Object[] args = boxedTypesCalls.get(0);
assertEquals(Integer.valueOf(42), args[0]);
assertEquals(Double.valueOf(3.14), args[1]);
assertEquals(Boolean.valueOf(true), args[2]);
}
public void testDynamicType() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnDynamicTypes();
waitForBridgeAndUIIdle();
List<Dynamic> dynamicCalls = mRecordingTestModule.getDynamicCalls();
assertEquals(2, dynamicCalls.size());
assertEquals("foo", dynamicCalls.get(0).asString());
assertEquals(3.14, dynamicCalls.get(1).asDouble());
}
public void testArrayWithBasicTypes() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnArrayWithBasicTypes();
waitForBridgeAndUIIdle();
List<ReadableArray> calls = mRecordingTestModule.getArrayCalls();
assertEquals(1, calls.size());
ReadableArray array = calls.get(0);
assertNotNull(array);
assertEquals(5, array.size());
assertFalse(array.isNull(0));
assertEquals("foo", array.getString(0));
assertFalse(array.isNull(1));
assertEquals(3.14, array.getDouble(1));
assertFalse(array.isNull(2));
assertEquals(-111, array.getInt(2));
assertFalse(array.isNull(3));
assertTrue(array.getBoolean(3));
assertTrue(array.isNull(4));
assertEquals(null, array.getString(4));
assertEquals(null, array.getMap(4));
assertEquals(null, array.getArray(4));
}
public void testNestedArray() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnNestedArray();
waitForBridgeAndUIIdle();
List<ReadableArray> calls = mRecordingTestModule.getArrayCalls();
assertEquals(1, calls.size());
ReadableArray array = calls.get(0);
assertNotNull(array);
assertEquals(2, array.size());
assertEquals("we", array.getString(0));
assertFalse(array.isNull(1));
ReadableArray subArray = array.getArray(1);
assertEquals(2, subArray.size());
assertEquals("have", subArray.getString(0));
subArray = subArray.getArray(1);
assertEquals(2, subArray.size());
assertEquals("to", subArray.getString(0));
subArray = subArray.getArray(1);
assertEquals(2, subArray.size());
assertEquals("go", subArray.getString(0));
subArray = subArray.getArray(1);
assertEquals(1, subArray.size());
assertEquals("deeper", subArray.getString(0));
}
public void testArrayWithMaps() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnArrayWithMaps();
waitForBridgeAndUIIdle();
List<ReadableArray> calls = mRecordingTestModule.getArrayCalls();
assertEquals(1, calls.size());
ReadableArray array = calls.get(0);
assertEquals(2, array.size());
assertFalse(array.isNull(0));
ReadableMap m1 = array.getMap(0);
ReadableMap m2 = array.getMap(1);
assertEquals("m1v1", m1.getString("m1k1"));
assertEquals("m1v2", m1.getString("m1k2"));
assertEquals("m2v1", m2.getString("m2k1"));
}
public void testMapWithBasicTypes() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapWithBasicTypes();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(1, calls.size());
ReadableMap map = calls.get(0);
assertNotNull(map);
assertTrue(map.hasKey("stringKey"));
assertFalse(map.isNull("stringKey"));
assertEquals("stringValue", map.getString("stringKey"));
assertTrue(map.hasKey("doubleKey"));
assertFalse(map.isNull("doubleKey"));
assertTrue(Math.abs(3.14 - map.getDouble("doubleKey")) < .0001);
assertTrue(map.hasKey("intKey"));
assertFalse(map.isNull("intKey"));
assertEquals(-11, map.getInt("intKey"));
assertTrue(map.hasKey("booleanKey"));
assertFalse(map.isNull("booleanKey"));
assertTrue(map.getBoolean("booleanKey"));
assertTrue(map.hasKey("nullKey"));
assertTrue(map.isNull("nullKey"));
assertNull(map.getString("nullKey"));
assertNull(map.getMap("nullKey"));
assertNull(map.getArray("nullKey"));
assertFalse(map.hasKey("nonExistentKey"));
}
public void testNestedMap() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnNestedMap();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(1, calls.size());
ReadableMap map = calls.get(0);
assertNotNull(map);
assertTrue(map.hasKey("weHaveToGoDeeper"));
assertFalse(map.isNull("weHaveToGoDeeper"));
ReadableMap nestedMap = map.getMap("weHaveToGoDeeper");
assertTrue(nestedMap.hasKey("inception"));
assertTrue(nestedMap.getBoolean("inception"));
}
public void testMapParameterWithArrays() throws InterruptedException {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapWithArrays();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(1, calls.size());
ReadableMap map = calls.get(0);
assertNotNull(map);
ReadableArray arrayParameter;
assertTrue(map.hasKey("empty"));
arrayParameter = map.getArray("empty");
assertNotNull(arrayParameter);
assertEquals(0, arrayParameter.size());
assertTrue(map.hasKey("ints"));
assertFalse(map.isNull("ints"));
arrayParameter = map.getArray("ints");
assertNotNull(arrayParameter);
assertEquals(2, arrayParameter.size());
assertEquals(43, arrayParameter.getInt(0));
assertEquals(44, arrayParameter.getInt(1));
assertTrue(map.hasKey("mixed"));
arrayParameter = map.getArray("mixed");
assertNotNull(arrayParameter);
assertEquals(3, arrayParameter.size());
assertEquals(77, arrayParameter.getInt(0));
assertEquals("string", arrayParameter.getString(1));
ReadableArray nestedArray = arrayParameter.getArray(2);
assertEquals(2, nestedArray.size());
}
public void testMapParameterDump() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapWithBasicTypes();
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnNestedMap();
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapWithArrays();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(3, calls.size());
// App should not crash while generating debug string representation of arguments
assertNotNull(calls.get(0).toString());
assertNotNull(calls.get(1).toString());
assertNotNull(calls.get(2).toString());
}
public void testGetTypeFromArray() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class)
.returnArrayWithStringDoubleIntMapArrayBooleanNull();
waitForBridgeAndUIIdle();
List<ReadableArray> calls = mRecordingTestModule.getArrayCalls();
assertEquals(1, calls.size());
ReadableArray array = calls.get(0);
assertEquals(ReadableType.String, array.getType(0));
assertEquals(ReadableType.Number, array.getType(1));
assertEquals(ReadableType.Number, array.getType(2));
assertEquals(ReadableType.Map, array.getType(3));
assertEquals(ReadableType.Array, array.getType(4));
assertEquals(ReadableType.Boolean, array.getType(5));
assertEquals(ReadableType.Null, array.getType(6));
}
public void testGetTypeFromMap() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class)
.returnMapWithStringDoubleIntMapArrayBooleanNull();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(1, calls.size());
ReadableMap map = calls.get(0);
assertEquals(ReadableType.String, map.getType("string"));
assertEquals(ReadableType.Number, map.getType("double"));
assertEquals(ReadableType.Number, map.getType("int"));
assertEquals(ReadableType.Map, map.getType("map"));
assertEquals(ReadableType.Array, map.getType("array"));
assertEquals(ReadableType.Boolean, map.getType("boolean"));
assertEquals(ReadableType.Null, map.getType("null"));
}
public void testGetWrongTypeFromArray() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class)
.returnArrayWithStringDoubleIntMapArrayBooleanNull();
waitForBridgeAndUIIdle();
List<ReadableArray> calls = mRecordingTestModule.getArrayCalls();
assertEquals(1, calls.size());
ReadableArray array = calls.get(0);
assertUnexpectedTypeExceptionThrown(array, 0, "boolean");
assertUnexpectedTypeExceptionThrown(array, 1, "string");
assertUnexpectedTypeExceptionThrown(array, 2, "array");
assertUnexpectedTypeExceptionThrown(array, 3, "double");
assertUnexpectedTypeExceptionThrown(array, 4, "map");
assertUnexpectedTypeExceptionThrown(array, 5, "array");
}
public void testGetWrongTypeFromMap() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class)
.returnMapWithStringDoubleIntMapArrayBooleanNull();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(1, calls.size());
ReadableMap map = calls.get(0);
assertUnexpectedTypeExceptionThrown(map, "string", "double");
assertUnexpectedTypeExceptionThrown(map, "double", "map");
assertUnexpectedTypeExceptionThrown(map, "int", "boolean");
assertUnexpectedTypeExceptionThrown(map, "map", "array");
assertUnexpectedTypeExceptionThrown(map, "array", "boolean");
assertUnexpectedTypeExceptionThrown(map, "boolean", "string");
}
public void testArrayOutOfBoundsExceptionThrown() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnArrayWithBasicTypes();
waitForBridgeAndUIIdle();
List<ReadableArray> calls = mRecordingTestModule.getArrayCalls();
assertEquals(1, calls.size());
ReadableArray array = calls.get(0);
assertNotNull(array);
assertArrayOutOfBoundsExceptionThrown(array, -1, "boolean");
assertArrayOutOfBoundsExceptionThrown(array, -1, "string");
assertArrayOutOfBoundsExceptionThrown(array, -1, "double");
assertArrayOutOfBoundsExceptionThrown(array, -1, "int");
assertArrayOutOfBoundsExceptionThrown(array, -1, "map");
assertArrayOutOfBoundsExceptionThrown(array, -1, "array");
assertArrayOutOfBoundsExceptionThrown(array, 10, "boolean");
assertArrayOutOfBoundsExceptionThrown(array, 10, "string");
assertArrayOutOfBoundsExceptionThrown(array, 10, "double");
assertArrayOutOfBoundsExceptionThrown(array, 10, "int");
assertArrayOutOfBoundsExceptionThrown(array, 10, "map");
assertArrayOutOfBoundsExceptionThrown(array, 10, "array");
}
public void testNoSuchKeyExceptionThrown() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapWithBasicTypes();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(1, calls.size());
ReadableMap map = calls.get(0);
assertNotNull(map);
assertNoSuchKeyExceptionThrown(map, "noSuchKey", "double");
assertNoSuchKeyExceptionThrown(map, "noSuchKey", "int");
assertNoSuchKeyExceptionThrown(map, "noSuchKey", "map");
assertNoSuchKeyExceptionThrown(map, "noSuchKey", "array");
assertNoSuchKeyExceptionThrown(map, "noSuchKey", "boolean");
assertNoSuchKeyExceptionThrown(map, "noSuchKey", "string");
}
public void testIntOutOfRangeThrown() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnArrayWithLargeInts();
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapWithLargeInts();
waitForBridgeAndUIIdle();
assertEquals(1, mRecordingTestModule.getArrayCalls().size());
assertEquals(1, mRecordingTestModule.getMapCalls().size());
ReadableArray array = mRecordingTestModule.getArrayCalls().get(0);
assertNotNull(array);
ReadableMap map = mRecordingTestModule.getMapCalls().get(0);
assertNotNull(map);
assertEquals(ReadableType.Number, array.getType(0));
assertUnexpectedTypeExceptionThrown(array, 0, "int");
assertEquals(ReadableType.Number, array.getType(1));
assertUnexpectedTypeExceptionThrown(array, 1, "int");
assertEquals(ReadableType.Number, map.getType("first"));
assertUnexpectedTypeExceptionThrown(map, "first", "int");
assertEquals(ReadableType.Number, map.getType("second"));
assertUnexpectedTypeExceptionThrown(map, "second", "int");
}
public void testMapMerging() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapForMerge1();
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapForMerge2();
waitForBridgeAndUIIdle();
List<ReadableMap> maps = mRecordingTestModule.getMapCalls();
assertEquals(2, maps.size());
WritableMap dest = new WritableNativeMap();
dest.merge(maps.get(0));
dest.merge(maps.get(1));
assertTrue(dest.hasKey("a"));
assertTrue(dest.hasKey("b"));
assertTrue(dest.hasKey("c"));
assertTrue(dest.hasKey("d"));
assertTrue(dest.hasKey("e"));
assertTrue(dest.hasKey("f"));
assertTrue(dest.hasKey("newkey"));
assertEquals("overwrite", dest.getString("a"));
assertEquals(41, dest.getInt("b"));
assertEquals("string", dest.getString("c"));
assertEquals(77, dest.getInt("d"));
assertTrue(dest.isNull("e"));
assertEquals(3, dest.getArray("f").size());
assertEquals("newvalue", dest.getString("newkey"));
}
public void testMapAccessibleAfterMerge() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapForMerge1();
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapForMerge2();
waitForBridgeAndUIIdle();
List<ReadableMap> maps = mRecordingTestModule.getMapCalls();
assertEquals(2, maps.size());
WritableMap dest = new WritableNativeMap();
dest.merge(maps.get(0));
dest.merge(maps.get(1));
ReadableMap source = maps.get(1);
assertTrue(source.hasKey("a"));
assertTrue(source.hasKey("d"));
assertTrue(source.hasKey("e"));
assertTrue(source.hasKey("f"));
assertTrue(source.hasKey("newkey"));
assertFalse(source.hasKey("b"));
assertFalse(source.hasKey("c"));
assertEquals("overwrite", source.getString("a"));
assertEquals(77, source.getInt("d"));
assertTrue(source.isNull("e"));
assertEquals(3, source.getArray("f").size());
assertEquals("newvalue", source.getString("newkey"));
}
public void testMapIterateOverMapWithBasicTypes() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapWithBasicTypes();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(1, calls.size());
ReadableNativeMap map = (ReadableNativeMap) calls.get(0);
assertNotNull(map);
ReadableMapKeySetIterator mapIterator = map.keySetIterator();
Set<String> keys = new HashSet<String>();
while (mapIterator.hasNextKey()) {
keys.add(mapIterator.nextKey());
}
Set<String> expectedKeys = new HashSet<String>(
Arrays.asList("stringKey", "doubleKey", "intKey", "booleanKey", "nullKey"));
assertEquals(keys, expectedKeys);
}
public void testMapIterateOverNestedMaps() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnNestedMap();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(1, calls.size());
ReadableNativeMap map = (ReadableNativeMap) calls.get(0);
assertNotNull(map);
ReadableMapKeySetIterator firstLevelIterator = map.keySetIterator();
String firstLevelKey = firstLevelIterator.nextKey();
assertEquals(firstLevelKey, "weHaveToGoDeeper");
ReadableNativeMap secondMap = map.getMap("weHaveToGoDeeper");
ReadableMapKeySetIterator secondLevelIterator = secondMap.keySetIterator();
String secondLevelKey = secondLevelIterator.nextKey();
assertEquals(secondLevelKey, "inception");
assertTrue(secondMap.getBoolean(secondLevelKey));
}
public void testInvalidIteratorExceptionThrown() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapWithBasicTypes();
waitForBridgeAndUIIdle();
List<ReadableMap> calls = mRecordingTestModule.getMapCalls();
assertEquals(1, calls.size());
ReadableNativeMap map = (ReadableNativeMap) calls.get(0);
assertNotNull(map);
ReadableMapKeySetIterator mapIterator = map.keySetIterator();
while (mapIterator.hasNextKey()) {
mapIterator.nextKey();
}
assertInvalidIteratorExceptionThrown(mapIterator);
}
public void testStringWithMultibyteUTF8Characters() {
TestJSToJavaParametersModule jsModule =
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class);
jsModule.returnMapWithMultibyteUTF8CharacterString();
jsModule.returnArrayWithMultibyteUTF8CharacterString();
waitForBridgeAndUIIdle();
List<ReadableMap> maps = mRecordingTestModule.getMapCalls();
assertEquals(1, maps.size());
ReadableMap map = maps.get(0);
assertEquals("a", map.getString("one-byte"));
assertEquals("\u00A2", map.getString("two-bytes"));
assertEquals("\u20AC", map.getString("three-bytes"));
assertEquals("\uD83D\uDE1C", map.getString("four-bytes"));
assertEquals(
"\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 \u6211 \uD83D\uDE0E ja\u017A\u0107",
map.getString("mixed"));
List<ReadableArray> arrays = mRecordingTestModule.getArrayCalls();
assertEquals(1, arrays.size());
ReadableArray array = arrays.get(0);
assertEquals("a", array.getString(0));
assertEquals("\u00A2", array.getString(1));
assertEquals("\u20AC", array.getString(2));
assertEquals("\uD83D\uDE1C", array.getString(3));
assertEquals(
"\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 \u6211 \uD83D\uDE0E ja\u017A\u0107",
array.getString(4));
}
private void assertUnexpectedTypeExceptionThrown(
ReadableArray array,
int index,
String typeToAskFor) {
boolean gotException = false;
try {
arrayGetByType(array, index, typeToAskFor);
} catch (UnexpectedNativeTypeException expected) {
gotException = true;
}
assertTrue(gotException);
}
private void assertUnexpectedTypeExceptionThrown(
ReadableMap map,
String key,
String typeToAskFor) {
boolean gotException = false;
try {
mapGetByType(map, key, typeToAskFor);
} catch (UnexpectedNativeTypeException expected) {
gotException = true;
}
assertTrue(gotException);
}
private void assertArrayOutOfBoundsExceptionThrown(
ReadableArray array,
int index,
String typeToAskFor) {
boolean gotException = false;
try {
arrayGetByType(array, index, typeToAskFor);
} catch (ArrayIndexOutOfBoundsException expected) {
gotException = true;
}
assertTrue(gotException);
}
private void assertNoSuchKeyExceptionThrown(
ReadableMap map,
String key,
String typeToAskFor) {
boolean gotException = false;
try {
mapGetByType(map, key, typeToAskFor);
} catch (NoSuchKeyException expected) {
gotException = true;
}
assertTrue(gotException);
}
private static void assertInvalidIteratorExceptionThrown(
ReadableMapKeySetIterator iterator) {
boolean gotException = false;
try {
iterator.nextKey();
} catch (InvalidIteratorException expected) {
gotException = true;
}
assertTrue(gotException);
}
private void arrayGetByType(ReadableArray array, int index, String typeToAskFor) {
if (typeToAskFor.equals("double")) {
array.getDouble(index);
} else if (typeToAskFor.equals("int")) {
array.getInt(index);
} else if (typeToAskFor.equals("string")) {
array.getString(index);
} else if (typeToAskFor.equals("array")) {
array.getArray(index);
} else if (typeToAskFor.equals("map")) {
array.getMap(index);
} else if (typeToAskFor.equals("boolean")) {
array.getBoolean(index);
} else {
throw new RuntimeException("Unknown type: " + typeToAskFor);
}
}
private void mapGetByType(ReadableMap map, String key, String typeToAskFor) {
if (typeToAskFor.equals("double")) {
map.getDouble(key);
} else if (typeToAskFor.equals("int")) {
map.getInt(key);
} else if (typeToAskFor.equals("string")) {
map.getString(key);
} else if (typeToAskFor.equals("array")) {
map.getArray(key);
} else if (typeToAskFor.equals("map")) {
map.getMap(key);
} else if (typeToAskFor.equals("boolean")) {
map.getBoolean(key);
} else {
throw new RuntimeException("Unknown type: " + typeToAskFor);
}
}
private static class RecordingTestModule extends BaseJavaModule {
private final List<Object[]> mBasicTypesCalls = new ArrayList<Object[]>();
private final List<Object[]> mBoxedTypesCalls = new ArrayList<Object[]>();
private final List<ReadableArray> mArrayCalls = new ArrayList<ReadableArray>();
private final List<ReadableMap> mMapCalls = new ArrayList<ReadableMap>();
private final List<Dynamic> mDynamicCalls = new ArrayList<Dynamic>();
@Override
public String getName() {
return "Recording";
}
@ReactMethod
public void receiveBasicTypes(String s, double d, boolean b, String nullableString) {
mBasicTypesCalls.add(new Object[]{s, d, b, nullableString});
}
@ReactMethod
public void receiveBoxedTypes(Integer i, Double d, Boolean b) {
mBoxedTypesCalls.add(new Object[]{i, d, b});
}
@ReactMethod
public void receiveArray(ReadableArray array) {
mArrayCalls.add(array);
}
@ReactMethod
public void receiveMap(ReadableMap map) {
mMapCalls.add(map);
}
@ReactMethod
public void receiveDynamic(Dynamic dynamic) {
mDynamicCalls.add(dynamic);
}
public List<Object[]> getBasicTypesCalls() {
return mBasicTypesCalls;
}
public List<Object[]> getBoxedTypesCalls() {
return mBoxedTypesCalls;
}
public List<ReadableArray> getArrayCalls() {
return mArrayCalls;
}
public List<ReadableMap> getMapCalls() {
return mMapCalls;
}
public List<Dynamic> getDynamicCalls() {
return mDynamicCalls;
}
}
}
... ...
/**
* 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.tests;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ObjectAlreadyConsumedException;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.appstate.AppStateModule;
import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
import com.facebook.react.testing.AssertModule;
import com.facebook.react.testing.FakeWebSocketModule;
import com.facebook.react.testing.ReactIntegrationTestCase;
import com.facebook.react.testing.ReactTestHelper;
import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.view.ReactViewManager;
import java.util.Arrays;
import java.util.List;
import org.junit.Ignore;
/**
* Test marshalling arguments from Java to JS to appropriate native classes.
*/
@Ignore("Fix prop types and view managers.")
public class CatalystNativeJavaToJSArgumentsTestCase extends ReactIntegrationTestCase {
private interface TestJavaToJSArgumentsModule extends JavaScriptModule {
void receiveBasicTypes(String s, double d, boolean b, String nullString);
void receiveArrayWithBasicTypes(WritableArray array);
void receiveNestedArray(WritableArray nestedArray);
void receiveArrayWithMaps(WritableArray arrayWithMaps);
void receiveMapWithBasicTypes(WritableMap map);
void receiveNestedMap(WritableMap nestedMap);
void receiveMapWithArrays(WritableMap mapWithArrays);
void receiveMapAndArrayWithNullValues(
WritableMap map,
WritableArray array);
void receiveMapWithMultibyteUTF8CharacterString(WritableMap map);
void receiveArrayWithMultibyteUTF8CharacterString(WritableArray array);
}
private AssertModule mAssertModule;
private CatalystInstance mInstance;
@Override
protected void setUp() throws Exception {
super.setUp();
List<ViewManager> viewManagers = Arrays.<ViewManager>asList(
new ReactViewManager());
final UIManagerModule mUIManager =
new UIManagerModule(getContext(), viewManagers, new UIImplementationProvider(), 0);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
mUIManager.onHostResume();
}
});
waitForIdleSync();
mAssertModule = new AssertModule();
mInstance = ReactTestHelper.catalystInstanceBuilder(this)
.addNativeModule(mAssertModule)
.addNativeModule(new DeviceInfoModule(getContext()))
.addNativeModule(new AppStateModule(getContext()))
.addNativeModule(new FakeWebSocketModule())
.addNativeModule(mUIManager)
.build();
}
public void testBasicTypes() {
mInstance.getJSModule(TestJavaToJSArgumentsModule.class)
.receiveBasicTypes("foo", 3.14, true, null);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testArrayWithBasicTypes() {
WritableNativeArray array = new WritableNativeArray();
array.pushString("red panda");
array.pushDouble(1.19);
array.pushBoolean(true);
array.pushNull();
mInstance.getJSModule(TestJavaToJSArgumentsModule.class).receiveArrayWithBasicTypes(array);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testNestedArray() {
WritableNativeArray level1 = new WritableNativeArray();
WritableNativeArray level2 = new WritableNativeArray();
WritableNativeArray level3 = new WritableNativeArray();
level3.pushString("level3");
level2.pushString("level2");
level2.pushArray(level3);
level1.pushString("level1");
level1.pushArray(level2);
mInstance.getJSModule(TestJavaToJSArgumentsModule.class).receiveNestedArray(level1);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testArrayWithMaps() {
WritableNativeMap m1 = new WritableNativeMap();
WritableNativeMap m2 = new WritableNativeMap();
m1.putString("m1k1", "m1v1");
m1.putString("m1k2", "m1v2");
m2.putString("m2k1", "m2v1");
WritableNativeArray array = new WritableNativeArray();
array.pushMap(m1);
array.pushMap(m2);
mInstance.getJSModule(TestJavaToJSArgumentsModule.class).receiveArrayWithMaps(array);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testMapWithBasicTypes() {
WritableNativeMap map = new WritableNativeMap();
map.putString("stringKey", "stringValue");
map.putDouble("doubleKey", 3.14);
map.putBoolean("booleanKey", true);
map.putNull("nullKey");
mInstance.getJSModule(TestJavaToJSArgumentsModule.class).receiveMapWithBasicTypes(map);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testNestedMap() {
WritableNativeMap map = new WritableNativeMap();
WritableNativeMap nestedMap = new WritableNativeMap();
nestedMap.putString("animals", "foxes");
map.putMap("nestedMap", nestedMap);
mInstance.getJSModule(TestJavaToJSArgumentsModule.class).receiveNestedMap(map);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testMapWithArrays() {
WritableNativeMap map = new WritableNativeMap();
WritableNativeArray a1 = new WritableNativeArray();
WritableNativeArray a2 = new WritableNativeArray();
a1.pushDouble(3);
a1.pushDouble(1);
a1.pushDouble(4);
a2.pushDouble(1);
a2.pushDouble(9);
map.putArray("array1", a1);
map.putArray("array2", a2);
mInstance.getJSModule(TestJavaToJSArgumentsModule.class).receiveMapWithArrays(map);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testMapWithNullStringValue() {
WritableNativeMap map = new WritableNativeMap();
map.putString("string", null);
map.putArray("array", null);
map.putMap("map", null);
WritableNativeArray array = new WritableNativeArray();
array.pushString(null);
array.pushArray(null);
array.pushMap(null);
mInstance.getJSModule(TestJavaToJSArgumentsModule.class)
.receiveMapAndArrayWithNullValues(map, array);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testStringWithMultibyteUTF8Characters() {
TestJavaToJSArgumentsModule jsModule = mInstance.getJSModule(TestJavaToJSArgumentsModule.class);
WritableNativeMap map = new WritableNativeMap();
map.putString("two-bytes", "\u00A2");
map.putString("three-bytes", "\u20AC");
map.putString("four-bytes", "\uD83D\uDE1C");
map.putString(
"mixed",
"\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 \u6211 \uD83D\uDE0E ja\u017A\u0107");
jsModule.receiveMapWithMultibyteUTF8CharacterString(map);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
WritableArray array = new WritableNativeArray();
array.pushString("\u00A2");
array.pushString("\u20AC");
array.pushString("\uD83D\uDE1C");
array.pushString(
"\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 \u6211 \uD83D\uDE0E ja\u017A\u0107");
jsModule.receiveArrayWithMultibyteUTF8CharacterString(array);
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testThrowWhenArrayReusedInArray() {
boolean gotException = false;
try {
WritableNativeArray array1 = new WritableNativeArray();
WritableNativeArray array2 = new WritableNativeArray();
WritableNativeArray child = new WritableNativeArray();
array1.pushArray(child);
array2.pushArray(child);
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
}
public void testThrowWhenArrayReusedInMap() {
boolean gotException = false;
try {
WritableNativeMap map1 = new WritableNativeMap();
WritableNativeMap map2 = new WritableNativeMap();
WritableNativeArray child = new WritableNativeArray();
map1.putArray("child", child);
map2.putArray("child", child);
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
}
public void testThrowWhenMapReusedInArray() {
boolean gotException = false;
try {
WritableNativeArray array1 = new WritableNativeArray();
WritableNativeArray array2 = new WritableNativeArray();
WritableNativeMap child = new WritableNativeMap();
array1.pushMap(child);
array2.pushMap(child);
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
}
public void testThrowWhenMapReusedInMap() {
boolean gotException = false;
try {
WritableNativeMap map1 = new WritableNativeMap();
WritableNativeMap map2 = new WritableNativeMap();
WritableNativeMap child = new WritableNativeMap();
map1.putMap("child", child);
map2.putMap("child", child);
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
}
public void testThrowWhenAddToConsumedArray() {
WritableNativeArray array = new WritableNativeArray();
WritableNativeArray parent = new WritableNativeArray();
parent.pushArray(array);
boolean gotException = false;
try {
array.pushNull();
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
array.pushBoolean(true);
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
array.pushDouble(1);
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
array.pushString("foo");
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
array.pushArray(new WritableNativeArray());
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
array.pushMap(new WritableNativeMap());
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
}
public void testThrowWhenAddToConsumedMap() {
WritableNativeMap map = new WritableNativeMap();
WritableNativeArray parent = new WritableNativeArray();
parent.pushMap(map);
boolean gotException = false;
try {
map.putNull("key");
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
map.putBoolean("key", true);
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
map.putDouble("key", 1);
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
map.putString("key", "foo");
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
map.putArray("key", new WritableNativeArray());
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
gotException = false;
try {
map.putMap("key", new WritableNativeMap());
} catch (ObjectAlreadyConsumedException e) {
gotException = true;
}
assertTrue(gotException);
}
}
... ...