react-native-notify-kit 10.2.1 → 10.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/android/src/main/java/app/notifee/core/ForegroundService.java +3 -1
  2. package/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java +4 -2
  3. package/android/src/main/java/app/notifee/core/NotificationManager.java +28 -35
  4. package/android/src/main/java/app/notifee/core/database/NotifeeCoreDatabase.java +1 -4
  5. package/android/src/main/java/app/notifee/core/model/ChannelModel.java +8 -13
  6. package/android/src/main/java/app/notifee/core/model/IntervalTriggerModel.java +2 -6
  7. package/android/src/main/java/app/notifee/core/model/NotificationAndroidModel.java +30 -33
  8. package/android/src/main/java/app/notifee/core/model/NotificationAndroidStyleModel.java +6 -4
  9. package/android/src/main/java/app/notifee/core/model/TimestampTriggerModel.java +7 -4
  10. package/android/src/main/java/app/notifee/core/utility/BundleValueReader.java +65 -0
  11. package/android/src/main/java/app/notifee/core/utility/ObjectUtils.java +1 -1
  12. package/android/src/main/java/app/notifee/core/utility/ParcelableCompatReader.java +64 -0
  13. package/android/src/main/kotlin/io/invertase/notifee/NotifeeApiModule.kt +1 -1
  14. package/android/src/main/kotlin/io/invertase/notifee/NotifeeInitProvider.kt +0 -26
  15. package/android/src/test/java/app/notifee/core/NotifeeAlarmManagerCurrentBehaviorTest.java +446 -0
  16. package/android/src/test/java/app/notifee/core/NotificationManagerCreateTriggerNotificationCurrentBehaviorTest.java +203 -0
  17. package/android/src/test/java/app/notifee/core/NotificationManagerPressActionOptOutTest.java +180 -0
  18. package/android/src/test/java/app/notifee/core/NotificationReceiverHandlerInitialNotificationTest.java +157 -0
  19. package/android/src/test/java/app/notifee/core/ReceiverServiceInitialNotificationTest.java +169 -0
  20. package/android/src/test/java/app/notifee/core/model/ChannelModelTest.java +116 -0
  21. package/android/src/test/java/app/notifee/core/model/IntervalTriggerModelTest.java +71 -0
  22. package/android/src/test/java/app/notifee/core/model/NotificationAndroidModelTest.java +309 -0
  23. package/android/src/test/java/app/notifee/core/model/NotificationAndroidStyleModelTest.java +154 -0
  24. package/android/src/test/java/app/notifee/core/model/TimestampTriggerModelTest.java +292 -6
  25. package/android/src/test/java/app/notifee/core/utility/BundleValueReaderTest.java +131 -0
  26. package/android/src/test/java/app/notifee/core/utility/ObjectUtilsTest.java +142 -0
  27. package/android/src/test/java/app/notifee/core/utility/ParcelableCompatReaderTest.java +124 -0
  28. package/android/src/test/java/io/invertase/notifee/HeadlessTaskReactHostTest.java +136 -0
  29. package/android/src/test/java/io/invertase/notifee/NotifeeEventSubscriberRoutingTest.java +262 -0
  30. package/android/src/test/java/io/invertase/notifee/NotifeeReactUtilsDrawerTest.java +24 -0
  31. package/dist/types/Notification.d.ts +5 -2
  32. package/dist/types/Notification.js +5 -2
  33. package/dist/types/Notification.js.map +1 -1
  34. package/dist/types/NotificationAndroid.d.ts +4 -0
  35. package/dist/types/NotificationAndroid.js +4 -0
  36. package/dist/types/NotificationAndroid.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/package.json +1 -1
  40. package/src/types/Notification.ts +5 -2
  41. package/src/types/NotificationAndroid.ts +4 -0
  42. package/src/version.ts +1 -1
@@ -0,0 +1,180 @@
1
+ package app.notifee.core;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+
19
+ import static org.junit.Assert.assertEquals;
20
+ import static org.junit.Assert.assertNotNull;
21
+ import static org.junit.Assert.assertNull;
22
+
23
+ import android.Manifest;
24
+ import android.app.Notification;
25
+ import android.app.NotificationChannel;
26
+ import android.content.Context;
27
+ import android.os.Bundle;
28
+ import app.notifee.core.model.NotificationModel;
29
+ import com.google.common.util.concurrent.ListenableFuture;
30
+ import java.util.ArrayList;
31
+ import java.util.concurrent.TimeUnit;
32
+ import org.junit.Before;
33
+ import org.junit.Test;
34
+ import org.junit.runner.RunWith;
35
+ import org.robolectric.RobolectricTestRunner;
36
+ import org.robolectric.RuntimeEnvironment;
37
+ import org.robolectric.Shadows;
38
+ import org.robolectric.annotation.Config;
39
+ import org.robolectric.shadows.ShadowNotificationManager;
40
+
41
+ @RunWith(RobolectricTestRunner.class)
42
+ @Config(sdk = 34)
43
+ public class NotificationManagerPressActionOptOutTest {
44
+ private static final String CHANNEL_ID = "press-action-opt-out-test-channel";
45
+
46
+ @Before
47
+ public void setUp() {
48
+ Context context = RuntimeEnvironment.getApplication();
49
+ ContextHolder.setApplicationContext(context);
50
+ Shadows.shadowOf(RuntimeEnvironment.getApplication())
51
+ .grantPermissions(Manifest.permission.POST_NOTIFICATIONS);
52
+ createChannel(context);
53
+
54
+ android.app.NotificationManager notificationManager =
55
+ (android.app.NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
56
+ notificationManager.cancelAll();
57
+ }
58
+
59
+ @Test
60
+ public void displayNotification_optOutPressActionOnAndroid12Plus_hasNoContentIntent()
61
+ throws Exception {
62
+ Notification notification =
63
+ displayNotification(buildNotificationModelWithOptOutPressAction("opt-out-android-12-plus"));
64
+
65
+ assertNull(
66
+ "pressAction opt-out must not set a body tap content intent on Android 12+",
67
+ notification.contentIntent);
68
+ }
69
+
70
+ @Test
71
+ @Config(sdk = 28)
72
+ public void displayNotification_optOutPressActionPreAndroid12_hasNoContentIntent()
73
+ throws Exception {
74
+ Notification notification =
75
+ displayNotification(buildNotificationModelWithOptOutPressAction("opt-out-pre-android-12"));
76
+
77
+ assertNull(
78
+ "pressAction opt-out must not set a body tap content intent before Android 12",
79
+ notification.contentIntent);
80
+ }
81
+
82
+ @Test
83
+ public void displayNotification_missingPressAction_keepsContentIntent() throws Exception {
84
+ Notification notification = displayNotification(buildNotificationModel("missing-press-action"));
85
+
86
+ assertNotNull(
87
+ "missing pressAction must synthesize the default body tap content intent",
88
+ notification.contentIntent);
89
+ }
90
+
91
+ @Test
92
+ public void displayNotification_actionButtonWithOptOutContent_keepsActionIntent()
93
+ throws Exception {
94
+ Notification notification =
95
+ displayNotification(buildNotificationModelWithOptOutPressActionAndAction("opt-out-action"));
96
+
97
+ assertNull(
98
+ "pressAction opt-out must only disable the body tap content intent",
99
+ notification.contentIntent);
100
+ assertNotNull("action array must be present", notification.actions);
101
+ assertEquals("one action must be built", 1, notification.actions.length);
102
+ assertNotNull(
103
+ "action button PendingIntent must remain independent of body tap opt-out",
104
+ notification.actions[0].actionIntent);
105
+ }
106
+
107
+ private static Notification displayNotification(NotificationModel notificationModel)
108
+ throws Exception {
109
+ ListenableFuture<Void> future = NotificationManager.displayNotification(notificationModel, null);
110
+ future.get(5, TimeUnit.SECONDS);
111
+
112
+ Context context = RuntimeEnvironment.getApplication();
113
+ android.app.NotificationManager notificationManager =
114
+ (android.app.NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
115
+ ShadowNotificationManager shadowNotificationManager = Shadows.shadowOf(notificationManager);
116
+ Notification notification =
117
+ shadowNotificationManager.getNotification(notificationModel.getHashCode());
118
+ assertNotNull("notification must be posted", notification);
119
+ return notification;
120
+ }
121
+
122
+ private static void createChannel(Context context) {
123
+ android.app.NotificationManager notificationManager =
124
+ (android.app.NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
125
+ if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
126
+ NotificationChannel channel =
127
+ new NotificationChannel(
128
+ CHANNEL_ID,
129
+ "Press action opt-out test",
130
+ android.app.NotificationManager.IMPORTANCE_LOW);
131
+ notificationManager.createNotificationChannel(channel);
132
+ }
133
+ }
134
+
135
+ private static NotificationModel buildNotificationModel(String id) {
136
+ return NotificationModel.fromBundle(buildNotificationBundle(id));
137
+ }
138
+
139
+ private static NotificationModel buildNotificationModelWithOptOutPressAction(String id) {
140
+ Bundle notificationBundle = buildNotificationBundle(id);
141
+ Bundle androidBundle = notificationBundle.getBundle("android");
142
+ androidBundle.putBundle(
143
+ "pressAction", buildPressAction(NotificationPendingIntent.PRESS_ACTION_OPT_OUT_ID));
144
+ return NotificationModel.fromBundle(notificationBundle);
145
+ }
146
+
147
+ private static NotificationModel buildNotificationModelWithOptOutPressActionAndAction(String id) {
148
+ Bundle notificationBundle = buildNotificationBundle(id);
149
+ Bundle androidBundle = notificationBundle.getBundle("android");
150
+ androidBundle.putBundle(
151
+ "pressAction", buildPressAction(NotificationPendingIntent.PRESS_ACTION_OPT_OUT_ID));
152
+
153
+ Bundle action = new Bundle();
154
+ action.putString("title", "Reply");
155
+ action.putBundle("pressAction", buildPressAction("reply"));
156
+ ArrayList<Bundle> actions = new ArrayList<>();
157
+ actions.add(action);
158
+ androidBundle.putParcelableArrayList("actions", actions);
159
+
160
+ return NotificationModel.fromBundle(notificationBundle);
161
+ }
162
+
163
+ private static Bundle buildNotificationBundle(String id) {
164
+ Bundle notificationBundle = new Bundle();
165
+ notificationBundle.putString("id", id);
166
+ notificationBundle.putString("title", "Press action opt-out " + id);
167
+ notificationBundle.putString("body", "Body " + id);
168
+
169
+ Bundle androidBundle = new Bundle();
170
+ androidBundle.putString("channelId", CHANNEL_ID);
171
+ notificationBundle.putBundle("android", androidBundle);
172
+ return notificationBundle;
173
+ }
174
+
175
+ private static Bundle buildPressAction(String id) {
176
+ Bundle pressAction = new Bundle();
177
+ pressAction.putString("id", id);
178
+ return pressAction;
179
+ }
180
+ }
@@ -0,0 +1,157 @@
1
+ package app.notifee.core;
2
+
3
+ import static app.notifee.core.NotificationPendingIntent.EVENT_TYPE_INTENT_KEY;
4
+ import static app.notifee.core.NotificationPendingIntent.NOTIFICATION_ID_INTENT_KEY;
5
+ import static org.junit.Assert.assertEquals;
6
+ import static org.junit.Assert.assertNotNull;
7
+
8
+ import android.content.Context;
9
+ import android.content.Intent;
10
+ import android.os.Bundle;
11
+ import app.notifee.core.event.InitialNotificationEvent;
12
+ import app.notifee.core.event.MainComponentEvent;
13
+ import app.notifee.core.event.NotificationEvent;
14
+ import java.util.ArrayList;
15
+ import java.util.List;
16
+ import org.greenrobot.eventbus.Subscribe;
17
+ import org.greenrobot.eventbus.ThreadMode;
18
+ import org.junit.After;
19
+ import org.junit.Before;
20
+ import org.junit.Test;
21
+ import org.junit.runner.RunWith;
22
+ import org.robolectric.RobolectricTestRunner;
23
+ import org.robolectric.RuntimeEnvironment;
24
+ import org.robolectric.annotation.Config;
25
+
26
+ @RunWith(RobolectricTestRunner.class)
27
+ @Config(sdk = 34)
28
+ public class NotificationReceiverHandlerInitialNotificationTest {
29
+
30
+ private NotificationEventCapture capture;
31
+ private Context context;
32
+
33
+ @Before
34
+ public void setUp() {
35
+ context = RuntimeEnvironment.getApplication();
36
+ ContextHolder.setApplicationContext(context);
37
+ clearStickyEvents();
38
+ capture = new NotificationEventCapture();
39
+ EventBus.register(capture);
40
+ }
41
+
42
+ @After
43
+ public void tearDown() {
44
+ EventBus.unregister(capture);
45
+ clearStickyEvents();
46
+ }
47
+
48
+ @Test
49
+ public void handleNotification_typePress_postsNotificationEventAndInitialNotification() {
50
+ Intent intent =
51
+ buildHandlerIntent(
52
+ NotificationEvent.TYPE_PRESS,
53
+ buildNotificationBundle("handler-press"),
54
+ buildPressActionBundle("default", "default", "HandlerMainComponent"));
55
+
56
+ NotificationReceiverHandler.handleNotification(context, intent);
57
+
58
+ assertEquals("one NotificationEvent should be posted", 1, capture.events.size());
59
+ NotificationEvent event = capture.events.get(0);
60
+ assertEquals(NotificationEvent.TYPE_PRESS, event.getType());
61
+ assertEquals("handler-press", event.getNotification().getId());
62
+ assertNotNull("PRESS extras should be preserved", event.getExtras());
63
+ assertPressAction(event.getExtras(), "default", "default", "HandlerMainComponent");
64
+
65
+ InitialNotificationEvent initialNotificationEvent =
66
+ EventBus.getStickyEvent(InitialNotificationEvent.class);
67
+ assertNotNull("TYPE_PRESS should post InitialNotificationEvent", initialNotificationEvent);
68
+ assertEquals("handler-press", initialNotificationEvent.getNotificationModel().getId());
69
+ assertPressAction(
70
+ initialNotificationEvent.getExtras(), "default", "default", "HandlerMainComponent");
71
+ }
72
+
73
+ @Test
74
+ public void handleNotification_typeActionPressWithoutLaunch_postsCurrentInitialNotification() {
75
+ Intent intent =
76
+ buildHandlerIntent(
77
+ NotificationEvent.TYPE_ACTION_PRESS,
78
+ buildNotificationBundle("handler-action-no-launch"),
79
+ buildPressActionBundle("reply", null, null));
80
+
81
+ NotificationReceiverHandler.handleNotification(context, intent);
82
+
83
+ assertEquals("one NotificationEvent should be posted", 1, capture.events.size());
84
+ NotificationEvent event = capture.events.get(0);
85
+ assertEquals(NotificationEvent.TYPE_ACTION_PRESS, event.getType());
86
+ assertEquals("handler-action-no-launch", event.getNotification().getId());
87
+ assertNotNull("ACTION_PRESS extras should be preserved", event.getExtras());
88
+ assertPressAction(event.getExtras(), "reply", null, null);
89
+
90
+ InitialNotificationEvent initialNotificationEvent =
91
+ EventBus.getStickyEvent(InitialNotificationEvent.class);
92
+ assertNotNull(
93
+ "Android 12+ handler currently posts InitialNotificationEvent for no-launch ACTION_PRESS",
94
+ initialNotificationEvent);
95
+ assertEquals("handler-action-no-launch", initialNotificationEvent.getNotificationModel().getId());
96
+ assertPressAction(initialNotificationEvent.getExtras(), "reply", null, null);
97
+ }
98
+
99
+ private static Intent buildHandlerIntent(
100
+ int type, Bundle notificationBundle, Bundle pressActionBundle) {
101
+ Intent intent = new Intent();
102
+ intent.putExtra(EVENT_TYPE_INTENT_KEY, type);
103
+ intent.putExtra(NOTIFICATION_ID_INTENT_KEY, notificationBundle.getString("id").hashCode());
104
+ intent.putExtra("notification", notificationBundle);
105
+ intent.putExtra("pressAction", pressActionBundle);
106
+ return intent;
107
+ }
108
+
109
+ private static Bundle buildNotificationBundle(String id) {
110
+ Bundle notificationBundle = new Bundle();
111
+ notificationBundle.putString("id", id);
112
+ notificationBundle.putString("title", "Handler test " + id);
113
+ notificationBundle.putString("body", "Body " + id);
114
+
115
+ Bundle androidBundle = new Bundle();
116
+ androidBundle.putString("channelId", "handler-initial-test-channel");
117
+ androidBundle.putBoolean("autoCancel", false);
118
+ notificationBundle.putBundle("android", androidBundle);
119
+ return notificationBundle;
120
+ }
121
+
122
+ private static Bundle buildPressActionBundle(
123
+ String id, String launchActivity, String mainComponent) {
124
+ Bundle pressActionBundle = new Bundle();
125
+ pressActionBundle.putString("id", id);
126
+ if (launchActivity != null) {
127
+ pressActionBundle.putString("launchActivity", launchActivity);
128
+ }
129
+ if (mainComponent != null) {
130
+ pressActionBundle.putString("mainComponent", mainComponent);
131
+ }
132
+ return pressActionBundle;
133
+ }
134
+
135
+ private static void assertPressAction(
136
+ Bundle extras, String id, String launchActivity, String mainComponent) {
137
+ Bundle pressAction = extras.getBundle("pressAction");
138
+ assertNotNull("pressAction should be present", pressAction);
139
+ assertEquals(id, pressAction.getString("id"));
140
+ assertEquals(launchActivity, pressAction.getString("launchActivity"));
141
+ assertEquals(mainComponent, pressAction.getString("mainComponent"));
142
+ }
143
+
144
+ private static void clearStickyEvents() {
145
+ EventBus.removeStickEvent(InitialNotificationEvent.class);
146
+ EventBus.removeStickEvent(MainComponentEvent.class);
147
+ }
148
+
149
+ public static class NotificationEventCapture {
150
+ final List<NotificationEvent> events = new ArrayList<>();
151
+
152
+ @Subscribe(threadMode = ThreadMode.POSTING)
153
+ public void onNotificationEvent(NotificationEvent event) {
154
+ events.add(event);
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,169 @@
1
+ package app.notifee.core;
2
+
3
+ import static org.junit.Assert.assertEquals;
4
+ import static org.junit.Assert.assertNotNull;
5
+ import static org.junit.Assert.assertNull;
6
+
7
+ import android.app.Activity;
8
+ import android.content.Context;
9
+ import android.content.Intent;
10
+ import android.os.Bundle;
11
+ import app.notifee.core.event.InitialNotificationEvent;
12
+ import app.notifee.core.event.MainComponentEvent;
13
+ import app.notifee.core.event.NotificationEvent;
14
+ import java.util.ArrayList;
15
+ import java.util.List;
16
+ import org.greenrobot.eventbus.Subscribe;
17
+ import org.greenrobot.eventbus.ThreadMode;
18
+ import org.junit.After;
19
+ import org.junit.Before;
20
+ import org.junit.Test;
21
+ import org.junit.runner.RunWith;
22
+ import org.robolectric.Robolectric;
23
+ import org.robolectric.RobolectricTestRunner;
24
+ import org.robolectric.RuntimeEnvironment;
25
+ import org.robolectric.android.controller.ServiceController;
26
+ import org.robolectric.annotation.Config;
27
+
28
+ @RunWith(RobolectricTestRunner.class)
29
+ @Config(sdk = 28)
30
+ public class ReceiverServiceInitialNotificationTest {
31
+
32
+ private NotificationEventCapture capture;
33
+ private ReceiverService service;
34
+ private ServiceController<ReceiverService> serviceController;
35
+
36
+ @Before
37
+ public void setUp() {
38
+ Context context = RuntimeEnvironment.getApplication();
39
+ ContextHolder.setApplicationContext(context);
40
+ clearStickyEvents();
41
+ capture = new NotificationEventCapture();
42
+ EventBus.register(capture);
43
+ serviceController = Robolectric.buildService(ReceiverService.class).create();
44
+ service = serviceController.get();
45
+ }
46
+
47
+ @After
48
+ public void tearDown() {
49
+ EventBus.unregister(capture);
50
+ serviceController.destroy();
51
+ clearStickyEvents();
52
+ }
53
+
54
+ @Test
55
+ public void onActionPressIntent_withoutLaunch_preservesCurrentNoInitialNotificationBehavior() {
56
+ Intent intent =
57
+ buildServiceIntent(
58
+ buildNotificationBundle("service-action-no-launch"),
59
+ buildPressActionBundle("reply", null, null));
60
+
61
+ service.onStartCommand(intent, 0, 1);
62
+
63
+ assertEquals("one NotificationEvent should be posted", 1, capture.events.size());
64
+ NotificationEvent event = capture.events.get(0);
65
+ assertEquals(NotificationEvent.TYPE_ACTION_PRESS, event.getType());
66
+ assertEquals("service-action-no-launch", event.getNotification().getId());
67
+ assertNotNull("ACTION_PRESS extras should be preserved", event.getExtras());
68
+ assertPressAction(event.getExtras(), "reply", null, null);
69
+
70
+ assertNull(
71
+ "pre-Android 12 ReceiverService currently does not post InitialNotificationEvent for"
72
+ + " no-launch ACTION_PRESS",
73
+ EventBus.getStickyEvent(InitialNotificationEvent.class));
74
+ }
75
+
76
+ @Test
77
+ public void onActionPressIntent_withLaunch_postsInitialNotificationAfterPendingIntentSend() {
78
+ Intent intent =
79
+ buildServiceIntent(
80
+ buildNotificationBundle("service-action-launch"),
81
+ buildPressActionBundle(
82
+ "reply-launch", TestLaunchActivity.class.getName(), "ServiceMainComponent"));
83
+
84
+ service.onStartCommand(intent, 0, 1);
85
+
86
+ assertEquals("one NotificationEvent should be posted", 1, capture.events.size());
87
+ NotificationEvent event = capture.events.get(0);
88
+ assertEquals(NotificationEvent.TYPE_ACTION_PRESS, event.getType());
89
+ assertEquals("service-action-launch", event.getNotification().getId());
90
+ assertNotNull("ACTION_PRESS extras should be preserved", event.getExtras());
91
+ assertPressAction(
92
+ event.getExtras(),
93
+ "reply-launch",
94
+ TestLaunchActivity.class.getName(),
95
+ "ServiceMainComponent");
96
+
97
+ InitialNotificationEvent initialNotificationEvent =
98
+ EventBus.getStickyEvent(InitialNotificationEvent.class);
99
+ assertNotNull(
100
+ "pre-Android 12 ReceiverService currently posts InitialNotificationEvent when ACTION_PRESS"
101
+ + " launches an activity",
102
+ initialNotificationEvent);
103
+ assertEquals("service-action-launch", initialNotificationEvent.getNotificationModel().getId());
104
+ assertPressAction(
105
+ initialNotificationEvent.getExtras(),
106
+ "reply-launch",
107
+ TestLaunchActivity.class.getName(),
108
+ "ServiceMainComponent");
109
+ }
110
+
111
+ private static Intent buildServiceIntent(Bundle notificationBundle, Bundle pressActionBundle) {
112
+ Intent intent = new Intent(RuntimeEnvironment.getApplication(), ReceiverService.class);
113
+ intent.setAction(ReceiverService.ACTION_PRESS_INTENT);
114
+ intent.putExtra("notification", notificationBundle);
115
+ intent.putExtra("pressAction", pressActionBundle);
116
+ return intent;
117
+ }
118
+
119
+ private static Bundle buildNotificationBundle(String id) {
120
+ Bundle notificationBundle = new Bundle();
121
+ notificationBundle.putString("id", id);
122
+ notificationBundle.putString("title", "ReceiverService test " + id);
123
+ notificationBundle.putString("body", "Body " + id);
124
+
125
+ Bundle androidBundle = new Bundle();
126
+ androidBundle.putString("channelId", "receiver-service-initial-test-channel");
127
+ androidBundle.putBoolean("autoCancel", false);
128
+ notificationBundle.putBundle("android", androidBundle);
129
+ return notificationBundle;
130
+ }
131
+
132
+ private static Bundle buildPressActionBundle(
133
+ String id, String launchActivity, String mainComponent) {
134
+ Bundle pressActionBundle = new Bundle();
135
+ pressActionBundle.putString("id", id);
136
+ if (launchActivity != null) {
137
+ pressActionBundle.putString("launchActivity", launchActivity);
138
+ }
139
+ if (mainComponent != null) {
140
+ pressActionBundle.putString("mainComponent", mainComponent);
141
+ }
142
+ return pressActionBundle;
143
+ }
144
+
145
+ private static void assertPressAction(
146
+ Bundle extras, String id, String launchActivity, String mainComponent) {
147
+ Bundle pressAction = extras.getBundle("pressAction");
148
+ assertNotNull("pressAction should be present", pressAction);
149
+ assertEquals(id, pressAction.getString("id"));
150
+ assertEquals(launchActivity, pressAction.getString("launchActivity"));
151
+ assertEquals(mainComponent, pressAction.getString("mainComponent"));
152
+ }
153
+
154
+ private static void clearStickyEvents() {
155
+ EventBus.removeStickEvent(InitialNotificationEvent.class);
156
+ EventBus.removeStickEvent(MainComponentEvent.class);
157
+ }
158
+
159
+ public static class TestLaunchActivity extends Activity {}
160
+
161
+ public static class NotificationEventCapture {
162
+ final List<NotificationEvent> events = new ArrayList<>();
163
+
164
+ @Subscribe(threadMode = ThreadMode.POSTING)
165
+ public void onNotificationEvent(NotificationEvent event) {
166
+ events.add(event);
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,116 @@
1
+ package app.notifee.core.model;
2
+
3
+ import static org.junit.Assert.assertArrayEquals;
4
+ import static org.junit.Assert.assertEquals;
5
+ import static org.junit.Assert.assertNull;
6
+ import static org.junit.Assert.assertThrows;
7
+
8
+ import android.os.Bundle;
9
+ import androidx.core.app.NotificationCompat;
10
+ import androidx.core.app.NotificationManagerCompat;
11
+ import java.util.ArrayList;
12
+ import org.junit.Test;
13
+ import org.junit.runner.RunWith;
14
+ import org.robolectric.RobolectricTestRunner;
15
+
16
+ @RunWith(RobolectricTestRunner.class)
17
+ public class ChannelModelTest {
18
+
19
+ @Test
20
+ public void fields_whenMissing_returnCurrentDefaults() {
21
+ ChannelModel model = ChannelModel.fromBundle(new Bundle());
22
+
23
+ assertEquals(NotificationManagerCompat.IMPORTANCE_DEFAULT, model.getImportance().intValue());
24
+ assertEquals(NotificationCompat.VISIBILITY_PRIVATE, model.getVisibility());
25
+ assertArrayEquals(new long[0], model.getVibrationPattern());
26
+ assertNull(model.getSound());
27
+ }
28
+
29
+ @Test
30
+ public void numericFields_withIntegerAndDoubleValues_preserveCurrentCoercion() {
31
+ Bundle integerBundle = new Bundle();
32
+ integerBundle.putInt("importance", NotificationManagerCompat.IMPORTANCE_HIGH);
33
+ integerBundle.putInt("visibility", NotificationCompat.VISIBILITY_SECRET);
34
+ ChannelModel integerModel = ChannelModel.fromBundle(integerBundle);
35
+
36
+ assertEquals(NotificationManagerCompat.IMPORTANCE_HIGH, integerModel.getImportance().intValue());
37
+ assertEquals(NotificationCompat.VISIBILITY_SECRET, integerModel.getVisibility());
38
+
39
+ Bundle doubleBundle = new Bundle();
40
+ doubleBundle.putDouble("importance", NotificationManagerCompat.IMPORTANCE_HIGH + 0.9d);
41
+ doubleBundle.putDouble("visibility", -1.9d);
42
+ ChannelModel doubleModel = ChannelModel.fromBundle(doubleBundle);
43
+
44
+ assertEquals(NotificationManagerCompat.IMPORTANCE_HIGH, doubleModel.getImportance().intValue());
45
+ assertEquals(NotificationCompat.VISIBILITY_SECRET, doubleModel.getVisibility());
46
+ }
47
+
48
+ @Test
49
+ public void numericFields_withExplicitNull_currentlyCoerceToZero() {
50
+ Bundle bundle = new Bundle();
51
+ bundle.putString("importance", null);
52
+ bundle.putString("visibility", null);
53
+ ChannelModel model = ChannelModel.fromBundle(bundle);
54
+
55
+ assertEquals(0, model.getImportance().intValue());
56
+ assertEquals(0, model.getVisibility());
57
+ }
58
+
59
+ @Test
60
+ public void numericFields_withUnsupportedTypes_currentlyThrowClassCastException() {
61
+ Bundle importanceBundle = new Bundle();
62
+ importanceBundle.putLong("importance", 4L);
63
+ assertThrows(
64
+ ClassCastException.class, () -> ChannelModel.fromBundle(importanceBundle).getImportance());
65
+
66
+ Bundle visibilityBundle = new Bundle();
67
+ visibilityBundle.putString("visibility", "1");
68
+ assertThrows(
69
+ ClassCastException.class, () -> ChannelModel.fromBundle(visibilityBundle).getVisibility());
70
+ }
71
+
72
+ @Test
73
+ public void vibrationPattern_missingEmptyIntegerAndUnsupportedValues_preserveCurrentBehavior() {
74
+ assertArrayEquals(new long[0], ChannelModel.fromBundle(new Bundle()).getVibrationPattern());
75
+
76
+ Bundle emptyBundle = new Bundle();
77
+ putRawParcelableArrayList(emptyBundle, "vibrationPattern", new ArrayList<Integer>());
78
+ assertArrayEquals(new long[0], ChannelModel.fromBundle(emptyBundle).getVibrationPattern());
79
+
80
+ ArrayList<Integer> vibrationPattern = new ArrayList<>();
81
+ vibrationPattern.add(100);
82
+ vibrationPattern.add(200);
83
+ Bundle validBundle = new Bundle();
84
+ putRawParcelableArrayList(validBundle, "vibrationPattern", vibrationPattern);
85
+
86
+ assertArrayEquals(new long[] {100L, 200L}, ChannelModel.fromBundle(validBundle).getVibrationPattern());
87
+
88
+ ArrayList<Object> invalidPattern = new ArrayList<>();
89
+ invalidPattern.add(100L);
90
+ Bundle invalidBundle = new Bundle();
91
+ putRawParcelableArrayList(invalidBundle, "vibrationPattern", invalidPattern);
92
+
93
+ assertThrows(
94
+ ClassCastException.class,
95
+ () -> ChannelModel.fromBundle(invalidBundle).getVibrationPattern());
96
+ }
97
+
98
+ @Test
99
+ public void sound_missingPresentAndExplicitNull_preserveCurrentBehavior() {
100
+ assertNull(ChannelModel.fromBundle(new Bundle()).getSound());
101
+
102
+ Bundle soundBundle = new Bundle();
103
+ soundBundle.putString("sound", "default");
104
+ assertEquals("default", ChannelModel.fromBundle(soundBundle).getSound());
105
+
106
+ Bundle nullSoundBundle = new Bundle();
107
+ nullSoundBundle.putString("sound", null);
108
+ assertNull(ChannelModel.fromBundle(nullSoundBundle).getSound());
109
+ }
110
+
111
+ @SuppressWarnings({"rawtypes", "unchecked"})
112
+ private static void putRawParcelableArrayList(
113
+ Bundle bundle, String key, ArrayList<?> arrayList) {
114
+ bundle.putParcelableArrayList(key, (ArrayList) arrayList);
115
+ }
116
+ }
@@ -0,0 +1,71 @@
1
+ package app.notifee.core.model;
2
+
3
+ import static org.junit.Assert.assertEquals;
4
+ import static org.junit.Assert.assertThrows;
5
+
6
+ import android.os.Bundle;
7
+ import org.junit.Test;
8
+ import org.junit.runner.RunWith;
9
+ import org.robolectric.RobolectricTestRunner;
10
+
11
+ @RunWith(RobolectricTestRunner.class)
12
+ public class IntervalTriggerModelTest {
13
+
14
+ @Test
15
+ public void getInterval_returnsMinusOneWhenMissing() {
16
+ IntervalTriggerModel model = IntervalTriggerModel.fromBundle(new Bundle());
17
+
18
+ assertEquals(-1, model.getInterval());
19
+ }
20
+
21
+ @Test
22
+ public void getInterval_returnsIntegerValue() {
23
+ Bundle bundle = new Bundle();
24
+ bundle.putInt("interval", 15);
25
+
26
+ IntervalTriggerModel model = IntervalTriggerModel.fromBundle(bundle);
27
+
28
+ assertEquals(15, model.getInterval());
29
+ }
30
+
31
+ @Test
32
+ public void getInterval_truncatesDoubleValue() {
33
+ Bundle bundle = new Bundle();
34
+ bundle.putDouble("interval", 15.9d);
35
+
36
+ IntervalTriggerModel model = IntervalTriggerModel.fromBundle(bundle);
37
+
38
+ assertEquals(15, model.getInterval());
39
+ }
40
+
41
+ @Test
42
+ public void getInterval_returnsZeroForExplicitNull() {
43
+ Bundle bundle = new Bundle();
44
+ bundle.putString("interval", null);
45
+
46
+ IntervalTriggerModel model = IntervalTriggerModel.fromBundle(bundle);
47
+
48
+ assertEquals(0, model.getInterval());
49
+ }
50
+
51
+ @Test
52
+ public void getInterval_preservesClassCastForUnsupportedNumericTypes() {
53
+ Bundle longBundle = new Bundle();
54
+ longBundle.putLong("interval", 15L);
55
+ assertThrows(
56
+ ClassCastException.class, () -> IntervalTriggerModel.fromBundle(longBundle).getInterval());
57
+
58
+ Bundle floatBundle = new Bundle();
59
+ floatBundle.putFloat("interval", 15.9f);
60
+ assertThrows(
61
+ ClassCastException.class, () -> IntervalTriggerModel.fromBundle(floatBundle).getInterval());
62
+ }
63
+
64
+ @Test
65
+ public void getInterval_preservesClassCastForString() {
66
+ Bundle bundle = new Bundle();
67
+ bundle.putString("interval", "15");
68
+
69
+ assertThrows(ClassCastException.class, () -> IntervalTriggerModel.fromBundle(bundle).getInterval());
70
+ }
71
+ }