Allow 'userInfo' for native promise.reject + add native error stack support (#20940)

Summary:
As mentioned [here](https://github.com/react-native-community/react-native-releases/issues/34#issuecomment-417718601), Android is missing native Promise reject with a `userInfo` `WritableMap` support and also `nativeStack` support (which addresses `TODO(8850038)`). This PR adds Android support for both of these.

React Native on iOS ([here](https://github.com/facebook/react-native/blob/master/React/Base/RCTUtils.m#L433)) and Windows ([here](https://github.com/Microsoft/react-native-windows/pull/732)) already support this so this is a relatively minor addition to bring Android in line with the other platforms. (JS support is also [here](https://github.com/facebook/react-native/blob/master/Libraries/BatchedBridge/NativeModules.js#L145-L148))

Existing methods remain unchanged other than general cleanup of variable names (`e -> throwable`) and adding code comments/docs.

Additionally, the `ShareTestModule` implementation of Promise (SimplePromise) was updated to reflect these changes - other line changes in this file are from formatting in Android Studio - if this is an issue let me know.

 - Currently inconsistent with other platforms.
 - Blocking a couple of issues over at [invertase/react-native-firebase](https://github.com/invertase/react-native-firebase) - save for re-writing everything to Promise resolve only - which is no small change and isn't a great solution either.
Pull Request resolved: https://github.com/facebook/react-native/pull/20940

Differential Revision: D13412527

Pulled By: cpojer

fbshipit-source-id: 2ca6c5f3db9ff2c2986b02edda80bc73432f66d3
This commit is contained in:
Salakar
2018-12-10 21:15:55 -08:00
committed by Facebook Github Bot
parent 5194495e97
commit 794d2264f9
5 changed files with 402 additions and 98 deletions
@@ -11,31 +11,31 @@ import android.app.Activity;
import android.content.Intent;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactTestHelper;
import com.facebook.react.bridge.JavaOnlyMap;
import javax.annotation.Nullable;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactTestHelper;
import com.facebook.react.bridge.WritableMap;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
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.PrepareForTest;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.internal.ShadowExtractor;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.internal.ShadowExtractor;
import org.robolectric.shadows.ShadowApplication;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -53,12 +53,14 @@ public class ShareModuleTest {
@Before
public void prepareModules() throws Exception {
PowerMockito.mockStatic(Arguments.class);
Mockito.when(Arguments.createMap()).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return new JavaOnlyMap();
}
});
Mockito
.when(Arguments.createMap())
.thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return new JavaOnlyMap();
}
});
mShareModule = new ShareModule(ReactTestHelper.createCatalystContextForTest());
}
@@ -83,17 +85,34 @@ public class ShareModuleTest {
mShareModule.share(content, dialogTitle, promise);
final Intent chooserIntent =
((ShadowApplication)ShadowExtractor.extract(RuntimeEnvironment.application)).getNextStartedActivity();
final Intent chooserIntent =
((ShadowApplication) ShadowExtractor.extract(RuntimeEnvironment.application)).getNextStartedActivity();
assertNotNull("Dialog was not displayed", chooserIntent);
assertEquals(Intent.ACTION_CHOOSER, chooserIntent.getAction());
assertEquals(dialogTitle, chooserIntent.getExtras().get(Intent.EXTRA_TITLE));
assertEquals(
dialogTitle,
chooserIntent
.getExtras()
.get(Intent.EXTRA_TITLE)
);
final Intent contentIntent = (Intent)chooserIntent.getExtras().get(Intent.EXTRA_INTENT);
final Intent contentIntent = (Intent) chooserIntent
.getExtras()
.get(Intent.EXTRA_INTENT);
assertNotNull("Intent was not built correctly", contentIntent);
assertEquals(Intent.ACTION_SEND, contentIntent.getAction());
assertEquals(title, contentIntent.getExtras().get(Intent.EXTRA_SUBJECT));
assertEquals(message, contentIntent.getExtras().get(Intent.EXTRA_TEXT));
assertEquals(
title,
contentIntent
.getExtras()
.get(Intent.EXTRA_SUBJECT)
);
assertEquals(
message,
contentIntent
.getExtras()
.get(Intent.EXTRA_TEXT)
);
assertEquals(1, promise.getResolved());
}
@@ -111,7 +130,8 @@ public class ShareModuleTest {
}
final static class SimplePromise implements Promise {
private static final String DEFAULT_ERROR = "EUNSPECIFIED";
private static final String ERROR_DEFAULT_CODE = "EUNSPECIFIED";
private static final String ERROR_DEFAULT_MESSAGE = "Error not specified.";
private int mResolved;
private int mRejected;
@@ -126,7 +146,7 @@ public class ShareModuleTest {
public int getRejected() {
return mRejected;
}
public Object getValue() {
return mValue;
}
@@ -147,31 +167,72 @@ public class ShareModuleTest {
@Override
public void reject(String code, String message) {
reject(code, message, /*Throwable*/null);
reject(code, message, /*Throwable*/null, /*WritableMap*/null);
}
@Override
public void reject(String code, Throwable throwable) {
reject(code, /*Message*/null, throwable, /*WritableMap*/null);
}
@Override
public void reject(String code, String message, Throwable throwable) {
reject(code, message, throwable, /*WritableMap*/null);
}
@Override
public void reject(Throwable throwable) {
reject(/*Code*/null, /*Message*/null, throwable, /*WritableMap*/null);
}
@Override
public void reject(Throwable throwable, WritableMap userInfo) {
reject(/*Code*/null, /*Message*/null, throwable, userInfo);
}
@Override
public void reject(String code, @Nonnull WritableMap userInfo) {
reject(code, /*Message*/null, /*Throwable*/null, userInfo);
}
@Override
public void reject(String code, Throwable throwable, WritableMap userInfo) {
reject(code, /*Message*/null, throwable, userInfo);
}
@Override
public void reject(String code, String message, @Nonnull WritableMap userInfo) {
reject(code, message, /*Throwable*/null, userInfo);
}
@Override
public void reject(
String code,
String message,
@Nullable Throwable throwable,
@Nullable WritableMap userInfo
) {
mRejected++;
if (code == null) {
mErrorCode = ERROR_DEFAULT_CODE;
} else {
mErrorCode = code;
}
if (message != null) {
mErrorMessage = message;
} else if (throwable != null) {
mErrorMessage = throwable.getMessage();
} else {
mErrorMessage = ERROR_DEFAULT_MESSAGE;
}
}
@Override
@Deprecated
public void reject(String message) {
reject(DEFAULT_ERROR, message, /*Throwable*/null);
}
@Override
public void reject(String code, Throwable e) {
reject(code, e.getMessage(), e);
}
@Override
public void reject(Throwable e) {
reject(DEFAULT_ERROR, e.getMessage(), e);
}
@Override
public void reject(String code, String message, @Nullable Throwable e) {
mRejected++;
mErrorCode = code;
mErrorMessage = message;
reject(/*Code*/null, message, /*Throwable*/null, /*WritableMap*/null);
}
}
}