Commit f3e4de90 authored by d4vidi's avatar d4vidi

Local notifications set/clear with tests and refactoring

parent 4353cc6f
......@@ -20,9 +20,11 @@ android {
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.+'
testCompile "org.robolectric:robolectric:3.1.4"
}
......@@ -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);
}
}
package com.wix.reactnativenotifications.core;
import com.facebook.react.bridge.ReactContext;
public interface AppLifecycleFacade {
interface AppVisibilityListener {
......@@ -8,6 +10,7 @@ public interface AppLifecycleFacade {
}
boolean isReactInitialized();
ReactContext getRunningReactContext();
boolean isAppVisible();
void addVisibilityListener(AppVisibilityListener listener);
void removeVisibilityListener(AppVisibilityListener listener);
......
package com.wix.reactnativenotifications.core;
import android.os.Bundle;
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 JsIOHelper {
public boolean sendEventToJS(String eventName, Bundle data, ReactContext reactContext) {
if (reactContext != null) {
sendEventToJS(eventName, Arguments.fromBundle(data), reactContext);
return true;
}
return false;
}
public boolean sendEventToJS(String eventName, WritableMap data, ReactContext reactContext) {
if (reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, data);
return true;
}
return false;
}
}
......@@ -13,7 +13,7 @@ public class NotificationIntentAdapter {
public static PendingIntent createPendingNotificationIntent(Context appContext, Intent intent, PushNotificationProps notification) {
intent.putExtra(PUSH_NOTIFICATION_EXTRA_NAME, notification.asBundle());
return PendingIntent.getService(appContext, PENDING_INTENT_CODE, intent, 0);
return PendingIntent.getService(appContext, PENDING_INTENT_CODE, intent, PendingIntent.FLAG_ONE_SHOT);
}
public static Bundle extractPendingNotificationDataFromIntent(Intent intent) {
......
......@@ -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();
}
}
......@@ -12,6 +12,9 @@ import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.wix.reactnativenotifications.core.notification.IPushNotification;
import com.wix.reactnativenotifications.core.notification.PushNotification;
import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer;
import com.wix.reactnativenotifications.core.notificationdrawer.PushNotificationsDrawer;
......@@ -66,6 +69,20 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
}
}
@ReactMethod
public void postLocalNotification(ReadableMap notificationPropsMap, int notificationId) {
Log.d(LOGTAG, "Native method invocation: postLocalNotification");
final Bundle notificationProps = Arguments.toBundle(notificationPropsMap);
final IPushNotification pushNotification = PushNotification.get(getReactApplicationContext().getApplicationContext(), notificationProps, ReactAppLifecycleFacade.get());
pushNotification.onPostRequest(notificationId);
}
@ReactMethod
public void cancelLocalNotification(int notificationId) {
IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
notificationsDrawer.onNotificationClearRequest(notificationId);
}
@Override
public void onAppVisible() {
final IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
......
......@@ -54,6 +54,16 @@ public class ReactAppLifecycleFacade implements AppLifecycleFacade {
return mReactContext.hasActiveCatalystInstance();
}
@Override
public ReactContext getRunningReactContext() {
ReactContext reactContext = mReactContext;
if (reactContext == null) {
return null;
}
return mReactContext;
}
@Override
public boolean isAppVisible() {
return mIsVisible;
......
......@@ -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);
}
......@@ -18,5 +18,13 @@ public interface IPushNotification {
*/
void onOpened();
/**
* Handle a request to post this notification.
*
* @param notificationId (optional) The specific ID to associated with the notification.
* @return The ID effectively assigned to the notification (Auto-assigned if not specified as a parameter).
*/
int onPostRequest(Integer notificationId);
PushNotificationProps asProps();
}
......@@ -7,20 +7,14 @@ import android.content.Context;
import android.content.Intent;
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;
import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.AppLifecycleFacade.AppVisibilityListener;
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.JsIOHelper;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_OPENED_EVENT_NAME;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
......@@ -29,6 +23,8 @@ public class PushNotification implements IPushNotification {
final protected Context mContext;
final protected AppLifecycleFacade mAppLifecycleFacade;
final protected AppLaunchHelper mAppLaunchHelper;
final protected JsIOHelper mJsIOHelper;
final protected PushNotificationProps mNotificationProps;
final protected AppVisibilityListener mAppVisibilityListener = new AppVisibilityListener() {
@Override
......@@ -42,30 +38,40 @@ 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 JsIOHelper());
}
protected PushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper JsIOHelper) {
mContext = context;
mAppLifecycleFacade = appLifecycleFacade;
mAppLaunchHelper = appLaunchHelper;
mJsIOHelper = JsIOHelper;
mNotificationProps = createProps(bundle);
}
@Override
public void onReceived() throws InvalidNotificationException {
postNotification();
postNotification(null);
notifyReceivedToJS();
}
@Override
public void onOpened() {
digestNotification();
PushNotificationsDrawer.get(mContext).onNotificationOpened();
}
@Override
public int onPostRequest(Integer notificationId) {
return postNotification(notificationId);
}
@Override
......@@ -73,10 +79,10 @@ public class PushNotification implements IPushNotification {
return mNotificationProps.copy();
}
protected void postNotification() {
protected int postNotification(Integer notificationId) {
final PendingIntent pendingIntent = getCTAPendingIntent();
final Notification notification = buildNotification(pendingIntent);
postNotification((int) System.currentTimeMillis(), notification);
return postNotification(notification, notificationId);
}
protected void digestNotification() {
......@@ -86,7 +92,7 @@ public class PushNotification implements IPushNotification {
return;
}
final ReactContext reactContext = getRunningReactContext();
final ReactContext reactContext = mAppLifecycleFacade.getRunningReactContext();
if (reactContext.getCurrentActivity() == null) {
setAsInitialNotification();
}
......@@ -141,51 +147,31 @@ public class PushNotification implements IPushNotification {
.setAutoCancel(true);
}
protected int postNotification(Notification notification, Integer notificationId) {
int id = notificationId != null ? notificationId : createNotificationId(notification);
postNotification(id, notification);
return id;
}
protected void postNotification(int id, Notification notification) {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(id, notification);
}
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;
protected int createNotificationId(Notification notification) {
return (int) System.nanoTime();
}
private void notifyReceivedToJS() {
notifyJS(NOTIFICATION_RECEIVED_EVENT_NAME, null);
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
}
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);
}
mJsIOHelper.sendEventToJS(NOTIFICATION_OPENED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
}
protected void launchOrResumeApp() {
final Intent intent = AppLaunchHelper.getLaunchIntent(mContext);
final Intent intent = mAppLaunchHelper.getLaunchIntent(mContext);
mContext.startActivity(intent);
}
}
......@@ -8,4 +8,5 @@ public interface IPushNotificationsDrawer {
void onNewActivity(Activity activity);
void onNotificationOpened();
void onNotificationClearRequest(int id);
}
......@@ -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();
}
}
......@@ -47,6 +53,12 @@ public class PushNotificationsDrawer implements IPushNotificationsDrawer {
clearAll();
}
@Override
public void onNotificationClearRequest(int id) {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(id);
}
protected void clearAll() {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
......
package com.wix.reactnativenotifications.core.notification;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
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.JsIOHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.Shadows;
import org.robolectric.shadows.ShadowNotification;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
public class PushNotificationTest {
private static final String NOTIFICATION_OPENED_EVENT_NAME = "notificationOpened";
private static final String NOTIFICATION_RECEIVED_EVENT_NAME = "notificationReceived";
private static final String DEFAULT_NOTIFICATION_TITLE = "Notification-title";
private static final String DEFAULT_NOTIFICATION_BODY = "Notification-body";
@Mock private ReactContext mReactContext;
@Mock private Context mContext;
@Mock private NotificationManager mNotificationManager;
@Mock private Bundle mDefaultBundle;
@Mock private Intent mLaunchIntent;
@Mock private AppLifecycleFacade mAppLifecycleFacade;
@Mock private AppLaunchHelper mAppLaunchHelper;
@Mock private JsIOHelper mJsIOHelper;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
when(mDefaultBundle.getString(eq("title"))).thenReturn(DEFAULT_NOTIFICATION_TITLE);
when(mDefaultBundle.getString(eq("body"))).thenReturn(DEFAULT_NOTIFICATION_BODY);
when(mDefaultBundle.clone()).thenReturn(mDefaultBundle);
when(mAppLaunchHelper.getLaunchIntent(eq(mContext))).thenReturn(mLaunchIntent);
ApplicationInfo ai = mock(ApplicationInfo.class);
when(mContext.getApplicationInfo()).thenReturn(ai);
when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(mNotificationManager);
}
@Test
public void onOpened_noReactContext_launchApp() throws Exception {
when(mAppLifecycleFacade.isReactInitialized()).thenReturn(false);
final PushNotification uut = createUUT();
uut.onOpened();
verify(mContext).startActivity(eq(mLaunchIntent));
// The unit shouldn't wait for visibility in this case cause we dont make the extra effort of
// notifying the notification upon app launch completion (simply cause we dont know when in completes).
// Instead, the user is expected to use getInitialNotification().
verify(mAppLifecycleFacade, never()).addVisibilityListener(any(AppVisibilityListener.class));
}
@Test
public void onOpened_appInvisible_resumeAppWaitForVisibility() throws Exception {
setUpBackgroundApp();
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
setUpBackgroundApp();
// Act
final PushNotification uut = createUUT();
uut.onOpened();
// Hijack and invoke visibility listener
ArgumentCaptor<AppVisibilityListener> listenerCaptor = ArgumentCaptor.forClass(AppVisibilityListener.class);
verify(mAppLifecycleFacade).addVisibilityListener(listenerCaptor.capture());
AppVisibilityListener listener = listenerCaptor.getValue();
listener.onAppVisible();
// Assert
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_OPENED_EVENT_NAME), eq(mDefaultBundle), eq(mReactContext));
}
@Test
public void onOpened_appVisible_notifyJS() throws Exception {
setUpForegroundApp();
final PushNotification uut = createUUT();
uut.onOpened();
verify(mContext, never()).startActivity(any(Intent.class));
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_OPENED_EVENT_NAME), eq(mDefaultBundle), eq(mReactContext));
}
@Test
public void onReceived_validData_postNotificationAndNotifyJS() throws Exception {
// Arrange
setUpForegroundApp();
// Act
final PushNotification uut = createUUT();
uut.onReceived();
// Assert
ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(anyInt(), notificationCaptor.capture());
verifyNotification(notificationCaptor.getValue());
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), eq(mDefaultBundle), eq(mReactContext));
}
@Test
public void onReceived_validDataForBackgroundApp_postNotificationAndNotifyJs() throws Exception {
// Arrange
setUpForegroundApp();
// Act
final PushNotification uut = createUUT();
uut.onReceived();
// Assert
ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(anyInt(), notificationCaptor.capture());
verifyNotification(notificationCaptor.getValue());
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), eq(mDefaultBundle), eq(mReactContext));
}
@Test
public void onReceived_validDataForDeadApp_postNotificationDontNotifyJS() throws Exception {
final PushNotification uut = createUUT();
uut.onReceived();
ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(anyInt(), notificationCaptor.capture());
verifyNotification(notificationCaptor.getValue());
verify(mJsIOHelper, never()).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), any(Bundle.class), any(ReactContext.class));
}
@Test
public void onPostRequest_withValidDataButNoId_postNotifications() throws Exception {
// Arrange
setUpForegroundApp();
// Act
final PushNotification uut = createUUT();
uut.onPostRequest(null);
// Assert
ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(anyInt(), notificationCaptor.capture());
verifyNotification(notificationCaptor.getValue());
// Shouldn't notify an event on an explicit call to notification posting
verify(mJsIOHelper, never()).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), any(Bundle.class), any(ReactContext.class));
}
@Test
public void onPostRequest_withValidDataButNoId_idsShouldBeUnique() throws Exception {
createUUT().onPostRequest(null);
createUUT().onPostRequest(null);
ArgumentCaptor<Integer> idsCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mNotificationManager, times(2)).notify(idsCaptor.capture(), any(Notification.class));
assertNotEquals(idsCaptor.getAllValues().get(0), idsCaptor.getAllValues().get(1));
}
@Test
public void onPostRequest_withValidDataAndExplicitId_postNotification() throws Exception {
final int id = 666;
final PushNotification uut = createUUT();
uut.onPostRequest(id);
verify(mNotificationManager).notify(eq(id), any(Notification.class));
}
@Test
public void onPostRequest_emptyData_postNotification() throws Exception {
PushNotification uut = createUUT(new Bundle());
uut.onPostRequest(null);
verify(mNotificationManager).notify(anyInt(), any(Notification.class));
}
protected PushNotification createUUT() {
return createUUT(mDefaultBundle);
}
protected PushNotification createUUT(Bundle bundle) {
return new PushNotification(mContext, bundle, mAppLifecycleFacade, mAppLaunchHelper, mJsIOHelper);
}
protected void setUpBackgroundApp() {
when(mAppLifecycleFacade.isReactInitialized()).thenReturn(true);
when(mAppLifecycleFacade.getRunningReactContext()).thenReturn(mReactContext);
when(mAppLifecycleFacade.isAppVisible()).thenReturn(false);
}
protected void setUpForegroundApp() {
when(mAppLifecycleFacade.isReactInitialized()).thenReturn(true);
when(mAppLifecycleFacade.getRunningReactContext()).thenReturn(mReactContext);
when(mAppLifecycleFacade.isAppVisible()).thenReturn(true);
}
protected void verifyNotification(Notification notification) {
ShadowNotification shadowNotification = Shadows.shadowOf(notification);
assertEquals(shadowNotification.getContentText(), DEFAULT_NOTIFICATION_BODY);
assertEquals(shadowNotification.getContentTitle(), DEFAULT_NOTIFICATION_TITLE);
}
}
package com.wix.reactnativenotifications.core.notificationdrawer;
import android.app.NotificationManager;
import android.content.Context;
import com.facebook.react.bridge.ReactContext;
import com.wix.reactnativenotifications.core.AppLaunchHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
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(RobolectricTestRunner.class)
public class PushNotificationsDrawerTest {
@Mock private ReactContext mReactContext;
@Mock private Context mContext;
@Mock private NotificationManager mNotificationManager;
@Mock private AppLaunchHelper mAppLaunchHelper;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(mNotificationManager);
}
@Test
public void onAppInit_clearAllNotifications() throws Exception {
createUUT().onAppInit();
verify(mNotificationManager).cancelAll();
}
@Test
public void onAppVisible_clearAllNotifications() throws Exception {
createUUT().onAppVisible();
verify(mNotificationManager).cancelAll();
}
@Test
public void onNotificationOpened_clearAllNotifications() throws Exception {
createUUT().onNotificationOpened();
verify(mNotificationManager).cancelAll();
}
@Test
public void onNotificationClearRequest_clearSpecificNotification() throws Exception {
createUUT().onNotificationClearRequest(666);
verify(mNotificationManager).cancel(eq(666));
verify(mNotificationManager, never()).cancelAll();
}
protected PushNotificationsDrawer createUUT() {
return new PushNotificationsDrawer(mContext, mAppLaunchHelper);
}
}
......@@ -24,11 +24,6 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".ChildActivity"
android:label="Child Activity">
<meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.wix.reactnativenotifications.app.MainActivity"/>
</activity>
</application>
......
......@@ -5,7 +5,8 @@ import {
AppRegistry,
StyleSheet,
Text,
View
View,
TouchableHighlight
} from 'react-native';
import {NotificationsAndroid, PendingNotifications} from 'react-native-notifications';
......@@ -42,7 +43,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
},
titleText: {
fontSize: 22,
fontSize: 24,
textAlign: 'center',
margin: 10,
},
......@@ -51,6 +52,19 @@ const styles = StyleSheet.create({
textAlign: 'center',
margin: 10,
},
mainButtonText: {
fontSize: 25,
fontStyle: 'italic',
fontWeight: 'bold',
textAlign: 'center',
margin: 10,
},
plainButtonText: {
fontSize: 18,
fontStyle: 'italic',
textAlign: 'center',
margin: 10,
},
});
class MainComponent extends Component {
......@@ -58,6 +72,9 @@ class MainComponent extends Component {
constructor(props) {
super(props);
this.onPostNotification = this.onPostNotification.bind(this);
this.onCancelNotification = this.onCancelNotification.bind(this);
this.state = {
elapsed: 0,
lastNotification: undefined
......@@ -84,6 +101,17 @@ class MainComponent extends Component {
this.setState({elapsed: this.state.elapsed + 1});
}
onPostNotification() {
this.lastNotificationId = NotificationsAndroid.localNotification({title: "Local notification", body: "This notification was generated by the app!"});
}
onCancelNotification() {
if (this.lastNotificationId) {
NotificationsAndroid.cancelLocalNotification(this.lastNotificationId);
this.lastNotificationId = undefined;
}
}
render() {
return (
<View style={styles.container}>
......@@ -91,6 +119,13 @@ class MainComponent extends Component {
<Text style={styles.bodyText}>{this.state.initialNotification ? 'Opened from notification' : ''}</Text>
<Text style={styles.bodyText}>Last notification: {this.state.lastNotification ? '\n'+this.state.lastNotification.body + ` (opened at ''${this.state.notificationRxTime})` : "N/A"}</Text>
<Text style={styles.bodyText}>Time elapsed: {this.state.elapsed}</Text>
<Text>{"\n\n"}</Text>
<TouchableHighlight onPress={() => this.onPostNotification()}>
<Text style={styles.mainButtonText}>Try Me!</Text>
</TouchableHighlight>
<TouchableHighlight onPress={() => this.onCancelNotification()}>
<Text style={styles.plainButtonText}>Undo last</Text>
</TouchableHighlight>
</View>
)
}
......
......@@ -44,6 +44,16 @@ export class NotificationsAndroid {
static refreshToken() {
RNNotifications.refreshToken();
}
static localNotification(notification: Object) {
const id = Date.now() | 0; // Bitwise-OR forces value onto a 32bit limit
RNNotifications.postLocalNotification(notification, id);
return id;
}
static cancelLocalNotification(id) {
RNNotifications.cancelLocalNotification(id);
}
}
export class PendingNotifications {
......
......@@ -3,16 +3,20 @@ let expect = require("chai").use(require("sinon-chai")).expect;
import proxyquire from "proxyquire";
import sinon from "sinon";
describe("Notifications-Android", () => {
describe("Notifications-Android > ", () => {
proxyquire.noCallThru();
let refreshTokenStub;
let getInitialNotificationStub;
let postLocalNotificationStub;
let cancelLocalNotificationStub;
let deviceEventEmitterListenerStub;
let libUnderTest;
beforeEach(() => {
refreshTokenStub = sinon.stub();
getInitialNotificationStub = sinon.stub();
postLocalNotificationStub = sinon.stub();
cancelLocalNotificationStub = sinon.stub();
deviceEventEmitterListenerStub = sinon.stub();
libUnderTest = proxyquire("../index.android", {
......@@ -20,7 +24,9 @@ describe("Notifications-Android", () => {
NativeModules: {
WixRNNotifications: {
refreshToken: refreshTokenStub,
getInitialNotification: getInitialNotificationStub
getInitialNotification: getInitialNotificationStub,
postLocalNotification: postLocalNotificationStub,
cancelLocalNotification: cancelLocalNotificationStub
}
},
DeviceEventEmitter: {
......@@ -153,10 +159,12 @@ describe("Notifications-Android", () => {
});
});
it("should refresh notification token upon refreshing request by the user", () => {
expect(refreshTokenStub).to.not.have.been.called;
libUnderTest.NotificationsAndroid.refreshToken();
expect(refreshTokenStub).to.have.been.calledOnce;
describe("Notification token", () => {
it("should refresh notification token upon refreshing request by the user", () => {
expect(refreshTokenStub).to.not.have.been.called;
libUnderTest.NotificationsAndroid.refreshToken();
expect(refreshTokenStub).to.have.been.calledOnce;
});
});
describe("Initial notification API", () => {
......@@ -187,4 +195,37 @@ describe("Notifications-Android", () => {
});
describe("Local notification", () => {
const notification = {
title: "notification-title",
body: "notification-body"
};
it("should get published when posted manually", () => {
expect(postLocalNotificationStub).to.not.have.been.called;
const id = libUnderTest.NotificationsAndroid.localNotification(notification);
expect(id).to.not.be.undefined;
expect(postLocalNotificationStub).to.have.been.calledWith(notification, id);
});
it("should be called with a unique ID", () => {
expect(postLocalNotificationStub).to.not.have.been.called;
const id = libUnderTest.NotificationsAndroid.localNotification(notification);
const id2 = libUnderTest.NotificationsAndroid.localNotification(notification);
expect(id).to.not.be.undefined;
expect(id2).to.not.be.undefined;
expect(id).to.not.equal(id2);
});
it("should be cancellable with an ID", () => {
expect(cancelLocalNotificationStub).to.not.have.been.called;
libUnderTest.NotificationsAndroid.cancelLocalNotification(666);
expect(cancelLocalNotificationStub).to.have.been.calledWith(666);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment