mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: fixes https://github.com/facebook/react-native/issues/27089 fixes https://github.com/facebook/react-native/issues/30191 fixes https://github.com/facebook/react-native/issues/26296 fixes https://github.com/facebook/react-native/issues/24353 Related https://github.com/facebook/react-native/issues/30052 https://github.com/facebook/react-native/issues/28004 https://github.com/facebook/react-native/issues/26536 The keyboard height of event keyboardDidShow is computed as the difference of two variables: - The screen height excluding the Android Notch DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels returns the screen height excluding the Android Notch - The Visible Area excluding the Keyboard, but including the Android Notch getWindowVisibleDisplayFrame() which returns the visible area including the Android Notch The computation of the keyboard height is wrong when the device has an Android Notch. This pr adds the Android Notch computation for API levels 28+ More info at https://github.com/facebook/react-native/issues/27089#issuecomment-775821333 ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [Android] [Fixed] - Compute Android Notch in keyboardDidShow height calculation API 28+ Pull Request resolved: https://github.com/facebook/react-native/pull/30919 Test Plan: adding a ReactRootViewTest for keyboardDidShow verifying correct functionality on API < 28 **<details><summary>TEST CASE - BEFORE FIX</summary>** <p> **WITHOUT NOTCH** - The black view on the bottom is visible - The keyboard height is 282 | **Full Screen** | **Keyboard Did Show** | |:-------------------------:|:-------------------------:| | <img src="https://user-images.githubusercontent.com/24992535/107212700-a1fd9d00-6a07-11eb-9248-26f9c4d92ae3.png" width="300" height="" /> | <img src="https://user-images.githubusercontent.com/24992535/107212590-7975a300-6a07-11eb-89f4-891a37a7c406.png" width="300" height="" /> | **WITH NOTCH** - The black view on the bottom is **not** visible. The black view is not visible because keyboardDidHide is sending the wrong keyboard height value. - The keyboard height changes to 234. The keyboard height is the same from the previous test, but the value sent from keyboardDidHide changed for the Notch. | **Full Screen** | **Keyboard Did Show** | |:-------------------------:|:-------------------------:| | <img src="https://user-images.githubusercontent.com/24992535/107212619-81cdde00-6a07-11eb-9630-7e7c8c34d798.png" width="300" height="" /> | <img src="https://user-images.githubusercontent.com/24992535/107212707-a4f88d80-6a07-11eb-9134-f077059c83a6.png" width="300" height="" /> | </p> </details> **<details><summary>TEST CASE - AFTER FIX</summary>** <p> **WITH NOTCH** - The black view on the bottom is visible - The keyboard height is 282 | **Full Screen** | **Keyboard Did Show** | |:-------------------------:|:-------------------------:| | <img src="https://user-images.githubusercontent.com/24992535/107212619-81cdde00-6a07-11eb-9630-7e7c8c34d798.png" width="300" height="" /> | <img src="https://user-images.githubusercontent.com/24992535/107349053-0d0ea880-6ac8-11eb-9695-33128080b6b8.png" width="300" height="" /> | </p> </details> Reviewed By: ShikaSD Differential Revision: D31207989 Pulled By: cortinico fbshipit-source-id: 0955a3884201122166c5c0ae2aca988a0ed4af53
256 lines
9.3 KiB
Java
256 lines
9.3 KiB
Java
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
package com.facebook.react;
|
|
|
|
import static org.fest.assertions.api.Assertions.assertThat;
|
|
import static org.mockito.Matchers.any;
|
|
import static org.mockito.Matchers.eq;
|
|
import static org.mockito.Mockito.mock;
|
|
import static org.mockito.Mockito.reset;
|
|
import static org.mockito.Mockito.verify;
|
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
import android.graphics.Rect;
|
|
import android.view.MotionEvent;
|
|
import com.facebook.react.bridge.Arguments;
|
|
import com.facebook.react.bridge.CatalystInstance;
|
|
import com.facebook.react.bridge.JavaOnlyArray;
|
|
import com.facebook.react.bridge.JavaOnlyMap;
|
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
import com.facebook.react.bridge.ReactContext;
|
|
import com.facebook.react.bridge.ReactTestHelper;
|
|
import com.facebook.react.bridge.WritableArray;
|
|
import com.facebook.react.bridge.WritableMap;
|
|
import com.facebook.react.common.SystemClock;
|
|
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
|
|
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
|
import com.facebook.react.uimanager.UIManagerModule;
|
|
import com.facebook.react.uimanager.events.Event;
|
|
import com.facebook.react.uimanager.events.EventDispatcher;
|
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
|
import java.util.Date;
|
|
import org.junit.Before;
|
|
import org.junit.Rule;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.mockito.ArgumentCaptor;
|
|
import org.mockito.Mockito;
|
|
import org.mockito.invocation.InvocationOnMock;
|
|
import org.mockito.stubbing.Answer;
|
|
import org.powermock.api.mockito.PowerMockito;
|
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
|
import org.powermock.modules.junit4.rule.PowerMockRule;
|
|
import org.robolectric.RobolectricTestRunner;
|
|
import org.robolectric.RuntimeEnvironment;
|
|
|
|
@PrepareForTest({Arguments.class, SystemClock.class})
|
|
@RunWith(RobolectricTestRunner.class)
|
|
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"})
|
|
public class RootViewTest {
|
|
|
|
@Rule public PowerMockRule rule = new PowerMockRule();
|
|
|
|
private ReactContext mReactContext;
|
|
private CatalystInstance mCatalystInstanceMock;
|
|
|
|
@Before
|
|
public void setUp() {
|
|
final long ts = SystemClock.uptimeMillis();
|
|
PowerMockito.mockStatic(Arguments.class);
|
|
PowerMockito.when(Arguments.createArray())
|
|
.thenAnswer(
|
|
new Answer<Object>() {
|
|
@Override
|
|
public Object answer(InvocationOnMock invocation) throws Throwable {
|
|
return new JavaOnlyArray();
|
|
}
|
|
});
|
|
PowerMockito.when(Arguments.createMap())
|
|
.thenAnswer(
|
|
new Answer<Object>() {
|
|
@Override
|
|
public Object answer(InvocationOnMock invocation) throws Throwable {
|
|
return new JavaOnlyMap();
|
|
}
|
|
});
|
|
PowerMockito.mockStatic(SystemClock.class);
|
|
PowerMockito.when(SystemClock.uptimeMillis())
|
|
.thenAnswer(
|
|
new Answer<Object>() {
|
|
@Override
|
|
public Object answer(InvocationOnMock invocation) throws Throwable {
|
|
return ts;
|
|
}
|
|
});
|
|
|
|
mCatalystInstanceMock = ReactTestHelper.createMockCatalystInstance();
|
|
mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
|
|
mReactContext.initializeWithInstance(mCatalystInstanceMock);
|
|
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(mReactContext);
|
|
|
|
UIManagerModule uiManagerModuleMock = mock(UIManagerModule.class);
|
|
when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class))
|
|
.thenReturn(uiManagerModuleMock);
|
|
}
|
|
|
|
@Test
|
|
public void testTouchEmitter() {
|
|
ReactInstanceManager instanceManager = mock(ReactInstanceManager.class);
|
|
when(instanceManager.getCurrentReactContext()).thenReturn(mReactContext);
|
|
|
|
UIManagerModule uiManager = mock(UIManagerModule.class);
|
|
EventDispatcher eventDispatcher = mock(EventDispatcher.class);
|
|
RCTEventEmitter eventEmitterModuleMock = mock(RCTEventEmitter.class);
|
|
when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class)).thenReturn(uiManager);
|
|
when(uiManager.getEventDispatcher()).thenReturn(eventDispatcher);
|
|
|
|
int rootViewId = 7;
|
|
|
|
ReactRootView rootView = new ReactRootView(mReactContext);
|
|
rootView.setId(rootViewId);
|
|
rootView.setRootViewTag(rootViewId);
|
|
rootView.startReactApplication(instanceManager, "");
|
|
rootView.simulateAttachForTesting();
|
|
|
|
long ts = SystemClock.currentTimeMillis();
|
|
|
|
// Test ACTION_DOWN event
|
|
rootView.onTouchEvent(MotionEvent.obtain(100, ts, MotionEvent.ACTION_DOWN, 0, 0, 0));
|
|
|
|
ArgumentCaptor<Event> downEventCaptor = ArgumentCaptor.forClass(Event.class);
|
|
verify(eventDispatcher).dispatchEvent(downEventCaptor.capture());
|
|
verifyNoMoreInteractions(eventDispatcher);
|
|
|
|
downEventCaptor.getValue().dispatch(eventEmitterModuleMock);
|
|
|
|
ArgumentCaptor<JavaOnlyArray> downActionTouchesArgCaptor =
|
|
ArgumentCaptor.forClass(JavaOnlyArray.class);
|
|
verify(eventEmitterModuleMock)
|
|
.receiveTouches(
|
|
eq("topTouchStart"), downActionTouchesArgCaptor.capture(), any(JavaOnlyArray.class));
|
|
verifyNoMoreInteractions(eventEmitterModuleMock);
|
|
|
|
assertThat(downActionTouchesArgCaptor.getValue().size()).isEqualTo(1);
|
|
assertThat(downActionTouchesArgCaptor.getValue().getMap(0))
|
|
.isEqualTo(
|
|
JavaOnlyMap.of(
|
|
"pageX",
|
|
0.,
|
|
"pageY",
|
|
0.,
|
|
"locationX",
|
|
0.,
|
|
"locationY",
|
|
0.,
|
|
"target",
|
|
rootViewId,
|
|
"timestamp",
|
|
(double) ts,
|
|
"identifier",
|
|
0.,
|
|
"targetSurface",
|
|
-1));
|
|
|
|
// Test ACTION_UP event
|
|
reset(eventEmitterModuleMock, eventDispatcher);
|
|
|
|
ArgumentCaptor<Event> upEventCaptor = ArgumentCaptor.forClass(Event.class);
|
|
ArgumentCaptor<JavaOnlyArray> upActionTouchesArgCaptor =
|
|
ArgumentCaptor.forClass(JavaOnlyArray.class);
|
|
|
|
rootView.onTouchEvent(MotionEvent.obtain(50, ts, MotionEvent.ACTION_UP, 0, 0, 0));
|
|
verify(eventDispatcher).dispatchEvent(upEventCaptor.capture());
|
|
verifyNoMoreInteractions(eventDispatcher);
|
|
|
|
upEventCaptor.getValue().dispatch(eventEmitterModuleMock);
|
|
verify(eventEmitterModuleMock)
|
|
.receiveTouches(
|
|
eq("topTouchEnd"), upActionTouchesArgCaptor.capture(), any(WritableArray.class));
|
|
verifyNoMoreInteractions(eventEmitterModuleMock);
|
|
|
|
assertThat(upActionTouchesArgCaptor.getValue().size()).isEqualTo(1);
|
|
assertThat(upActionTouchesArgCaptor.getValue().getMap(0))
|
|
.isEqualTo(
|
|
JavaOnlyMap.of(
|
|
"pageX",
|
|
0.,
|
|
"pageY",
|
|
0.,
|
|
"locationX",
|
|
0.,
|
|
"locationY",
|
|
0.,
|
|
"target",
|
|
rootViewId,
|
|
"timestamp",
|
|
(double) ts,
|
|
"identifier",
|
|
0.,
|
|
"targetSurface",
|
|
-1));
|
|
|
|
// Test other action
|
|
reset(eventDispatcher);
|
|
rootView.onTouchEvent(
|
|
MotionEvent.obtain(50, new Date().getTime(), MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0));
|
|
verifyNoMoreInteractions(eventDispatcher);
|
|
}
|
|
|
|
@Test
|
|
public void testRemountApplication() {
|
|
ReactInstanceManager instanceManager = mock(ReactInstanceManager.class);
|
|
|
|
ReactRootView rootView = new ReactRootView(mReactContext);
|
|
|
|
rootView.startReactApplication(instanceManager, "");
|
|
rootView.unmountReactApplication();
|
|
rootView.startReactApplication(instanceManager, "");
|
|
}
|
|
|
|
@Test
|
|
public void testCheckForKeyboardEvents() {
|
|
ReactInstanceManager instanceManager = mock(ReactInstanceManager.class);
|
|
RCTDeviceEventEmitter eventEmitterModuleMock = mock(RCTDeviceEventEmitter.class);
|
|
|
|
when(instanceManager.getCurrentReactContext()).thenReturn(mReactContext);
|
|
when(mReactContext.getJSModule(RCTDeviceEventEmitter.class)).thenReturn(eventEmitterModuleMock);
|
|
|
|
ReactRootView rootView =
|
|
new ReactRootView(mReactContext) {
|
|
@Override
|
|
public void getWindowVisibleDisplayFrame(Rect outRect) {
|
|
if (outRect.bottom == 0) {
|
|
outRect.bottom += 100;
|
|
outRect.right += 370;
|
|
} else {
|
|
outRect.bottom += 370;
|
|
}
|
|
}
|
|
};
|
|
|
|
rootView.startReactApplication(instanceManager, "");
|
|
rootView.simulateCheckForKeyboardForTesting();
|
|
|
|
WritableMap params = Arguments.createMap();
|
|
WritableMap endCoordinates = Arguments.createMap();
|
|
double screenHeight = 470.0;
|
|
double keyboardHeight = 100.0;
|
|
params.putDouble("duration", 0.0);
|
|
endCoordinates.putDouble("width", screenHeight - keyboardHeight);
|
|
endCoordinates.putDouble("screenX", 0.0);
|
|
endCoordinates.putDouble("height", screenHeight - keyboardHeight);
|
|
endCoordinates.putDouble("screenY", keyboardHeight);
|
|
params.putMap("endCoordinates", endCoordinates);
|
|
params.putString("easing", "keyboard");
|
|
|
|
verify(eventEmitterModuleMock, Mockito.times(1)).emit("keyboardDidShow", params);
|
|
}
|
|
}
|