mirror of
https://github.com/react/react-native.git
synced 2026-10-01 01:37:59 +08:00
Summary: <!-- Thank you for sending the PR! We appreciate you spending the time to work on these changes. Help us understand your motivation by explaining why you decided to make this change. You can learn more about contributing to React Native here: http://facebook.github.io/react-native/docs/contributing.html Happy contributing! --> * To be on par with Apple TV support, this makes it possible to run React Native apps on Android TV devices (See also: https://react-native.canny.io/feature-requests/p/android-tv-support) * These changes also make it possible to navigate through the app using D-PAD buttons that are present on some mobile devices * Since these changes affect, among others, `ReactRootView.java` and `Touchable.js` code and are closely related to Apple TV implementation, it makes sense for them to be included in the core - React native apps can be launched on Android TV devices and properly render their content - Navigation is possible using left, right, top, bottom arrows from the remote (or D-PAD) - Touchable components can handle D-PAD center button press events and correctly fire their `onPress` handlers - Touchable components will receive `onPressIn` and `onPressOut` events and can react to focus/blur changes appropriately (just like on Apple TV) - `Platform` constants allow to check if the react-native app is running on TV (`Platform.isTV`) - `ScrollView`s behave correctly (same as native implementation) when switching to view outside bounds – that is, the container would scroll such that the newly focused element is fully visible - Native "clicking" sounds are played when moving between focusable elements - Play/Pause click event is send to `TVEventHandler` - Rewind and FastForward events are send to `TVEventHandler` - Back button behaves as a normal Android back button - Diagonal buttons work correctly on Android TV, e.g. if there is no button directly to the right from the focused one, but there is one to the right but a bit higher/lower it will grab focus - Dev menu can be accessed by long pressing fast forward button A demo showing RNTester app running on Android TV device (Amazon Fire TV Stick) can be found here: [](http://www.youtube.com/watch?v=EzIQErHhY20) - `TextInput` will not work on Android TV devices. There's an issue with native `ReactEditText` implementation that prevents it from receiving focus. This makes it impossible to navigate to `TextInput`. This will be fixed next, but will be included in a separate Pull Request - ~Overlay permissions cannot be granted on Android TV devices running Android version >= 6.0 This is because the overlay permission can only be granted by firing an Intent to open settings page (`ACTION_MANAGE_OVERLAY_PERMISSION`). Since this page does not exist on TV devices the permission cannot be requested. This will make the app crash when trying to open dev menu (⌘+M) or displaying a redbox error. Note: This does not affect devices running Android version < 6.0 (for example Amazon Fire TV Stick)~ This is now fixed by: https://github.com/facebook/react-native/pull/16596 * Launch the RNTester app on Android TV device. * Ensure it launches without a crash * Ensure basic navigation is possible * Ensure Touchable components can receive select events * Ensure the changes do not break current Android and iOS mobile devices functionality. * Ensure the changes do not break current Apple TV functionality. [RNAndroidTVDemo video](http://img.youtube.com/vi/EzIQErHhY20/0.jpg) * Added `ReactAndroidTVViewManager` that handles TV `KeyEvent`s and dispatches events to JS - This is the core that enables basic navigation functionality on Android TV devices * Following the above change we copy `TVEventHandler.ios.js` into `TVEventHandler.android.js` to enable JS to pick up those native navigation events and dispatch them further to subscribed views. (Note: We do not have a native `TVNavigationEventEmitter` implementation on Android, thus this file is slightly modified, e.g. it does pass `null` to `NativeEventEmitter` constructor) * Added `uiMode` to `AndroidInfoModule`. (**Note**: This required changing `extends BaseJavaModule` to `extends ReactContextBaseJavaModule` to be able to use `getSystemService` which requires `Context` instance! * Added `isTV` constants to both `Platform.ios.js` (keeping the deprecated `isTVOS` as well) and `Platform.android.js` * Changed condition check on `Touchable.js` to use the newly added `isTV` flag to properly handle TV navigation events on Android as well * Added `LEANBACK_LAUNCHER` to `RNTester` `intent-filter` so that it is possible to launch it on Android TV devices. * See also a PR to `react-native-website` repo with updated docs for Android TV: https://github.com/facebook/react-native-website/pull/59 - [ ] Fix `TextInput` components handling by allowing them to be focused and making a proper navigation between them (and/or other components) possible. One thing to note here that the default behavior to immediately open software keyboard when focused on `TextInput` field will need to be adjusted on Android TV as well) - [x] Fix overlay permissions issue by changing the way redbox/dev menu are displayed (see: https://github.com/facebook/react-native/pull/16596) - [ ] Adjust placement of TV-related files (e.g. the `TVEventHandler.js` file is placed inside `AppleTV` directory which is not accurate, since it does handle Android TV events as well) Previous discussion: https://github.com/SoftwareMansion/react-native/pull/1 <!-- Help reviewers and the release process by writing your own release notes **INTERNAL and MINOR tagged notes will not be included in the next version's final release notes.** CATEGORY [----------] TYPE [ CLI ] [-------------] LOCATION [ DOCS ] [ BREAKING ] [-------------] [ GENERAl ] [ BUGFIX ] [-{Component}-] [ INTERNAL ] [ ENHANCEMENT ] [ {File} ] [ IOS ] [ FEATURE ] [ {Directory} ] |-----------| [ ANDROID ] [ MINOR ] [ {Framework} ] - | {Message} | [----------] [-------------] [-------------] |-----------| [CATEGORY] [TYPE] [LOCATION] - MESSAGE EXAMPLES: [IOS] [BREAKING] [FlatList] - Change a thing that breaks other things [ANDROID] [BUGFIX] [TextInput] - Did a thing to TextInput [CLI] [FEATURE] [local-cli/info/info.js] - CLI easier to do things with [DOCS] [BUGFIX] [GettingStarted.md] - Accidentally a thing/word [GENERAL] [ENHANCEMENT] [Yoga] - Added new yoga thing/position [INTERNAL] [FEATURE] [./scripts] - Added thing to script that nobody will see --> [ANDROID] [FEATURE] [TV] - Added support for Android TV devices Closes https://github.com/facebook/react-native/pull/16500 Differential Revision: D6536847 Pulled By: hramos fbshipit-source-id: 17bbb11e8583b97f195ced5fd9762f8902fb8a3d
761 lines
26 KiB
Java
761 lines
26 KiB
Java
/**
|
|
* Copyright (c) 2014-present, Facebook, Inc.
|
|
*
|
|
* 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.WritableMap;
|
|
import com.facebook.react.bridge.WritableNativeMap;
|
|
import com.facebook.react.modules.appstate.AppStateModule;
|
|
import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
|
|
import com.facebook.react.modules.systeminfo.AndroidInfoModule;
|
|
import com.facebook.react.testing.FakeWebSocketModule;
|
|
import com.facebook.react.testing.ReactIntegrationTestCase;
|
|
import com.facebook.react.testing.ReactTestHelper;
|
|
import com.facebook.react.uimanager.UIImplementationProvider;
|
|
import com.facebook.react.uimanager.UIManagerModule;
|
|
import com.facebook.react.uimanager.ViewManager;
|
|
import com.facebook.react.views.view.ReactViewManager;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import org.junit.Ignore;
|
|
|
|
/**
|
|
* Integration test to verify passing various types of parameters from JS to Java works
|
|
*
|
|
* TODO: we should run these tests with isBlockingSynchronousMethod = true as well,
|
|
* since they currently use a completely different codepath
|
|
*/
|
|
@Ignore("Fix prop types and view managers.")
|
|
public class CatalystNativeJSToJavaParametersTestCase extends ReactIntegrationTestCase {
|
|
|
|
private interface TestJSToJavaParametersModule extends JavaScriptModule {
|
|
void returnBasicTypes();
|
|
void returnBoxedTypes();
|
|
void returnDynamicTypes();
|
|
|
|
void returnArrayWithBasicTypes();
|
|
void returnNestedArray();
|
|
void returnArrayWithMaps();
|
|
|
|
void returnMapWithBasicTypes();
|
|
void returnNestedMap();
|
|
void returnMapWithArrays();
|
|
|
|
void returnArrayWithStringDoubleIntMapArrayBooleanNull();
|
|
void returnMapWithStringDoubleIntMapArrayBooleanNull();
|
|
|
|
void returnMapForMerge1();
|
|
void returnMapForMerge2();
|
|
|
|
void returnMapWithMultibyteUTF8CharacterString();
|
|
void returnArrayWithMultibyteUTF8CharacterString();
|
|
|
|
void returnArrayWithLargeInts();
|
|
void returnMapWithLargeInts();
|
|
}
|
|
|
|
private RecordingTestModule mRecordingTestModule;
|
|
private CatalystInstance mCatalystInstance;
|
|
|
|
@Override
|
|
protected void setUp() throws Exception {
|
|
super.setUp();
|
|
|
|
List<ViewManager> viewManagers = Arrays.<ViewManager>asList(
|
|
new ReactViewManager());
|
|
final UIManagerModule mUIManager =
|
|
new UIManagerModule(getContext(), viewManagers, new UIImplementationProvider(), 0);
|
|
UiThreadUtil.runOnUiThread(
|
|
new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
mUIManager.onHostResume();
|
|
}
|
|
});
|
|
waitForIdleSync();
|
|
|
|
mRecordingTestModule = new RecordingTestModule();
|
|
mCatalystInstance = ReactTestHelper.catalystInstanceBuilder(this)
|
|
.addNativeModule(mRecordingTestModule)
|
|
.addNativeModule(new AndroidInfoModule(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 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;
|
|
}
|
|
}
|
|
}
|