From 12dff6de4ef875aa4a288f47c831aa4128edc159 Mon Sep 17 00:00:00 2001 From: d4vidi Date: Sun, 27 Nov 2016 16:01:02 +0200 Subject: [PATCH] Add java-code tests --- android/build.gradle | 7 +- .../core/AppLaunchHelper.java | 6 +- .../core/ProxyService.java | 5 + .../core/ReactContextAdapter.java | 51 ++++++++ .../INotificationsApplication.java | 3 +- .../core/notification/PushNotification.java | 63 ++++------ .../PushNotificationsDrawer.java | 18 ++- .../notification/PushNotificationTest.java | 109 ++++++++++++++++++ 8 files changed, 208 insertions(+), 54 deletions(-) create mode 100644 android/src/main/java/com/wix/reactnativenotifications/core/ReactContextAdapter.java create mode 100644 android/src/test/java/com/wix/reactnativenotifications/core/notification/PushNotificationTest.java diff --git a/android/build.gradle b/android/build.gradle index 4e2b7cf..e412eea 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -16,13 +16,18 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + testOptions { + unitTests.returnDefaultValues = true + } } dependencies { // Google's GCM. - compile "com.google.android.gms:play-services-gcm:9.4.0" + compile 'com.google.android.gms:play-services-gcm:9.4.0' compile 'com.facebook.react:react-native:+' testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:2.+' } diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/AppLaunchHelper.java b/android/src/main/java/com/wix/reactnativenotifications/core/AppLaunchHelper.java index b098e7c..e2d2431 100644 --- a/android/src/main/java/com/wix/reactnativenotifications/core/AppLaunchHelper.java +++ b/android/src/main/java/com/wix/reactnativenotifications/core/AppLaunchHelper.java @@ -10,7 +10,7 @@ public class AppLaunchHelper { private static final String LAUNCH_FLAG_KEY_NAME = "launchedFromNotification"; - public static Intent getLaunchIntent(Context appContext) { + public Intent getLaunchIntent(Context appContext) { try { // The desired behavior upon notification opening is as follows: // - If app is in foreground (and possibly has several activities in stack), simply keep it as-is in foreground. @@ -32,14 +32,14 @@ public class AppLaunchHelper { } } - public static boolean isLaunchIntentsActivity(Activity activity) { + public boolean isLaunchIntentsActivity(Activity activity) { final Intent helperIntent = activity.getPackageManager().getLaunchIntentForPackage(activity.getPackageName()); final String activityName = activity.getComponentName().getClassName(); final String launchIntentActivityName = helperIntent.getComponent().getClassName(); return activityName.equals(launchIntentActivityName); } - public static boolean isLaunchIntent(Intent intent) { + public boolean isLaunchIntent(Intent intent) { return intent.getBooleanExtra(LAUNCH_FLAG_KEY_NAME, false); } } diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/ProxyService.java b/android/src/main/java/com/wix/reactnativenotifications/core/ProxyService.java index bbc10c8..5f9e031 100644 --- a/android/src/main/java/com/wix/reactnativenotifications/core/ProxyService.java +++ b/android/src/main/java/com/wix/reactnativenotifications/core/ProxyService.java @@ -7,6 +7,8 @@ import android.util.Log; import com.wix.reactnativenotifications.core.notification.IPushNotification; import com.wix.reactnativenotifications.core.notification.PushNotification; +import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer; +import com.wix.reactnativenotifications.core.notificationdrawer.PushNotificationsDrawer; public class ProxyService extends IntentService { @@ -24,5 +26,8 @@ public class ProxyService extends IntentService { if (pushNotification != null) { pushNotification.onOpened(); } + + final IPushNotificationsDrawer pushNotificationDrawer = PushNotificationsDrawer.get(this); + pushNotificationDrawer.onNotificationOpened(); } } diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/ReactContextAdapter.java b/android/src/main/java/com/wix/reactnativenotifications/core/ReactContextAdapter.java new file mode 100644 index 0000000..9dfea28 --- /dev/null +++ b/android/src/main/java/com/wix/reactnativenotifications/core/ReactContextAdapter.java @@ -0,0 +1,51 @@ +package com.wix.reactnativenotifications.core; + +import android.content.Context; +import android.os.Bundle; + +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +public class ReactContextAdapter { + public ReactContext getRunningReactContext(Context context) { + final ReactNativeHost rnHost = ((ReactApplication) context.getApplicationContext()).getReactNativeHost(); + if (!rnHost.hasInstance()) { + return null; + } + + final ReactInstanceManager instanceManager = rnHost.getReactInstanceManager(); + final ReactContext reactContext = instanceManager.getCurrentReactContext(); + if (reactContext == null || !reactContext.hasActiveCatalystInstance()) { + return null; + } + + return reactContext; + } + + public void sendEventToJS(String eventName, Bundle data, Context context) { + final ReactContext reactContext = getRunningReactContext(context); + if (reactContext != null) { + sendEventToJS(eventName, data, reactContext); + } + } + + public void sendEventToJS(String eventName, WritableMap data, Context context) { + final ReactContext reactContext = getRunningReactContext(context); + if (reactContext != null) { + sendEventToJS(eventName, data, reactContext); + } + } + + public void sendEventToJS(String eventName, Bundle data, ReactContext reactContext) { + sendEventToJS(eventName, Arguments.fromBundle(data), reactContext); + } + + public void sendEventToJS(String eventName, WritableMap data, ReactContext reactContext) { + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, data); + } +} diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notification/INotificationsApplication.java b/android/src/main/java/com/wix/reactnativenotifications/core/notification/INotificationsApplication.java index 6b46988..ba88dbd 100644 --- a/android/src/main/java/com/wix/reactnativenotifications/core/notification/INotificationsApplication.java +++ b/android/src/main/java/com/wix/reactnativenotifications/core/notification/INotificationsApplication.java @@ -3,8 +3,9 @@ package com.wix.reactnativenotifications.core.notification; import android.content.Context; import android.os.Bundle; +import com.wix.reactnativenotifications.core.AppLaunchHelper; import com.wix.reactnativenotifications.core.AppLifecycleFacade; public interface INotificationsApplication { - IPushNotification getPushNotification(Context context, Bundle bundle, AppLifecycleFacade facade); + IPushNotification getPushNotification(Context context, Bundle bundle, AppLifecycleFacade defaultFacade, AppLaunchHelper defaultAppLaunchHelper); } diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java b/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java index 5b8224e..4d1715f 100644 --- a/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java +++ b/android/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java @@ -20,7 +20,7 @@ import com.wix.reactnativenotifications.core.AppLifecycleFacade.AppVisibilityLis import com.wix.reactnativenotifications.core.InitialNotification; import com.wix.reactnativenotifications.core.NotificationIntentAdapter; import com.wix.reactnativenotifications.core.ProxyService; -import com.wix.reactnativenotifications.core.notificationdrawer.PushNotificationsDrawer; +import com.wix.reactnativenotifications.core.ReactContextAdapter; import static com.wix.reactnativenotifications.Defs.NOTIFICATION_OPENED_EVENT_NAME; import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME; @@ -29,6 +29,8 @@ public class PushNotification implements IPushNotification { final protected Context mContext; final protected AppLifecycleFacade mAppLifecycleFacade; + final protected AppLaunchHelper mAppLaunchHelper; + final protected ReactContextAdapter mReactContextAdapter; final protected PushNotificationProps mNotificationProps; final protected AppVisibilityListener mAppVisibilityListener = new AppVisibilityListener() { @Override @@ -42,18 +44,24 @@ public class PushNotification implements IPushNotification { } }; - protected PushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade) { - mContext = context; - mAppLifecycleFacade = appLifecycleFacade; - mNotificationProps = createProps(bundle); + public static IPushNotification get(Context context, Bundle bundle, AppLifecycleFacade facade) { + return PushNotification.get(context, bundle, facade, new AppLaunchHelper()); } - public static IPushNotification get(Context context, Bundle bundle, AppLifecycleFacade facade) { + public static IPushNotification get(Context context, Bundle bundle, AppLifecycleFacade facade, AppLaunchHelper appLaunchHelper) { Context appContext = context.getApplicationContext(); if (appContext instanceof INotificationsApplication) { - return ((INotificationsApplication) appContext).getPushNotification(context, bundle, facade); + return ((INotificationsApplication) appContext).getPushNotification(context, bundle, facade, appLaunchHelper); } - return new PushNotification(context, bundle, facade); + return new PushNotification(context, bundle, facade, appLaunchHelper, new ReactContextAdapter()); + } + + protected PushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, ReactContextAdapter reactContextAdapter) { + mContext = context; + mAppLifecycleFacade = appLifecycleFacade; + mAppLaunchHelper = appLaunchHelper; + mReactContextAdapter = reactContextAdapter; + mNotificationProps = createProps(bundle); } @Override @@ -65,7 +73,6 @@ public class PushNotification implements IPushNotification { @Override public void onOpened() { digestNotification(); - PushNotificationsDrawer.get(mContext).onNotificationOpened(); } @Override @@ -91,7 +98,7 @@ public class PushNotification implements IPushNotification { return; } - final ReactContext reactContext = getRunningReactContext(); + final ReactContext reactContext = mReactContextAdapter.getRunningReactContext(mContext); if (reactContext.getCurrentActivity() == null) { setAsInitialNotification(); } @@ -161,46 +168,16 @@ public class PushNotification implements IPushNotification { return (int) System.currentTimeMillis(); } - protected ReactContext getRunningReactContext() { - final ReactNativeHost rnHost = ((ReactApplication) mContext.getApplicationContext()).getReactNativeHost(); - if (!rnHost.hasInstance()) { - return null; - } - - final ReactInstanceManager instanceManager = rnHost.getReactInstanceManager(); - final ReactContext reactContext = instanceManager.getCurrentReactContext(); - if (reactContext == null || !reactContext.hasActiveCatalystInstance()) { - return null; - } - - return reactContext; - } - private void notifyReceivedToJS() { - notifyJS(NOTIFICATION_RECEIVED_EVENT_NAME, null); + mReactContextAdapter.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mContext); } private void notifyOpenedToJS() { - notifyOpenedToJS(null); - } - - private void notifyOpenedToJS(ReactContext reactContext) { - notifyJS(NOTIFICATION_OPENED_EVENT_NAME, reactContext); - } - - private void notifyJS(String eventName, ReactContext reactContext) { - if (reactContext == null) { - reactContext = getRunningReactContext(); - } - - if (reactContext != null) { - final WritableMap notificationAsMap = Arguments.fromBundle(mNotificationProps.asBundle()); - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, notificationAsMap); - } + mReactContextAdapter.sendEventToJS(NOTIFICATION_OPENED_EVENT_NAME, mNotificationProps.asBundle(), mContext); } protected void launchOrResumeApp() { - final Intent intent = AppLaunchHelper.getLaunchIntent(mContext); + final Intent intent = mAppLaunchHelper.getLaunchIntent(mContext); mContext.startActivity(intent); } } diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java b/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java index 5c96426..e9f791f 100644 --- a/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java +++ b/android/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java @@ -10,18 +10,24 @@ import com.wix.reactnativenotifications.core.InitialNotification; public class PushNotificationsDrawer implements IPushNotificationsDrawer { final protected Context mContext; + final protected AppLaunchHelper mAppLaunchHelper; - public PushNotificationsDrawer(Context context) { - mContext = context; + public static IPushNotificationsDrawer get(Context context) { + return PushNotificationsDrawer.get(context, new AppLaunchHelper()); } - public static IPushNotificationsDrawer get(Context context) { + public static IPushNotificationsDrawer get(Context context, AppLaunchHelper appLaunchHelper) { final Context appContext = context.getApplicationContext(); if (appContext instanceof INotificationsDrawerApplication) { return ((INotificationsDrawerApplication) appContext).getPushNotificationsDrawer(context); } - return new PushNotificationsDrawer(context); + return new PushNotificationsDrawer(context, appLaunchHelper); + } + + protected PushNotificationsDrawer(Context context, AppLaunchHelper appLaunchHelper) { + mContext = context; + mAppLaunchHelper = appLaunchHelper; } @Override @@ -36,8 +42,8 @@ public class PushNotificationsDrawer implements IPushNotificationsDrawer { @Override public void onNewActivity(Activity activity) { - if (AppLaunchHelper.isLaunchIntentsActivity(activity) && - !AppLaunchHelper.isLaunchIntent(activity.getIntent())) { + if (mAppLaunchHelper.isLaunchIntentsActivity(activity) && + !mAppLaunchHelper.isLaunchIntent(activity.getIntent())) { InitialNotification.clear(); } } diff --git a/android/src/test/java/com/wix/reactnativenotifications/core/notification/PushNotificationTest.java b/android/src/test/java/com/wix/reactnativenotifications/core/notification/PushNotificationTest.java new file mode 100644 index 0000000..bde7d32 --- /dev/null +++ b/android/src/test/java/com/wix/reactnativenotifications/core/notification/PushNotificationTest.java @@ -0,0 +1,109 @@ +package com.wix.reactnativenotifications.core.notification; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import com.facebook.react.bridge.ReactContext; +import com.wix.reactnativenotifications.core.AppLaunchHelper; +import com.wix.reactnativenotifications.core.AppLifecycleFacade; +import com.wix.reactnativenotifications.core.AppLifecycleFacade.AppVisibilityListener; +import com.wix.reactnativenotifications.core.ReactContextAdapter; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class PushNotificationTest { + + @Mock private ReactContext mReactContext; + @Mock private Context mContext; + + @Mock private Bundle mDefaultBundle; + @Mock private Intent mLaunchIntent; + @Mock private AppLifecycleFacade mAppLifecycleFacade; + @Mock private AppLaunchHelper mAppLaunchHelper; + @Mock private ReactContextAdapter mReactContextAdapter; + + @Before + public void setup() throws Exception { + when(mDefaultBundle.getString(eq("title"))).thenReturn("Notification-title"); + when(mDefaultBundle.getString(eq("body"))).thenReturn("Notification-body"); + when(mDefaultBundle.clone()).thenReturn(mDefaultBundle); + + when(mAppLaunchHelper.getLaunchIntent(eq(mContext))).thenReturn(mLaunchIntent); + when(mReactContextAdapter.getRunningReactContext(mContext)).thenReturn(mReactContext); + } + + @Test + public void onOpened_noReactContext_launchApp() throws Exception { + when(mAppLifecycleFacade.isReactInitialized()).thenReturn(false); + + final PushNotification uut = createUUT(); + uut.onOpened(); + + verify(mContext).startActivity(eq(mLaunchIntent)); + } + + @Test + public void onOpened_appInvisible_resumeAppWaitForVisibility() throws Exception { + when(mAppLifecycleFacade.isReactInitialized()).thenReturn(true); + when(mAppLifecycleFacade.isAppVisible()).thenReturn(false); + + final PushNotification uut = createUUT(); + uut.onOpened(); + + verify(mContext).startActivity(any(Intent.class)); + verify(mAppLifecycleFacade).addVisibilityListener(any(AppVisibilityListener.class)); + } + + @Test + public void onOpened_appGoesVisible_resumeAppAndNotifyJs() throws Exception { + + // Arrange + + when(mAppLifecycleFacade.isReactInitialized()).thenReturn(true); + when(mAppLifecycleFacade.isAppVisible()).thenReturn(false); + + // Act + + final PushNotification uut = createUUT(); + uut.onOpened(); + + // Hijack and invoke visibility listener + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(AppVisibilityListener.class); + verify(mAppLifecycleFacade).addVisibilityListener(listenerCaptor.capture()); + AppVisibilityListener listener = listenerCaptor.getValue(); + listener.onAppVisible(); + + // Assert + + verify(mReactContextAdapter).sendEventToJS(eq("notificationOpened"), eq(mDefaultBundle), eq(mContext)); + } + + @Test + public void onOpened_appVisible_notifyJS() throws Exception { + when(mAppLifecycleFacade.isReactInitialized()).thenReturn(true); + when(mAppLifecycleFacade.isAppVisible()).thenReturn(true); + + final PushNotification uut = createUUT(); + uut.onOpened(); + + verify(mContext, never()).startActivity(any(Intent.class)); + verify(mReactContextAdapter).sendEventToJS(eq("notificationOpened"), eq(mDefaultBundle), eq(mContext)); + } + + protected PushNotification createUUT() { + return new PushNotification(mContext, mDefaultBundle, mAppLifecycleFacade, mAppLaunchHelper, mReactContextAdapter); + } +} -- 2.26.2