diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystMultitouchHandlingTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystMultitouchHandlingTestCase.java new file mode 100644 index 000000000..ec21f0c2f --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystMultitouchHandlingTestCase.java @@ -0,0 +1,656 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.tests; + +import java.util.List; + +import android.view.MotionEvent; + +import com.facebook.react.testing.ReactInstanceSpecForTest; +import com.facebook.react.testing.ReactAppInstrumentationTestCase; +import com.facebook.react.testing.StringRecordingModule; + +/** + * Test case for verifying that multitouch events are directed to the React's view touch handlers + * properly + */ +public class CatalystMultitouchHandlingTestCase extends ReactAppInstrumentationTestCase { + + private final StringRecordingModule mRecordingModule = new StringRecordingModule(); + + @Override + protected String getReactApplicationKeyUnderTest() { + return "MultitouchHandlingTestAppModule"; + } + + @Override + protected ReactInstanceSpecForTest createReactInstanceSpecForTest() { + return new ReactInstanceSpecForTest() + .addNativeModule(mRecordingModule); + } + + /** + * In this test case we send pre-recorded stream of pinch out gesture and verify that we have + * recorded important touch events in JS module + */ + public void testMultitouchEvents() throws InterruptedException { + generateRecordedPinchTouchEvents(); + waitForBridgeAndUIIdle(); + + // Expect to receive at least 5 events (DOWN for each pointer, UP for each pointer and at least + // one MOVE event with both pointers down) + List 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 occurence + moveWithBothPointersEventIndex = i; + } + } + + assertEquals(0, startEventIndex); + assertTrue(-1 != startExtraPointerEventIndex); + assertTrue(-1 != moveWithBothPointersEventIndex); + assertTrue(-1 != endExtraPointerEventIndex); + assertTrue(startExtraPointerEventIndex < moveWithBothPointersEventIndex); + assertTrue(endExtraPointerEventIndex > moveWithBothPointersEventIndex); + assertEquals(calls.size() - 1, endEventIndex); + } + + private MotionEvent.PointerProperties createPointerProps(int id, int toolType) { + MotionEvent.PointerProperties pointerProps = new MotionEvent.PointerProperties(); + pointerProps.id = id; + pointerProps.toolType = toolType; + return pointerProps; + } + + private MotionEvent.PointerCoords createPointerCoords(float x, float y) { + MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords(); + pointerCoords.x = x; + pointerCoords.y = y; + return pointerCoords; + } + + private void dispatchEvent( + final int action, + final long start, + final long when, + final int pointerCount, + final MotionEvent.PointerProperties[] pointerProps, + final MotionEvent.PointerCoords[] pointerCoords) { + getRootView().post( + new Runnable() { + @Override + public void run() { + MotionEvent event = + MotionEvent.obtain(start, when, action, pointerCount, pointerProps, pointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0); + getRootView().dispatchTouchEvent(event); + event.recycle(); + } + }); + getInstrumentation().waitForIdleSync(); + } + + /** + * This method "replay" multi-touch gesture recorded with modified TouchesHelper class that + * generated this piece of code (see https://phabricator.fb.com/P19756940). + * This is not intended to be copied/reused and once we need to have more multitouch gestures + * in instrumentation tests we should either: + * - implement nice generator similar to {@link SingleTouchGestureGenerator} + * - implement gesture recorded that will record touch data using arbitrary format and then read + * this recorded touch sequence during tests instead of generating code like this + */ + private void generateRecordedPinchTouchEvents() { + // START OF GENERATED CODE + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[1]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(268.0f, 347.0f); + dispatchEvent(MotionEvent.ACTION_DOWN, 446560605, 446560605, 1, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[1]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(267.0f, 346.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560630, 1, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(267.0f, 346.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(225.0f, 542.0f); + dispatchEvent(MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT), 446560605, 446560630, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(266.0f, 345.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(225.0f, 542.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560647, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(265.0f, 344.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(224.0f, 541.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560664, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(264.0f, 342.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(223.0f, 540.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560681, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(263.0f, 340.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(222.0f, 539.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560698, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(262.0f, 337.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(221.0f, 538.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560714, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(262.0f, 333.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(220.0f, 537.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560731, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(261.0f, 328.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(219.0f, 536.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560748, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(260.0f, 321.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(218.0f, 536.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560765, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(260.0f, 313.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(216.0f, 536.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560781, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(260.0f, 304.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(214.0f, 537.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560798, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(260.0f, 295.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(211.0f, 539.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560815, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(261.0f, 285.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(208.0f, 542.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560832, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(264.0f, 274.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(203.0f, 547.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560849, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(266.0f, 264.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(199.0f, 551.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560865, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(269.0f, 254.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(194.0f, 556.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560882, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(273.0f, 245.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(190.0f, 561.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560899, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(276.0f, 236.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(186.0f, 567.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560916, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(280.0f, 227.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(183.0f, 573.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560933, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(283.0f, 219.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(181.0f, 579.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560949, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(287.0f, 211.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(179.0f, 584.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560966, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(291.0f, 202.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(177.0f, 589.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446560983, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(296.0f, 193.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(175.0f, 593.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561000, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(301.0f, 184.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(174.0f, 598.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561016, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(307.0f, 176.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(173.0f, 603.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561033, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(313.0f, 168.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(172.0f, 608.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561050, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(317.0f, 160.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(171.0f, 613.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561067, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(320.0f, 154.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(170.0f, 619.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561084, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(323.0f, 149.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(169.0f, 624.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561100, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(325.0f, 145.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(168.0f, 628.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561117, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(328.0f, 141.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(167.0f, 632.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561134, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(331.0f, 137.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(166.0f, 636.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561151, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(334.0f, 134.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(165.0f, 639.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561167, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(337.0f, 131.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(164.0f, 643.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561184, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(338.0f, 128.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(164.0f, 646.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561201, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(340.0f, 126.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(164.0f, 649.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561218, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(341.0f, 124.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(163.0f, 652.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561234, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(342.0f, 122.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(163.0f, 655.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561251, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(343.0f, 120.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(162.0f, 659.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561268, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(344.0f, 118.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(161.0f, 664.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561285, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(345.0f, 116.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(160.0f, 667.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561302, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(346.0f, 115.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(158.0f, 670.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561318, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(347.0f, 114.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(157.0f, 673.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561335, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(348.0f, 113.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(156.0f, 676.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561352, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(348.0f, 112.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(155.0f, 677.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561369, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(349.0f, 111.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(154.0f, 678.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561386, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(349.0f, 110.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(153.0f, 679.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561402, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(349.0f, 109.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(152.0f, 680.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561419, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(349.0f, 110.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(151.0f, 680.0f); + dispatchEvent(MotionEvent.ACTION_MOVE, 446560605, 446561435, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[2]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[2]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(349.0f, 110.0f); + pointerProps[1] = createPointerProps(1, 1); + pointerCoords[1] = createPointerCoords(151.0f, 680.0f); + dispatchEvent(MotionEvent.ACTION_POINTER_UP | (0 << MotionEvent.ACTION_POINTER_INDEX_SHIFT), 446560605, 446561443, 2, pointerProps, pointerCoords); + } + + { + MotionEvent.PointerProperties[] pointerProps = new MotionEvent.PointerProperties[1]; + MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1]; + pointerProps[0] = createPointerProps(0, 1); + pointerCoords[0] = createPointerCoords(151.0f, 680.0f); + dispatchEvent(MotionEvent.ACTION_UP, 446560605, 446561451, 1, pointerProps, pointerCoords); + } + // END OF GENERATED CODE + } + +} diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java new file mode 100644 index 000000000..f6130be3a --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java @@ -0,0 +1,709 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.tests; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.facebook.react.bridge.BaseJavaModule; +import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.InvalidIteratorException; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NoSuchKeyException; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.ReadableNativeMap; +import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.facebook.react.bridge.UnexpectedNativeTypeException; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.modules.systeminfo.AndroidInfoModule; +import com.facebook.react.testing.ReactIntegrationTestCase; +import com.facebook.react.testing.ReactTestHelper; +import com.facebook.react.uimanager.UIImplementation; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.views.view.ReactViewManager; + +import org.junit.Ignore; + +/** + * Integration test to verify passing various types of parameters from JS to Java works + */ +public class CatalystNativeJSToJavaParametersTestCase extends ReactIntegrationTestCase { + + private interface TestJSToJavaParametersModule extends JavaScriptModule { + void returnBasicTypes(); + + 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 viewManagers = Arrays.asList( + new ReactViewManager()); + final UIManagerModule mUIManager = new UIManagerModule( + getContext(), + viewManagers, + new UIImplementation(getContext(), viewManagers)); + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + mUIManager.onHostResume(); + } + }); + waitForIdleSync(); + + mRecordingTestModule = new RecordingTestModule(); + mCatalystInstance = ReactTestHelper.catalystInstanceBuilder(this) + .addNativeModule(mRecordingTestModule) + .addNativeModule(new AndroidInfoModule()) + .addNativeModule(mUIManager) + .addJSModule(TestJSToJavaParametersModule.class) + .build(); + } + + public void testBasicTypes() { + mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnBasicTypes(); + waitForBridgeAndUIIdle(); + + List 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 testArrayWithBasicTypes() { + mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnArrayWithBasicTypes(); + waitForBridgeAndUIIdle(); + + List 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 calls = mRecordingTestModule.getMapCalls(); + assertEquals(1, calls.size()); + ReadableNativeMap map = (ReadableNativeMap) calls.get(0); + assertNotNull(map); + + ReadableMapKeySetIterator mapIterator = map.keySetIterator(); + Set keys = new HashSet(); + while (mapIterator.hasNextKey()) { + keys.add(mapIterator.nextKey()); + } + + Set expectedKeys = new HashSet( + Arrays.asList("stringKey", "doubleKey", "intKey", "booleanKey", "nullKey")); + assertEquals(keys, expectedKeys); + } + + public void testMapIterateOverNestedMaps() { + mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnNestedMap(); + waitForBridgeAndUIIdle(); + + List 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 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 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 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 class RecordingTestModule extends BaseJavaModule { + + private final List mBasicTypesCalls = new ArrayList(); + private final List mArrayCalls = new ArrayList(); + private final List mMapCalls = new ArrayList(); + + @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 receiveArray(ReadableArray array) { + mArrayCalls.add(array); + } + + @ReactMethod + public void receiveMap(ReadableMap map) { + mMapCalls.add(map); + } + + public List getBasicTypesCalls() { + return mBasicTypesCalls; + } + + public List getArrayCalls() { + return mArrayCalls; + } + + public List getMapCalls() { + return mMapCalls; + } + } +} diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJavaToJSArgumentsTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJavaToJSArgumentsTestCase.java new file mode 100644 index 000000000..fd6b1d779 --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJavaToJSArgumentsTestCase.java @@ -0,0 +1,379 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.tests; + +import java.util.Arrays; +import java.util.List; + +import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.ObjectAlreadyConsumedException; +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.bridge.UiThreadUtil; +import com.facebook.react.testing.AssertModule; +import com.facebook.react.testing.ReactIntegrationTestCase; +import com.facebook.react.testing.ReactTestHelper; +import com.facebook.react.uimanager.UIImplementation; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.views.view.ReactViewManager; + +/** + * Test marshalling arguments from Java to JS to appropriate native classes. + */ +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 viewManagers = Arrays.asList( + new ReactViewManager()); + final UIManagerModule mUIManager = new UIManagerModule( + getContext(), + viewManagers, + new UIImplementation(getContext(), viewManagers)); + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + mUIManager.onHostResume(); + } + }); + waitForIdleSync(); + + mAssertModule = new AssertModule(); + + mInstance = ReactTestHelper.catalystInstanceBuilder(this) + .addNativeModule(mAssertModule) + .addJSModule(TestJavaToJSArgumentsModule.class) + .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); + } +} diff --git a/ReactAndroid/src/androidTest/js/Asserts.js b/ReactAndroid/src/androidTest/js/Asserts.js new file mode 100644 index 000000000..9a4e9654a --- /dev/null +++ b/ReactAndroid/src/androidTest/js/Asserts.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Asserts + */ + +'use strict'; + +var Assert = require('NativeModules').Assert; + +var 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; diff --git a/ReactAndroid/src/androidTest/js/MultitouchHandlingTestAppModule.js b/ReactAndroid/src/androidTest/js/MultitouchHandlingTestAppModule.js new file mode 100644 index 000000000..7c51afbee --- /dev/null +++ b/ReactAndroid/src/androidTest/js/MultitouchHandlingTestAppModule.js @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule MultitouchHandlingTestAppModule + */ + +'use strict'; + +var React = require('React'); +var Recording = require('NativeModules').Recording; +var StyleSheet = require('StyleSheet'); +var TouchEventUtils = require('fbjs/lib/TouchEventUtils'); +var View = require('View'); + +var TouchTestApp = React.createClass({ + handleStartShouldSetResponder: function(e) { + return true; + }, + handleOnResponderMove: function(e) { + e = TouchEventUtils.extractSingleTouch(e.nativeEvent); + Recording.record('move;' + e.touches.length); + }, + handleResponderStart: function(e) { + e = TouchEventUtils.extractSingleTouch(e.nativeEvent); + if (e.touches) { + Recording.record('start;' + e.touches.length); + } else { + Recording.record('start;ExtraPointer'); + } + }, + handleResponderEnd: function(e) { + e = TouchEventUtils.extractSingleTouch(e.nativeEvent); + if (e.touches) { + Recording.record('end;' + e.touches.length); + } else { + Recording.record('end;ExtraPointer'); + } + }, + render: function() { + return ( + + ); + }, +}); + +var styles = StyleSheet.create({ + container: { + flex: 1, + }, +}); + +module.exports = TouchTestApp; diff --git a/ReactAndroid/src/androidTest/js/TestBundle.js b/ReactAndroid/src/androidTest/js/TestBundle.js index 9af72339c..39eb37c7b 100644 --- a/ReactAndroid/src/androidTest/js/TestBundle.js +++ b/ReactAndroid/src/androidTest/js/TestBundle.js @@ -15,6 +15,8 @@ console.disableYellowBox = true; // Include callable JS modules first, in case one of the other ones below throws require('ProgressBarTestModule'); require('ViewRenderingTestModule'); +require('TestJavaToJSArgumentsModule'); +require('TestJSToJavaParametersModule'); require('PickerAndroidTestModule'); require('CatalystRootViewTestModule'); @@ -40,6 +42,10 @@ var apps = [ appKey: 'HorizontalScrollViewTestApp', component: () => require('ScrollViewTestModule').HorizontalScrollViewTestApp, }, +{ + appKey: 'MultitouchHandlingTestAppModule', + component: () => require('MultitouchHandlingTestAppModule') +}, { appKey: 'PickerAndroidTestApp', component: () => require('PickerAndroidTestModule').PickerAndroidTestApp, diff --git a/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js b/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js new file mode 100644 index 000000000..57380f3bc --- /dev/null +++ b/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule TestJSToJavaParametersModule + */ + +'use strict'; + +var BatchedBridge = require('BatchedBridge'); +var Recording = require('NativeModules').Recording; + +var TestJSToJavaParametersModule = { + returnBasicTypes: function() { + Recording.receiveBasicTypes('foo', 3.14, true, null); + }, + 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; diff --git a/ReactAndroid/src/androidTest/js/TestJavaToJSArgumentsModule.js b/ReactAndroid/src/androidTest/js/TestJavaToJSArgumentsModule.js new file mode 100644 index 000000000..4073d4d6a --- /dev/null +++ b/ReactAndroid/src/androidTest/js/TestJavaToJSArgumentsModule.js @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule TestJavaToJSArgumentsModule + */ + +'use strict'; + +var BatchedBridge = require('BatchedBridge'); +var {assertEquals, assertTrue} = require('Asserts'); + +function strictStringCompare(a, b) { + if (typeof a !== 'string' || typeof b !== 'string' || a.length !== b.length) { + return false; + } + for (var 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); +} + +var 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]); + var arr2 = arr[1]; + assertEquals("level2", arr2[0]); + var arr3 = arr2[1]; + assertEquals("level3", arr3[0]); + }, + receiveArrayWithMaps: function(arr) { + assertEquals(2, arr.length); + var m1 = arr[0]; + var 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) { + var nestedMap = map["nestedMap"]; + assertEquals("foxes", nestedMap["animals"]); + }, + receiveMapWithArrays: function(map) { + var a1 = map["array1"]; + var 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;