Commit dcdbe3f4 authored by d4vidi's avatar d4vidi

Add tesing + more refactoring

parent c569ebcc
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;
}
}
......@@ -80,7 +80,7 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
@ReactMethod
public void cancelLocalNotification(int notificationId) {
IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
notificationsDrawer.onNotificationClear(notificationId);
notificationsDrawer.onNotificationClearRequest(notificationId);
}
@Override
......
......@@ -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;
......
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);
}
}
......@@ -14,7 +14,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.ReactContextAdapter;
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;
......@@ -24,7 +24,7 @@ public class PushNotification implements IPushNotification {
final protected Context mContext;
final protected AppLifecycleFacade mAppLifecycleFacade;
final protected AppLaunchHelper mAppLaunchHelper;
final protected ReactContextAdapter mReactContextAdapter;
final protected JsIOHelper mJsIOHelper;
final protected PushNotificationProps mNotificationProps;
final protected AppVisibilityListener mAppVisibilityListener = new AppVisibilityListener() {
@Override
......@@ -47,14 +47,14 @@ public class PushNotification implements IPushNotification {
if (appContext instanceof INotificationsApplication) {
return ((INotificationsApplication) appContext).getPushNotification(context, bundle, facade, appLaunchHelper);
}
return new PushNotification(context, bundle, facade, appLaunchHelper, new ReactContextAdapter());
return new PushNotification(context, bundle, facade, appLaunchHelper, new JsIOHelper());
}
protected PushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, ReactContextAdapter reactContextAdapter) {
protected PushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper JsIOHelper) {
mContext = context;
mAppLifecycleFacade = appLifecycleFacade;
mAppLaunchHelper = appLaunchHelper;
mReactContextAdapter = reactContextAdapter;
mJsIOHelper = JsIOHelper;
mNotificationProps = createProps(bundle);
}
......@@ -92,7 +92,7 @@ public class PushNotification implements IPushNotification {
return;
}
final ReactContext reactContext = mReactContextAdapter.getRunningReactContext(mContext);
final ReactContext reactContext = mAppLifecycleFacade.getRunningReactContext();
if (reactContext.getCurrentActivity() == null) {
setAsInitialNotification();
}
......@@ -159,15 +159,15 @@ public class PushNotification implements IPushNotification {
}
protected int createNotificationId(Notification notification) {
return (int) System.currentTimeMillis();
return (int) System.nanoTime();
}
private void notifyReceivedToJS() {
mReactContextAdapter.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mContext);
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
}
private void notifyOpenedToJS() {
mReactContextAdapter.sendEventToJS(NOTIFICATION_OPENED_EVENT_NAME, mNotificationProps.asBundle(), mContext);
mJsIOHelper.sendEventToJS(NOTIFICATION_OPENED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
}
protected void launchOrResumeApp() {
......
......@@ -8,5 +8,5 @@ public interface IPushNotificationsDrawer {
void onNewActivity(Activity activity);
void onNotificationOpened();
void onNotificationClear(int id);
void onNotificationClearRequest(int id);
}
......@@ -54,7 +54,7 @@ public class PushNotificationsDrawer implements IPushNotificationsDrawer {
}
@Override
public void onNotificationClear(int id) {
public void onNotificationClearRequest(int id) {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(id);
}
......
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.ReactContextAdapter;
import com.wix.reactnativenotifications.core.JsIOHelper;
import org.junit.Before;
import org.junit.Test;
......@@ -16,12 +19,18 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
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;
......@@ -31,25 +40,33 @@ 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 ReactContextAdapter mReactContextAdapter;
@Mock private JsIOHelper mJsIOHelper;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
when(mDefaultBundle.getString(eq("title"))).thenReturn("Notification-title");
when(mDefaultBundle.getString(eq("body"))).thenReturn("Notification-body");
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);
when(mReactContextAdapter.getRunningReactContext(mContext)).thenReturn(mReactContext);
ApplicationInfo ai = mock(ApplicationInfo.class);
when(mContext.getApplicationInfo()).thenReturn(ai);
when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(mNotificationManager);
}
@Test
......@@ -60,12 +77,16 @@ public class PushNotificationTest {
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 {
when(mAppLifecycleFacade.isReactInitialized()).thenReturn(true);
when(mAppLifecycleFacade.isAppVisible()).thenReturn(false);
setUpBackgroundApp();
final PushNotification uut = createUUT();
uut.onOpened();
......@@ -79,8 +100,7 @@ public class PushNotificationTest {
// Arrange
when(mAppLifecycleFacade.isReactInitialized()).thenReturn(true);
when(mAppLifecycleFacade.isAppVisible()).thenReturn(false);
setUpBackgroundApp();
// Act
......@@ -95,30 +115,145 @@ public class PushNotificationTest {
// Assert
verify(mReactContextAdapter).sendEventToJS(eq(NOTIFICATION_OPENED_EVENT_NAME), eq(mDefaultBundle), eq(mContext));
verify(mJsIOHelper).sendEventToJS(eq(NOTIFICATION_OPENED_EVENT_NAME), eq(mDefaultBundle), eq(mReactContext));
}
@Test
public void onOpened_appVisible_notifyJS() throws Exception {
when(mAppLifecycleFacade.isReactInitialized()).thenReturn(true);
when(mAppLifecycleFacade.isAppVisible()).thenReturn(true);
setUpForegroundApp();
final PushNotification uut = createUUT();
uut.onOpened();
verify(mContext, never()).startActivity(any(Intent.class));
verify(mReactContextAdapter).sendEventToJS(eq(NOTIFICATION_OPENED_EVENT_NAME), eq(mDefaultBundle), eq(mContext));
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_postNotifAndNotifyJS() throws Exception {
public void onPostRequest_withValidDataButNoId_postNotifications() throws Exception {
// Arrange
setUpForegroundApp();
// Act
final PushNotification uut = createUUT();
uut.onPostRequest(null);
verify(mReactContextAdapter).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), eq(mDefaultBundle), eq(mContext));
// 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 new PushNotification(mContext, mDefaultBundle, mAppLifecycleFacade, mAppLaunchHelper, mReactContextAdapter);
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);
}
}
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