This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

3
node_modules/react-native/ReactAndroid/.npmignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Make sure we never publish ReactAndroid/build (Gradle output) to npm.
# The folder is huge (> 100MB)!
build/

View File

@ -0,0 +1,23 @@
Here's how to test the whole dev experience end-to-end. This will be eventually merged into the [Getting Started guide](https://reactnative.dev/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/react-native-community/cli/blob/master/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

7
node_modules/react-native/ReactAndroid/README.md generated vendored Normal file
View File

@ -0,0 +1,7 @@
# Building React Native for Android
See the [docs on the website](https://reactnative.dev/docs/building-from-source.html#android).
# Running tests
When you submit a pull request CircleCI will automatically run all tests. To run tests locally, see [Testing](https://reactnative.dev/docs/testing.html).

470
node_modules/react-native/ReactAndroid/build.gradle generated vendored Normal file
View File

@ -0,0 +1,470 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
plugins {
id("com.android.library")
id("maven")
id("de.undercouch.download")
}
import java.nio.file.Paths
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 customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
def downloadsDir = customDownloadsDir ? new File(customDownloadsDir) : 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.6
// - folly-deprecate-dynamic-initializer
// - glog-0.3.5
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")
// Setup build type for NDK, supported values: {debug, release}
def nativeBuildType = System.getenv("NATIVE_BUILD_TYPE") ?: "release"
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/v${BOOST_VERSION.replace("_", ".")}-0/boost_${BOOST_VERSION}.tar.gz")
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadsDir, "boost_${BOOST_VERSION}.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_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp")
includeEmptyDirs = false
into("$thirdPartyNdkDir/boost")
doLast {
file("$thirdPartyNdkDir/boost/boost").renameTo("$thirdPartyNdkDir/boost/boost_${BOOST_VERSION}")
}
}
task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) {
src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz")
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.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-${DOUBLE_CONVERSION_VERSION}/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/v${FOLLY_VERSION}.tar.gz")
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadsDir, "folly-${FOLLY_VERSION}.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-${FOLLY_VERSION}/folly/**/*", "Android.mk")
eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") }
includeEmptyDirs = false
into("$thirdPartyNdkDir/folly")
}
task prepareHermes() {
def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine")
if (!hermesPackagePath) {
throw new GradleScriptException("Could not find the hermes-engine npm package")
}
def hermesAAR = file("$hermesPackagePath/android/hermes-debug.aar")
if (!hermesAAR.exists()) {
throw new GradleScriptException("The hermes-engine npm package is missing \"android/hermes-debug.aar\"")
}
def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
copy {
from soFiles
from "src/main/jni/first-party/hermes/Android.mk"
into "$thirdPartyNdkDir/hermes"
}
}
task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) {
src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz")
onlyIfNewer(true)
overwrite(false)
dest(new File(downloadsDir, "glog-${GLOG_VERSION}.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-${GLOG_VERSION}/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")
doLast {
copy {
from(fileTree(dir: "$thirdPartyNdkDir/glog", includes: ["stl_logging.h", "logging.h", "raw_logging.h", "vlog_is_on.h", "**/src/glog/log_severity.h"]).files)
includeEmptyDirs = false
into("$thirdPartyNdkDir/glog/exported/glog")
}
}
}
// Create Android.mk library module based on jsc from npm
task prepareJSC {
doLast {
def jscPackagePath = findNodeModulePath(projectDir, "jsc-android")
if (!jscPackagePath) {
throw new GradleScriptException("Could not find the jsc-android npm package")
}
def jscDist = file("$jscPackagePath/dist")
if (!jscDist.exists()) {
throw new GradleScriptException("The jsc-android npm package is missing its \"dist\" directory")
}
def jscAAR = fileTree(jscDist).matching({ it.include "**/android-jsc/**/*.aar" }).singleFile
def soFiles = zipTree(jscAAR).matching({ it.include "**/*.so" })
def headerFiles = fileTree(jscDist).matching({ it.include "**/include/*.h" })
copy {
from(soFiles)
from(headerFiles)
from("src/main/jni/third-party/jsc/Android.mk")
filesMatching("**/*.h", { it.path = "JavaScriptCore/${it.name}" })
includeEmptyDirs(false)
into("$thirdPartyNdkDir/jsc")
}
}
}
task downloadNdkBuildDependencies {
if (!boostPath) {
dependsOn(downloadBoost)
}
dependsOn(downloadDoubleConversion)
dependsOn(downloadFolly)
dependsOn(downloadGlog)
}
/**
* Finds the path of the installed npm package with the given name using Node's
* module resolution algorithm, which searches "node_modules" directories up to
* the file system root. This handles various cases, including:
*
* - Working in the open-source RN repo:
* Gradle: /path/to/react-native/ReactAndroid
* Node module: /path/to/react-native/node_modules/[package]
*
* - Installing RN as a dependency of an app and searching for hoisted
* dependencies:
* Gradle: /path/to/app/node_modules/react-native/ReactAndroid
* Node module: /path/to/app/node_modules/[package]
*
* - Working in a larger repo (e.g., Facebook) that contains RN:
* Gradle: /path/to/repo/path/to/react-native/ReactAndroid
* Node module: /path/to/repo/node_modules/[package]
*
* The search begins at the given base directory (a File object). The returned
* path is a string.
*/
def findNodeModulePath(baseDir, packageName) {
def basePath = baseDir.toPath().normalize()
// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath) {
def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
if (candidatePath.toFile().exists()) {
return candidatePath.toString()
}
basePath = basePath.getParent()
}
return null
}
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.ndkDirectory ? android.ndkDirectory.absolutePath : null
if (ndkDir) {
return new File(ndkDir, getNdkBuildName()).getAbsolutePath()
}
return null
}
def reactNativeDevServerPort() {
def value = project.getProperties().get("reactNativeDevServerPort")
return value != null ? value : "8081"
}
def reactNativeInspectorProxyPort() {
def value = project.getProperties().get("reactNativeInspectorProxyPort")
return value != null ? value : reactNativeDevServerPort()
}
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
}
def buildReactNdkLib = tasks.register("buildReactNdkLib", Exec) {
dependsOn(prepareJSC, prepareHermes, prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog, extractAARHeaders, extractJNIFiles)
inputs.dir("$projectDir/../ReactCommon")
inputs.dir("src/main/jni")
inputs.dir("src/main/java/com/facebook/react/modules/blob")
outputs.dir("$buildDir/react-ndk/all")
commandLine(getNdkBuildFullPath(),
"NDK_DEBUG=" + (nativeBuildType.equalsIgnoreCase("debug") ? "1" : "0"),
"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",
"REACT_SRC_DIR=$projectDir/src/main/java/com/facebook/react",
"-C", file("src/main/jni/react/jni").absolutePath,
"--jobs", project.findProperty("jobs") ?: Runtime.runtime.availableProcessors()
)
}
def cleanReactNdkLib = tasks.register("cleanReactNdkLib", Exec) {
ignoreExitValue(true)
errorOutput(new ByteArrayOutputStream())
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")
doLast {
file(AAR_OUTPUT_URL).delete()
println("Deleted aar output dir at ${file(AAR_OUTPUT_URL)}")
}
}
def packageReactNdkLibs = tasks.register("packageReactNdkLibs", Copy) {
dependsOn(buildReactNdkLib)
from("$buildDir/react-ndk/all")
into("$buildDir/react-ndk/exported")
exclude("**/libjsc.so")
exclude("**/libhermes.so")
}
def packageReactNdkLibsForBuck = tasks.register("packageReactNdkLibsForBuck", Copy) {
dependsOn(packageReactNdkLibs)
from("$buildDir/react-ndk/exported")
into("src/main/jni/prebuilt/lib")
}
task extractAARHeaders {
doLast {
configurations.extractHeaders.files.each {
def file = it.absoluteFile
def packageName = file.name.tokenize('-')[0]
copy {
from zipTree(file)
into "$projectDir/src/main/jni/first-party/$packageName/headers"
include "**/*.h"
}
}
}
}
task extractJNIFiles {
doLast {
configurations.extractJNI.files.each {
def file = it.absoluteFile
def packageName = file.name.tokenize('-')[0]
copy {
from zipTree(file)
into "$projectDir/src/main/jni/first-party/$packageName/"
include "jni/**/*"
}
}
}
}
android {
compileSdkVersion 29
compileOptions {
sourceCompatibility(JavaVersion.VERSION_1_8)
targetCompatibility(JavaVersion.VERSION_1_8)
}
defaultConfig {
minSdkVersion(16)
targetSdkVersion(28)
versionCode(1)
versionName("1.0")
consumerProguardFiles("proguard-rules.pro")
ndk {
moduleName("reactnativejni")
}
buildConfigField("boolean", "IS_INTERNAL_BUILD", "false")
buildConfigField("int", "EXOPACKAGE_FLAGS", "0")
resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort()
resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort()
testApplicationId("com.facebook.react.tests.gradle")
testInstrumentationRunner("androidx.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")
}
configurations {
extractHeaders
extractJNI
}
}
dependencies {
api("com.facebook.infer.annotation:infer-annotation:0.11.2")
api("com.facebook.yoga:proguard-annotations:1.14.1")
api("javax.inject:javax.inject:1")
api("androidx.appcompat:appcompat:1.0.2")
api("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
api("com.facebook.fresco:fresco:${FRESCO_VERSION}")
api("com.facebook.fresco:imagepipeline-okhttp3:${FRESCO_VERSION}")
api("com.facebook.soloader:soloader:${SO_LOADER_VERSION}")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}")
api("com.squareup.okhttp3:okhttp-urlconnection:${OKHTTP_VERSION}")
api("com.squareup.okio:okio:1.15.0")
api("com.facebook.fbjni:fbjni-java-only:0.0.3")
extractHeaders("com.facebook.fbjni:fbjni:0.0.2:headers")
extractJNI("com.facebook.fbjni:fbjni:0.0.2")
testImplementation("junit:junit:${JUNIT_VERSION}")
testImplementation("org.powermock:powermock-api-mockito:${POWERMOCK_VERSION}")
testImplementation("org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}")
testImplementation("org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}")
testImplementation("org.mockito:mockito-core:${MOCKITO_CORE_VERSION}")
testImplementation("org.easytesting:fest-assert-core:${FEST_ASSERT_CORE_VERSION}")
testImplementation("org.robolectric:robolectric:${ROBOLECTRIC_VERSION}")
androidTestImplementation(fileTree(dir: "src/main/third-party/java/buck-android-support/", include: ["*.jar"]))
androidTestImplementation("androidx.test:runner:${ANDROIDX_TEST_VERSION}")
androidTestImplementation("androidx.test:rules:${ANDROIDX_TEST_VERSION}")
androidTestImplementation("org.mockito:mockito-core:${MOCKITO_CORE_VERSION}")
}
apply(from: "release.gradle")

View File

@ -0,0 +1,26 @@
VERSION_NAME=0.63.4
GROUP=com.facebook.react
POM_NAME=ReactNative
POM_ARTIFACT_ID=react-native
POM_PACKAGING=aar
MOCKITO_CORE_VERSION=2.19.1
POWERMOCK_VERSION=1.6.2
ROBOLECTRIC_VERSION=3.0
JUNIT_VERSION=4.12
FEST_ASSERT_CORE_VERSION=2.0M10
ANDROIDX_TEST_VERSION=1.1.0
FRESCO_VERSION=2.0.0
OKHTTP_VERSION=3.12.12
SO_LOADER_VERSION=0.9.0
BOOST_VERSION=1_63_0
DOUBLE_CONVERSION_VERSION=1.1.6
FOLLY_VERSION=2020.01.13.00
GLOG_VERSION=0.3.5
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=false

25
node_modules/react-native/ReactAndroid/libs/BUCK generated vendored Normal file
View File

@ -0,0 +1,25 @@
load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native")
load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_prebuilt_aar")
rn_android_prebuilt_aar(
name = "appcompat",
aar = ":appcompat-binary-aar",
visibility = ["//ReactAndroid/..."],
)
fb_native.remote_file(
name = "appcompat-binary-aar",
sha1 = "132586ec59604a86703796851a063a0ac61f697b",
url = "mvn:com.android.support:appcompat-v7:aar:28.0.0",
)
rn_android_prebuilt_aar(
name = "android-jsc",
aar = ":android-jsc-aar",
)
fb_native.remote_file(
name = "android-jsc-aar",
sha1 = "880cedd93f43e0fc841f01f2fa185a63d9230f85",
url = "mvn:org.webkit:android-jsc:aar:r174650",
)

View File

@ -0,0 +1,70 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# Disabling obfuscation is useful if you collect stack traces from production crashes
# (unless you are using a system that supports de-obfuscate the stack traces).
# -dontobfuscate
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
-keep,allowobfuscation @interface com.facebook.jni.annotations.DoNotStrip
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keep @com.facebook.common.internal.DoNotStrip class *
-keep @com.facebook.jni.annotations.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
@com.facebook.common.internal.DoNotStrip *;
@com.facebook.jni.annotations.DoNotStrip *;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}
-keep class * implements com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * implements com.facebook.react.bridge.NativeModule { *; }
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
-dontwarn com.facebook.react.**
-keep,includedescriptorclasses class com.facebook.react.bridge.** { *; }
# hermes
-keep class com.facebook.jni.** { *; }
# okhttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**

142
node_modules/react-native/ReactAndroid/release.gradle generated vendored Normal file
View File

@ -0,0 +1,142 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
apply(plugin: "maven")
apply(plugin: "signing")
ext {
AAR_OUTPUT_URL = "file://${projectDir}/../android"
}
// 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 project.findProperty("repositoryUrl") ?: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}
def getRepositoryUsername() {
return project.findProperty("repositoryUsername") ?: ""
}
def getRepositoryPassword() {
return project.findProperty("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("MIT 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.javaCompileProvider.get()) {
from(variant.javaCompileProvider.get().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: AAR_OUTPUT_URL)
configureReactNativePom(pom)
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.facebook.react.tests.gradle">
<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" />
<!-- needed for image onError tests -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:hardwareAccelerated="false">
<activity
android:name="com.facebook.react.testing.ReactAppTestActivity"
android:theme="@style/Theme.ReactNative.AppCompat.Light.NoActionBar.FullScreen"
/>
</application>
</manifest>

View File

@ -0,0 +1,7 @@
load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_resource")
rn_android_resource(
name = "assets",
assets = ".",
visibility = ["PUBLIC"],
)

View File

@ -0,0 +1,27 @@
<?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" />
<!-- needed for image onError tests -->
<uses-permission android:name="android.permission.INTERNET" />
<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="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.facebook.react.tests"
android:label="Buck runs instrumentation tests"/>
</manifest>

View File

@ -0,0 +1,23 @@
load("//tools/build_defs/oss:rn_defs.bzl", "KEYSTORE_TARGET", "react_native_dep", "react_native_integration_tests_target", "react_native_target", "rn_android_binary")
# 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 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("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"),
],
)

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridge;
import static com.facebook.react.bridge.Arguments.fromBundle;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.facebook.soloader.SoLoader;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ArgumentsTest {
@Before
public void setUp() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
SoLoader.init(context, false);
}
@Test
public void testFromBundle() {
verifyBundle(createBundle());
}
/**
* When passing a bundle via {@link Intent} extras, it gets parceled and unparceled. Any array of
* bundles will return as an array of {@link Parcelable} instead. This test verifies that {@link
* Arguments#fromArray} handles this situation correctly.
*/
@Test
public void testFromMarshaledBundle() {
verifyBundle(marshalAndUnmarshalBundle(createBundle()));
}
private void verifyBundle(@NonNull Bundle bundle) {
WritableMap map = fromBundle(bundle);
assertNotNull(map);
assertEquals(ReadableType.Array, map.getType("children"));
ReadableArray children = map.getArray("children");
assertNotNull(children);
assertEquals(1, children.size());
assertEquals(ReadableType.Map, children.getType(0));
ReadableMap child = children.getMap(0);
assertNotNull(child);
assertEquals("exampleChild", child.getString("exampleChildKey"));
}
@NonNull
private Bundle marshalAndUnmarshalBundle(@NonNull Bundle bundle) {
Parcel parcel = null;
try {
parcel = Parcel.obtain();
bundle.writeToParcel(parcel, 0);
byte[] bytes = parcel.marshall();
parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0);
return Bundle.CREATOR.createFromParcel(parcel);
} finally {
if (parcel != null) {
parcel.recycle();
}
}
}
@NonNull
private Bundle createBundle() {
Bundle bundle = new Bundle();
Bundle[] children = new Bundle[1];
children[0] = new Bundle();
children[0].putString("exampleChildKey", "exampleChild");
bundle.putSerializable("children", children);
return bundle;
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 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;
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
/** 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;
}
}

View File

@ -0,0 +1,45 @@
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_integration_tests_target", "react_native_target", "rn_android_library")
rn_android_library(
name = "testing",
srcs = glob(
["*.java"],
exclude = [
"idledetection/**/*.java",
"network/**/*.java",
],
),
is_androidx = True,
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("java/com/facebook/hermes/reactexecutor:reactexecutor"),
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
react_native_dep("third-party/android/androidx:annotation"),
react_native_dep("third-party/android/androidx:core"),
react_native_dep("third-party/android/androidx:fragment"),
react_native_dep("third-party/android/androidx:legacy-support-core-ui"),
react_native_dep("third-party/android/androidx:legacy-support-core-utils"),
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/fabric:fabric"),
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"),
],
)

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.uimanager.ViewManagerRegistry;
/** Factory used to create FabricUIManager in Testing infrastructure. */
public interface FabricUIManagerFactory {
UIManager getFabricUIManager(
ReactApplicationContext reactApplicationContext,
ViewManagerRegistry viewManagerRegistry,
JavaScriptContextHolder jsContext);
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import androidx.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) {}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.List;
/**
* 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();
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 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);
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactMethod;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 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;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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();
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import android.content.Intent;
import android.graphics.Bitmap;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.testing.idledetection.IdleWaiter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/** 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();
Intent intent = new Intent();
intent.putExtra(ReactAppTestActivity.EXTRA_IS_FABRIC_TEST, isFabricTest());
setActivityIntent(intent);
final ReactAppTestActivity activity = getActivity();
activity.loadApp(
getReactApplicationKeyUnderTest(), createReactInstanceSpecForTest(), getEnableDevSupport());
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;
}
protected boolean isFabricTest() {
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;
}
}

View File

@ -0,0 +1,374 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
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.JSIModulePackage;
import com.facebook.react.bridge.JSIModuleProvider;
import com.facebook.react.bridge.JSIModuleSpec;
import com.facebook.react.bridge.JSIModuleType;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UIManager;
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.ViewManagerRegistry;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ReactAppTestActivity extends FragmentActivity
implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
public static final String EXTRA_IS_FABRIC_TEST = "is_fabric_test";
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 = 240000;
private final CountDownLatch mDestroyCountDownLatch = new CountDownLatch(1);
private CountDownLatch mLayoutEvent = new CountDownLatch(1);
private @Nullable ReactBridgeIdleSignaler mBridgeIdleSignaler;
private ScreenshotingFrameLayout mScreenshotingFrameLayout;
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);
// This is useful for standalone instrumentation tests, but not SSTs
Intent intent = getIntent();
if (intent != null && intent.getBooleanExtra(EXTRA_IS_FABRIC_TEST, false)) {
mReactRootView.setIsFabric(true);
}
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 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) {
loadBundle(spec, bundleName, useDevSupport);
renderComponent(appKey, initialProps);
}
public void renderComponent(String appKey) {
renderComponent(appKey, null);
}
public void renderComponent(final String appKey, final @Nullable Bundle initialProps) {
// This is used by SSTs
boolean fabricEnabled =
(initialProps != null ? initialProps.getBoolean(EXTRA_IS_FABRIC_TEST, false) : false);
mReactRootView.setIsFabric(fabricEnabled);
final CountDownLatch currentLayoutEvent = mLayoutEvent = new CountDownLatch(1);
runOnUiThread(
new Runnable() {
@Override
public void run() {
Assertions.assertNotNull(mReactRootView)
.getViewTreeObserver()
.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
currentLayoutEvent.countDown();
Assertions.assertNotNull(mReactRootView)
.getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
});
Assertions.assertNotNull(mReactRootView)
.startReactApplication(mReactInstanceManager, appKey, initialProps);
}
});
try {
waitForBridgeAndUIIdle();
waitForLayout(5000);
} catch (InterruptedException e) {
throw new RuntimeException("Layout never occurred for component " + appKey, e);
}
}
public void loadBundle(
final ReactInstanceSpecForTest spec, String bundleName, boolean useDevSupport) {
mBridgeIdleSignaler = new ReactBridgeIdleSignaler();
final ReactInstanceManagerBuilder builder =
ReactTestHelper.getReactTestFactory()
.getReactInstanceManagerBuilder()
.setApplication(getApplication())
.setBundleAssetName(bundleName);
if (spec.getJavaScriptExecutorFactory() != null) {
builder.setJavaScriptExecutorFactory(spec.getJavaScriptExecutorFactory());
}
if (spec.getNativeModuleCallExceptionHandler() != null) {
builder.setNativeModuleCallExceptionHandler(spec.getNativeModuleCallExceptionHandler());
}
if (!spec.getAlternativeReactPackagesForTest().isEmpty()) {
builder.addPackages(spec.getAlternativeReactPackagesForTest());
} else {
builder.addPackage(new MainReactPackage());
}
/**
* The {@link ReactContext#mCurrentActivity} never to be set if initial lifecycle state is
* resumed. So we should call {@link ReactInstanceManagerBuilder#setCurrentActivity}.
*
* <p>Finally,{@link ReactInstanceManagerBuilder#build()} will create instance of {@link
* ReactInstanceManager}. And also will set {@link ReactContext#mCurrentActivity}.
*/
builder.setCurrentActivity(this);
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)
.setJSIModulesPackage(
new JSIModulePackage() {
@Override
public List<JSIModuleSpec> getJSIModules(
final ReactApplicationContext reactApplicationContext,
final JavaScriptContextHolder jsContext) {
return Arrays.<JSIModuleSpec>asList(
new JSIModuleSpec() {
@Override
public JSIModuleType getJSIModuleType() {
return JSIModuleType.UIManager;
}
@Override
public JSIModuleProvider getJSIModuleProvider() {
return new JSIModuleProvider() {
@Override
public UIManager get() {
ViewManagerRegistry viewManagerRegistry =
new ViewManagerRegistry(
mReactInstanceManager.getOrCreateViewManagers(
reactApplicationContext));
FabricUIManagerFactory factory = spec.getFabricUIManagerFactory();
return factory != null
? factory.getFabricUIManager(
reactApplicationContext, viewManagerRegistry, jsContext)
: null;
}
};
}
});
}
});
final CountDownLatch latch = new CountDownLatch(1);
runOnUiThread(
new Runnable() {
@Override
public void run() {
mReactInstanceManager = builder.build();
mReactInstanceManager.onHostResume(
ReactAppTestActivity.this, ReactAppTestActivity.this);
latch.countDown();
}
});
try {
latch.await(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(
"ReactInstanceManager never finished initializing " + bundleName, e);
}
}
private ReactInstanceManager getReactInstanceManager() {
return mReactInstanceManager;
}
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) {}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import android.annotation.SuppressLint;
import androidx.annotation.Nullable;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
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<>();
@Nullable private NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler = null;
@Nullable private FabricUIManagerFactory mFabricUIManagerFactory = null;
@Nullable private JavaScriptExecutorFactory mJavaScriptExecutorFactory = null;
public ReactInstanceSpecForTest addNativeModule(NativeModule module) {
mNativeModules.add(module);
return this;
}
public ReactInstanceSpecForTest setNativeModuleCallExceptionHandler(
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
return this;
}
public NativeModuleCallExceptionHandler getNativeModuleCallExceptionHandler() {
return mNativeModuleCallExceptionHandler;
}
public ReactInstanceSpecForTest setJavaScriptExecutorFactory(
JavaScriptExecutorFactory javaScriptExecutorFactory) {
mJavaScriptExecutorFactory = javaScriptExecutorFactory;
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 setFabricUIManagerFactory(
@Nullable FabricUIManagerFactory fabricUIManagerFactory) {
mFabricUIManagerFactory = fabricUIManagerFactory;
return this;
}
@Nullable
public FabricUIManagerFactory getFabricUIManagerFactory() {
return mFabricUIManagerFactory;
}
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);
}
@Nullable
public JavaScriptExecutorFactory getJavaScriptExecutorFactory() {
return mJavaScriptExecutorFactory;
}
public List<ReactPackage> getAlternativeReactPackagesForTest() {
return mReactPackages;
}
public List<ViewManager> getExtraViewManagers() {
return mViewManagers;
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import android.content.Intent;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.testing.idledetection.IdleWaiter;
/**
* Base class for instrumentation tests that runs React based application.
*
* <p>This is similar to ReactAppInstrumentationTestCase except ReactInstrumentationTest allows
* optional rendering of components. A test case can render no components or render multiple
* components.
*/
public abstract class ReactInstrumentationTest
extends ActivityInstrumentationTestCase2<ReactAppTestActivity> implements IdleWaiter {
protected StringRecordingModule mRecordingModule;
@Nullable protected FabricUIManagerFactory mFabricUIManagerFactory = null;
@Nullable protected JavaScriptExecutorFactory mJavaScriptExecutorFactory = null;
public ReactInstrumentationTest() {
super(ReactAppTestActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
Intent intent = new Intent();
intent.putExtra(ReactAppTestActivity.EXTRA_IS_FABRIC_TEST, isFabricTest());
setActivityIntent(intent);
mRecordingModule = new StringRecordingModule();
final ReactAppTestActivity activity = getActivity();
activity.loadBundle(createReactInstanceSpecForTest(), getBundleName(), getEnableDevSupport());
}
/** Renders this component within this test's activity */
public void renderComponent(final String componentName) {
getActivity().renderComponent(componentName, null);
waitForBridgeAndUIIdle();
}
@Override
protected void tearDown() throws Exception {
ReactAppTestActivity activity = getActivity();
super.tearDown();
activity.waitForDestroy(5000);
}
public ViewGroup getRootView() {
return (ViewGroup) getActivity().getRootView();
}
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 boolean getEnableDevSupport() {
return false;
}
protected boolean isFabricTest() {
return false;
}
protected <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return getReactContext().getJSModule(jsInterface);
}
/** Override this method to provide extra native modules to be loaded before the app starts */
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
ReactInstanceSpecForTest reactInstanceSpecForTest =
new ReactInstanceSpecForTest().addNativeModule(mRecordingModule);
if (mJavaScriptExecutorFactory != null) {
reactInstanceSpecForTest.setJavaScriptExecutorFactory(mJavaScriptExecutorFactory);
}
if (mFabricUIManagerFactory != null) {
reactInstanceSpecForTest.setFabricUIManagerFactory(mFabricUIManagerFactory);
}
return reactInstanceSpecForTest;
}
/** Implement this method to provide the bundle for this test */
protected abstract String getBundleName();
protected ReactContext getReactContext() {
return getActivity().getReactContext();
}
}

View File

@ -0,0 +1,209 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import static org.mockito.Mockito.mock;
import android.test.AndroidTestCase;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
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.ReactChoreographer;
import com.facebook.react.modules.core.TimingModule;
import com.facebook.react.testing.idledetection.ReactBridgeIdleSignaler;
import com.facebook.react.testing.idledetection.ReactIdleDetectionUtil;
import com.facebook.soloader.SoLoader;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 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.
*
* <p>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.
*
* <p>In order to write catalyst integration:
*
* <ol>
* <li>Make {@link ReactIntegrationTestCase} a base class of your test case
* <li>Use {@link ReactTestHelper#catalystInstanceBuilder()} instead of {@link
* com.facebook.react.bridge.CatalystInstanceImpl.Builder} to build catalyst instance for
* testing purposes
* </ol>
*/
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 TimingModule createTimingModule() {
final SimpleSettableFuture<TimingModule> simpleSettableFuture =
new SimpleSettableFuture<TimingModule>();
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
ReactChoreographer.initialize();
TimingModule timingModule =
new TimingModule(getContext(), mock(DevSupportManager.class));
simpleSettableFuture.set(timingModule);
}
});
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);
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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) {}
@Override
public boolean isStartSamplingProfilerOnInit() {
return false;
}
@Override
public void addMenuItem(String title) {}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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.
*
* <p>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);
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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 {
interface ReactInstanceEasyBuilder {
ReactInstanceEasyBuilder setContext(Context context);
ReactInstanceEasyBuilder addNativeModule(NativeModule module);
CatalystInstance build();
}
ReactInstanceEasyBuilder getCatalystInstanceBuilder();
ReactInstanceManagerBuilder getReactInstanceManagerBuilder();
}

View File

@ -0,0 +1,217 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import android.app.Instrumentation;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.Predicate;
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
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.ReactPackage;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.CatalystInstanceImpl;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
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(final NativeModule nativeModule) {
if (mNativeModuleRegistryBuilder == null) {
mNativeModuleRegistryBuilder =
new NativeModuleRegistryBuilder((ReactApplicationContext) mContext, null);
}
Assertions.assertNotNull(nativeModule);
mNativeModuleRegistryBuilder.processPackage(
new ReactPackage() {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.asList(nativeModule);
}
});
return this;
}
@Override
public CatalystInstance build() {
if (mNativeModuleRegistryBuilder == null) {
mNativeModuleRegistryBuilder =
new NativeModuleRegistryBuilder((ReactApplicationContext) mContext, null);
}
JavaScriptExecutor executor = null;
try {
executor = new HermesExecutorFactory().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()
.setJavaScriptExecutorFactory(new HermesExecutorFactory());
}
}
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);
}
};
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Looper;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
/**
* 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();
}
}

View File

@ -0,0 +1,150 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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);
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.ArrayList;
import java.util.List;
/**
* Native module provides single method {@link #record} which records its single string argument in
* calls array
*/
public class StringRecordingModule extends BaseJavaModule {
public static final String NAME = "Recording";
private final List<String> mCalls = new ArrayList<String>();
@Override
public String getName() {
return NAME;
}
@ReactMethod
public void record(String text) {
mCalls.add(text);
}
public void reset() {
mCalls.clear();
}
public List<String> getCalls() {
return mCalls;
}
}

View File

@ -0,0 +1,15 @@
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library")
rn_android_library(
name = "idledetection",
srcs = glob(["**/*.java"]),
is_androidx = True,
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"),
],
)

View File

@ -0,0 +1,14 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing.idledetection;
/** Interface for something that knows how to wait for bridge and UI idle. */
public interface IdleWaiter {
void waitForBridgeAndUIIdle();
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing.idledetection;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 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 onBridgeDestroyed() {
// Do nothing
}
@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);
}
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing.idledetection;
import android.app.Instrumentation;
import android.os.SystemClock;
import androidx.test.InstrumentationRegistry;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.modules.core.ChoreographerCompat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
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() {
final ChoreographerCompat choreographerCompat = ChoreographerCompat.getInstance();
choreographerCompat.postFrameCallback(
new ChoreographerCompat.FrameCallback() {
private int frameCount = 0;
@Override
public void doFrame(long frameTimeNanos) {
frameCount++;
if (frameCount == waitFrameCount) {
latch.countDown();
} else {
choreographerCompat.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!");
}
}

View File

@ -0,0 +1,20 @@
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library")
rn_android_library(
name = "network",
srcs = glob(["**/*.java"]),
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("third-party/android/androidx:annotation"),
react_native_dep("third-party/android/androidx:core"),
react_native_dep("third-party/android/androidx:fragment"),
react_native_dep("third-party/android/androidx:legacy-support-core-ui"),
react_native_dep("third-party/android/androidx:legacy-support-core-utils"),
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"),
],
)

View File

@ -0,0 +1,147 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing.network;
import androidx.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 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);
}
}

View File

@ -0,0 +1,42 @@
# BUILD FILE SYNTAX: SKYLARK
load(
"//tools/build_defs/oss:rn_defs.bzl",
"IS_OSS_BUILD",
"react_native_dep",
"react_native_integration_tests_target",
"react_native_target",
"rn_android_library",
)
rn_android_library(
name = "rule",
srcs = glob(["*.java"]),
is_androidx = True,
visibility = [
"PUBLIC",
],
deps = ([
react_native_dep("third-party/android/androidx:test-espresso-core"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_dep("third-party/java/junit:junit"),
react_native_dep("third-party/java/testing-support-lib:testing-support-lib"),
react_native_dep("third-party/android/androidx:annotation"),
react_native_dep("third-party/android/androidx:appcompat"),
react_native_dep("third-party/android/androidx:core"),
react_native_dep("third-party/android/androidx:fragment"),
react_native_dep("third-party/android/androidx:legacy-support-core-ui"),
react_native_dep("third-party/android/androidx:legacy-support-core-utils"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_integration_tests_target("java/com/facebook/react/testing:testing"),
react_native_integration_tests_target("java/com/facebook/react/testing/idledetection:idledetection"),
react_native_target("java/com/facebook/react:react"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/modules/core:core"),
react_native_target("java/com/facebook/react/shell:shell"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
]) + ([
react_native_dep("java/com/facebook/testing/instrumentation:instrumentation"),
react_native_dep("java/com/facebook/testing/instrumentation/base:base"),
]) if not IS_OSS_BUILD else [],
)

View File

@ -0,0 +1,178 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.testing.rule;
import android.app.Activity;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import androidx.test.rule.ActivityTestRule;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.testing.ReactTestHelper;
import com.facebook.react.testing.idledetection.ReactBridgeIdleSignaler;
import com.facebook.react.testing.idledetection.ReactIdleDetectionUtil;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.UIImplementation;
import com.facebook.react.uimanager.UIManagerModule;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/** A test rule to simplify React Native rendering tests. */
public class ReactNativeTestRule implements TestRule {
// we need a bigger timeout for CI builds because they run on a slow emulator
private static final long IDLE_TIMEOUT_MS = 120000;
@Rule public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
private final String mBundleName;
private ReactPackage mReactPackage;
private ReactInstanceManager mReactInstanceManager;
private ReactBridgeIdleSignaler mBridgeIdleSignaler;
private ReactRootView mView;
private CountDownLatch mLatch;
public ReactNativeTestRule(String bundleName) {
this(bundleName, null);
}
public ReactNativeTestRule(String bundleName, ReactPackage reactPackage) {
mBundleName = bundleName;
mReactPackage = reactPackage;
}
@Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
setUp();
base.evaluate();
tearDown();
}
};
}
@SuppressWarnings("deprecation")
private void setUp() {
final Activity activity = mActivityRule.launchActivity(null);
mView = new ReactRootView(activity);
activity.runOnUiThread(
new Runnable() {
@Override
public void run() {
mBridgeIdleSignaler = new ReactBridgeIdleSignaler();
mReactInstanceManager =
ReactTestHelper.getReactTestFactory()
.getReactInstanceManagerBuilder()
.setApplication(activity.getApplication())
.setBundleAssetName(mBundleName)
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
.setBridgeIdleDebugListener(mBridgeIdleSignaler)
.addPackage(mReactPackage != null ? mReactPackage : new MainReactPackage())
.build();
mReactInstanceManager.onHostResume(activity);
// This threading garbage will be replaced by Surface
final AtomicBoolean isLayoutUpdated = new AtomicBoolean(false);
mReactInstanceManager.addReactInstanceEventListener(
new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
final UIManagerModule uiManagerModule =
reactContext.getCatalystInstance().getNativeModule(UIManagerModule.class);
uiManagerModule
.getUIImplementation()
.setLayoutUpdateListener(
new UIImplementation.LayoutUpdateListener() {
@Override
public void onLayoutUpdated(ReactShadowNode reactShadowNode) {
uiManagerModule.getUIImplementation().removeLayoutUpdateListener();
isLayoutUpdated.set(true);
}
});
}
});
mView
.getViewTreeObserver()
.addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (isLayoutUpdated.get()) {
mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
mLatch.countDown();
}
}
});
}
});
}
private void tearDown() {
final ReactRootView view = mView;
final ReactInstanceManager reactInstanceManager = mReactInstanceManager;
mView = null;
mReactInstanceManager = null;
mActivityRule
.getActivity()
.runOnUiThread(
new Runnable() {
@Override
public void run() {
view.unmountReactApplication();
reactInstanceManager.destroy();
}
});
}
/** Renders the react component and waits until the layout has completed before returning */
public void render(final String componentName) {
mLatch = new CountDownLatch(1);
final Activity activity = mActivityRule.getActivity();
activity.runOnUiThread(
new Runnable() {
@Override
public void run() {
ReactRootView view = getView();
view.startReactApplication(mReactInstanceManager, componentName);
activity.setContentView(view);
}
});
int timeoutSec = 10;
try {
mLatch.await(timeoutSec, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(
"Failed to render " + componentName + " after " + timeoutSec + " seconds");
}
}
public void waitForIdleSync() {
ReactIdleDetectionUtil.waitForBridgeAndUIIdle(
mBridgeIdleSignaler, mReactInstanceManager.getCurrentReactContext(), IDLE_TIMEOUT_MS);
}
/** Returns the react view */
public ReactRootView getView() {
return mView;
}
public ReactContext getContext() {
return mReactInstanceManager.getCurrentReactContext();
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.view.View;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.testing.ReactTestHelper;
import com.facebook.react.testing.StringRecordingModule;
/**
* Integration test for {@code removeClippedSubviews} property that verify correct scrollview
* behavior
*/
public class AnimatedTransformTest extends ReactAppInstrumentationTestCase {
private StringRecordingModule mStringRecordingModule;
@Override
protected String getReactApplicationKeyUnderTest() {
return "AnimatedTransformTestApp";
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
mStringRecordingModule = new StringRecordingModule();
return super.createReactInstanceSpecForTest().addNativeModule(mStringRecordingModule);
}
public void testAnimatedRotation() {
waitForBridgeAndUIIdle();
View button =
ReactTestHelper.getViewWithReactTestId(getActivity().getRootView(), "TouchableOpacity");
// Tap the button which triggers the animated transform containing the
// rotation strings.
createGestureGenerator().startGesture(button).endGesture();
waitForBridgeAndUIIdle();
// The previous cast error will prevent it from getting here
assertEquals(2, mStringRecordingModule.getCalls().size());
}
}

View File

@ -0,0 +1,45 @@
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_integration_tests_target", "react_native_target", "rn_android_library")
rn_android_library(
name = "tests",
srcs = glob(["*.java"]),
is_androidx = True,
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("third-party/android/androidx:annotation"),
react_native_dep("third-party/android/androidx:appcompat"),
react_native_dep("third-party/android/androidx:core"),
react_native_dep("third-party/android/androidx:fragment"),
react_native_dep("third-party/android/androidx:legacy-support-core-ui"),
react_native_dep("third-party/android/androidx:legacy-support-core-utils"),
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"),
],
)

View File

@ -0,0 +1,138 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.testing.AssertModule;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.uimanager.UIManagerModule;
/**
* Tests for {@link UIManagerModule#measure}, {@link UIManagerModule#measureLayout}, and {@link
* UIManagerModule#measureLayoutRelativeToParent}. Tests measurement for views in the following
* hierarchy:
*
* <pre>
* +---------------------------------------------+
* | A |
* | |
* | +-----------+ +---------+ |
* | | B | | D | |
* | | +---+ | | | |
* | | | C | | | | |
* | | | | | +---------+ |
* | | +---+ | |
* | +-----------+ |
* | |
* | |
* | |
* +---------------------------------------------+
* </pre>
*
* <p>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();
}
}

View File

@ -0,0 +1,681 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.view.MotionEvent;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.testing.StringRecordingModule;
import java.util.List;
/**
* 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:
*
* <ul>
* <li>implement nice generator similar to {@link SingleTouchGestureGenerator}
* <li>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
* </ul>
*/
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
}
}

View File

@ -0,0 +1,837 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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.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.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.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
*
* <p>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, 0);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
mUIManager.onHostResume();
}
});
waitForIdleSync();
mRecordingTestModule = new RecordingTestModule();
mCatalystInstance =
ReactTestHelper.catalystInstanceBuilder(this)
.addNativeModule(mRecordingTestModule)
.addNativeModule(new AndroidInfoModule(getContext()))
.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 testEqualityMapAfterMerge() {
mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnMapForMerge1();
waitForBridgeAndUIIdle();
List<ReadableMap> maps = mRecordingTestModule.getMapCalls();
assertEquals(1, maps.size());
WritableMap map1 = new WritableNativeMap();
map1.merge(maps.get(0));
WritableMap map2 = new WritableNativeMap();
map2.merge(maps.get(0));
assertTrue(map1.equals(map2));
}
public void testWritableNativeMapEquals() {
WritableMap map1 = new WritableNativeMap();
WritableMap map2 = new WritableNativeMap();
map1.putInt("key1", 123);
map2.putInt("key1", 123);
map1.putString("key2", "value");
map2.putString("key2", "value");
assertTrue(map1.equals(map2));
}
public void testWritableNativeMapArraysEquals() {
WritableMap map1 = new WritableNativeMap();
WritableMap map2 = new WritableNativeMap();
map1.putInt("key1", 123);
map2.putInt("key1", 123);
map1.putString("key2", "value");
map2.putString("key2", "value");
WritableArray array1 = new WritableNativeArray();
array1.pushInt(321);
array1.pushNull();
array1.pushString("test");
map1.putArray("key3", array1);
WritableArray array2 = new WritableNativeArray();
array1.pushInt(321);
array1.pushNull();
array1.pushString("test");
map2.putArray("key3", array2);
assertTrue(map1.equals(map2));
}
public void testWritableNativeMapArraysNonEquals() {
WritableMap map1 = new WritableNativeMap();
WritableMap map2 = new WritableNativeMap();
map1.putInt("key1", 123);
map2.putInt("key1", 123);
map1.putString("key2", "value");
map2.putString("key2", "value");
WritableArray array1 = new WritableNativeArray();
array1.pushInt(321);
array1.pushNull();
array1.pushString("test");
map1.putArray("key3", array1);
WritableArray array2 = new WritableNativeArray();
array1.pushNull();
array1.pushString("test");
map2.putArray("key3", array2);
assertTrue(map1.equals(map2));
}
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;
}
}
}

View File

@ -0,0 +1,385 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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.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, 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);
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ReactMethod;
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.module.annotations.ReactModule;
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.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import org.junit.Ignore;
/** Test marshalling return values from Java to JS */
@Ignore("Fix prop types and view managers.")
public class CatalystNativeJavaToJSReturnValuesTestCase extends ReactIntegrationTestCase {
private interface TestJavaToJSReturnValuesModule extends JavaScriptModule {
void callMethod(String methodName, String expectedReturnType, String expectedJSON);
void triggerException();
}
@ReactModule(name = "TestModule")
private static class TestModule extends BaseJavaModule {
@Override
public String getName() {
return "TestModule";
}
@ReactMethod(isBlockingSynchronousMethod = true)
boolean getBoolean() {
return true;
}
@ReactMethod(isBlockingSynchronousMethod = true)
Boolean getBoxedBoolean() {
return Boolean.valueOf(true);
}
@ReactMethod(isBlockingSynchronousMethod = true)
int getInt() {
return 42;
}
@ReactMethod(isBlockingSynchronousMethod = true)
Integer getBoxedInt() {
return Integer.valueOf(42);
}
@ReactMethod(isBlockingSynchronousMethod = true)
double getDouble() {
return 3.14159;
}
@ReactMethod(isBlockingSynchronousMethod = true)
Double getBoxedDouble() {
return Double.valueOf(3.14159);
}
@ReactMethod(isBlockingSynchronousMethod = true)
String getString() {
return "Hello world!";
}
@ReactMethod(isBlockingSynchronousMethod = true)
WritableArray getArray() {
WritableArray arr = new WritableNativeArray();
arr.pushString("a");
arr.pushBoolean(true);
return arr;
}
@ReactMethod(isBlockingSynchronousMethod = true)
WritableMap getMap() {
WritableMap map = new WritableNativeMap();
map.putBoolean("a", true);
map.putBoolean("b", false);
return map;
}
@ReactMethod(isBlockingSynchronousMethod = true)
boolean triggerException() {
throw new RuntimeException("Exception triggered");
}
}
private AssertModule mAssertModule;
private CatalystInstance mInstance;
@Override
protected void setUp() throws Exception {
super.setUp();
final UIManagerModule mUIManager =
new UIManagerModule(getContext(), new ArrayList<ViewManager>(), 0);
mAssertModule = new AssertModule();
mInstance =
ReactTestHelper.catalystInstanceBuilder(this)
.addNativeModule(mAssertModule)
.addNativeModule(new DeviceInfoModule(getContext()))
.addNativeModule(new AppStateModule(getContext()))
.addNativeModule(new FakeWebSocketModule())
.addNativeModule(mUIManager)
.addNativeModule(new TestModule())
.build();
}
public void testGetPrimitives() {
TestJavaToJSReturnValuesModule m = mInstance.getJSModule(TestJavaToJSReturnValuesModule.class);
// jboolean is actually an unsigned char, so we don't get JS booleans
m.callMethod("getBoolean", "number", "1");
m.callMethod("getBoxedBoolean", "number", "1");
m.callMethod("getInt", "number", "42");
m.callMethod("getBoxedInt", "number", "42");
m.callMethod("getDouble", "number", "3.14159");
m.callMethod("getBoxedDouble", "number", "3.14159");
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testObjectTypes() {
TestJavaToJSReturnValuesModule m = mInstance.getJSModule(TestJavaToJSReturnValuesModule.class);
m.callMethod("getString", "string", "\"Hello world!\"");
m.callMethod("getArray", "object", "[\"a\",true]");
m.callMethod("getMap", "object", "{\"b\":false,\"a\":true}");
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
public void testThrowsException() {
TestJavaToJSReturnValuesModule m = mInstance.getJSModule(TestJavaToJSReturnValuesModule.class);
m.triggerException();
waitForBridgeAndUIIdle();
mAssertModule.verifyAssertsAndReset();
}
}

View File

@ -0,0 +1,301 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.content.Context;
import android.widget.ScrollView;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.view.ReactViewGroup;
import com.facebook.react.views.view.ReactViewManager;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
/**
* Integration test for {@code removeClippedSubviews} property that verify correct scrollview
* behavior
*/
public class CatalystSubviewsClippingTestCase extends ReactAppInstrumentationTestCase {
private interface SubviewsClippingTestModule extends JavaScriptModule {
void renderClippingSample1();
void renderClippingSample2();
void renderScrollViewTest();
void renderUpdatingSample1(boolean update1, boolean update2);
void renderUpdatingSample2(boolean update);
}
private final List<String> mEvents = new ArrayList<>();
@Override
protected String getReactApplicationKeyUnderTest() {
return "SubviewsClippingTestApp";
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return super.createReactInstanceSpecForTest().addViewManager(new ClippableViewManager(mEvents));
}
/**
* In this test view are layout in a following way:
*
* <pre>
* +-----------------------------+
* | |
* | +---------------------+ |
* | | inner1 | |
* | +---------------------+ |
* | +-------------------------+ |
* | | outer (clip=true) | |
* | | +---------------------+ | |
* | | | inner2 | | |
* | | +---------------------+ | |
* | | | |
* | +-------------------------+ |
* | +---------------------+ |
* | | inner3 | |
* | +---------------------+ |
* | |
* +-----------------------------+
* </pre>
*
* <p>We expect only outer and inner2 to be attached
*/
public void XtestOneLevelClippingInView() throws Throwable {
mEvents.clear();
getReactContext().getJSModule(SubviewsClippingTestModule.class).renderClippingSample1();
waitForBridgeAndUIIdle();
Assert.assertArrayEquals(new String[] {"Attach_outer", "Attach_inner2"}, mEvents.toArray());
}
/**
* In this test view are layout in a following way:
*
* <pre>
* In this test view are layout in a following way:
* +-----------------------------+
* | outer (clip=true) |
* | |
* | |
* | |
* | +-----------------------------+
* | | complexInner (clip=true) |
* | | +----------+ | +---------+ |
* | | | inner1 | | | inner2 | |
* | | | | | | | |
* | | +----------+ | +---------+ |
* +--------------+--------------+ |
* | +----------+ +---------+ |
* | | inner3 | | inner4 | |
* | | | | | |
* | +----------+ +---------+ |
* | |
* +-----------------------------+
* </pre>
*
* <p>We expect outer, complexInner & inner1 to be attached
*/
public void XtestTwoLevelClippingInView() throws Throwable {
mEvents.clear();
getReactContext().getJSModule(SubviewsClippingTestModule.class).renderClippingSample2();
waitForBridgeAndUIIdle();
Assert.assertArrayEquals(
new String[] {"Attach_outer", "Attach_complexInner", "Attach_inner1"}, mEvents.toArray());
}
/**
* This test verifies that we update clipped subviews appropriately when some of them gets
* re-layouted.
*
* <p>In this test scenario we render clipping view ("outer") with two subviews, one is outside
* and clipped and one is inside (absolutely positioned). By updating view props we first change
* the height of the first element so that it should intersect with clipping "outer" view. Then we
* update top position of the second view so that is should go off screen.
*/
public void testClippingAfterLayoutInner() {
SubviewsClippingTestModule subviewsClippingTestModule =
getReactContext().getJSModule(SubviewsClippingTestModule.class);
mEvents.clear();
subviewsClippingTestModule.renderUpdatingSample1(false, false);
waitForBridgeAndUIIdle();
Assert.assertArrayEquals(new String[] {"Attach_outer", "Attach_inner2"}, mEvents.toArray());
mEvents.clear();
subviewsClippingTestModule.renderUpdatingSample1(true, false);
waitForBridgeAndUIIdle();
Assert.assertArrayEquals(new String[] {"Attach_inner1"}, mEvents.toArray());
mEvents.clear();
subviewsClippingTestModule.renderUpdatingSample1(true, true);
waitForBridgeAndUIIdle();
Assert.assertArrayEquals(new String[] {"Detach_inner2"}, mEvents.toArray());
}
/**
* This test verifies that we update clipping views appropriately when parent view layout changes
* in a way that affects clipping.
*
* <p>In this test we render clipping view ("outer") set to be 100x100dp with inner view that is
* absolutely positioned out of the clipping area of the parent view. Then we resize parent view
* so that inner view should be visible.
*/
public void testClippingAfterLayoutParent() {
SubviewsClippingTestModule subviewsClippingTestModule =
getReactContext().getJSModule(SubviewsClippingTestModule.class);
mEvents.clear();
subviewsClippingTestModule.renderUpdatingSample2(false);
waitForBridgeAndUIIdle();
Assert.assertArrayEquals(new String[] {"Attach_outer"}, mEvents.toArray());
mEvents.clear();
subviewsClippingTestModule.renderUpdatingSample2(true);
waitForBridgeAndUIIdle();
Assert.assertArrayEquals(new String[] {"Attach_inner"}, mEvents.toArray());
}
public void testOneLevelClippingInScrollView() throws Throwable {
getReactContext().getJSModule(SubviewsClippingTestModule.class).renderScrollViewTest();
waitForBridgeAndUIIdle();
// Only 3 first views should be attached at the beginning
Assert.assertArrayEquals(new String[] {"Attach_0", "Attach_1", "Attach_2"}, mEvents.toArray());
mEvents.clear();
// We scroll down such that first view get out of the bounds, we expect the first view to be
// detached and 4th view to get attached
scrollToDpInUIThread(120);
Assert.assertArrayEquals(new String[] {"Detach_0", "Attach_3"}, mEvents.toArray());
}
public void testTwoLevelClippingInScrollView() throws Throwable {
getReactContext().getJSModule(SubviewsClippingTestModule.class).renderScrollViewTest();
waitForBridgeAndUIIdle();
final int complexViewOffset = 4 * 120 - 300;
// Step 1
// We scroll down such that first "complex" view is clipped & just below the bottom of the
// scroll view
scrollToDpInUIThread(complexViewOffset);
mEvents.clear();
// Step 2
// Scroll a little bit so that "complex" view is visible, but it's inner views are not
scrollToDpInUIThread(complexViewOffset + 5);
Assert.assertArrayEquals(new String[] {"Attach_C0"}, mEvents.toArray());
mEvents.clear();
// Step 3
// Scroll even more so that first subview of "complex" view is visible, view 1 will get off
// screen
scrollToDpInUIThread(complexViewOffset + 100);
Assert.assertArrayEquals(new String[] {"Detach_1", "Attach_C0.1"}, mEvents.toArray());
mEvents.clear();
// Step 4
// Scroll even more to reveal second subview of "complex" view
scrollToDpInUIThread(complexViewOffset + 150);
Assert.assertArrayEquals(new String[] {"Attach_C0.2"}, mEvents.toArray());
mEvents.clear();
// Step 5
// Scroll back to previous position (Step 3), second view should get detached
scrollToDpInUIThread(complexViewOffset + 100);
Assert.assertArrayEquals(new String[] {"Detach_C0.2"}, mEvents.toArray());
mEvents.clear();
// Step 6
// Scroll back to Step 2, complex view should be visible but all subviews should be detached
scrollToDpInUIThread(complexViewOffset + 5);
Assert.assertArrayEquals(new String[] {"Attach_1", "Detach_C0.1"}, mEvents.toArray());
mEvents.clear();
// Step 7
// Scroll back to Step 1, complex view should be gone
scrollToDpInUIThread(complexViewOffset);
Assert.assertArrayEquals(new String[] {"Detach_C0"}, mEvents.toArray());
}
private void scrollToDpInUIThread(final int yPositionInDP) throws Throwable {
final ScrollView mainScrollView = getViewByTestId("scroll_view");
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
mainScrollView.scrollTo(0, (int) PixelUtil.toPixelFromDIP(yPositionInDP));
}
});
waitForBridgeAndUIIdle();
}
private static class ClippableView extends ReactViewGroup {
private String mClippableViewID;
private final List<String> mEvents;
public ClippableView(Context context, List<String> events) {
super(context);
mEvents = events;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mEvents.add("Attach_" + mClippableViewID);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mEvents.add("Detach_" + mClippableViewID);
}
public void setClippableViewID(String clippableViewID) {
mClippableViewID = clippableViewID;
}
}
private static class ClippableViewManager extends ReactViewManager {
private final List<String> mEvents;
public ClippableViewManager(List<String> events) {
mEvents = events;
}
@Override
public String getName() {
return "ClippableView";
}
@Override
public ReactViewGroup createViewInstance(ThemedReactContext context) {
return new ClippableView(context, mEvents);
}
@ReactProp(name = "clippableViewID")
public void setClippableViewId(ReactViewGroup view, @Nullable String clippableViewId) {
((ClippableView) view).setClippableViewID(clippableViewId);
}
}
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.view.View;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.testing.SingleTouchGestureGenerator;
import com.facebook.react.testing.StringRecordingModule;
/**
* This test is to verify that touch events bubbles up to the right handler. We emulate couple of
* different gestures on top of the application reflecting following layout:
*
* <pre>
* +---------------------------------------------------------------------------------------+
* | |
* | +----------------------------------------------------------------------------------+ |
* | | +-------------+ +----------------+ | |
* | | | +---+ | | | | |
* | | | | A | | | | | |
* | | | +---+ | | C | | |
* | | | {B} | | | | |
* | | | | {D} | | | |
* | | +-------------+ +----------------+ | |
* | | | |
* | | | |
* | +----------------------------------------------------------------------------------+ |
* |
* | +----------------------------------------------------------------------------------+ |
* | | | |
* | | | |
* | | | |
* | | {E} | |
* | | | |
* | | | |
* | +----------------------------------------------------------------------------------+ |
* +---------------------------------------------------------------------------------------+
* </pre>
*
* <p>Then in each test case we either tap the center of a particular view (from A to E) or we start
* a gesture in one view and end it with another. View with names in brackets (e.g. {D}) have touch
* handlers set whereas all other views are not declared to handler touch events.
*/
public class CatalystTouchBubblingTestCase extends ReactAppInstrumentationTestCase {
private final StringRecordingModule mRecordingModule = new StringRecordingModule();
@Override
protected String getReactApplicationKeyUnderTest() {
return "TouchBubblingTestAppModule";
}
/**
* 1) Simulate touch event at view A, expect {B} touch handler to fire 2) Simulate touch event at
* view C, expect {D} touch handler to fire
*/
public void testSimpleClickAtInnerElements() {
mRecordingModule.reset();
View innerButton = getViewByTestId("A");
assertNotNull(innerButton);
createGestureGenerator().startGesture(innerButton).endGesture();
waitForBridgeAndUIIdle();
assertEquals(1, mRecordingModule.getCalls().size());
assertEquals("inner", mRecordingModule.getCalls().get(0));
mRecordingModule.reset();
innerButton = getViewByTestId("C");
assertNotNull(innerButton);
createGestureGenerator().startGesture(innerButton).endGesture();
waitForBridgeAndUIIdle();
assertEquals(1, mRecordingModule.getCalls().size());
assertEquals("outer", mRecordingModule.getCalls().get(0));
}
/**
* 1) Start touch at view A, then drag and release on view {B} (but outside of A), expect {B}'s
* touch handler to fire 2) Do the same with view C and {D}
*/
public void testDownOnInnerUpOnTouchableParent() {
View innerButton = getViewByTestId("A");
View touchableParent = getViewByTestId("B");
SingleTouchGestureGenerator gestureGenerator = createGestureGenerator();
gestureGenerator.startGesture(innerButton);
// wait for tapped view measurements
waitForBridgeAndUIIdle();
gestureGenerator.dragTo(touchableParent, 15).endGesture();
waitForBridgeAndUIIdle();
assertEquals(1, mRecordingModule.getCalls().size());
assertEquals("inner", mRecordingModule.getCalls().get(0));
// Do same with second inner view
mRecordingModule.reset();
touchableParent = getViewByTestId("D");
innerButton = getViewByTestId("C");
gestureGenerator = createGestureGenerator();
gestureGenerator.startGesture(innerButton);
// wait for tapped view measurements
waitForBridgeAndUIIdle();
gestureGenerator.dragTo(touchableParent, 15).endGesture();
waitForBridgeAndUIIdle();
assertEquals(1, mRecordingModule.getCalls().size());
assertEquals("outer", mRecordingModule.getCalls().get(0));
}
/**
* Start gesture at view A, then drag and release on view {E}. Expect no touch handlers to fire
*/
public void testDragOutOfTouchable() {
View outsideView = getViewByTestId("E");
View innerButton = getViewByTestId("A");
SingleTouchGestureGenerator gestureGenerator = createGestureGenerator();
gestureGenerator.startGesture(innerButton);
// wait for tapped view measurements
waitForBridgeAndUIIdle();
gestureGenerator.dragTo(outsideView, 15).endGesture();
waitForBridgeAndUIIdle();
assertTrue(mRecordingModule.getCalls().isEmpty());
}
/**
* In this scenario we start gesture at view A (has two touchable parents {B} and {D}) then we
* drag and release gesture on view {D}, but outside of {B}. We expect no touch handler to fire
*/
public void testNoEventWhenDragOutOfFirstTouchableParentToItsTouchableParent() {
View topLevelTouchable = getViewByTestId("C");
View innerButton = getViewByTestId("A");
SingleTouchGestureGenerator gestureGenerator = createGestureGenerator();
gestureGenerator.startGesture(innerButton);
// wait for tapped view measurements
waitForBridgeAndUIIdle();
gestureGenerator.dragTo(topLevelTouchable, 15).endGesture();
waitForBridgeAndUIIdle();
assertTrue(mRecordingModule.getCalls().isEmpty());
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return new ReactInstanceSpecForTest().addNativeModule(mRecordingModule);
}
}

View File

@ -0,0 +1,256 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.UiThreadUtil;
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.PixelUtil;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.text.ReactRawTextManager;
import com.facebook.react.views.text.ReactTextViewManager;
import com.facebook.react.views.view.ReactViewManager;
import java.util.Arrays;
import java.util.List;
/** Test case for basic {@link UIManagerModule} functionality. */
public class CatalystUIManagerTestCase extends ReactIntegrationTestCase {
private interface UIManagerTestModule extends JavaScriptModule {
void renderFlexTestApplication(int rootTag);
void renderFlexWithTextApplication(int rootTag);
void renderAbsolutePositionTestApplication(int rootTag);
void renderAbsolutePositionBottomRightTestApplication(int rootTag);
void renderCenteredTextViewTestApplication(int rootTag, String text);
void renderUpdatePositionInListTestApplication(int rootTag);
void flushUpdatePositionInList();
}
private UIManagerTestModule jsModule;
private UIManagerModule uiManager;
private int inPixelRounded(int val) {
return Math.round(PixelUtil.toPixelFromDIP(val));
}
private boolean isWithinRange(float value, float lower, float upper) {
return value >= lower && value <= upper;
}
private ReactRootView createRootView() {
ReactRootView rootView = new ReactRootView(getContext());
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
rootView.setLayoutParams(
new FrameLayout.LayoutParams(metrics.widthPixels, metrics.heightPixels));
uiManager.addRootView(rootView);
// We add the root view by posting to the main thread so wait for that to complete so that the
// root view tag is added to the view
waitForIdleSync();
return rootView;
}
@Override
protected void setUp() throws Exception {
super.setUp();
List<ViewManager> viewManagers =
Arrays.<ViewManager>asList(
new ReactViewManager(), new ReactTextViewManager(), new ReactRawTextManager());
uiManager = new UIManagerModule(getContext(), viewManagers, 0);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
uiManager.onHostResume();
}
});
waitForIdleSync();
jsModule =
ReactTestHelper.catalystInstanceBuilder(this)
.addNativeModule(uiManager)
.addNativeModule(new AndroidInfoModule(getContext()))
.addNativeModule(new DeviceInfoModule(getContext()))
.addNativeModule(new AppStateModule(getContext()))
.addNativeModule(new FakeWebSocketModule())
.build()
.getJSModule(UIManagerTestModule.class);
}
public void testFlexUIRendered() {
FrameLayout rootView = createRootView();
jsModule.renderFlexTestApplication(rootView.getId());
waitForBridgeAndUIIdle();
assertEquals(1, rootView.getChildCount());
ViewGroup container = getViewByTestId(rootView, "container");
assertEquals(inPixelRounded(200), container.getWidth());
assertEquals(inPixelRounded(200), container.getHeight());
assertEquals(2, container.getChildCount());
View child0 = container.getChildAt(0);
assertEquals(inPixelRounded(100), child0.getWidth());
assertEquals(inPixelRounded(200), child0.getHeight());
View child1 = container.getChildAt(1);
assertEquals(inPixelRounded(100), child1.getWidth());
assertEquals(inPixelRounded(200), child1.getHeight());
}
// TODO t13583009
// Breaks OSS CI but runs fine locally
// Find what could be different and make the test independent of env
// public void testFlexWithTextViews() {
// FrameLayout rootView = createRootView();
// jsModule.renderFlexWithTextApplication(rootView.getId());
// waitForBridgeAndUIIdle();
//
// assertEquals(1, rootView.getChildCount());
//
// ViewGroup container = getViewByTestId(rootView, "container");
// assertEquals(inPixelRounded(300), container.getHeight());
// assertEquals(1, container.getChildCount());
//
// ViewGroup row = (ViewGroup) container.getChildAt(0);
// assertEquals(inPixelRounded(300), row.getHeight());
// assertEquals(2, row.getChildCount());
//
// // Text measurement adds padding that isn't completely dependent on density so we can't
// easily
// // get an exact value here
// float approximateExpectedTextHeight = inPixelRounded(19);
// View leftText = row.getChildAt(0);
// assertTrue(
// isWithinRange(
// leftText.getHeight(),
// approximateExpectedTextHeight - PixelUtil.toPixelFromDIP(1),
// approximateExpectedTextHeight + PixelUtil.toPixelFromDIP(1)));
// assertEquals(row.getWidth() / 2 - inPixelRounded(20), leftText.getWidth());
// assertEquals(inPixelRounded(290), (leftText.getTop() + leftText.getHeight()));
//
// View rightText = row.getChildAt(1);
// assertTrue(
// isWithinRange(
// rightText.getHeight(),
// approximateExpectedTextHeight - PixelUtil.toPixelFromDIP(1),
// approximateExpectedTextHeight + PixelUtil.toPixelFromDIP(1)));
// assertEquals(leftText.getWidth(), rightText.getWidth());
// assertEquals(leftText.getTop(), rightText.getTop());
// assertEquals(leftText.getWidth() + inPixelRounded(30), rightText.getLeft());
// }
public void testAbsolutePositionUIRendered() {
FrameLayout rootView = createRootView();
jsModule.renderAbsolutePositionTestApplication(rootView.getId());
waitForBridgeAndUIIdle();
assertEquals(1, rootView.getChildCount());
View absoluteView = getViewByTestId(rootView, "absolute");
assertEquals(inPixelRounded(50), absoluteView.getWidth());
assertEquals(inPixelRounded(60), absoluteView.getHeight());
assertEquals(inPixelRounded(10), absoluteView.getLeft());
assertEquals(inPixelRounded(15), absoluteView.getTop());
}
public void testUpdatePositionInList() {
FrameLayout rootView = createRootView();
jsModule.renderUpdatePositionInListTestApplication(rootView.getId());
waitForBridgeAndUIIdle();
ViewGroup containerView = getViewByTestId(rootView, "container");
View c0 = containerView.getChildAt(0);
View c1 = containerView.getChildAt(1);
View c2 = containerView.getChildAt(2);
assertEquals(inPixelRounded(10), c0.getHeight());
assertEquals(inPixelRounded(0), c0.getTop());
assertEquals(inPixelRounded(10), c1.getHeight());
assertEquals(inPixelRounded(10), c1.getTop());
assertEquals(inPixelRounded(10), c2.getHeight());
assertEquals(inPixelRounded(20), c2.getTop());
// Let's make the second elements 50px height instead of 10px
jsModule.flushUpdatePositionInList();
waitForBridgeAndUIIdle();
assertEquals(inPixelRounded(10), c0.getHeight());
assertEquals(inPixelRounded(0), c0.getTop());
assertEquals(inPixelRounded(50), c1.getHeight());
assertEquals(inPixelRounded(10), c1.getTop());
assertEquals(inPixelRounded(10), c2.getHeight());
assertEquals(inPixelRounded(60), c2.getTop());
}
public void testAbsolutePositionBottomRightUIRendered() {
FrameLayout rootView = createRootView();
jsModule.renderAbsolutePositionBottomRightTestApplication(rootView.getId());
waitForBridgeAndUIIdle();
assertEquals(1, rootView.getChildCount());
ViewGroup containerView = getViewByTestId(rootView, "container");
View absoluteView = containerView.getChildAt(0);
assertEquals(inPixelRounded(50), absoluteView.getWidth());
assertEquals(inPixelRounded(60), absoluteView.getHeight());
assertEquals(inPixelRounded(100 - 50 - 10), Math.round(absoluteView.getLeft()));
assertEquals(inPixelRounded(100 - 60 - 15), Math.round(absoluteView.getTop()));
}
public void _testCenteredText(String text) {
ReactRootView rootView = new ReactRootView(getContext());
int rootTag = uiManager.addRootView(rootView);
jsModule.renderCenteredTextViewTestApplication(rootTag, text);
waitForBridgeAndUIIdle();
TextView textView = getViewByTestId(rootView, "text");
// text view should be centered
String msg = "text `" + text + "` is not centered";
assertTrue(msg, textView.getLeft() > 0.1);
assertTrue(msg, textView.getTop() > 0.1);
assertEquals(
msg,
(int) Math.ceil((inPixelRounded(200) - textView.getWidth()) * 0.5f),
textView.getLeft());
assertEquals(
msg,
(int) Math.ceil((inPixelRounded(100) - textView.getHeight()) * 0.5f),
textView.getTop());
}
public void testCenteredTextCases() {
String[] cases =
new String[] {
"test", "with whitespace",
};
for (String text : cases) {
_testCenteredText(text);
}
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.app.DatePickerDialog;
import android.content.DialogInterface;
import android.widget.DatePicker;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.datepicker.DatePickerDialogModule;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/** Test case for {@link DatePickerDialogModule} options and callbacks. */
public class DatePickerDialogTestCase extends ReactAppInstrumentationTestCase {
private static interface DatePickerDialogTestModule extends JavaScriptModule {
public void showDatePickerDialog(WritableMap options);
}
private static class DatePickerDialogRecordingModule extends BaseJavaModule {
private final List<Integer[]> mDates = new ArrayList<Integer[]>();
private int mDismissed = 0;
private int mErrors = 0;
@Override
public String getName() {
return "DatePickerDialogRecordingModule";
}
@ReactMethod
public void recordDate(int year, int month, int day) {
mDates.add(new Integer[] {year, month, day});
}
@ReactMethod
public void recordDismissed() {
mDismissed++;
}
@ReactMethod
public void recordError() {
mErrors++;
}
public List<Integer[]> getDates() {
return new ArrayList<Integer[]>(mDates);
}
public int getDismissed() {
return mDismissed;
}
public int getErrors() {
return mErrors;
}
}
final DatePickerDialogRecordingModule mRecordingModule = new DatePickerDialogRecordingModule();
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return super.createReactInstanceSpecForTest().addNativeModule(mRecordingModule);
}
@Override
protected String getReactApplicationKeyUnderTest() {
return "DatePickerDialogTestApp";
}
private static long getDateInMillis(int year, int month, int date) {
final Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, year);
c.set(Calendar.MONTH, month);
c.set(Calendar.DATE, date);
return c.getTimeInMillis();
}
private DatePickerDialogTestModule getTestModule() {
return getReactContext().getCatalystInstance().getJSModule(DatePickerDialogTestModule.class);
}
private DialogFragment showDialog(WritableMap options) {
getTestModule().showDatePickerDialog(options);
waitForBridgeAndUIIdle();
getInstrumentation().waitForIdleSync();
return (DialogFragment)
getActivity()
.getSupportFragmentManager()
.findFragmentByTag(DatePickerDialogModule.FRAGMENT_TAG);
}
public void testShowBasicDatePicker() {
final Fragment datePickerFragment = showDialog(null);
assertNotNull(datePickerFragment);
}
public void testPresetDate() {
final WritableMap options = new WritableNativeMap();
options.putDouble("date", getDateInMillis(2020, 5, 6));
final DialogFragment datePickerFragment = showDialog(options);
final DatePicker datePicker =
((DatePickerDialog) datePickerFragment.getDialog()).getDatePicker();
assertEquals(2020, datePicker.getYear());
assertEquals(5, datePicker.getMonth());
assertEquals(6, datePicker.getDayOfMonth());
}
public void testCallback() throws Throwable {
final WritableMap options = new WritableNativeMap();
options.putDouble("date", getDateInMillis(2020, 5, 6));
final DialogFragment datePickerFragment = showDialog(options);
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
((DatePickerDialog) datePickerFragment.getDialog())
.getButton(DialogInterface.BUTTON_POSITIVE)
.performClick();
}
});
getInstrumentation().waitForIdleSync();
waitForBridgeAndUIIdle();
assertEquals(0, mRecordingModule.getErrors());
assertEquals(1, mRecordingModule.getDates().size());
assertEquals(2020, (int) mRecordingModule.getDates().get(0)[0]);
assertEquals(5, (int) mRecordingModule.getDates().get(0)[1]);
assertEquals(6, (int) mRecordingModule.getDates().get(0)[2]);
}
public void testDismissCallback() throws Throwable {
final DialogFragment datePickerFragment = showDialog(null);
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
datePickerFragment.getDialog().dismiss();
}
});
getInstrumentation().waitForIdleSync();
waitForBridgeAndUIIdle();
assertEquals(0, mRecordingModule.getErrors());
assertEquals(0, mRecordingModule.getDates().size());
assertEquals(1, mRecordingModule.getDismissed());
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.testing.StringRecordingModule;
/** Simple test case to check that onError does not get called with undefined */
public class ImageErrorTestCase extends ReactAppInstrumentationTestCase {
private StringRecordingModule mStringRecordingModule;
@Override
protected String getReactApplicationKeyUnderTest() {
return "ImageErrorTestApp";
}
public void testErrorHasCause() throws Exception {
assertNotNull(getViewByTestId("image-1"));
assertNotNull(getViewByTestId("image-2"));
assertNotNull(getViewByTestId("image-3"));
Thread.sleep(3000);
assertEquals(3, mStringRecordingModule.getCalls().size());
assertEquals(
"Got error: Unsupported uri scheme! Uri is: ", mStringRecordingModule.getCalls().get(0));
assertEquals(
"Got error: /does/not/exist: open failed: ENOENT (No such file or directory)",
mStringRecordingModule.getCalls().get(1));
assertEquals(
"Got error: Unexpected HTTP code Response{protocol=http/1.1, code=404, message=Not Found, url=https://typo_error_facebook.github.io/react/logo-og.png}",
mStringRecordingModule.getCalls().get(2));
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
mStringRecordingModule = new StringRecordingModule();
return super.createReactInstanceSpecForTest().addNativeModule(mStringRecordingModule);
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.view.View;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
/** Simple test case for passing overlayColor prop to the Image component */
public class ImageOverlayColorTestCase extends ReactAppInstrumentationTestCase {
@Override
protected String getReactApplicationKeyUnderTest() {
return "ImageOverlayColorTestApp";
}
public void testOverlayColorDoesNotCrash() {
View image = getViewByTestId("image");
assertNotNull(image);
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.os.Bundle;
import android.test.ActivityInstrumentationTestCase2;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.testing.FakeWebSocketModule;
import com.facebook.react.testing.ReactAppTestActivity;
import com.facebook.react.testing.ReactInstanceSpecForTest;
/** Simple test case for passing initial props to the root React application. */
public class InitialPropsTestCase extends ActivityInstrumentationTestCase2<ReactAppTestActivity> {
public static final String DEFAULT_JS_BUNDLE = "AndroidTestBundle.js";
private static class RecordingModule extends BaseJavaModule {
private int mCount = 0;
private ReadableMap mProps;
@Override
public String getName() {
return "InitialPropsRecordingModule";
}
@ReactMethod
public void recordProps(ReadableMap props) {
mProps = props;
mCount++;
}
public int getCount() {
return mCount;
}
public ReadableMap getProps() {
return mProps;
}
}
private RecordingModule mRecordingModule;
public InitialPropsTestCase() {
super(ReactAppTestActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
mRecordingModule = new RecordingModule();
}
public void testInitialProps() throws Throwable {
final ReactAppTestActivity activity = getActivity();
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
ReactInstanceSpecForTest catalystInstanceSpec = new ReactInstanceSpecForTest();
catalystInstanceSpec.addNativeModule(new FakeWebSocketModule());
catalystInstanceSpec.addNativeModule(mRecordingModule);
Bundle props = new Bundle();
props.putString("key1", "string");
props.putInt("key2", 5);
props.putDouble("key3", 5.5);
props.putFloat("key4", 5.6f);
props.putBoolean("key5", true);
props.putStringArray("key6", new String[] {"one", "two", "three"});
props.putIntArray("key7", new int[] {1, 2, 3});
props.putDoubleArray("key8", new double[] {1.5, 2.5, 3.5});
props.putFloatArray("key9", new float[] {1.6f, 2.6f, 3.6f});
props.putBooleanArray("key10", new boolean[] {true, false});
activity.loadApp(
"InitialPropsTestApp", catalystInstanceSpec, props, DEFAULT_JS_BUNDLE, false);
}
});
activity.waitForBridgeAndUIIdle();
assertEquals(1, mRecordingModule.getCount());
ReadableMap props = mRecordingModule.getProps();
assertEquals("string", props.getString("key1"));
assertEquals(5, props.getInt("key2"));
assertEquals(5.5, props.getDouble("key3"));
assertEquals(5.6f, (float) props.getDouble("key4"));
assertEquals(true, props.getBoolean("key5"));
ReadableArray stringArray = props.getArray("key6");
assertEquals("one", stringArray.getString(0));
assertEquals("two", stringArray.getString(1));
assertEquals("three", stringArray.getString(2));
ReadableArray intArray = props.getArray("key7");
assertEquals(1, intArray.getInt(0));
assertEquals(2, intArray.getInt(1));
assertEquals(3, intArray.getInt(2));
ReadableArray doubleArray = props.getArray("key8");
assertEquals(1.5, doubleArray.getDouble(0));
assertEquals(2.5, doubleArray.getDouble(1));
assertEquals(3.5, doubleArray.getDouble(2));
ReadableArray floatArray = props.getArray("key9");
assertEquals(1.6f, (float) floatArray.getDouble(0));
assertEquals(2.6f, (float) floatArray.getDouble(1));
assertEquals(3.6f, (float) floatArray.getDouble(2));
ReadableArray booleanArray = props.getArray("key10");
assertEquals(true, booleanArray.getBoolean(0));
assertEquals(false, booleanArray.getBoolean(1));
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.modules.appstate.AppStateModule;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
import com.facebook.react.testing.FakeWebSocketModule;
import com.facebook.react.testing.ReactIntegrationTestCase;
import com.facebook.react.testing.ReactTestHelper;
import com.facebook.react.testing.StringRecordingModule;
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;
/** Test locale-based functionality of JS VM */
public class JSLocaleTest extends ReactIntegrationTestCase {
private interface TestJSLocaleModule extends JavaScriptModule {
void toUpper(String string);
void toLower(String string);
}
StringRecordingModule mStringRecordingModule;
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, 0);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
ReactChoreographer.initialize();
mUIManager.onHostResume();
}
});
waitForIdleSync();
mStringRecordingModule = new StringRecordingModule();
mInstance =
ReactTestHelper.catalystInstanceBuilder(this)
.addNativeModule(mStringRecordingModule)
.addNativeModule(mUIManager)
.addNativeModule(new DeviceInfoModule(getContext()))
.addNativeModule(new AppStateModule(getContext()))
.addNativeModule(new FakeWebSocketModule())
.build();
}
public void testToUpper() {
TestJSLocaleModule testModule = mInstance.getJSModule(TestJSLocaleModule.class);
waitForBridgeAndUIIdle();
testModule.toUpper("test");
testModule.toUpper("W niżach mógł zjeść truflę koń bądź psy");
testModule.toUpper("Шеф взъярён тчк щипцы с эхом гудбай Жюль");
testModule.toUpper("Γαζίες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο");
testModule.toUpper("chinese: 幓 厏吪吙 鈊釿閍 碞碠粻 曮禷");
waitForBridgeAndUIIdle();
String[] answers = mStringRecordingModule.getCalls().toArray(new String[0]);
assertEquals("TEST", answers[0]);
assertEquals("W NIŻACH MÓGŁ ZJEŚĆ TRUFLĘ KOŃ BĄDŹ PSY", answers[1]);
assertEquals("ШЕФ ВЗЪЯРЁН ТЧК ЩИПЦЫ С ЭХОМ ГУДБАЙ ЖЮЛЬ", answers[2]);
assertEquals("ΓΑΖΊΕΣ ΚΑῚ ΜΥΡΤΙῈΣ ΔῈΝ ΘᾺ ΒΡΩ͂ ΠΙᾺ ΣΤῸ ΧΡΥΣΑΦῚ ΞΈΦΩΤΟ", answers[3]);
assertEquals("CHINESE: 幓 厏吪吙 鈊釿閍 碞碠粻 曮禷", answers[4]);
}
public void testToLower() {
TestJSLocaleModule testModule = mInstance.getJSModule(TestJSLocaleModule.class);
testModule.toLower("TEST");
testModule.toLower("W NIŻACH MÓGŁ ZJEŚĆ TRUFLĘ KOŃ BĄDŹ psy");
testModule.toLower("ШЕФ ВЗЪЯРЁН ТЧК ЩИПЦЫ С ЭХОМ ГУДБАЙ ЖЮЛЬ");
testModule.toLower("ΓΑΖΊΕΣ ΚΑῚ ΜΥΡΤΙῈΣ ΔῈΝ ΘᾺ ΒΡΩ͂ ΠΙᾺ ΣΤῸ ΧΡΥΣΑΦῚ ΞΈΦΩΤΟ");
testModule.toLower("CHINESE: 幓 厏吪吙 鈊釿閍 碞碠粻 曮禷");
waitForBridgeAndUIIdle();
String[] answers = mStringRecordingModule.getCalls().toArray(new String[0]);
assertEquals("test", answers[0]);
assertEquals("w niżach mógł zjeść truflę koń bądź psy", answers[1]);
assertEquals("шеф взъярён тчк щипцы с эхом гудбай жюль", answers[2]);
assertEquals("γαζίες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο", answers[3]);
assertEquals("chinese: 幓 厏吪吙 鈊釿閍 碞碠粻 曮禷", answers[4]);
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.widget.ScrollView;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.SingleTouchGestureGenerator;
import com.facebook.react.uimanager.PixelUtil;
/**
* Test case to verify that JSResponder flow work correctly.
*
* <p>In a single test case scenario we have a view with pan gesture recognizer containing a
* scrollview We verify that by vertical drags affects a scrollview while horizontal drags are
* suppose to be recognized by pan responder and setJSResponder should be triggered resulting in
* scrollview events being intercepted.
*/
public class JSResponderTestCase extends ReactAppInstrumentationTestCase {
@Override
protected String getReactApplicationKeyUnderTest() {
return "JSResponderTestApp";
}
public void testResponderLocksScrollView() {
ScrollView scrollView = getViewByTestId("scroll_view");
assertNotNull(scrollView);
assertEquals(0, scrollView.getScrollY());
float inpx40dp = PixelUtil.toPixelFromDIP(40f);
float inpx100dp = PixelUtil.toPixelFromDIP(100f);
SingleTouchGestureGenerator gestureGenerator = createGestureGenerator();
gestureGenerator
.startGesture(30, 30 + inpx100dp)
.dragTo(30 + inpx40dp, 30, 10, 1200)
.endGesture(180, 100);
waitForBridgeAndUIIdle();
assertTrue("Expected to scroll by at least 80 dp", scrollView.getScrollY() >= inpx100dp * .8f);
int previousScroll = scrollView.getScrollY();
gestureGenerator
.startGesture(30, 30 + inpx100dp)
.dragTo(30 + inpx40dp, 30 + inpx100dp, 10, 1200);
waitForBridgeAndUIIdle();
gestureGenerator.dragTo(30 + inpx40dp, 30, 10, 1200).endGesture();
waitForBridgeAndUIIdle();
assertEquals("Expected not to scroll", scrollView.getScrollY(), previousScroll);
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.testing.StringRecordingModule;
/** Simple test to verify that layout events (onLayout) propagate to JS from native. */
public class LayoutEventsTestCase extends ReactAppInstrumentationTestCase {
private StringRecordingModule mStringRecordingModule;
@Override
protected String getReactApplicationKeyUnderTest() {
return "LayoutEventsTestApp";
}
/** Creates a UI in JS and verifies the onLayout handler is called. */
public void testOnLayoutCalled() {
assertEquals(3, mStringRecordingModule.getCalls().size());
assertEquals("10,10-100x100", mStringRecordingModule.getCalls().get(0));
assertEquals("10,10-50x50", mStringRecordingModule.getCalls().get(1));
assertEquals("0,0-50x50", mStringRecordingModule.getCalls().get(2));
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
mStringRecordingModule = new StringRecordingModule();
return super.createReactInstanceSpecForTest().addNativeModule(mStringRecordingModule);
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.view.View;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.uimanager.util.ReactFindViewUtil;
import java.util.Arrays;
import java.util.List;
/**
* Tests that the 'nativeID' property can be set on various views. The 'nativeID' property is used
* to reference react managed views from native code.
*/
public class NativeIdTestCase extends ReactAppInstrumentationTestCase {
@Override
protected String getReactApplicationKeyUnderTest() {
return "NativeIdTestApp";
}
private final List<String> viewTags =
Arrays.asList(
"Image",
"Text",
"TouchableBounce",
"TouchableHighlight",
"TouchableOpacity",
"TouchableWithoutFeedback",
"TextInput",
"View");
private boolean mViewFound;
@Override
protected void setUp() throws Exception {
mViewFound = false;
ReactFindViewUtil.addViewListener(
new ReactFindViewUtil.OnViewFoundListener() {
@Override
public String getNativeId() {
return viewTags.get(0);
}
@Override
public void onViewFound(View view) {
mViewFound = true;
}
});
super.setUp();
}
public void testPropertyIsSetForViews() {
for (String nativeId : viewTags) {
View viewWithTag = ReactFindViewUtil.findView(getActivity().getRootView(), nativeId);
assertNotNull(
"View with nativeID " + nativeId + " was not found. Check NativeIdTestModule.js.",
viewWithTag);
}
}
public void testViewListener() {
assertTrue("OnViewFound callback was never invoked", mViewFound);
}
public void testFindView() {
mViewFound = false;
ReactFindViewUtil.findView(
getActivity().getRootView(),
new ReactFindViewUtil.OnViewFoundListener() {
@Override
public String getNativeId() {
return viewTags.get(0);
}
@Override
public void onViewFound(View view) {
mViewFound = true;
}
});
assertTrue(
"OnViewFound callback should have successfully been invoked synchronously", mViewFound);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.UiThreadUtil;
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.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.progressbar.ReactProgressBarViewManager;
import com.facebook.react.views.view.ReactViewManager;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/** Test to verify that Progress bar renders as a view of the right size */
public class ProgressBarTestCase extends ReactIntegrationTestCase {
// Has same order of progressBars in ProgressBarTestModule
private static final String[] styleList =
new String[] {"Horizontal", "Small", "Large", "Inverse", "SmallInverse", "LargeInverse"};
private static final HashMap<String, Integer> styles = new HashMap<String, Integer>();
static {
styles.put("Horizontal", android.R.attr.progressBarStyleHorizontal);
styles.put("Small", android.R.attr.progressBarStyleSmall);
styles.put("Large", android.R.attr.progressBarStyleLarge);
styles.put("Inverse", android.R.attr.progressBarStyleInverse);
styles.put("SmallInverse", android.R.attr.progressBarStyleSmallInverse);
styles.put("LargeInverse", android.R.attr.progressBarStyleLargeInverse);
}
private static interface ProgressBarTestModule extends JavaScriptModule {
public void renderProgressBarApplication(int rootTag);
}
private UIManagerModule mUIManager;
private CatalystInstance mInstance;
private ReactRootView mRootView;
@Override
protected void setUp() throws Exception {
super.setUp();
List<ViewManager> viewManagers =
Arrays.<ViewManager>asList(new ReactViewManager(), new ReactProgressBarViewManager());
mUIManager = new UIManagerModule(getContext(), viewManagers, 0);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
mUIManager.onHostResume();
}
});
waitForIdleSync();
mInstance =
ReactTestHelper.catalystInstanceBuilder(this)
.addNativeModule(mUIManager)
.addNativeModule(new AndroidInfoModule(getContext()))
.addNativeModule(new DeviceInfoModule(getContext()))
.addNativeModule(new AppStateModule(getContext()))
.addNativeModule(new FakeWebSocketModule())
.build();
mRootView = new ReactRootView(getContext());
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
mRootView.setLayoutParams(
new FrameLayout.LayoutParams(metrics.widthPixels, metrics.heightPixels));
int rootTag = mUIManager.addRootView(mRootView);
mInstance.getJSModule(ProgressBarTestModule.class).renderProgressBarApplication(rootTag);
waitForBridgeAndUIIdle();
}
/** Test that the sizes of the progressBars are setup correctly */
public void testProgressBarSizes() {
for (String style : styleList) {
ProgressBar newProgressBar = new ProgressBar(getContext(), null, styles.get(style));
final int spec =
View.MeasureSpec.makeMeasureSpec(
ViewGroup.LayoutParams.WRAP_CONTENT, View.MeasureSpec.UNSPECIFIED);
newProgressBar.measure(spec, spec);
final int expectedHeight = newProgressBar.getMeasuredHeight();
// verify correct size of view containing ProgressBar
View viewContainingProgressBar = getViewByTestId(mRootView, style);
assertEquals(expectedHeight, viewContainingProgressBar.getHeight());
assertTrue(((ViewGroup) viewContainingProgressBar).getChildAt(0) instanceof ProgressBar);
}
}
public void testProgressBarWidth() {
View viewContainingProgressBar = getViewByTestId(mRootView, "Horizontal200");
assertEquals(viewContainingProgressBar.getWidth(), dpToPixels(200));
ProgressBar progressBar = (ProgressBar) ((ViewGroup) viewContainingProgressBar).getChildAt(0);
assertEquals(progressBar.getWidth(), dpToPixels(200));
}
private int dpToPixels(int dp) {
Resources r = getContext().getResources();
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.view.View;
import android.widget.HorizontalScrollView;
import com.facebook.react.testing.AbstractScrollViewTestCase;
import com.facebook.react.testing.SingleTouchGestureGenerator;
import com.facebook.react.uimanager.PixelUtil;
import java.util.ArrayList;
/** Integration test for horizontal ScrollView. See ScrollViewTestModule.js */
public class ReactHorizontalScrollViewTestCase extends AbstractScrollViewTestCase {
@Override
protected String getReactApplicationKeyUnderTest() {
return "HorizontalScrollViewTestApp";
}
private void dragLeft() {
dragLeft(200);
}
private void dragLeft(int durationMs) {
createGestureGenerator()
.startGesture(150, 50)
.dragTo(50, 60, 10, durationMs)
.endGesture(50, 60);
}
public void testScrolling() {
HorizontalScrollView scrollView = getViewAtPath(0);
assertNotNull(scrollView);
assertEquals(0, scrollView.getScrollX());
dragLeft();
assertTrue("Expected to scroll by at least 50 pixels", scrollView.getScrollX() >= 50);
}
public void testScrollEvents() {
HorizontalScrollView scrollView = getViewAtPath(0);
dragLeft();
waitForBridgeAndUIIdle();
mScrollListenerModule.waitForScrollIdle();
waitForBridgeAndUIIdle();
ArrayList<Double> xOffsets = mScrollListenerModule.getXOffsets();
assertFalse("Expected to receive at least one scroll event", xOffsets.isEmpty());
assertTrue("Expected offset to be greater than 0", xOffsets.get(xOffsets.size() - 1) > 0);
assertTrue(
"Expected no item click event fired", mScrollListenerModule.getItemsPressed().isEmpty());
assertEquals(
"Expected last offset to be offset of scroll view",
PixelUtil.toDIPFromPixel(scrollView.getScrollX()),
xOffsets.get(xOffsets.size() - 1).doubleValue(),
1e-5);
}
public void testScrollAndClick() throws Exception {
SingleTouchGestureGenerator gestureGenerator = createGestureGenerator();
// Slowly drag the ScrollView to prevent fling
dragLeft(15000);
waitForBridgeAndUIIdle();
getInstrumentation().waitForIdleSync();
// Find visible item to be clicked
View visibleItem = null;
int visibleItemNumber = 0;
for (; visibleItemNumber < 100; visibleItemNumber++) {
visibleItem = getViewAtPath(0, 0, visibleItemNumber);
int pos[] = new int[2];
visibleItem.getLocationInWindow(pos);
if (pos[0] >= 0) {
break;
}
}
// Click first visible item
gestureGenerator.startGesture(visibleItem).endGesture();
waitForBridgeAndUIIdle();
ArrayList<Double> xOffsets = mScrollListenerModule.getXOffsets();
ArrayList<Integer> itemIds = mScrollListenerModule.getItemsPressed();
assertFalse("Expected to receive at least one scroll event", xOffsets.isEmpty());
assertTrue("Expected offset to be greater than 0", xOffsets.get(xOffsets.size() - 1) > 0);
assertEquals("Expected to receive exactly one item click event", 1, itemIds.size());
assertEquals(visibleItemNumber, (int) itemIds.get(0));
}
/** Verify that 'scrollTo' command makes ScrollView start scrolling */
public void testScrollToCommand() throws Exception {
HorizontalScrollView scrollView = getViewAtPath(0);
ScrollViewTestModule jsModule =
getReactContext().getCatalystInstance().getJSModule(ScrollViewTestModule.class);
assertEquals(0, scrollView.getScrollX());
jsModule.scrollTo(300, 0);
waitForBridgeAndUIIdle();
getInstrumentation().waitForIdleSync();
// Unfortunately we need to use timeouts here in order to wait for scroll animation to happen
// there is no better way (yet) for waiting for scroll animation to finish
long timeout = 10000;
long interval = 50;
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < timeout) {
if (scrollView.getScrollX() > 0) {
break;
}
Thread.sleep(interval);
}
assertNotSame(0, scrollView.getScrollX());
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.graphics.Color;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.views.picker.ReactDialogPickerManager;
import com.facebook.react.views.picker.ReactDropdownPickerManager;
import com.facebook.react.views.picker.ReactPicker;
import com.facebook.react.views.picker.ReactPickerManager;
import java.util.ArrayList;
import java.util.List;
/**
* Integration test for {@link ReactDialogPickerManager} and {@link ReactDropdownPickerManager}
* (and, implicitly, {@link ReactPickerManager}). Tests basic properties, events and switching
* between spinner modes (which changes the used manager).
*/
public class ReactPickerTestCase extends ReactAppInstrumentationTestCase {
private static interface PickerAndroidTestModule extends JavaScriptModule {
public void selectItem(int position);
public void setMode(String mode);
public void setPrimaryColor(String color);
}
public static class PickerAndroidRecordingModule extends BaseJavaModule {
private final List<Integer> mSelections = new ArrayList<Integer>();
@Override
public String getName() {
return "PickerAndroidRecordingModule";
}
@ReactMethod
public void recordSelection(int position) {
mSelections.add(position);
}
public List<Integer> getSelections() {
return new ArrayList<Integer>(mSelections);
}
}
private PickerAndroidRecordingModule mRecordingModule;
@Override
protected String getReactApplicationKeyUnderTest() {
return "PickerAndroidTestApp";
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
mRecordingModule = new PickerAndroidRecordingModule();
return super.createReactInstanceSpecForTest().addNativeModule(mRecordingModule);
}
public void testBasicProperties() {
ReactPicker spinner = getViewAtPath(0, 0);
SpinnerAdapter adapter = spinner.getAdapter();
assertEquals(Spinner.MODE_DIALOG, spinner.getMode());
assertEquals("prompt", spinner.getPrompt());
assertNotNull(adapter);
assertEquals(3, adapter.getCount());
assertEquals("item1", ((TextView) adapter.getView(0, null, null)).getText());
assertEquals("item2", ((TextView) adapter.getView(1, null, null)).getText());
assertEquals("item3", ((TextView) adapter.getView(2, null, null)).getText());
assertEquals(1, spinner.getSelectedItemPosition());
// test colors
assertEquals(Color.RED, ((TextView) adapter.getView(0, null, null)).getCurrentTextColor());
assertEquals(Color.GREEN, ((TextView) adapter.getView(1, null, null)).getCurrentTextColor());
assertEquals(Color.BLUE, ((TextView) adapter.getView(2, null, null)).getCurrentTextColor());
assertEquals(
Color.RED, ((TextView) adapter.getDropDownView(0, null, null)).getCurrentTextColor());
assertEquals(
Color.GREEN, ((TextView) adapter.getDropDownView(1, null, null)).getCurrentTextColor());
assertEquals(
Color.BLUE, ((TextView) adapter.getDropDownView(2, null, null)).getCurrentTextColor());
getTestModule().setPrimaryColor("black");
waitForBridgeAndUIIdle();
assertEquals(Color.BLACK, ((TextView) adapter.getView(0, null, null)).getCurrentTextColor());
assertEquals(Color.BLACK, ((TextView) adapter.getView(1, null, null)).getCurrentTextColor());
assertEquals(Color.BLACK, ((TextView) adapter.getView(2, null, null)).getCurrentTextColor());
assertEquals(
Color.RED, ((TextView) adapter.getDropDownView(0, null, null)).getCurrentTextColor());
assertEquals(
Color.GREEN, ((TextView) adapter.getDropDownView(1, null, null)).getCurrentTextColor());
assertEquals(
Color.BLUE, ((TextView) adapter.getDropDownView(2, null, null)).getCurrentTextColor());
}
public void testDropdownPicker() {
ReactPicker spinner = getViewAtPath(0, 1);
assertEquals(Spinner.MODE_DROPDOWN, spinner.getMode());
}
public void testDisabledPicker() {
ReactPicker spinner = getViewAtPath(0, 2);
assertFalse(spinner.isEnabled());
}
public void testUpdateSelectedItem() {
ReactPicker spinner = getViewAtPath(0, 0);
assertEquals(1, spinner.getSelectedItemPosition());
getTestModule().selectItem(2);
waitForBridgeAndUIIdle();
getInstrumentation().waitForIdleSync();
assertEquals(2, spinner.getSelectedItemPosition());
}
public void testUpdateMode() {
ReactPicker spinner = getViewAtPath(0, 1);
assertEquals(Spinner.MODE_DROPDOWN, spinner.getMode());
getTestModule().setMode("dialog");
waitForBridgeAndUIIdle();
getInstrumentation().waitForIdleSync();
// changing the spinner mode in JS actually creates a new component on the native side, as
// there's no way to change the mode once you have constructed a Spinner.
ReactPicker newPicker = getViewAtPath(0, 1);
assertTrue(spinner != newPicker);
assertEquals(Spinner.MODE_DIALOG, newPicker.getMode());
}
public void testOnSelect() throws Throwable {
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
ReactPicker spinner = getViewAtPath(0, 0);
spinner.setSelection(2);
}
});
getInstrumentation().waitForIdleSync();
waitForBridgeAndUIIdle();
List<Integer> selections = mRecordingModule.getSelections();
assertEquals(1, selections.size());
assertEquals(2, (int) selections.get(0));
}
public void testOnSelectSequence() throws Throwable {
updateFirstSpinnerAndCheckLastSpinnerMatches(0);
updateFirstSpinnerAndCheckLastSpinnerMatches(2);
updateFirstSpinnerAndCheckLastSpinnerMatches(0);
updateFirstSpinnerAndCheckLastSpinnerMatches(2);
}
private void updateFirstSpinnerAndCheckLastSpinnerMatches(final int indexToSelect)
throws Throwable {
// The last spinner has the same selected value as the first one.
// Test that user selection is propagated correctly to JS, to setState, and to Spinners.
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
ReactPicker spinner = getViewAtPath(0, 0);
spinner.setSelection(indexToSelect);
}
});
getInstrumentation().waitForIdleSync();
waitForBridgeAndUIIdle();
ReactPicker spinnerInSync = getViewAtPath(0, 3);
assertEquals(
"Picker selection was not updated correctly via setState.",
indexToSelect,
spinnerInSync.getSelectedItemPosition());
}
private PickerAndroidTestModule getTestModule() {
return getReactContext().getCatalystInstance().getJSModule(PickerAndroidTestModule.class);
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.view.View;
import android.widget.ScrollView;
import com.facebook.react.testing.AbstractScrollViewTestCase;
import com.facebook.react.testing.SingleTouchGestureGenerator;
import com.facebook.react.uimanager.PixelUtil;
import java.util.ArrayList;
/** Integration test for vertical ScrollView. See ScrollViewTestModule.js */
public class ReactScrollViewTestCase extends AbstractScrollViewTestCase {
@Override
protected String getReactApplicationKeyUnderTest() {
return "ScrollViewTestApp";
}
private void dragUp() {
dragUp(200);
}
private void dragUp(int durationMs) {
createGestureGenerator()
.startGesture(200, 200)
.dragTo(180, 100, 10, durationMs)
.endGesture(180, 100);
}
public void testScrolling() {
ScrollView scrollView = getViewAtPath(0);
assertNotNull(scrollView);
assertEquals(0, scrollView.getScrollY());
dragUp();
assertTrue("Expected to scroll by at least 50 pixels", scrollView.getScrollY() >= 50);
}
public void testScrollEvents() {
ScrollView scrollView = getViewAtPath(0);
dragUp();
waitForBridgeAndUIIdle();
mScrollListenerModule.waitForScrollIdle();
waitForBridgeAndUIIdle();
ArrayList<Double> yOffsets = mScrollListenerModule.getYOffsets();
assertFalse("Expected to receive at least one scroll event", yOffsets.isEmpty());
assertTrue("Expected offset to be greater than 0", yOffsets.get(yOffsets.size() - 1) > 0);
assertTrue(
"Expected no item click event fired", mScrollListenerModule.getItemsPressed().isEmpty());
assertEquals(
"Expected last offset to be offset of scroll view",
PixelUtil.toDIPFromPixel(scrollView.getScrollY()),
yOffsets.get(yOffsets.size() - 1).doubleValue(),
1e-5);
assertTrue("Begin and End Drag should be called", mScrollListenerModule.dragEventsMatch());
}
public void testScrollAndClick() throws Exception {
SingleTouchGestureGenerator gestureGenerator = createGestureGenerator();
// Slowly drag the ScrollView to prevent fling
dragUp(15000);
waitForBridgeAndUIIdle();
getInstrumentation().waitForIdleSync();
// Find visible item to be clicked
View visibleItem = null;
int visibleItemNumber = 0;
for (; visibleItemNumber < 100; visibleItemNumber++) {
visibleItem = getViewAtPath(0, 0, visibleItemNumber);
int pos[] = new int[2];
visibleItem.getLocationInWindow(pos);
if (pos[1] >= 0) {
break;
}
}
// Click first visible item
gestureGenerator.startGesture(visibleItem).endGesture();
waitForBridgeAndUIIdle();
ArrayList<Double> yOffsets = mScrollListenerModule.getYOffsets();
ArrayList<Integer> itemIds = mScrollListenerModule.getItemsPressed();
assertFalse("Expected to receive at least one scroll event", yOffsets.isEmpty());
assertTrue("Expected offset to be greater than 0", yOffsets.get(yOffsets.size() - 1) > 0);
assertEquals("Expected to receive exactly one item click event", 1, itemIds.size());
assertEquals(visibleItemNumber, (int) itemIds.get(0));
}
/** Verify that 'scrollTo' command makes ScrollView start scrolling */
public void testScrollToCommand() throws Exception {
ScrollView scrollView = getViewAtPath(0);
ScrollViewTestModule jsModule =
getReactContext().getCatalystInstance().getJSModule(ScrollViewTestModule.class);
assertEquals(0, scrollView.getScrollY());
jsModule.scrollTo(0, 300);
waitForBridgeAndUIIdle();
getInstrumentation().waitForIdleSync();
// Unfortunately we need to use timeouts here in order to wait for scroll animation to happen
// there is no better way (yet) for waiting for scroll animation to finish
long timeout = 10000;
long interval = 50;
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < timeout) {
if (scrollView.getScrollY() > 0) {
break;
}
Thread.sleep(interval);
}
assertNotSame(0, scrollView.getScrollY());
assertFalse("Drag should not be called with scrollTo", mScrollListenerModule.dragEventsMatch());
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.view.View;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.views.swiperefresh.ReactSwipeRefreshLayout;
/** Test case for {@link ReactSwipeRefreshLayout}. */
public class ReactSwipeRefreshLayoutTestCase extends ReactAppInstrumentationTestCase {
private class SwipeRefreshLayoutRecordingModule extends BaseJavaModule {
private int mCount = 0;
@Override
public String getName() {
return "SwipeRefreshLayoutRecordingModule";
}
@ReactMethod
public void onRefresh() {
mCount++;
}
public int getCount() {
return mCount;
}
}
private interface SwipeRefreshLayoutTestModule extends JavaScriptModule {
void setRows(int rows);
}
private final SwipeRefreshLayoutRecordingModule mRecordingModule =
new SwipeRefreshLayoutRecordingModule();
@Override
protected String getReactApplicationKeyUnderTest() {
return "SwipeRefreshLayoutTestApp";
}
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return super.createReactInstanceSpecForTest().addNativeModule(mRecordingModule);
}
public void testRefreshNoScroll() {
View refreshLayout = getViewAtPath(0);
createGestureGenerator()
.startGesture(refreshLayout.getWidth() / 2, 10)
.dragTo(refreshLayout.getWidth() / 2, refreshLayout.getHeight() / 2, 100, 1000)
.endGesture();
waitForBridgeAndUIIdle();
assertEquals(1, mRecordingModule.getCount());
}
public void testRefreshScroll() {
View refreshLayout = getViewAtPath(0);
getReactContext().getJSModule(SwipeRefreshLayoutTestModule.class).setRows(100);
createGestureGenerator()
.startGesture(refreshLayout.getWidth() / 2, 10)
.dragTo(refreshLayout.getWidth() / 2, refreshLayout.getHeight() / 2, 100, 1000)
.endGesture();
waitForBridgeAndUIIdle();
assertEquals(1, mRecordingModule.getCount());
}
public void testNoRefreshAfterScroll() {
View refreshLayout = getViewAtPath(0);
getReactContext().getJSModule(SwipeRefreshLayoutTestModule.class).setRows(100);
createGestureGenerator()
.startGesture(refreshLayout.getWidth() / 2, refreshLayout.getHeight() / 2)
.dragTo(refreshLayout.getWidth() / 2, 10, 100, 1000)
.endGesture();
waitForBridgeAndUIIdle();
createGestureGenerator()
.startGesture(refreshLayout.getWidth() / 2, 10)
.dragTo(refreshLayout.getWidth() / 2, refreshLayout.getHeight() / 2, 100, 1000)
.endGesture();
waitForBridgeAndUIIdle();
assertEquals(0, mRecordingModule.getCount());
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.app.Instrumentation.ActivityMonitor;
import android.content.Intent;
import android.content.IntentFilter;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.share.ShareModule;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
/** Test case for {@link ShareModule}. */
public class ShareTestCase extends ReactAppInstrumentationTestCase {
private static interface ShareTestModule extends JavaScriptModule {
public void showShareDialog(WritableMap content, WritableMap options);
}
private static class ShareRecordingModule extends BaseJavaModule {
private int mOpened = 0;
private int mErrors = 0;
@Override
public String getName() {
return "ShareRecordingModule";
}
@ReactMethod
public void recordOpened() {
mOpened++;
}
@ReactMethod
public void recordError() {
mErrors++;
}
public int getOpened() {
return mOpened;
}
public int getErrors() {
return mErrors;
}
}
final ShareRecordingModule mRecordingModule = new ShareRecordingModule();
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return super.createReactInstanceSpecForTest().addNativeModule(mRecordingModule);
}
@Override
protected String getReactApplicationKeyUnderTest() {
return "ShareTestApp";
}
private ShareTestModule getTestModule() {
return getReactContext().getCatalystInstance().getJSModule(ShareTestModule.class);
}
public void testShowBasicShareDialog() {
final WritableMap content = new WritableNativeMap();
content.putString("message", "Hello, ReactNative!");
final WritableMap options = new WritableNativeMap();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_CHOOSER);
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
ActivityMonitor monitor = getInstrumentation().addMonitor(intentFilter, null, true);
getTestModule().showShareDialog(content, options);
waitForBridgeAndUIIdle();
getInstrumentation().waitForIdleSync();
assertEquals(1, monitor.getHits());
assertEquals(1, mRecordingModule.getOpened());
assertEquals(0, mRecordingModule.getErrors());
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.view.View;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactTestHelper;
import java.util.Arrays;
import java.util.List;
/**
* Tests that the 'testID' property can be set on various views. The 'testID' property is used to
* locate views in UI tests.
*/
public class TestIdTestCase extends ReactAppInstrumentationTestCase {
@Override
protected String getReactApplicationKeyUnderTest() {
return "TestIdTestApp";
}
private final List<String> viewTags =
Arrays.asList(
"Image",
"Text",
"TouchableBounce",
"TouchableHighlight",
"TouchableOpacity",
"TouchableWithoutFeedback",
"TextInput",
"View");
public void testPropertyIsSetForViews() {
for (String tag : viewTags) {
View viewWithTag = ReactTestHelper.getViewWithReactTestId(getActivity().getRootView(), tag);
assertNotNull(
"View with testID tag " + tag + " was not found. Check TestIdTestModule.js.",
viewWithTag);
}
}
}

View File

@ -0,0 +1,297 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.graphics.Color;
import android.text.style.ForegroundColorSpan;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import com.facebook.react.testing.StringRecordingModule;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.views.textinput.ReactEditText;
/** Test to verify that TextInput renders correctly */
public class TextInputTestCase extends ReactAppInstrumentationTestCase {
private final StringRecordingModule mRecordingModule = new StringRecordingModule();
private interface TextInputTestModule extends JavaScriptModule {
void setValueRef(String ref, String value);
}
/**
* Test that the actual height of the text input is not dependant on the font size of the text
* within.
*/
public void testTextInputMeasurements() {
View textInputViewHeightSet = getViewByTestId("textInput1");
EditText textInputViewNoHeight = getViewByTestId("textInput2");
int expectedHeight = Math.round(PixelUtil.toPixelFromDIP(30));
assertEquals(expectedHeight, textInputViewHeightSet.getHeight());
EditText editText = new EditText(textInputViewNoHeight.getContext());
editText.setTextSize(
TypedValue.COMPLEX_UNIT_PX, (float) Math.ceil(PixelUtil.toPixelFromSP(21.f)));
editText.setPadding(0, 0, 0, 0);
int measureSpec =
View.MeasureSpec.makeMeasureSpec(
ViewGroup.LayoutParams.WRAP_CONTENT, View.MeasureSpec.UNSPECIFIED);
editText.measure(measureSpec, measureSpec);
assertEquals(editText.getMeasuredHeight(), textInputViewNoHeight.getHeight());
}
/** Test that the cursor moves to the end of the word. */
public void testTextInputCursorPosition() throws Throwable {
final EditText textInputWithText = getViewByTestId("textInput3");
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
textInputWithText.setSelection(3);
}
});
getReactContext()
.getJSModule(TextInputTestModule.class)
.setValueRef("textInput3", "Some other value");
waitForBridgeAndUIIdle();
assertEquals(4, textInputWithText.getSelectionStart());
assertEquals(4, textInputWithText.getSelectionEnd());
}
/** Test that the colors are applied to new text */
public void testTextInputColors() throws Throwable {
String testIDs[] = new String[] {"textInput4", "textInput5", "textInput6"};
for (String testID : testIDs) {
getReactContext().getJSModule(TextInputTestModule.class).setValueRef(testID, "NewText");
}
waitForBridgeAndUIIdle();
for (String testID : testIDs) {
ReactEditText reactEditText = getViewByTestId(testID);
assertEquals(
Color.GREEN,
reactEditText
.getText()
.getSpans(0, 1, ForegroundColorSpan.class)[0]
.getForegroundColor());
}
}
public void testOnSubmitEditing() throws Throwable {
String testId = "onSubmitTextInput";
ReactEditText reactEditText = getViewByTestId(testId);
fireEditorActionAndCheckRecording(reactEditText, EditorInfo.IME_ACTION_GO);
fireEditorActionAndCheckRecording(reactEditText, EditorInfo.IME_ACTION_DONE);
fireEditorActionAndCheckRecording(reactEditText, EditorInfo.IME_ACTION_NEXT);
fireEditorActionAndCheckRecording(reactEditText, EditorInfo.IME_ACTION_PREVIOUS);
fireEditorActionAndCheckRecording(reactEditText, EditorInfo.IME_ACTION_SEARCH);
fireEditorActionAndCheckRecording(reactEditText, EditorInfo.IME_ACTION_SEND);
fireEditorActionAndCheckRecording(reactEditText, EditorInfo.IME_ACTION_UNSPECIFIED);
fireEditorActionAndCheckRecording(reactEditText, EditorInfo.IME_ACTION_NONE);
}
public void testRequestFocusDoesNothing() throws Throwable {
String testId = "textInput1";
final ReactEditText reactEditText = getViewByTestId(testId);
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
reactEditText.clearFocus();
}
});
waitForBridgeAndUIIdle();
assertFalse(reactEditText.isFocused());
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
reactEditText.requestFocus();
}
});
waitForBridgeAndUIIdle();
// Calling requestFocus() directly should no-op
assertFalse(reactEditText.isFocused());
}
public void testRequestFocusFromJS() throws Throwable {
String testId = "textInput1";
final ReactEditText reactEditText = getViewByTestId(testId);
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
reactEditText.clearFocus();
}
});
waitForBridgeAndUIIdle();
assertFalse(reactEditText.isFocused());
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
reactEditText.requestFocusFromJS();
}
});
waitForBridgeAndUIIdle();
assertTrue(reactEditText.isFocused());
}
public void testAccessibilityFocus() throws Throwable {
String testId = "textInput1";
final ReactEditText reactEditText = getViewByTestId(testId);
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
reactEditText.clearFocus();
}
});
waitForBridgeAndUIIdle();
assertFalse(reactEditText.isFocused());
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
reactEditText.performAccessibilityAction(
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
reactEditText.performAccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, null);
}
});
waitForBridgeAndUIIdle();
assertTrue(reactEditText.isFocused());
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
reactEditText.performAccessibilityAction(
AccessibilityNodeInfo.ACTION_CLEAR_FOCUS, null);
}
});
waitForBridgeAndUIIdle();
assertFalse(reactEditText.isFocused());
}
private void fireEditorActionAndCheckRecording(
final ReactEditText reactEditText, final int actionId) throws Throwable {
fireEditorActionAndCheckRecording(reactEditText, actionId, true);
fireEditorActionAndCheckRecording(reactEditText, actionId, false);
}
private void fireEditorActionAndCheckRecording(
final ReactEditText reactEditText, final int actionId, final boolean blurOnSubmit)
throws Throwable {
mRecordingModule.reset();
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
reactEditText.requestFocusFromJS();
reactEditText.setBlurOnSubmit(blurOnSubmit);
reactEditText.onEditorAction(actionId);
}
});
waitForBridgeAndUIIdle();
assertEquals(1, mRecordingModule.getCalls().size());
assertEquals(!blurOnSubmit, reactEditText.isFocused());
}
/**
* Test that the mentions input has colors displayed correctly. Removed for being flaky in open
* source, December 2016 public void testMetionsInputColors() throws Throwable { EventDispatcher
* eventDispatcher =
* getReactContext().getNativeModule(UIManagerModule.class).getEventDispatcher(); ReactEditText
* reactEditText = getViewByTestId("tokenizedInput"); String newText = "#Things and more #things";
* int contentWidth = reactEditText.getWidth(); int contentHeight = reactEditText.getHeight(); int
* start = 0; int count = newText.length();
*
* <p>eventDispatcher.dispatchEvent( new ReactTextChangedEvent( reactEditText.getId(),
* newText.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int)
* PixelUtil.toDIPFromPixel(contentHeight), reactEditText.incrementAndGetEventCounter()));
*
* <p>eventDispatcher.dispatchEvent( new ReactTextInputEvent( reactEditText.getId(),
* newText.toString(), "", start, start + count - 1)); waitForBridgeAndUIIdle();
*
* <p>ForegroundColorSpan[] spans = reactEditText .getText().getSpans(0,
* reactEditText.getText().length(), ForegroundColorSpan.class); assertEquals(2, spans.length);
* assertEquals(spans[0].getForegroundColor(), spans[1].getForegroundColor()); assertEquals(0,
* reactEditText.getText().getSpanStart(spans[1])); assertEquals(7,
* reactEditText.getText().getSpanEnd(spans[1])); assertEquals(newText.length() - 7,
* reactEditText.getText().getSpanStart(spans[0])); assertEquals(newText.length(),
* reactEditText.getText().getSpanEnd(spans[0]));
*
* <p>String moreText = "andsuch "; String previousText = newText; newText += moreText; count =
* moreText.length(); start = previousText.length();
*
* <p>eventDispatcher.dispatchEvent( new ReactTextChangedEvent( reactEditText.getId(),
* newText.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int)
* PixelUtil.toDIPFromPixel(contentHeight), reactEditText.incrementAndGetEventCounter()));
*
* <p>eventDispatcher.dispatchEvent( new ReactTextInputEvent( reactEditText.getId(), moreText, "",
* start, start + count - 1)); waitForBridgeAndUIIdle();
*
* <p>spans = reactEditText.getText() .getSpans(0, reactEditText.getText().length(),
* ForegroundColorSpan.class); assertEquals(2, spans.length);
* assertEquals(spans[0].getForegroundColor(), spans[1].getForegroundColor()); assertEquals(0,
* reactEditText.getText().getSpanStart(spans[1])); assertEquals(7,
* reactEditText.getText().getSpanEnd(spans[1])); assertEquals(newText.length() - 15,
* reactEditText.getText().getSpanStart(spans[0])); assertEquals(newText.length() - 1,
* reactEditText.getText().getSpanEnd(spans[0]));
*
* <p>moreText = "morethings"; previousText = newText; newText += moreText; count =
* moreText.length(); start = previousText.length();
*
* <p>eventDispatcher.dispatchEvent( new ReactTextChangedEvent( reactEditText.getId(),
* newText.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int)
* PixelUtil.toDIPFromPixel(contentHeight), reactEditText.incrementAndGetEventCounter()));
*
* <p>eventDispatcher.dispatchEvent( new ReactTextInputEvent( reactEditText.getId(), moreText, "",
* start, start + count - 1)); waitForBridgeAndUIIdle();
*
* <p>spans = reactEditText.getText() .getSpans(0, reactEditText.getText().length(),
* ForegroundColorSpan.class); assertEquals(spans[0].getForegroundColor(),
* spans[1].getForegroundColor()); assertEquals(2, spans.length); assertEquals(0,
* reactEditText.getText().getSpanStart(spans[1])); assertEquals(7,
* reactEditText.getText().getSpanEnd(spans[1])); assertEquals(newText.length() - 25,
* reactEditText.getText().getSpanStart(spans[0])); assertEquals(newText.length() - 11,
* reactEditText.getText().getSpanEnd(spans[0])); }
*/
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return super.createReactInstanceSpecForTest().addNativeModule(mRecordingModule);
}
@Override
protected String getReactApplicationKeyUnderTest() {
return "TextInputTestApp";
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.app.TimePickerDialog;
import android.content.DialogInterface;
import androidx.fragment.app.DialogFragment;
import com.facebook.react.bridge.BaseJavaModule;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.timepicker.TimePickerDialogModule;
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
import com.facebook.react.testing.ReactInstanceSpecForTest;
import java.util.ArrayList;
import java.util.List;
/** Test case for {@link TimePickerDialogModule} options and callbacks. */
public class TimePickerDialogTestCase extends ReactAppInstrumentationTestCase {
private static interface TimePickerDialogTestModule extends JavaScriptModule {
public void showTimePickerDialog(WritableMap options);
}
private static class TimePickerDialogRecordingModule extends BaseJavaModule {
private final List<Integer[]> mTimes = new ArrayList<Integer[]>();
private int mDismissed = 0;
private int mErrors = 0;
@Override
public String getName() {
return "TimePickerDialogRecordingModule";
}
@ReactMethod
public void recordTime(int hour, int minute) {
mTimes.add(new Integer[] {hour, minute});
}
@ReactMethod
public void recordDismissed() {
mDismissed++;
}
@ReactMethod
public void recordError() {
mErrors++;
}
public List<Integer[]> getTimes() {
return new ArrayList<Integer[]>(mTimes);
}
public int getDismissed() {
return mDismissed;
}
public int getErrors() {
return mErrors;
}
}
final TimePickerDialogRecordingModule mRecordingModule = new TimePickerDialogRecordingModule();
@Override
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return super.createReactInstanceSpecForTest().addNativeModule(mRecordingModule);
}
@Override
protected String getReactApplicationKeyUnderTest() {
return "TimePickerDialogTestApp";
}
private TimePickerDialogTestModule getTestModule() {
return getReactContext().getCatalystInstance().getJSModule(TimePickerDialogTestModule.class);
}
private DialogFragment showDialog(WritableMap options) {
getTestModule().showTimePickerDialog(options);
waitForBridgeAndUIIdle();
getInstrumentation().waitForIdleSync();
return (DialogFragment)
getActivity()
.getSupportFragmentManager()
.findFragmentByTag(TimePickerDialogModule.FRAGMENT_TAG);
}
public void testShowBasicTimePicker() {
final DialogFragment fragment = showDialog(null);
assertNotNull(fragment);
}
public void testPresetTimeAndCallback() throws Throwable {
final WritableMap options = new WritableNativeMap();
options.putInt("hour", 4);
options.putInt("minute", 5);
final DialogFragment fragment = showDialog(options);
List<Integer[]> recordedTimes = mRecordingModule.getTimes();
assertEquals(0, recordedTimes.size());
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
((TimePickerDialog) fragment.getDialog())
.getButton(DialogInterface.BUTTON_POSITIVE)
.performClick();
}
});
getInstrumentation().waitForIdleSync();
waitForBridgeAndUIIdle();
assertEquals(0, mRecordingModule.getErrors());
assertEquals(0, mRecordingModule.getDismissed());
recordedTimes = mRecordingModule.getTimes();
assertEquals(1, recordedTimes.size());
assertEquals(4, (int) recordedTimes.get(0)[0]);
assertEquals(5, (int) recordedTimes.get(0)[1]);
}
public void testDismissCallback() throws Throwable {
final DialogFragment fragment = showDialog(null);
assertEquals(0, mRecordingModule.getDismissed());
runTestOnUiThread(
new Runnable() {
@Override
public void run() {
fragment.getDialog().dismiss();
}
});
getInstrumentation().waitForIdleSync();
waitForBridgeAndUIIdle();
assertEquals(0, mRecordingModule.getErrors());
assertEquals(0, mRecordingModule.getTimes().size());
assertEquals(1, mRecordingModule.getDismissed());
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.UiThreadUtil;
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.PixelUtil;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.view.ReactViewGroup;
import com.facebook.react.views.view.ReactViewManager;
import java.util.Arrays;
import java.util.List;
public class ViewRenderingTestCase extends ReactIntegrationTestCase {
private interface ViewRenderingTestModule extends JavaScriptModule {
void renderViewApplication(int rootTag);
void renderMarginApplication(int rootTag);
void renderBorderApplication(int rootTag);
void updateMargins();
void renderTransformApplication(int rootTag);
}
private CatalystInstance mCatalystInstance;
private ReactRootView mRootView;
private int mRootTag;
@Override
protected void setUp() throws Exception {
super.setUp();
List<ViewManager> viewManagers = Arrays.<ViewManager>asList(new ReactViewManager());
final UIManagerModule uiManager = new UIManagerModule(getContext(), viewManagers, 0);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
uiManager.onHostResume();
}
});
waitForIdleSync();
mCatalystInstance =
ReactTestHelper.catalystInstanceBuilder(this)
.addNativeModule(uiManager)
.addNativeModule(new AndroidInfoModule(getContext()))
.addNativeModule(new DeviceInfoModule(getContext()))
.addNativeModule(new AppStateModule(getContext()))
.addNativeModule(new FakeWebSocketModule())
.build();
mRootView = new ReactRootView(getContext());
mRootTag = uiManager.addRootView(mRootView);
}
public void testViewRenderedWithCorrectProperties() {
float expectedOpacity = 0.75f;
int expectedBackgroundColor = Color.rgb(255, 0, 0);
mCatalystInstance.getJSModule(ViewRenderingTestModule.class).renderViewApplication(mRootTag);
waitForBridgeAndUIIdle();
ReactViewGroup view = getViewAtPath(mRootView);
assertEquals("Incorrect (or not applied) opacity", expectedOpacity, view.getAlpha());
assertEquals(
"Incorrect (or not applied) backgroundColor",
expectedBackgroundColor,
view.getBackgroundColor());
}
public void testMarginsApplied() {
mCatalystInstance.getJSModule(ViewRenderingTestModule.class).renderMarginApplication(mRootTag);
waitForBridgeAndUIIdle();
View view = getViewAtPath(mRootView);
int expectedMargin = Math.round(PixelUtil.toPixelFromDIP(10));
int expectedMarginLeft = Math.round(PixelUtil.toPixelFromDIP(20));
assertEquals(expectedMarginLeft, (int) view.getX());
assertEquals(expectedMargin, (int) view.getY());
}
public void testMarginUpdateDoesntForgetPreviousValue() {
mCatalystInstance.getJSModule(ViewRenderingTestModule.class).renderMarginApplication(mRootTag);
waitForBridgeAndUIIdle();
View view = getViewAtPath(mRootView);
// before: margin: 10, marginLeft: 20
mCatalystInstance.getJSModule(ViewRenderingTestModule.class).updateMargins();
waitForBridgeAndUIIdle();
// after: margin: 15; it should not forget marginLeft was set to 20
int expectedMargin = Math.round(PixelUtil.toPixelFromDIP(15));
int expectedMarginLeft = Math.round(PixelUtil.toPixelFromDIP(20));
assertEquals(expectedMarginLeft, (int) view.getX());
assertEquals(expectedMargin, (int) view.getY());
}
public void testBordersApplied() {
mCatalystInstance.getJSModule(ViewRenderingTestModule.class).renderBorderApplication(mRootTag);
waitForBridgeAndUIIdle();
View view = getViewAtPath(mRootView);
View child = ((ViewGroup) view).getChildAt(0);
int expectedBorderX = Math.round(PixelUtil.toPixelFromDIP(20));
int expectedBorderY = Math.round(PixelUtil.toPixelFromDIP(5));
assertEquals(expectedBorderX, (int) child.getX());
assertEquals(expectedBorderY, (int) child.getY());
}
public void testTransformations() {
mCatalystInstance
.getJSModule(ViewRenderingTestModule.class)
.renderTransformApplication(mRootTag);
waitForBridgeAndUIIdle();
View view = getViewAtPath(mRootView);
float expectedTranslateX = PixelUtil.toPixelFromDIP(20);
float expectedTranslateY = PixelUtil.toPixelFromDIP(25);
assertEquals(5f, view.getScaleX());
assertEquals(10f, view.getScaleY());
assertEquals(15f, view.getRotation());
assertEquals(expectedTranslateX, view.getTranslationX());
assertEquals(expectedTranslateY, view.getTranslationY());
}
}

View File

@ -0,0 +1,32 @@
# BUILD FILE SYNTAX: SKYLARK
load(
"//tools/build_defs/oss:rn_defs.bzl",
"IS_OSS_BUILD",
"react_native_dep",
"react_native_integration_tests_target",
"react_native_target",
"rn_android_library",
)
rn_android_library(
name = "core",
srcs = glob(["*.java"]),
is_androidx = True,
deps = ([
react_native_dep("third-party/android/androidx:test-espresso-core"),
react_native_dep("third-party/java/fest:fest"),
react_native_dep("third-party/java/junit:junit"),
react_native_dep("third-party/java/testing-support-lib:testing-support-lib"),
react_native_integration_tests_target("java/com/facebook/react/testing:testing"),
react_native_integration_tests_target("java/com/facebook/react/testing/rule:rule"),
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/module/model:model"),
react_native_target("java/com/facebook/react/shell:shell"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
]) + ([
react_native_dep("java/com/facebook/fbreact/testing:testing"),
]) if not IS_OSS_BUILD else [],
)

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests.core;
import android.app.Activity;
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.testing.ReactTestHelper;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ReactInstanceManagerTest {
private static final String TEST_MODULE = "ViewLayoutTestApp";
private ReactInstanceManager mReactInstanceManager;
private ReactRootView mReactRootView;
@Rule public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
@Before
public void setup() {
Activity activity = mActivityRule.getActivity();
mReactRootView = new ReactRootView(activity);
mReactInstanceManager =
ReactTestHelper.getReactTestFactory()
.getReactInstanceManagerBuilder()
.setApplication(activity.getApplication())
.setBundleAssetName("AndroidTestBundle.js")
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
.addPackage(new MainReactPackage())
.build();
}
@After
public void tearDown() {
final ReactRootView reactRootView = mReactRootView;
final ReactInstanceManager reactInstanceManager = mReactInstanceManager;
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
new Runnable() {
@Override
public void run() {
reactRootView.unmountReactApplication();
reactInstanceManager.destroy();
}
});
}
@Test
@UiThreadTest
public void testMountUnmount() {
mReactInstanceManager.onHostResume(mActivityRule.getActivity());
mReactRootView.startReactApplication(mReactInstanceManager, TEST_MODULE);
mReactRootView.unmountReactApplication();
}
@Test
@UiThreadTest
public void testResume() throws InterruptedException {
mReactInstanceManager.onHostResume(mActivityRule.getActivity());
mReactRootView.startReactApplication(mReactInstanceManager, TEST_MODULE);
mReactInstanceManager.onHostResume(mActivityRule.getActivity());
}
@Test
@UiThreadTest
public void testRecreateContext() throws InterruptedException {
mReactInstanceManager.onHostResume(mActivityRule.getActivity());
mReactInstanceManager.createReactContextInBackground();
mReactRootView.startReactApplication(mReactInstanceManager, TEST_MODULE);
}
@Test
@UiThreadTest
public void testMountTwice() {
mReactInstanceManager.onHostResume(mActivityRule.getActivity());
mReactRootView.startReactApplication(mReactInstanceManager, TEST_MODULE);
mReactInstanceManager.attachRootView(mReactRootView);
}
}

View File

@ -0,0 +1,203 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests.core;
import static org.fest.assertions.api.Assertions.assertThat;
import android.app.Instrumentation;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.testing.StringRecordingModule;
import com.facebook.react.testing.rule.ReactNativeTestRule;
import com.facebook.react.uimanager.PixelUtil;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ReactRootViewTest {
private interface ReactRootViewTestModule extends JavaScriptModule {
void setHeight(int height);
}
final StringRecordingModule mRecordingModule = new StringRecordingModule();
final ReactPackage mReactPackage =
new MainReactPackage() {
@Override
public NativeModule getModule(String name, ReactApplicationContext context) {
if (name.equals(StringRecordingModule.NAME)) {
return mRecordingModule;
}
return super.getModule(name, context);
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
final ReactModuleInfoProvider provider = super.getReactModuleInfoProvider();
return new ReactModuleInfoProvider() {
private Map<String, ReactModuleInfo> mModuleInfos = null;
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
if (mModuleInfos != null) {
return mModuleInfos;
}
mModuleInfos = new HashMap<>();
mModuleInfos.putAll(provider.getReactModuleInfos());
Class<? extends NativeModule> moduleClass = StringRecordingModule.class;
ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class);
mModuleInfos.put(
reactModule.name(),
new ReactModuleInfo(
reactModule.name(),
moduleClass.getName(),
reactModule.canOverrideExistingModule(),
reactModule.needsEagerInit(),
reactModule.hasConstants(),
reactModule.isCxxModule(),
false));
return mModuleInfos;
}
};
}
};
@Rule
public ReactNativeTestRule mReactNativeRule =
new ReactNativeTestRule("AndroidTestBundle.js", mReactPackage);
@Before
public void setup() {
mReactNativeRule.render("CatalystRootViewTestApp");
}
@Test
public void testResizeRootView() {
final ReactRootView rootView = mReactNativeRule.getView();
final View childView = rootView.getChildAt(0);
assertThat(rootView.getWidth()).isEqualTo(childView.getWidth());
final int newWidth = rootView.getWidth() / 2;
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.runOnMainSync(
new Runnable() {
@Override
public void run() {
rootView.setLayoutParams(
new FrameLayout.LayoutParams(newWidth, ViewGroup.LayoutParams.MATCH_PARENT));
}
});
instrumentation.waitForIdleSync();
mReactNativeRule.waitForIdleSync();
assertThat(newWidth).isEqualTo(childView.getWidth());
}
@Test
public void testRootViewWrapContent() {
final ReactRootView rootView = mReactNativeRule.getView();
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.runOnMainSync(
new Runnable() {
@Override
public void run() {
rootView.setLayoutParams(
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
});
instrumentation.waitForIdleSync();
mReactNativeRule.waitForIdleSync();
int newComponentHeight = 500;
mReactNativeRule
.getContext()
.getJSModule(ReactRootViewTestModule.class)
.setHeight(newComponentHeight);
instrumentation.waitForIdleSync();
mReactNativeRule.waitForIdleSync();
instrumentation.waitForIdleSync();
// added 0.5 to account for rounding issues
assertThat(rootView.getMeasuredHeight())
.isEqualTo((int) (PixelUtil.toPixelFromDIP(newComponentHeight) + 0.5));
}
/**
* Verify that removing the root view from hierarchy will trigger subviews removal both on JS and
* native side
*/
@Test
public void testRemoveRootView() {
final ReactRootView rootView = mReactNativeRule.getView();
assertThat(rootView.getChildCount()).isEqualTo(1);
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
instrumentation.runOnMainSync(
new Runnable() {
@Override
public void run() {
ViewGroup parent = (ViewGroup) rootView.getParent();
parent.removeView(rootView);
// removing from parent should not remove child views, child views should be removed as
// an effect of native call to UIManager.removeRootView
assertThat(rootView.getChildCount()).isEqualTo(1);
}
});
instrumentation.waitForIdleSync();
mReactNativeRule.waitForIdleSync();
assertThat(mRecordingModule.getCalls().size())
.isEqualTo(0)
.overridingErrorMessage("root component should not be automatically unmounted");
assertThat(rootView.getChildCount()).isEqualTo(1);
instrumentation.runOnMainSync(
new Runnable() {
@Override
public void run() {
rootView.unmountReactApplication();
}
});
mReactNativeRule.waitForIdleSync();
assertThat(mRecordingModule.getCalls().size()).isEqualTo(1);
assertThat(mRecordingModule.getCalls().get(0)).isEqualTo("RootComponentWillUnmount");
assertThat(rootView.getChildCount()).isEqualTo(0);
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tests.core;
import static org.fest.assertions.api.Assertions.assertThat;
import androidx.test.runner.AndroidJUnit4;
import com.facebook.react.bridge.NoSuchKeyException;
import com.facebook.react.bridge.UnexpectedNativeTypeException;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class WritableNativeMapTest {
private static final String ARRAY = "array";
private static final String MAP = "map";
private WritableNativeMap mMap;
@Before
public void setup() {
mMap = new WritableNativeMap();
mMap.putBoolean("boolean", true);
mMap.putDouble("double", 1.2);
mMap.putInt("int", 1);
mMap.putString("string", "abc");
mMap.putMap(MAP, new WritableNativeMap());
mMap.putArray(ARRAY, new WritableNativeArray());
mMap.putBoolean("dvacca", true);
}
@Test
public void testBoolean() {
assertThat(mMap.getBoolean("boolean")).isEqualTo(true);
}
@Test(expected = UnexpectedNativeTypeException.class)
public void testBooleanInvalidType() {
mMap.getBoolean("string");
}
@Test
public void testDouble() {
assertThat(mMap.getDouble("double")).isEqualTo(1.2);
}
@Test(expected = UnexpectedNativeTypeException.class)
public void testDoubleInvalidType() {
mMap.getDouble("string");
}
@Test
public void testInt() {
assertThat(mMap.getInt("int")).isEqualTo(1);
}
@Test(expected = UnexpectedNativeTypeException.class)
public void testIntInvalidType() {
mMap.getInt("string");
}
@Test
public void testString() {
assertThat(mMap.getString("string")).isEqualTo("abc");
}
@Test(expected = UnexpectedNativeTypeException.class)
public void testStringInvalidType() {
mMap.getString("int");
}
@Test
public void testMap() {
assertThat(mMap.getMap("map")).isNotNull();
}
@Test(expected = UnexpectedNativeTypeException.class)
public void testMapInvalidType() {
mMap.getMap("string");
}
@Test
public void testArray() {
assertThat(mMap.getArray("array")).isNotNull();
}
@Test(expected = UnexpectedNativeTypeException.class)
public void testArrayInvalidType() {
mMap.getArray("string");
}
@Test
public void testErrorMessageContainsKey() {
String key = "fkg";
try {
mMap.getString(key);
Assert.fail("Expected an NoSuchKeyException to be thrown");
} catch (NoSuchKeyException e) {
assertThat(e.getMessage()).contains(key);
}
}
@Test
public void testCopy() {
final WritableMap copy = mMap.copy();
assertThat(copy).isNotSameAs(mMap);
assertThat(copy.getMap(MAP)).isNotSameAs(mMap.getMap(MAP));
assertThat(copy.getArray(ARRAY)).isNotSameAs(mMap.getArray(ARRAY));
}
@Test
public void testCopyModification() {
final WritableMap copy = mMap.copy();
copy.putString("string", "foo");
assertThat(copy.getString("string")).isEqualTo("foo");
assertThat(mMap.getString("string")).isEqualTo("abc");
}
}

View File

@ -0,0 +1,85 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {
NativeModules,
StyleSheet,
Text,
TouchableOpacity,
View,
} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {Recording: RecordingModule} = NativeModules;
const styles = StyleSheet.create({
base: {
width: 200,
height: 200,
backgroundColor: 'red',
transform: [{rotate: '0deg'}],
},
transformed: {
transform: [{rotate: '45deg'}],
},
});
/**
* This app presents a TouchableOpacity which was the simplest way to
* demonstrate this issue. Tapping the TouchableOpacity causes an animated
* transform to be created for the rotation property. Since the property isn't
* animated itself, it comes through as a static property, but static properties
* can't currently handle strings which causes a string->double cast exception.
*/
class AnimatedTransformTestApp extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
}
state = {
flag: false,
};
toggle() {
this.setState({
flag: !this.state.flag,
});
}
render() {
// Using this to verify if its fixed.
RecordingModule.record('render');
return (
<View style={StyleSheet.absoluteFill}>
<TouchableOpacity
onPress={this.toggle}
testID="TouchableOpacity"
style={[styles.base, this.state.flag ? styles.transformed : null]}>
<Text>TouchableOpacity</Text>
</TouchableOpacity>
</View>
);
}
}
var AnimatedTransformTestModule = {
AnimatedTransformTestApp: AnimatedTransformTestApp,
};
BatchedBridge.registerCallableModule(
'AnimatedTransformTestModule',
AnimatedTransformTestModule,
);
module.exports = AnimatedTransformTestModule;

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const {NativeModules} = require('react-native');
const {Assert} = NativeModules;
const Asserts = {
assertEquals: function(expected, actual, msg) {
if (expected !== actual) {
Assert.fail(
msg ||
'Expected: ' +
expected +
', received: ' +
actual +
'\n' +
'at ' +
new Error().stack,
);
} else {
Assert.success();
}
},
assertTrue: function(expr, msg) {
Asserts.assertEquals(true, expr, msg);
},
};
module.exports = Asserts;

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {NativeModules, StyleSheet, View} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {Recording} = NativeModules;
let that;
class CatalystRootViewTestApp extends React.Component {
state = {
height: 300,
};
componentDidMount() {
that = this;
}
componentWillUnmount() {
Recording.record('RootComponentWillUnmount');
}
render() {
return (
<View
collapsable={false}
style={[styles.container, {height: this.state.height}]}
/>
);
}
}
const ReactRootViewTestModule = {
setHeight: function(height) {
that.setState({height: height});
},
};
const styles = StyleSheet.create({
container: {
alignSelf: 'stretch',
},
});
BatchedBridge.registerCallableModule(
'ReactRootViewTestModule',
ReactRootViewTestModule,
);
module.exports = {
CatalystRootViewTestApp: CatalystRootViewTestApp,
};

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {DatePickerAndroid, NativeModules, View} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {DatePickerDialogRecordingModule: RecordingModule} = NativeModules;
class DatePickerDialogTestApp extends React.Component {
render() {
return <View />;
}
}
const DatePickerDialogTestModule = {
DatePickerDialogTestApp: DatePickerDialogTestApp,
showDatePickerDialog: function(options) {
DatePickerAndroid.open(options).then(
({action, year, month, day}) => {
if (action === DatePickerAndroid.dateSetAction) {
RecordingModule.recordDate(year, month, day);
} else if (action === DatePickerAndroid.dismissedAction) {
RecordingModule.recordDismissed();
}
},
({code, message}) => RecordingModule.recordError(),
);
},
};
BatchedBridge.registerCallableModule(
'DatePickerDialogTestModule',
DatePickerDialogTestModule,
);
module.exports = DatePickerDialogTestModule;

View File

@ -0,0 +1,56 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {Image, NativeModules, StyleSheet, View} = require('react-native');
const {Recording: RecordingModule} = NativeModules;
class ImageErrorTestApp extends React.Component {
onError = e => {
RecordingModule.record('Got error: ' + e.nativeEvent.error);
};
render() {
// For some reason image-2 needs explicit height. Without it onError is not triggered.
return (
<View>
<Image
testID="image-1"
source={{uri: '/does/not/exist'}}
onError={this.onError}
/>
<Image
testID="image-2"
source={{uri: 'file:///does/not/exist'}}
style={styles.image}
onError={this.onError}
/>
<Image
testID="image-3"
source={{
uri: 'https://TYPO_ERROR_facebook.github.io/react/logo-og.png',
}}
onError={this.onError}
/>
</View>
);
}
}
const styles = StyleSheet.create({
image: {
height: 50,
width: 50,
},
});
module.exports = ImageErrorTestApp;

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {Image} = require('react-native');
class ImageOverlayColorTestApp extends React.Component {
render() {
const uri =
'data:image/gif;base64,' +
'R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapy' +
'uvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/' +
'TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5' +
'iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97V' +
'riy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7';
return <Image testID="image" source={{uri: uri}} overlayColor="#FF0000" />;
}
}
module.exports = ImageOverlayColorTestApp;

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {NativeModules, Text} = require('react-native');
class InitialPropsTestApp extends React.Component {
componentDidMount() {
NativeModules.InitialPropsRecordingModule.recordProps(this.props);
}
render() {
return <Text>dummy</Text>;
}
}
module.exports = InitialPropsTestApp;

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {
PanResponder,
ScrollView,
StyleSheet,
Text,
View,
} = require('react-native');
class JSResponderTestApp extends React.Component {
_handleMoveShouldSetPanResponder = (e, gestureState) => {
return Math.abs(gestureState.dx) > 30;
};
UNSAFE_componentWillMount() {
this.panGesture = PanResponder.create({
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
});
}
render() {
const views = [];
for (let i = 0; i < 100; i++) {
views[i] = (
<View key={i} style={styles.row} collapsable={false}>
<Text>I am row {i}</Text>
</View>
);
}
return (
<View
style={styles.container}
{...this.panGesture.panHandlers}
collapsable={false}>
<ScrollView style={styles.scrollview} testID="scroll_view">
{views}
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scrollview: {
flex: 1,
},
row: {
height: 30,
},
});
module.exports = JSResponderTestApp;

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {NativeModules, StyleSheet, View} = require('react-native');
const {Recording: RecordingModule} = NativeModules;
const LAYOUT_SPECS = [
[10, 10, 100, 100],
[10, 10, 50, 50],
[0, 0, 50, 50],
[0, 0, 50, 50],
];
class LayoutEventsTestApp extends React.Component {
constructor() {
super();
this.state = {
specNumber: 0,
};
this.numParentLayouts = 0;
}
handleOnLayout = e => {
const layout = e.nativeEvent.layout;
RecordingModule.record(
layout.x + ',' + layout.y + '-' + layout.width + 'x' + layout.height,
);
if (this.state.specNumber >= LAYOUT_SPECS.length) {
// This will cause the test to fail
RecordingModule.record('Got an extraneous layout call');
} else {
this.setState({
specNumber: this.state.specNumber + 1,
});
}
};
handleParentOnLayout = e => {
if (this.numParentLayouts > 0) {
// This will cause the test to fail - the parent's layout doesn't change
// so we should only get the event once.
RecordingModule.record('Got an extraneous layout call on the parent');
}
this.numParentLayouts++;
};
render() {
const layout = LAYOUT_SPECS[this.state.specNumber];
return (
<View
onLayout={this.handleParentOnLayout}
testID="parent"
style={styles.container}>
<View
onLayout={this.handleOnLayout}
testID="container"
style={{
left: layout[0],
top: layout[1],
width: layout[2],
height: layout[3],
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
left: 0,
top: 0,
width: 500,
height: 500,
},
});
module.exports = LayoutEventsTestApp;

View File

@ -0,0 +1,211 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {StyleSheet, UIManager, View, findNodeHandle} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const assertEquals = require('./Asserts').assertEquals;
const styles = StyleSheet.create({
A: {
width: 500,
height: 500,
},
B: {
backgroundColor: 'rgb(255, 0, 0)',
left: 50,
top: 80,
width: 200,
height: 300,
},
C: {
backgroundColor: 'rgb(0, 255, 0)',
left: 100,
top: 70,
width: 50,
height: 150,
},
D: {
backgroundColor: 'rgb(0, 0, 255)',
left: 400,
top: 100,
width: 50,
height: 200,
},
});
let A, B, C, D;
class MeasureLayoutTestApp extends React.Component {
componentDidMount() {
A = findNodeHandle(this.refs.A);
B = findNodeHandle(this.refs.B);
C = findNodeHandle(this.refs.C);
D = findNodeHandle(this.refs.D);
}
render() {
return (
<View ref="A" style={styles.A} collapsable={false}>
<View ref="B" style={styles.B} collapsable={false}>
<View ref="C" style={styles.C} collapsable={false} />
</View>
<View ref="D" style={styles.D} collapsable={false} />
</View>
);
}
}
function shouldNotCallThisCallback() {
assertEquals(false, true);
}
const MeasureLayoutTestModule = {
MeasureLayoutTestApp: MeasureLayoutTestApp,
verifyMeasureOnViewA: function() {
UIManager.measure(A, function(a, b, width, height, x, y) {
assertEquals(500, width);
assertEquals(500, height);
assertEquals(0, x);
assertEquals(0, y);
});
},
verifyMeasureOnViewC: function() {
UIManager.measure(C, function(a, b, width, height, x, y) {
assertEquals(50, width);
assertEquals(150, height);
assertEquals(150, x);
assertEquals(150, y);
});
},
verifyMeasureLayoutCRelativeToA: function() {
UIManager.measureLayout(C, A, shouldNotCallThisCallback, function(
x,
y,
width,
height,
) {
assertEquals(50, width);
assertEquals(150, height);
assertEquals(150, x);
assertEquals(150, y);
});
},
verifyMeasureLayoutCRelativeToB: function() {
UIManager.measureLayout(C, B, shouldNotCallThisCallback, function(
x,
y,
width,
height,
) {
assertEquals(50, width);
assertEquals(150, height);
assertEquals(100, x);
assertEquals(70, y);
});
},
verifyMeasureLayoutCRelativeToSelf: function() {
UIManager.measureLayout(C, C, shouldNotCallThisCallback, function(
x,
y,
width,
height,
) {
assertEquals(50, width);
assertEquals(150, height);
assertEquals(0, x);
assertEquals(0, y);
});
},
verifyMeasureLayoutRelativeToParentOnViewA: function() {
UIManager.measureLayoutRelativeToParent(
A,
shouldNotCallThisCallback,
function(x, y, width, height) {
assertEquals(500, width);
assertEquals(500, height);
assertEquals(0, x);
assertEquals(0, y);
},
);
},
verifyMeasureLayoutRelativeToParentOnViewB: function() {
UIManager.measureLayoutRelativeToParent(
B,
shouldNotCallThisCallback,
function(x, y, width, height) {
assertEquals(200, width);
assertEquals(300, height);
assertEquals(50, x);
assertEquals(80, y);
},
);
},
verifyMeasureLayoutRelativeToParentOnViewC: function() {
UIManager.measureLayoutRelativeToParent(
C,
shouldNotCallThisCallback,
function(x, y, width, height) {
assertEquals(50, width);
assertEquals(150, height);
assertEquals(100, x);
assertEquals(70, y);
},
);
},
verifyMeasureLayoutDRelativeToB: function() {
UIManager.measureLayout(
D,
B,
function() {
assertEquals(true, true);
},
shouldNotCallThisCallback,
);
},
verifyMeasureLayoutNonExistentTag: function() {
UIManager.measureLayout(
192,
A,
function() {
assertEquals(true, true);
},
shouldNotCallThisCallback,
);
},
verifyMeasureLayoutNonExistentAncestor: function() {
UIManager.measureLayout(
B,
192,
function() {
assertEquals(true, true);
},
shouldNotCallThisCallback,
);
},
verifyMeasureLayoutRelativeToParentNonExistentTag: function() {
UIManager.measureLayoutRelativeToParent(
192,
function() {
assertEquals(true, true);
},
shouldNotCallThisCallback,
);
},
};
BatchedBridge.registerCallableModule(
'MeasureLayoutTestModule',
MeasureLayoutTestModule,
);
module.exports = MeasureLayoutTestModule;

View File

@ -0,0 +1,78 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {NativeModules, StyleSheet, View} = require('react-native');
const {Recording} = NativeModules;
const extractSingleTouch = nativeEvent => {
const touches = nativeEvent.touches;
const changedTouches = nativeEvent.changedTouches;
const hasTouches = touches && touches.length > 0;
const hasChangedTouches = changedTouches && changedTouches.length > 0;
return !hasTouches && hasChangedTouches
? changedTouches[0]
: hasTouches
? touches[0]
: nativeEvent;
};
class TouchTestApp extends React.Component {
handleStartShouldSetResponder = e => {
return true;
};
handleOnResponderMove = e => {
e = extractSingleTouch(e.nativeEvent);
Recording.record('move;' + e.touches.length);
};
handleResponderStart = e => {
e = extractSingleTouch(e.nativeEvent);
if (e.touches) {
Recording.record('start;' + e.touches.length);
} else {
Recording.record('start;ExtraPointer');
}
};
handleResponderEnd = e => {
e = extractSingleTouch(e.nativeEvent);
if (e.touches) {
Recording.record('end;' + e.touches.length);
} else {
Recording.record('end;ExtraPointer');
}
};
render() {
return (
<View
style={styles.container}
onStartShouldSetResponder={this.handleStartShouldSetResponder}
onResponderMove={this.handleOnResponderMove}
onResponderStart={this.handleResponderStart}
onResponderEnd={this.handleResponderEnd}
collapsable={false}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
module.exports = TouchTestApp;

View File

@ -0,0 +1,76 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
const React = require('react');
const TouchableBounce = require('react-native/Libraries/Components/Touchable/TouchableBounce');
const {
Image,
StyleSheet,
Text,
TextInput,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
View,
} = require('react-native');
/**
* All the views implemented on Android, each with the nativeID property set.
* We test that:
* - The app renders fine
* - The nativeID property is passed to the native views
*/
class NativeIdTestApp extends React.Component<{...}> {
render(): React.Node {
const uri =
'data:image/gif;base64,' +
'R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapy' +
'uvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/' +
'TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5' +
'iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97V' +
'riy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7';
return (
<View>
<Image nativeID="Image" source={{uri: uri}} style={styles.base} />
<Text nativeID="Text">text</Text>
<TextInput nativeID="TextInput" value="Text input" />
<TouchableBounce nativeID="TouchableBounce">
<Text>TouchableBounce</Text>
</TouchableBounce>
<TouchableHighlight nativeID="TouchableHighlight">
<Text>TouchableHighlight</Text>
</TouchableHighlight>
<TouchableOpacity nativeID="TouchableOpacity">
<Text>TouchableOpacity</Text>
</TouchableOpacity>
<TouchableWithoutFeedback nativeID="TouchableWithoutFeedback">
<View>
<Text>TouchableWithoutFeedback</Text>
</View>
</TouchableWithoutFeedback>
<View nativeID="View" />
</View>
);
}
}
const styles = StyleSheet.create({
base: {
width: 150,
height: 50,
},
});
module.exports = {
NativeIdTestApp: NativeIdTestApp,
};

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {NativeModules, Picker, View} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {Recording: RecordingModule} = NativeModules;
const Item = Picker.Item;
let appInstance;
class PickerAndroidTestApp extends React.Component {
state = {
selected: 1,
mode: 'dropdown',
style: {},
};
UNSAFE_componentWillMount() {
appInstance = this;
}
render() {
return (
<View collapsable={false}>
<Picker
mode="dialog"
prompt="prompt"
style={this.state.style}
selectedValue={this.state.selected}
onValueChange={this.onValueChange}>
<Item label="item1" color="#ff0000" value={0} />
<Item label="item2" color="#00ff00" value={1} />
<Item label="item3" color="#0000ff" value={2} />
</Picker>
<Picker mode={this.state.mode}>
<Item label="item1" />
<Item label="item2" />
</Picker>
<Picker enabled={false}>
<Item label="item1" />
<Item label="item2" />
</Picker>
<Picker
mode="dropdown"
selectedValue={this.state.selected}
onValueChange={this.onValueChange}>
<Item label="item in sync 1" value={0} />
<Item label="item in sync 2" value={1} />
<Item label="item in sync 3" value={2} />
</Picker>
</View>
);
}
onValueChange = value => {
this.setState({selected: value});
RecordingModule.recordSelection(value);
};
}
const PickerAndroidTestModule = {
PickerAndroidTestApp: PickerAndroidTestApp,
selectItem: function(value) {
appInstance.setState({selected: value});
},
setMode: function(mode) {
appInstance.setState({mode: mode});
},
setPrimaryColor: function(color) {
appInstance.setState({style: {color}});
},
};
BatchedBridge.registerCallableModule(
'PickerAndroidTestModule',
PickerAndroidTestModule,
);
module.exports = PickerAndroidTestModule;

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {
ProgressBarAndroid: ProgressBar,
StyleSheet,
View,
} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const renderApplication = require('react-native/Libraries/ReactNative/renderApplication');
class ProgressBarSampleApp extends React.Component {
state = {};
render() {
return (
<View>
<ProgressBar styleAttr="Horizontal" testID="Horizontal" />
<ProgressBar styleAttr="Small" testID="Small" />
<ProgressBar styleAttr="Large" testID="Large" />
<ProgressBar styleAttr="Normal" testID="Normal" />
<ProgressBar styleAttr="Inverse" testID="Inverse" />
<ProgressBar styleAttr="SmallInverse" testID="SmallInverse" />
<ProgressBar styleAttr="LargeInverse" testID="LargeInverse" />
<View style={styles.wrapper}>
<ProgressBar styleAttr="Horizontal" testID="Horizontal200" />
</View>
</View>
);
}
}
const ProgressBarTestModule = {
renderProgressBarApplication: function(rootTag) {
renderApplication(ProgressBarSampleApp, {}, rootTag);
},
};
BatchedBridge.registerCallableModule(
'ProgressBarTestModule',
ProgressBarTestModule,
);
const styles = StyleSheet.create({
wrapper: {
width: 200,
},
});
module.exports = ProgressBarTestModule;

View File

@ -0,0 +1,183 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const React = require('react');
const {
NativeModules,
ScrollView,
StyleSheet,
Text,
TouchableWithoutFeedback,
View,
} = require('react-native');
const {ScrollListener} = NativeModules;
const NUM_ITEMS = 100;
import type {PressEvent} from 'react-native/Libraries/Types/CoreEventTypes';
// Shared by integration tests for ScrollView and HorizontalScrollView
let scrollViewApp;
type ItemProps = $ReadOnly<{|
onPress: (event: PressEvent) => void,
text: string,
|}>;
type ItemState = {||};
class Item extends React.Component<ItemProps, ItemState> {
render() {
return (
<TouchableWithoutFeedback onPress={this.props.onPress}>
<View style={styles.item_container}>
<Text style={styles.item_text}>{this.props.text}</Text>
</View>
</TouchableWithoutFeedback>
);
}
}
const getInitialState = function() {
const data = [];
for (let i = 0; i < NUM_ITEMS; i++) {
data[i] = {text: 'Item ' + i + '!'};
}
return {
data: data,
};
};
const onScroll = function(e) {
ScrollListener.onScroll(
e.nativeEvent.contentOffset.x,
e.nativeEvent.contentOffset.y,
);
};
const onScrollBeginDrag = function(e) {
ScrollListener.onScrollBeginDrag(
e.nativeEvent.contentOffset.x,
e.nativeEvent.contentOffset.y,
);
};
const onScrollEndDrag = function(e) {
ScrollListener.onScrollEndDrag(
e.nativeEvent.contentOffset.x,
e.nativeEvent.contentOffset.y,
);
};
const onItemPress = function(itemNumber) {
ScrollListener.onItemPress(itemNumber);
};
type Props = $ReadOnly<{||}>;
type State = {|
data: $ReadOnlyArray<{|text: string|}>,
|};
class ScrollViewTestApp extends React.Component<Props, State> {
scrollView: {|current: any | null|} = React.createRef();
state: State = getInitialState();
scrollTo(destX: number, destY: number) {
const scrollView = this.scrollView.current;
if (scrollView == null) {
return;
}
scrollView.scrollTo(destY, destX);
}
render(): React.Node {
scrollViewApp = this;
const children = this.state.data.map((item, index) => (
<Item
key={index}
text={item.text}
onPress={onItemPress.bind(this, index)}
/>
));
return (
<ScrollView
onScroll={onScroll}
onScrollBeginDrag={onScrollBeginDrag}
onScrollEndDrag={onScrollEndDrag}
ref={this.scrollView}>
{children}
</ScrollView>
);
}
}
class HorizontalScrollViewTestApp extends React.Component<Props, State> {
scrollView: {|current: any | null|} = React.createRef();
state: State = getInitialState();
scrollTo(destX: number, destY: number) {
const scrollView = this.scrollView.current;
if (scrollView == null) {
return;
}
scrollView.scrollTo(destY, destX);
}
render(): React.Node {
scrollViewApp = this;
const children = this.state.data.map((item, index) => (
<Item
key={index}
text={item.text}
onPress={onItemPress.bind(this, index)}
/>
));
return (
<ScrollView horizontal={true} onScroll={onScroll} ref={this.scrollView}>
{children}
</ScrollView>
);
}
}
const styles = StyleSheet.create({
item_container: {
padding: 30,
backgroundColor: '#ffffff',
},
item_text: {
flex: 1,
fontSize: 18,
alignSelf: 'center',
},
});
const ScrollViewTestModule = {
ScrollViewTestApp: ScrollViewTestApp,
HorizontalScrollViewTestApp: HorizontalScrollViewTestApp,
scrollTo(destX: number, destY: number) {
scrollViewApp.scrollTo(destX, destY);
},
};
BatchedBridge.registerCallableModule(
'ScrollViewTestModule',
ScrollViewTestModule,
);
module.exports = ScrollViewTestModule;

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {NativeModules, Share, View} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {ShareRecordingModule: RecordingModule} = NativeModules;
class ShareTestApp extends React.Component {
render() {
return <View />;
}
}
const ShareTestModule = {
ShareTestApp: ShareTestApp,
showShareDialog: function(content, options) {
Share.share(content, options).then(
() => RecordingModule.recordOpened(),
({code, message}) => RecordingModule.recordError(),
);
},
};
BatchedBridge.registerCallableModule('ShareTestModule', ShareTestModule);
module.exports = ShareTestModule;

View File

@ -0,0 +1,369 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {
ScrollView,
StyleSheet,
View,
requireNativeComponent,
} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const ClippableView = requireNativeComponent('ClippableView');
class ClippingSample1 extends React.Component {
render() {
return (
<View>
<ClippableView
clippableViewID="outer"
style={sample1Styles.outer}
removeClippedSubviews={true}>
<ClippableView
clippableViewID="inner1"
style={[sample1Styles.inner, sample1Styles.inner1]}
/>
<ClippableView
clippableViewID="inner2"
style={[sample1Styles.inner, sample1Styles.inner2]}
/>
<ClippableView
clippableViewID="inner3"
style={[sample1Styles.inner, sample1Styles.inner3]}
/>
<ClippableView
clippableViewID="inner4"
style={[sample1Styles.inner, sample1Styles.inner4]}
/>
<ClippableView
clippableViewID="inner5"
style={[sample1Styles.inner, sample1Styles.inner5]}
/>
</ClippableView>
</View>
);
}
}
const sample1Styles = StyleSheet.create({
outer: {
width: 200,
height: 200,
backgroundColor: 'red',
},
inner: {
position: 'absolute',
width: 100,
height: 100,
backgroundColor: 'green',
},
inner1: {
top: -150,
left: 50,
},
inner2: {
top: 50,
left: 50,
},
inner3: {
top: 250,
left: 50,
},
inner4: {
left: -150,
top: 50,
},
inner5: {
left: 250,
top: 50,
},
});
class ClippingSample2 extends React.Component {
render() {
return (
<View>
<ClippableView
clippableViewID="outer"
style={sample2Styles.outer}
removeClippedSubviews={true}>
<ClippableView
clippableViewID="complexInner"
style={sample2Styles.complexInner}
removeClippedSubviews={true}>
<ClippableView
clippableViewID="inner1"
style={[sample2Styles.inner, sample2Styles.inner1]}
/>
<ClippableView
clippableViewID="inner2"
style={[sample2Styles.inner, sample2Styles.inner2]}
/>
<ClippableView
clippableViewID="inner3"
style={[sample2Styles.inner, sample2Styles.inner3]}
/>
<ClippableView
clippableViewID="inner4"
style={[sample2Styles.inner, sample2Styles.inner4]}
/>
</ClippableView>
</ClippableView>
</View>
);
}
}
const sample2Styles = StyleSheet.create({
outer: {
width: 200,
height: 200,
backgroundColor: 'red',
},
complexInner: {
position: 'absolute',
width: 200,
height: 200,
left: 100,
top: 100,
backgroundColor: 'green',
},
inner: {
position: 'absolute',
width: 80,
height: 80,
backgroundColor: 'blue',
},
inner1: {
left: 10,
top: 10,
},
inner2: {
right: 10,
top: 10,
},
inner3: {
left: 10,
bottom: 10,
},
inner4: {
right: 10,
bottom: 10,
},
});
class UpdatingSample1 extends React.Component {
render() {
const inner1Styles = [
updating1Styles.inner1,
{height: this.props.update1 ? 200 : 100},
];
const inner2Styles = [
updating1Styles.inner2,
{top: this.props.update2 ? 200 : 50},
];
return (
<View>
<ClippableView
clippableViewID="outer"
style={updating1Styles.outer}
removeClippedSubviews={true}>
<ClippableView clippableViewID="inner1" style={inner1Styles} />
<ClippableView clippableViewID="inner2" style={inner2Styles} />
</ClippableView>
</View>
);
}
}
const updating1Styles = StyleSheet.create({
outer: {
width: 200,
height: 200,
backgroundColor: 'red',
},
inner1: {
position: 'absolute',
width: 100,
height: 100,
left: 50,
top: -100,
backgroundColor: 'green',
},
inner2: {
position: 'absolute',
width: 100,
height: 100,
left: 50,
top: 50,
backgroundColor: 'green',
},
});
class UpdatingSample2 extends React.Component {
render() {
const outerStyles = [
updating2Styles.outer,
{height: this.props.update ? 200 : 100},
];
return (
<View>
<ClippableView
clippableViewID="outer"
style={outerStyles}
removeClippedSubviews={true}>
<ClippableView
clippableViewID="inner"
style={updating2Styles.inner}
/>
</ClippableView>
</View>
);
}
}
const updating2Styles = StyleSheet.create({
outer: {
width: 100,
height: 100,
backgroundColor: 'red',
},
inner: {
position: 'absolute',
width: 100,
height: 100,
top: 100,
backgroundColor: 'green',
},
});
class ScrollViewTest extends React.Component {
render() {
const children = [];
for (let i = 0; i < 4; i++) {
children[i] = (
<ClippableView
key={i}
style={scrollTestStyles.row}
clippableViewID={'' + i}
/>
);
}
for (let i = 4; i < 6; i++) {
const viewID = 'C' + (i - 4);
children[i] = (
<ClippableView
key={i}
style={scrollTestStyles.complex}
clippableViewID={viewID}
removeClippedSubviews={true}>
<ClippableView
style={scrollTestStyles.inner}
clippableViewID={viewID + '.1'}
/>
<ClippableView
style={scrollTestStyles.inner}
clippableViewID={viewID + '.2'}
/>
</ClippableView>
);
}
return (
<ScrollView
removeClippedSubviews={true}
style={scrollTestStyles.scrollView}
testID="scroll_view">
{children}
</ScrollView>
);
}
}
const scrollTestStyles = StyleSheet.create({
scrollView: {
width: 200,
height: 300,
},
row: {
flex: 1,
height: 120,
backgroundColor: 'red',
borderColor: 'blue',
borderBottomWidth: 1,
},
complex: {
flex: 1,
height: 240,
backgroundColor: 'yellow',
borderColor: 'blue',
borderBottomWidth: 1,
},
inner: {
flex: 1,
margin: 10,
height: 100,
backgroundColor: 'gray',
borderColor: 'green',
borderWidth: 1,
},
});
let appInstance = null;
class SubviewsClippingTestApp extends React.Component {
state = {};
UNSAFE_componentWillMount() {
appInstance = this;
}
setComponent = component => {
this.setState({component: component});
};
render() {
const component = this.state.component;
return <View>{component}</View>;
}
}
const SubviewsClippingTestModule = {
App: SubviewsClippingTestApp,
renderClippingSample1: function() {
appInstance.setComponent(<ClippingSample1 />);
},
renderClippingSample2: function() {
appInstance.setComponent(<ClippingSample2 />);
},
renderUpdatingSample1: function(update1, update2) {
appInstance.setComponent(
<UpdatingSample1 update1={update1} update2={update2} />,
);
},
renderUpdatingSample2: function(update) {
appInstance.setComponent(<UpdatingSample2 update={update} />);
},
renderScrollViewTest: function() {
appInstance.setComponent(<ScrollViewTest />);
},
};
BatchedBridge.registerCallableModule(
'SubviewsClippingTestModule',
SubviewsClippingTestModule,
);
module.exports = SubviewsClippingTestModule;

View File

@ -0,0 +1,101 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {
NativeModules,
RefreshControl,
ScrollView,
StyleSheet,
Text,
TouchableWithoutFeedback,
View,
} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {SwipeRefreshLayoutRecordingModule: RecordingModule} = NativeModules;
class Row extends React.Component {
state = {
clicks: 0,
};
render() {
return (
<TouchableWithoutFeedback onPress={this._onPress}>
<View>
<Text>{this.state.clicks + ' clicks'}</Text>
</View>
</TouchableWithoutFeedback>
);
}
_onPress = () => {
this.setState({clicks: this.state.clicks + 1});
};
}
let app = null;
class SwipeRefreshLayoutTestApp extends React.Component {
state = {
rows: 2,
};
componentDidMount() {
app = this;
}
render() {
const rows = [];
for (let i = 0; i < this.state.rows; i++) {
rows.push(<Row key={i} />);
}
return (
<ScrollView
style={styles.container}
refreshControl={
<RefreshControl
style={styles.content}
refreshing={false}
onRefresh={() => RecordingModule.onRefresh()}
/>
}>
{rows}
</ScrollView>
);
}
}
const SwipeRefreshLayoutTestModule = {
SwipeRefreshLayoutTestApp,
setRows: function(rows) {
if (app != null) {
app.setState({rows});
}
},
};
BatchedBridge.registerCallableModule(
'SwipeRefreshLayoutTestModule',
SwipeRefreshLayoutTestModule,
);
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
},
});
module.exports = SwipeRefreshLayoutTestModule;

View File

@ -0,0 +1,125 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
// Disable LogBox so we do not have to mock its dependencies.
require('react-native').LogBox.ignoreAllLogs(true);
// Include callable JS modules first, in case one of the other ones below throws
require('./ProgressBarTestModule');
require('./ViewRenderingTestModule');
require('./TestJavaToJSArgumentsModule');
require('./TestJSLocaleModule');
require('./TestJSToJavaParametersModule');
require('./TestJavaToJSReturnValuesModule');
require('./UIManagerTestModule');
require('./CatalystRootViewTestModule');
require('./DatePickerDialogTestModule');
require('./MeasureLayoutTestModule');
require('./PickerAndroidTestModule');
require('./ScrollViewTestModule');
require('./ShareTestModule');
require('./SwipeRefreshLayoutTestModule');
require('./TextInputTestModule');
// Define catalyst test apps used in integration tests
const {AppRegistry} = require('react-native');
const apps = [
{
appKey: 'AnimatedTransformTestApp',
component: () =>
require('./AnimatedTransformTestModule').AnimatedTransformTestApp,
},
{
appKey: 'CatalystRootViewTestApp',
component: () =>
require('./CatalystRootViewTestModule').CatalystRootViewTestApp,
},
{
appKey: 'DatePickerDialogTestApp',
component: () =>
require('./DatePickerDialogTestModule').DatePickerDialogTestApp,
},
{
appKey: 'JSResponderTestApp',
component: () => require('./JSResponderTestApp'),
},
{
appKey: 'HorizontalScrollViewTestApp',
component: () =>
require('./ScrollViewTestModule').HorizontalScrollViewTestApp,
},
{
appKey: 'ImageOverlayColorTestApp',
component: () => require('./ImageOverlayColorTestApp'),
},
{
appKey: 'ImageErrorTestApp',
component: () => require('./ImageErrorTestApp'),
},
{
appKey: 'InitialPropsTestApp',
component: () => require('./InitialPropsTestApp'),
},
{
appKey: 'LayoutEventsTestApp',
component: () => require('./LayoutEventsTestApp'),
},
{
appKey: 'MeasureLayoutTestApp',
component: () => require('./MeasureLayoutTestModule').MeasureLayoutTestApp,
},
{
appKey: 'MultitouchHandlingTestAppModule',
component: () => require('./MultitouchHandlingTestAppModule'),
},
{
appKey: 'NativeIdTestApp',
component: () => require('./NativeIdTestModule').NativeIdTestApp,
},
{
appKey: 'PickerAndroidTestApp',
component: () => require('./PickerAndroidTestModule').PickerAndroidTestApp,
},
{
appKey: 'ScrollViewTestApp',
component: () => require('./ScrollViewTestModule').ScrollViewTestApp,
},
{
appKey: 'ShareTestApp',
component: () => require('./ShareTestModule').ShareTestApp,
},
{
appKey: 'SubviewsClippingTestApp',
component: () => require('./SubviewsClippingTestModule').App,
},
{
appKey: 'SwipeRefreshLayoutTestApp',
component: () =>
require('./SwipeRefreshLayoutTestModule').SwipeRefreshLayoutTestApp,
},
{
appKey: 'TextInputTestApp',
component: () => require('./TextInputTestModule').TextInputTestApp,
},
{
appKey: 'TestIdTestApp',
component: () => require('./TestIdTestModule').TestIdTestApp,
},
{
appKey: 'TouchBubblingTestAppModule',
component: () => require('./TouchBubblingTestAppModule'),
},
];
module.exports = apps;
AppRegistry.registerConfig(apps);

View File

@ -0,0 +1,86 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const React = require('react');
const {
Image,
StyleSheet,
Text,
TextInput,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
View,
} = require('react-native');
const TouchableBounce = require('react-native/Libraries/Components/Touchable/TouchableBounce');
/**
* All the views implemented on Android, each with the testID property set.
* We test that:
* - The app renders fine
* - The testID property is passed to the native views
*/
class TestIdTestApp extends React.Component {
render() {
return (
<View>
<Image
testID="Image"
source={{
uri:
'data:image/gif;base64,' +
'R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapy' +
'uvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/' +
'TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5' +
'iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97V' +
'riy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7',
}}
style={styles.base}
/>
<Text testID="Text">text</Text>
<TextInput testID="TextInput" value="Text input" />
<TouchableBounce testID="TouchableBounce">
<Text>TouchableBounce</Text>
</TouchableBounce>
<TouchableHighlight testID="TouchableHighlight">
<Text>TouchableHighlight</Text>
</TouchableHighlight>
<TouchableOpacity testID="TouchableOpacity">
<Text>TouchableOpacity</Text>
</TouchableOpacity>
<TouchableWithoutFeedback testID="TouchableWithoutFeedback">
<View>
<Text>TouchableWithoutFeedback</Text>
</View>
</TouchableWithoutFeedback>
<View testID="View" />
</View>
);
}
}
const styles = StyleSheet.create({
base: {
width: 150,
height: 50,
},
});
module.exports = {
TestIdTestApp: TestIdTestApp,
};

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const {NativeModules} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {Recording} = NativeModules;
const TestJSLocaleModule = {
toUpper: function(s) {
Recording.record(s.toUpperCase());
},
toLower: function(s) {
Recording.record(s.toLowerCase());
},
};
BatchedBridge.registerCallableModule('TestJSLocaleModule', TestJSLocaleModule);
module.exports = TestJSLocaleModule;

View File

@ -0,0 +1,125 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const {NativeModules} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {Recording} = NativeModules;
const TestJSToJavaParametersModule = {
returnBasicTypes: function() {
Recording.receiveBasicTypes('foo', 3.14, true, null);
},
returnBoxedTypes: function() {
Recording.receiveBoxedTypes(42, 3.14, true);
},
returnDynamicTypes: function() {
Recording.receiveDynamic('foo');
Recording.receiveDynamic(3.14);
},
returnArrayWithBasicTypes: function() {
Recording.receiveArray(['foo', 3.14, -111, true, null]);
},
returnNestedArray: function() {
Recording.receiveArray(['we', ['have', ['to', ['go', ['deeper']]]]]);
},
returnArrayWithMaps: function() {
Recording.receiveArray([{m1k1: 'm1v1', m1k2: 'm1v2'}, {m2k1: 'm2v1'}]);
},
returnMapWithBasicTypes: function() {
Recording.receiveMap({
stringKey: 'stringValue',
doubleKey: 3.14,
intKey: -11,
booleanKey: true,
nullKey: null,
});
},
returnNestedMap: function() {
Recording.receiveMap({
weHaveToGoDeeper: {
inception: true,
},
});
},
returnMapWithArrays: function() {
Recording.receiveMap({
empty: [],
ints: [43, 44],
mixed: [77, 'string', ['another', 'array']],
});
},
returnArrayWithStringDoubleIntMapArrayBooleanNull: function() {
Recording.receiveArray(['string', 3.14, 555, {}, [], true, null]);
},
returnMapWithStringDoubleIntMapArrayBooleanNull: function() {
Recording.receiveMap({
string: 'string',
double: 3,
map: {},
int: -55,
array: [],
boolean: true,
null: null,
});
},
returnArrayWithLargeInts: function() {
Recording.receiveArray([2147483648, -5555555555]);
},
returnMapWithLargeInts: function() {
Recording.receiveMap({first: -2147483649, second: 5551231231});
},
returnMapForMerge1: function() {
Recording.receiveMap({
a: 1,
b: 41,
c: 'string',
d: 'other string',
e: [1, 'foo', 'bar'],
f: null,
});
},
returnMapForMerge2: function() {
Recording.receiveMap({
a: 'overwrite',
d: 77,
e: null,
f: ['array', 'with', 'stuff'],
newkey: 'newvalue',
});
},
returnMapWithMultibyteUTF8CharacterString: function() {
Recording.receiveMap({
'one-byte': 'a',
'two-bytes': '\u00A2',
'three-bytes': '\u20AC',
'four-bytes': '\uD83D\uDE1C',
mixed:
'\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 \u6211 \uD83D\uDE0E ja\u017A\u0107',
});
},
returnArrayWithMultibyteUTF8CharacterString: function() {
Recording.receiveArray([
'a',
'\u00A2',
'\u20AC',
'\uD83D\uDE1C',
'\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 \u6211 \uD83D\uDE0E ja\u017A\u0107',
]);
},
};
BatchedBridge.registerCallableModule(
'TestJSToJavaParametersModule',
TestJSToJavaParametersModule,
);
module.exports = TestJSToJavaParametersModule;

View File

@ -0,0 +1,114 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {assertEquals, assertTrue} = require('./Asserts');
function strictStringCompare(a, b) {
if (typeof a !== 'string' || typeof b !== 'string' || a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a.charCodeAt(i) !== b.charCodeAt(i)) {
return false;
}
}
return true;
}
function assertStrictStringEquals(a, b) {
assertTrue(strictStringCompare(a, b), 'Expected: ' + a + ', received: ' + b);
}
const TestJavaToJSArgumentsModule = {
receiveBasicTypes: function(str, dbl, bool, null_arg) {
assertEquals('foo', str);
assertEquals(3.14, dbl);
assertEquals(true, bool);
assertEquals(null, null_arg);
},
receiveArrayWithBasicTypes: function(arr) {
assertEquals(4, arr.length);
assertEquals('red panda', arr[0]);
assertEquals(1.19, arr[1]);
assertEquals(true, arr[2]);
assertEquals(null, arr[3]);
},
receiveNestedArray: function(arr) {
assertEquals(2, arr.length);
assertEquals('level1', arr[0]);
const arr2 = arr[1];
assertEquals('level2', arr2[0]);
const arr3 = arr2[1];
assertEquals('level3', arr3[0]);
},
receiveArrayWithMaps: function(arr) {
assertEquals(2, arr.length);
const m1 = arr[0];
const m2 = arr[1];
assertEquals('m1v1', m1.m1k1);
assertEquals('m1v2', m1.m1k2);
assertEquals('m2v1', m2.m2k1);
},
receiveMapWithBasicTypes: function(map) {
assertEquals('stringValue', map.stringKey);
assertEquals(3.14, map.doubleKey);
assertEquals(true, map.booleanKey);
assertEquals(null, map.nullKey);
},
receiveNestedMap: function(map) {
const nestedMap = map.nestedMap;
assertEquals('foxes', nestedMap.animals);
},
receiveMapWithArrays: function(map) {
const a1 = map.array1;
const a2 = map.array2;
assertEquals(3, a1.length);
assertEquals(2, a2.length);
assertEquals(3, a1[0]);
assertEquals(9, a2[1]);
},
receiveMapAndArrayWithNullValues: function(map, array) {
assertEquals(null, map.string);
assertEquals(null, map.array);
assertEquals(null, map.map);
assertEquals(null, array[0]);
assertEquals(null, array[1]);
assertEquals(null, array[2]);
},
receiveMapWithMultibyteUTF8CharacterString: function(map) {
assertStrictStringEquals('\u00A2', map['two-bytes']);
assertStrictStringEquals('\u20AC', map['three-bytes']);
assertStrictStringEquals('\uD83D\uDE1C', map['four-bytes']);
assertStrictStringEquals(
'\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 \u6211 \uD83D\uDE0E ja\u017A\u0107',
map.mixed,
);
},
receiveArrayWithMultibyteUTF8CharacterString: function(array) {
assertTrue(true);
assertStrictStringEquals('\u00A2', array[0]);
assertStrictStringEquals('\u20AC', array[1]);
assertStrictStringEquals('\uD83D\uDE1C', array[2]);
assertStrictStringEquals(
'\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 \u6211 \uD83D\uDE0E ja\u017A\u0107',
array[3],
);
},
};
BatchedBridge.registerCallableModule(
'TestJavaToJSArgumentsModule',
TestJavaToJSArgumentsModule,
);
module.exports = TestJavaToJSArgumentsModule;

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const {NativeModules} = require('react-native');
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
const {assertEquals, assertTrue} = require('./Asserts');
const {TestModule} = NativeModules;
const TestJavaToJSReturnValuesModule = {
callMethod: function(methodName, expectedType, expectedJSON) {
const result = TestModule[methodName]();
assertEquals(expectedType, typeof result);
assertEquals(expectedJSON, JSON.stringify(result));
},
triggerException: function() {
try {
TestModule.triggerException();
} catch (ex) {
assertTrue(ex.message.indexOf('Exception triggered') !== -1);
}
},
};
BatchedBridge.registerCallableModule(
'TestJavaToJSReturnValuesModule',
TestJavaToJSReturnValuesModule,
);
module.exports = TestJavaToJSReturnValuesModule;

Some files were not shown because too many files have changed in this diff Show More