Commit c569ebcc authored by d4vidi's avatar d4vidi

Move notification ID to JS, add testing

parent 12dff6de
......@@ -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"
}
......@@ -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);
}
pushNotification.onPostRequest(notificationId);
}
@ReactMethod
public void removeLocalNotification(int notificationId) {
public void cancelLocalNotification(int notificationId) {
IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
notificationsDrawer.onNotificationClear(notificationId);
}
......
......@@ -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();
}
......@@ -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;
}
......
......@@ -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() {
......
......@@ -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,11 +159,13 @@ describe("Notifications-Android", () => {
});
});
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", () => {
it("should return initial notification data if available", (done) => {
......@@ -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