From c569ebcc089c94241c343452f231bc52ee188a33 Mon Sep 17 00:00:00 2001 From: d4vidi Date: Sun, 27 Nov 2016 18:41:32 +0200 Subject: [PATCH] Move notification ID to JS, add testing --- android/build.gradle | 5 +- .../core/RNNotificationsModule.java | 17 ++---- .../core/notification/IPushNotification.java | 6 ++- .../core/notification/PushNotification.java | 20 +++---- .../notification/PushNotificationTest.java | 21 ++++++-- .../src/main/AndroidManifest.xml | 5 -- example/index.android.js | 39 +++++++++++++- index.android.js | 10 ++++ test/index.android.spec.js | 53 ++++++++++++++++--- 9 files changed, 129 insertions(+), 47 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index e412eea..7be952c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -16,10 +16,6 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - - testOptions { - unitTests.returnDefaultValues = true - } } dependencies { @@ -30,4 +26,5 @@ dependencies { testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.+' + testCompile "org.robolectric:robolectric:3.1.4" } diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/RNNotificationsModule.java b/android/src/main/java/com/wix/reactnativenotifications/core/RNNotificationsModule.java index 150196c..cb8935b 100644 --- a/android/src/main/java/com/wix/reactnativenotifications/core/RNNotificationsModule.java +++ b/android/src/main/java/com/wix/reactnativenotifications/core/RNNotificationsModule.java @@ -70,22 +70,15 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements } @ReactMethod - public void postLocalNotification(ReadableMap notificationPropsMap, final Promise promise) { + public void postLocalNotification(ReadableMap notificationPropsMap, int notificationId) { Log.d(LOGTAG, "Native method invocation: postLocalNotification"); - Object result = null; - - try { - final Bundle notificationProps = Arguments.toBundle(notificationPropsMap); - final IPushNotification pushNotification = PushNotification.get(getReactApplicationContext().getApplicationContext(), notificationProps, ReactAppLifecycleFacade.get()); - int id = pushNotification.onPostRequest(); - result = id; - } finally { - promise.resolve(result); - } + final Bundle notificationProps = Arguments.toBundle(notificationPropsMap); + final IPushNotification pushNotification = PushNotification.get(getReactApplicationContext().getApplicationContext(), notificationProps, ReactAppLifecycleFacade.get()); + pushNotification.onPostRequest(notificationId); } @ReactMethod - public void removeLocalNotification(int notificationId) { + public void cancelLocalNotification(int notificationId) { IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext()); notificationsDrawer.onNotificationClear(notificationId); } diff --git a/android/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java b/android/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java index 46f1015..0d70024 100644 --- a/android/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java +++ b/android/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java @@ -20,9 +20,11 @@ public interface IPushNotification { /** * Handle a request to post this notification. - * @return ID to optionally use for notification deletion. + * + * @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(); + int onPostRequest(Integer notificationId); PushNotificationProps asProps(); } 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 4d1715f..5bc7db2 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 @@ -7,13 +7,7 @@ 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; @@ -66,7 +60,7 @@ public class PushNotification implements IPushNotification { @Override public void onReceived() throws InvalidNotificationException { - postNotification(); + postNotification(null); notifyReceivedToJS(); } @@ -76,8 +70,8 @@ public class PushNotification implements IPushNotification { } @Override - public int onPostRequest() { - return postNotification(); + public int onPostRequest(Integer notificationId) { + return postNotification(notificationId); } @Override @@ -85,10 +79,10 @@ public class PushNotification implements IPushNotification { return mNotificationProps.copy(); } - protected int postNotification() { + protected int postNotification(Integer notificationId) { final PendingIntent pendingIntent = getCTAPendingIntent(); final Notification notification = buildNotification(pendingIntent); - return postNotification(notification); + return postNotification(notification, notificationId); } protected void digestNotification() { @@ -153,8 +147,8 @@ public class PushNotification implements IPushNotification { .setAutoCancel(true); } - protected int postNotification(Notification notification) { - int id = createNotificationId(notification); + protected int postNotification(Notification notification, Integer notificationId) { + int id = notificationId != null ? notificationId : createNotificationId(notification); postNotification(id, notification); return id; } 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 index bde7d32..99fde0d 100644 --- a/android/src/test/java/com/wix/reactnativenotifications/core/notification/PushNotificationTest.java +++ b/android/src/test/java/com/wix/reactnativenotifications/core/notification/PushNotificationTest.java @@ -15,7 +15,9 @@ import org.junit.Test; 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 static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -23,9 +25,12 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.Silent.class) +@RunWith(RobolectricTestRunner.class) public class PushNotificationTest { + private static final String NOTIFICATION_OPENED_EVENT_NAME = "notificationOpened"; + private static final String NOTIFICATION_RECEIVED_EVENT_NAME = "notificationReceived"; + @Mock private ReactContext mReactContext; @Mock private Context mContext; @@ -37,6 +42,8 @@ public class PushNotificationTest { @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.clone()).thenReturn(mDefaultBundle); @@ -88,7 +95,7 @@ public class PushNotificationTest { // Assert - verify(mReactContextAdapter).sendEventToJS(eq("notificationOpened"), eq(mDefaultBundle), eq(mContext)); + verify(mReactContextAdapter).sendEventToJS(eq(NOTIFICATION_OPENED_EVENT_NAME), eq(mDefaultBundle), eq(mContext)); } @Test @@ -100,7 +107,15 @@ public class PushNotificationTest { uut.onOpened(); verify(mContext, never()).startActivity(any(Intent.class)); - verify(mReactContextAdapter).sendEventToJS(eq("notificationOpened"), eq(mDefaultBundle), eq(mContext)); + verify(mReactContextAdapter).sendEventToJS(eq(NOTIFICATION_OPENED_EVENT_NAME), eq(mDefaultBundle), eq(mContext)); + } + + @Test + public void onPostRequest_withValidDataButNoId_postNotifAndNotifyJS() throws Exception { + final PushNotification uut = createUUT(); + uut.onPostRequest(null); + + verify(mReactContextAdapter).sendEventToJS(eq(NOTIFICATION_RECEIVED_EVENT_NAME), eq(mDefaultBundle), eq(mContext)); } protected PushNotification createUUT() { diff --git a/example/android/myapplication/src/main/AndroidManifest.xml b/example/android/myapplication/src/main/AndroidManifest.xml index 214751c..3df23d4 100644 --- a/example/android/myapplication/src/main/AndroidManifest.xml +++ b/example/android/myapplication/src/main/AndroidManifest.xml @@ -24,11 +24,6 @@ - - - diff --git a/example/index.android.js b/example/index.android.js index b0af4b4..24274b1 100644 --- a/example/index.android.js +++ b/example/index.android.js @@ -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 ( @@ -91,6 +119,13 @@ class MainComponent extends Component { {this.state.initialNotification ? 'Opened from notification' : ''} Last notification: {this.state.lastNotification ? '\n'+this.state.lastNotification.body + ` (opened at ''${this.state.notificationRxTime})` : "N/A"} Time elapsed: {this.state.elapsed} + {"\n\n"} + this.onPostNotification()}> + Try Me! + + this.onCancelNotification()}> + Undo last + ) } diff --git a/index.android.js b/index.android.js index f78dc23..e98936d 100644 --- a/index.android.js +++ b/index.android.js @@ -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 { diff --git a/test/index.android.spec.js b/test/index.android.spec.js index 4462924..d471737 100644 --- a/test/index.android.spec.js +++ b/test/index.android.spec.js @@ -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); + }); + }); + }); -- 2.26.2