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
@@ -6,7 +6,6 @@ package io.invertase.notifee
6
6
 
7
7
  import app.notifee.core.InitProvider
8
8
  import app.notifee.core.Notifee
9
- import com.facebook.react.modules.systeminfo.ReactNativeVersion
10
9
 
11
10
  class NotifeeInitProvider : InitProvider() {
12
11
 
@@ -15,29 +14,4 @@ class NotifeeInitProvider : InitProvider() {
15
14
  Notifee.initialize(NotifeeEventSubscriber())
16
15
  return result
17
16
  }
18
-
19
- private fun getApplicationVersionString(): String {
20
- val context = this.context ?: return "unknown"
21
- return try {
22
- val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
23
- pInfo.versionName ?: "unknown"
24
- } catch (e: Exception) {
25
- "unknown"
26
- }
27
- }
28
-
29
- private fun getReactNativeVersionString(): String {
30
- val versionMap = ReactNativeVersion.VERSION
31
- val major = versionMap["major"] as Int
32
- val minor = versionMap["minor"] as Int
33
- val patch = versionMap["patch"] as Int
34
- val prerelease = versionMap["prerelease"] as? String
35
-
36
- return buildString {
37
- append("$major.$minor.$patch")
38
- if (prerelease != null) {
39
- append(".$prerelease")
40
- }
41
- }
42
- }
43
17
  }
@@ -0,0 +1,446 @@
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
+ import static org.junit.Assert.assertThrows;
23
+ import static org.junit.Assert.assertTrue;
24
+ import static org.mockito.ArgumentMatchers.any;
25
+ import static org.mockito.Mockito.mock;
26
+ import static org.mockito.Mockito.mockStatic;
27
+ import static org.mockito.Mockito.never;
28
+ import static org.mockito.Mockito.timeout;
29
+ import static org.mockito.Mockito.verify;
30
+ import static org.mockito.Mockito.when;
31
+
32
+ import android.app.AlarmManager;
33
+ import android.content.BroadcastReceiver;
34
+ import android.content.Context;
35
+ import android.os.Bundle;
36
+ import androidx.core.app.NotificationChannelCompat;
37
+ import androidx.core.app.NotificationManagerCompat;
38
+ import app.notifee.core.database.WorkDataEntity;
39
+ import app.notifee.core.database.WorkDataRepository;
40
+ import app.notifee.core.utility.AlarmUtils;
41
+ import app.notifee.core.utility.ObjectUtils;
42
+ import com.google.common.util.concurrent.Futures;
43
+ import com.google.common.util.concurrent.ListenableFuture;
44
+ import java.util.List;
45
+ import java.util.concurrent.TimeUnit;
46
+ import org.junit.After;
47
+ import org.junit.Before;
48
+ import org.junit.Test;
49
+ import org.junit.runner.RunWith;
50
+ import org.mockito.ArgumentCaptor;
51
+ import org.mockito.MockedStatic;
52
+ import org.robolectric.RobolectricTestRunner;
53
+ import org.robolectric.RuntimeEnvironment;
54
+ import org.robolectric.annotation.Config;
55
+
56
+ @RunWith(RobolectricTestRunner.class)
57
+ @Config(sdk = 28)
58
+ public class NotifeeAlarmManagerCurrentBehaviorTest {
59
+ private static final String CHANNEL_ID = "manager-current-behavior-channel";
60
+ private static final long OLD_ANCHOR_OFFSET_MS = TimeUnit.HOURS.toMillis(2);
61
+
62
+ private Context context;
63
+ private WorkDataRepository repo;
64
+
65
+ @Before
66
+ public void setUp() throws Exception {
67
+ context = RuntimeEnvironment.getApplication();
68
+ ContextHolder.setApplicationContext(context);
69
+ repo = WorkDataRepository.getInstance(context);
70
+ repo.deleteAll().get(5, TimeUnit.SECONDS);
71
+
72
+ NotificationChannelCompat channel =
73
+ new NotificationChannelCompat.Builder(
74
+ CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
75
+ .setName("Manager Current Behavior")
76
+ .build();
77
+ NotificationManagerCompat.from(context).createNotificationChannel(channel);
78
+ }
79
+
80
+ @After
81
+ public void tearDown() throws Exception {
82
+ if (repo != null) {
83
+ repo.deleteAll().get(5, TimeUnit.SECONDS);
84
+ }
85
+ if (context != null) {
86
+ NotificationManagerCompat.from(context).cancelAll();
87
+ }
88
+ }
89
+
90
+ @Test
91
+ public void displayScheduledNotification_missingRepeatFrequency_deletesRoomRow()
92
+ throws Exception {
93
+ String id = "am-repeat-missing";
94
+ long oldAnchor = oldAnchor();
95
+ seedDisplayRow(id, timestampTriggerWithRepeatValue(null, false, oldAnchor));
96
+
97
+ runDisplayScheduledNotification(id);
98
+
99
+ assertNull(
100
+ "missing repeatFrequency must take the one-shot delete branch",
101
+ repo.getWorkDataById(id).get(5, TimeUnit.SECONDS));
102
+ }
103
+
104
+ @Test
105
+ public void displayScheduledNotification_explicitNullRepeatFrequency_updatesRoomRow()
106
+ throws Exception {
107
+ String id = "am-repeat-null";
108
+ long oldAnchor = oldAnchor();
109
+ seedDisplayRow(id, timestampTriggerWithRepeatValue(null, true, oldAnchor));
110
+
111
+ long minExpectedAnchor = System.currentTimeMillis();
112
+ runDisplayScheduledNotification(id);
113
+
114
+ assertRowUpdatedToFuture(id, oldAnchor, minExpectedAnchor);
115
+ }
116
+
117
+ @Test
118
+ public void displayScheduledNotification_minusOneRepeatFrequency_deletesRoomRow()
119
+ throws Exception {
120
+ String id = "am-repeat-minus-one";
121
+ long oldAnchor = oldAnchor();
122
+ seedDisplayRow(id, timestampTriggerWithRepeatValue(Integer.valueOf(-1), true, oldAnchor));
123
+
124
+ runDisplayScheduledNotification(id);
125
+
126
+ assertNull(
127
+ "repeatFrequency -1 must take the one-shot delete branch",
128
+ repo.getWorkDataById(id).get(5, TimeUnit.SECONDS));
129
+ }
130
+
131
+ @Test
132
+ public void displayScheduledNotification_integerRepeatFrequencies_updateRoomRow()
133
+ throws Exception {
134
+ for (int repeatFrequency = 0; repeatFrequency <= 3; repeatFrequency++) {
135
+ String id = "am-repeat-int-" + repeatFrequency;
136
+ long oldAnchor = oldAnchor();
137
+ seedDisplayRow(id, timestampTriggerWithRepeatValue(repeatFrequency, true, oldAnchor));
138
+
139
+ long minExpectedAnchor = System.currentTimeMillis();
140
+ runDisplayScheduledNotification(id);
141
+
142
+ assertRowUpdatedToFuture(id, oldAnchor, minExpectedAnchor);
143
+ }
144
+ }
145
+
146
+ @Test
147
+ public void displayScheduledNotification_doubleRepeatFrequency_truncatesAndUpdatesRoomRow()
148
+ throws Exception {
149
+ String id = "am-repeat-double";
150
+ long oldAnchor = oldAnchor();
151
+ seedDisplayRow(id, timestampTriggerWithRepeatValue(Double.valueOf(0.9d), true, oldAnchor));
152
+
153
+ long minExpectedAnchor = System.currentTimeMillis();
154
+ runDisplayScheduledNotification(id);
155
+
156
+ assertRowUpdatedToFuture(id, oldAnchor, minExpectedAnchor);
157
+ }
158
+
159
+ @Test
160
+ public void displayScheduledNotification_unsupportedRepeatFrequency_keepsRoomRowUnchanged()
161
+ throws Exception {
162
+ assertUnsupportedRepeatFrequencyKeepsRow(Long.valueOf(0L), "long");
163
+ assertUnsupportedRepeatFrequencyKeepsRow(Float.valueOf(0.5f), "float");
164
+ assertUnsupportedRepeatFrequencyKeepsRow("0", "string");
165
+ }
166
+
167
+ @Test
168
+ public void rescheduleNotification_missingType_dispatchesTimestampAndUpdatesRoomRow()
169
+ throws Exception {
170
+ WorkDataEntity updated =
171
+ runRescheduleNotificationExpectingUpdate(rescheduleTriggerWithType(null, false));
172
+
173
+ assertEquals("am-type", updated.getId());
174
+ }
175
+
176
+ @Test
177
+ public void rescheduleNotification_explicitNullType_dispatchesTimestampAndUpdatesRoomRow()
178
+ throws Exception {
179
+ WorkDataEntity updated =
180
+ runRescheduleNotificationExpectingUpdate(rescheduleTriggerWithType(null, true));
181
+
182
+ assertEquals("am-type", updated.getId());
183
+ }
184
+
185
+ @Test
186
+ public void rescheduleNotification_integerZero_dispatchesTimestampAndUpdatesRoomRow()
187
+ throws Exception {
188
+ WorkDataEntity updated =
189
+ runRescheduleNotificationExpectingUpdate(
190
+ rescheduleTriggerWithType(Integer.valueOf(0), true));
191
+
192
+ assertEquals("am-type", updated.getId());
193
+ }
194
+
195
+ @Test
196
+ public void rescheduleNotification_integerOne_completesWithoutRoomUpdate() throws Exception {
197
+ runRescheduleNotificationExpectingNoUpdate(rescheduleTriggerWithType(Integer.valueOf(1), true));
198
+ }
199
+
200
+ @Test
201
+ public void rescheduleNotification_otherInteger_completesWithoutRoomUpdate() throws Exception {
202
+ runRescheduleNotificationExpectingNoUpdate(rescheduleTriggerWithType(Integer.valueOf(99), true));
203
+ }
204
+
205
+ @Test
206
+ public void rescheduleNotification_doubleType_truncatesToTimestampBranch() throws Exception {
207
+ WorkDataEntity updated =
208
+ runRescheduleNotificationExpectingUpdate(
209
+ rescheduleTriggerWithType(Double.valueOf(0.9d), true));
210
+
211
+ assertEquals("am-type", updated.getId());
212
+ }
213
+
214
+ @Test
215
+ public void rescheduleNotification_unsupportedType_preservesClassCastException() {
216
+ assertUnsupportedTypeThrows(Long.valueOf(0L));
217
+ assertUnsupportedTypeThrows(Float.valueOf(0.5f));
218
+ assertUnsupportedTypeThrows("0");
219
+ }
220
+
221
+ @Test
222
+ public void rescheduleNotifications_intervalAndDefaultType_keepRoomRowsUnchanged()
223
+ throws Exception {
224
+ assertRescheduleNotificationsKeepsRowUnchanged(Integer.valueOf(1), "interval");
225
+ assertRescheduleNotificationsKeepsRowUnchanged(Integer.valueOf(99), "default");
226
+ }
227
+
228
+ @Test
229
+ public void rescheduleNotifications_unsupportedType_keepsRoomRowAfterPerEntityCatch()
230
+ throws Exception {
231
+ assertRescheduleNotificationsKeepsRowUnchanged(Long.valueOf(0L), "long");
232
+ assertRescheduleNotificationsKeepsRowUnchanged(Float.valueOf(0.5f), "float");
233
+ assertRescheduleNotificationsKeepsRowUnchanged("0", "string");
234
+ }
235
+
236
+ private void assertUnsupportedRepeatFrequencyKeepsRow(Object repeatFrequency, String suffix)
237
+ throws Exception {
238
+ String id = "am-repeat-" + suffix;
239
+ long oldAnchor = oldAnchor();
240
+ seedDisplayRow(id, timestampTriggerWithRepeatValue(repeatFrequency, true, oldAnchor));
241
+
242
+ runDisplayScheduledNotification(id);
243
+
244
+ WorkDataEntity row = repo.getWorkDataById(id).get(5, TimeUnit.SECONDS);
245
+ assertNotNull(
246
+ "unsupported repeatFrequency must fail before update/delete and leave the row", row);
247
+ assertEquals(
248
+ "unsupported repeatFrequency must not rewrite the persisted timestamp",
249
+ oldAnchor,
250
+ triggerTimestamp(row));
251
+ }
252
+
253
+ private void assertRescheduleNotificationsKeepsRowUnchanged(Object typeValue, String suffix)
254
+ throws Exception {
255
+ repo.deleteAll().get(5, TimeUnit.SECONDS);
256
+ String id = "am-type-reboot-" + suffix;
257
+ Bundle triggerBundle = rescheduleTriggerWithType(typeValue, true);
258
+ seedDisplayRow(id, triggerBundle);
259
+ long originalTimestamp =
260
+ triggerTimestamp(repo.getWorkDataById(id).get(5, TimeUnit.SECONDS));
261
+
262
+ runRescheduleNotificationsFromRoom();
263
+
264
+ WorkDataEntity row = repo.getWorkDataById(id).get(5, TimeUnit.SECONDS);
265
+ assertNotNull("reboot recovery must leave no-op/wrong-type rows in Room", row);
266
+ assertEquals(
267
+ "reboot recovery must not rewrite no-op/wrong-type trigger rows",
268
+ originalTimestamp,
269
+ triggerTimestamp(row));
270
+ }
271
+
272
+ private WorkDataEntity runRescheduleNotificationExpectingUpdate(Bundle triggerBundle)
273
+ throws Exception {
274
+ WorkDataRepository mockRepo = mock(WorkDataRepository.class);
275
+ when(mockRepo.update(any())).thenReturn(Futures.immediateFuture(null));
276
+ AlarmManager mockAlarmManager = mock(AlarmManager.class);
277
+ ArgumentCaptor<WorkDataEntity> updateCaptor = ArgumentCaptor.forClass(WorkDataEntity.class);
278
+
279
+ try (MockedStatic<WorkDataRepository> repoMock = mockStatic(WorkDataRepository.class);
280
+ MockedStatic<AlarmUtils> alarmUtilsMock = mockStatic(AlarmUtils.class)) {
281
+ repoMock.when(() -> WorkDataRepository.getInstance(any())).thenReturn(mockRepo);
282
+ alarmUtilsMock.when(AlarmUtils::getAlarmManager).thenReturn(mockAlarmManager);
283
+
284
+ new NotifeeAlarmManager()
285
+ .rescheduleNotification(buildEntity("am-type", triggerBundle))
286
+ .get(5, TimeUnit.SECONDS);
287
+
288
+ verify(mockRepo).update(updateCaptor.capture());
289
+ return updateCaptor.getValue();
290
+ }
291
+ }
292
+
293
+ private void runRescheduleNotificationExpectingNoUpdate(Bundle triggerBundle) throws Exception {
294
+ WorkDataRepository mockRepo = mock(WorkDataRepository.class);
295
+ AlarmManager mockAlarmManager = mock(AlarmManager.class);
296
+
297
+ try (MockedStatic<WorkDataRepository> repoMock = mockStatic(WorkDataRepository.class);
298
+ MockedStatic<AlarmUtils> alarmUtilsMock = mockStatic(AlarmUtils.class)) {
299
+ repoMock.when(() -> WorkDataRepository.getInstance(any())).thenReturn(mockRepo);
300
+ alarmUtilsMock.when(AlarmUtils::getAlarmManager).thenReturn(mockAlarmManager);
301
+
302
+ new NotifeeAlarmManager()
303
+ .rescheduleNotification(buildEntity("am-type", triggerBundle))
304
+ .get(5, TimeUnit.SECONDS);
305
+
306
+ verify(mockRepo, never()).update(any());
307
+ }
308
+ }
309
+
310
+ private void assertUnsupportedTypeThrows(Object typeValue) {
311
+ WorkDataRepository mockRepo = mock(WorkDataRepository.class);
312
+
313
+ try (MockedStatic<WorkDataRepository> repoMock = mockStatic(WorkDataRepository.class)) {
314
+ repoMock.when(() -> WorkDataRepository.getInstance(any())).thenReturn(mockRepo);
315
+
316
+ assertThrows(
317
+ ClassCastException.class,
318
+ () ->
319
+ new NotifeeAlarmManager()
320
+ .rescheduleNotification(
321
+ buildEntity("am-type", rescheduleTriggerWithType(typeValue, true))));
322
+
323
+ verify(mockRepo, never()).update(any());
324
+ }
325
+ }
326
+
327
+ private void runDisplayScheduledNotification(String id) {
328
+ BroadcastReceiver.PendingResult pendingResult = mock(BroadcastReceiver.PendingResult.class);
329
+ Bundle alarmManagerNotification = new Bundle();
330
+ alarmManagerNotification.putString("notificationId", id);
331
+
332
+ NotifeeAlarmManager.displayScheduledNotification(alarmManagerNotification, pendingResult);
333
+
334
+ verify(pendingResult, timeout(10_000)).finish();
335
+ }
336
+
337
+ private void runRescheduleNotificationsFromRoom() {
338
+ BroadcastReceiver.PendingResult pendingResult = mock(BroadcastReceiver.PendingResult.class);
339
+
340
+ new RoomBackedAlarmManager().rescheduleNotifications(pendingResult);
341
+
342
+ verify(pendingResult, timeout(10_000)).finish();
343
+ }
344
+
345
+ private void seedDisplayRow(String id, Bundle triggerBundle) throws Exception {
346
+ repo.insert(buildEntity(id, triggerBundle)).get(5, TimeUnit.SECONDS);
347
+ }
348
+
349
+ private WorkDataEntity buildEntity(String id, Bundle triggerBundle) {
350
+ return new WorkDataEntity(
351
+ id,
352
+ ObjectUtils.bundleToBytes(notificationBundle(id)),
353
+ ObjectUtils.bundleToBytes(triggerBundle),
354
+ true);
355
+ }
356
+
357
+ private void assertRowUpdatedToFuture(String id, long oldAnchor, long minExpectedAnchor)
358
+ throws Exception {
359
+ WorkDataEntity row = repo.getWorkDataById(id).get(5, TimeUnit.SECONDS);
360
+ assertNotNull("repeating trigger row must survive after update branch", row);
361
+
362
+ long updatedAnchor = triggerTimestamp(row);
363
+ assertTrue(
364
+ "repeating trigger timestamp must be advanced by the update branch",
365
+ updatedAnchor > oldAnchor);
366
+ assertTrue(
367
+ "repeating trigger timestamp must be advanced to the future",
368
+ updatedAnchor >= minExpectedAnchor);
369
+ }
370
+
371
+ private static long triggerTimestamp(WorkDataEntity row) {
372
+ return ObjectUtils.getLong(ObjectUtils.bytesToBundle(row.getTrigger()).get("timestamp"));
373
+ }
374
+
375
+ private static long oldAnchor() {
376
+ return System.currentTimeMillis() - OLD_ANCHOR_OFFSET_MS;
377
+ }
378
+
379
+ private static Bundle notificationBundle(String id) {
380
+ Bundle notificationBundle = new Bundle();
381
+ notificationBundle.putString("id", id);
382
+ notificationBundle.putString("title", "Manager Current Behavior " + id);
383
+
384
+ Bundle androidBundle = new Bundle();
385
+ androidBundle.putString("channelId", CHANNEL_ID);
386
+ notificationBundle.putBundle("android", androidBundle);
387
+
388
+ return notificationBundle;
389
+ }
390
+
391
+ private static Bundle timestampTriggerWithRepeatValue(
392
+ Object repeatFrequency, boolean includeRepeatFrequency, long timestamp) {
393
+ Bundle triggerBundle = new Bundle();
394
+ triggerBundle.putInt("type", 0);
395
+ triggerBundle.putLong("timestamp", timestamp);
396
+ if (includeRepeatFrequency) {
397
+ putBundleValue(triggerBundle, "repeatFrequency", repeatFrequency);
398
+ }
399
+
400
+ Bundle alarmManagerBundle = new Bundle();
401
+ alarmManagerBundle.putInt("type", 3);
402
+ triggerBundle.putBundle("alarmManager", alarmManagerBundle);
403
+
404
+ return triggerBundle;
405
+ }
406
+
407
+ private static Bundle rescheduleTriggerWithType(Object typeValue, boolean includeType) {
408
+ Bundle triggerBundle = new Bundle();
409
+ if (includeType) {
410
+ putBundleValue(triggerBundle, "type", typeValue);
411
+ }
412
+ triggerBundle.putLong("timestamp", System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10));
413
+ triggerBundle.putInt("repeatFrequency", 0);
414
+
415
+ Bundle alarmManagerBundle = new Bundle();
416
+ alarmManagerBundle.putInt("type", 3);
417
+ triggerBundle.putBundle("alarmManager", alarmManagerBundle);
418
+
419
+ return triggerBundle;
420
+ }
421
+
422
+ private static void putBundleValue(Bundle bundle, String key, Object value) {
423
+ if (value == null) {
424
+ bundle.putString(key, null);
425
+ } else if (value instanceof Integer) {
426
+ bundle.putInt(key, (Integer) value);
427
+ } else if (value instanceof Double) {
428
+ bundle.putDouble(key, (Double) value);
429
+ } else if (value instanceof Long) {
430
+ bundle.putLong(key, (Long) value);
431
+ } else if (value instanceof Float) {
432
+ bundle.putFloat(key, (Float) value);
433
+ } else if (value instanceof String) {
434
+ bundle.putString(key, (String) value);
435
+ } else {
436
+ throw new IllegalArgumentException("Unsupported test value: " + value);
437
+ }
438
+ }
439
+
440
+ private class RoomBackedAlarmManager extends NotifeeAlarmManager {
441
+ @Override
442
+ ListenableFuture<List<WorkDataEntity>> getScheduledNotifications() {
443
+ return repo.getAllWithAlarmManager(true);
444
+ }
445
+ }
446
+ }
@@ -0,0 +1,203 @@
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.assertThrows;
20
+ import static org.mockito.ArgumentMatchers.any;
21
+ import static org.mockito.ArgumentMatchers.same;
22
+ import static org.mockito.Mockito.CALLS_REAL_METHODS;
23
+ import static org.mockito.Mockito.mockStatic;
24
+ import static org.mockito.Mockito.never;
25
+
26
+ import android.os.Bundle;
27
+ import app.notifee.core.model.NotificationModel;
28
+ import com.google.common.util.concurrent.Futures;
29
+ import com.google.common.util.concurrent.ListenableFuture;
30
+ import java.util.concurrent.TimeUnit;
31
+ import org.junit.After;
32
+ import org.junit.Before;
33
+ import org.junit.Test;
34
+ import org.junit.runner.RunWith;
35
+ import org.mockito.MockedStatic;
36
+ import org.robolectric.RobolectricTestRunner;
37
+
38
+ @RunWith(RobolectricTestRunner.class)
39
+ public class NotificationManagerCreateTriggerNotificationCurrentBehaviorTest {
40
+ private MockedStatic<NotificationManager> notificationManagerMock;
41
+ private NotificationModel notificationModel;
42
+
43
+ @Before
44
+ public void setUp() {
45
+ notificationModel = buildNotificationModel("create-trigger-current-behavior");
46
+ notificationManagerMock = mockStatic(NotificationManager.class, CALLS_REAL_METHODS);
47
+ notificationManagerMock
48
+ .when(() -> NotificationManager.createTimestampTriggerNotification(any(), any()))
49
+ .thenReturn(Futures.immediateFuture(null));
50
+ notificationManagerMock
51
+ .when(() -> NotificationManager.createIntervalTriggerNotification(any(), any()))
52
+ .thenReturn(Futures.immediateFuture(null));
53
+ }
54
+
55
+ @After
56
+ public void tearDown() {
57
+ if (notificationManagerMock != null) {
58
+ notificationManagerMock.close();
59
+ }
60
+ }
61
+
62
+ @Test
63
+ public void createTriggerNotification_missingType_dispatchesTimestampBranch() throws Exception {
64
+ Bundle triggerBundle = new Bundle();
65
+
66
+ assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
67
+
68
+ verifyTimestampBranch(triggerBundle);
69
+ verifyIntervalBranchNeverCalled();
70
+ }
71
+
72
+ @Test
73
+ public void createTriggerNotification_explicitNullType_dispatchesTimestampBranch()
74
+ throws Exception {
75
+ Bundle triggerBundle = new Bundle();
76
+ triggerBundle.putString("type", null);
77
+
78
+ assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
79
+
80
+ verifyTimestampBranch(triggerBundle);
81
+ verifyIntervalBranchNeverCalled();
82
+ }
83
+
84
+ @Test
85
+ public void createTriggerNotification_integerZero_dispatchesTimestampBranch() throws Exception {
86
+ Bundle triggerBundle = triggerBundleWithType(Integer.valueOf(0));
87
+
88
+ assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
89
+
90
+ verifyTimestampBranch(triggerBundle);
91
+ verifyIntervalBranchNeverCalled();
92
+ }
93
+
94
+ @Test
95
+ public void createTriggerNotification_integerOne_dispatchesIntervalBranch() throws Exception {
96
+ Bundle triggerBundle = triggerBundleWithType(Integer.valueOf(1));
97
+
98
+ assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
99
+
100
+ verifyIntervalBranch(triggerBundle);
101
+ verifyTimestampBranchNeverCalled();
102
+ }
103
+
104
+ @Test
105
+ public void createTriggerNotification_otherInteger_completesWithoutScheduling() throws Exception {
106
+ Bundle triggerBundle = triggerBundleWithType(Integer.valueOf(99));
107
+
108
+ assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
109
+
110
+ verifyTimestampBranchNeverCalled();
111
+ verifyIntervalBranchNeverCalled();
112
+ }
113
+
114
+ @Test
115
+ public void createTriggerNotification_doubleType_truncatesBeforeDispatch() throws Exception {
116
+ Bundle triggerBundle = triggerBundleWithType(Double.valueOf(1.9d));
117
+
118
+ assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
119
+
120
+ verifyIntervalBranch(triggerBundle);
121
+ verifyTimestampBranchNeverCalled();
122
+ }
123
+
124
+ @Test
125
+ public void createTriggerNotification_unsupportedType_preservesClassCastException() {
126
+ assertThrows(
127
+ ClassCastException.class,
128
+ () ->
129
+ NotificationManager.createTriggerNotification(
130
+ notificationModel, triggerBundleWithType(Long.valueOf(1L))));
131
+ assertThrows(
132
+ ClassCastException.class,
133
+ () ->
134
+ NotificationManager.createTriggerNotification(
135
+ notificationModel, triggerBundleWithType(Float.valueOf(1.5f))));
136
+ assertThrows(
137
+ ClassCastException.class,
138
+ () ->
139
+ NotificationManager.createTriggerNotification(
140
+ notificationModel, triggerBundleWithType("1")));
141
+
142
+ verifyTimestampBranchNeverCalled();
143
+ verifyIntervalBranchNeverCalled();
144
+ }
145
+
146
+ private void verifyTimestampBranch(Bundle triggerBundle) {
147
+ notificationManagerMock.verify(
148
+ () ->
149
+ NotificationManager.createTimestampTriggerNotification(
150
+ same(notificationModel), same(triggerBundle)));
151
+ }
152
+
153
+ private void verifyIntervalBranch(Bundle triggerBundle) {
154
+ notificationManagerMock.verify(
155
+ () ->
156
+ NotificationManager.createIntervalTriggerNotification(
157
+ same(notificationModel), same(triggerBundle)));
158
+ }
159
+
160
+ private void verifyTimestampBranchNeverCalled() {
161
+ notificationManagerMock.verify(
162
+ () -> NotificationManager.createTimestampTriggerNotification(any(), any()), never());
163
+ }
164
+
165
+ private void verifyIntervalBranchNeverCalled() {
166
+ notificationManagerMock.verify(
167
+ () -> NotificationManager.createIntervalTriggerNotification(any(), any()), never());
168
+ }
169
+
170
+ private static void assertCompletes(ListenableFuture<Void> future) throws Exception {
171
+ future.get(5, TimeUnit.SECONDS);
172
+ }
173
+
174
+ private static NotificationModel buildNotificationModel(String id) {
175
+ Bundle notificationBundle = new Bundle();
176
+ notificationBundle.putString("id", id);
177
+ return NotificationModel.fromBundle(notificationBundle);
178
+ }
179
+
180
+ private static Bundle triggerBundleWithType(Object type) {
181
+ Bundle triggerBundle = new Bundle();
182
+ putBundleValue(triggerBundle, "type", type);
183
+ return triggerBundle;
184
+ }
185
+
186
+ private static void putBundleValue(Bundle bundle, String key, Object value) {
187
+ if (value == null) {
188
+ bundle.putString(key, null);
189
+ } else if (value instanceof Integer) {
190
+ bundle.putInt(key, (Integer) value);
191
+ } else if (value instanceof Double) {
192
+ bundle.putDouble(key, (Double) value);
193
+ } else if (value instanceof Long) {
194
+ bundle.putLong(key, (Long) value);
195
+ } else if (value instanceof Float) {
196
+ bundle.putFloat(key, (Float) value);
197
+ } else if (value instanceof String) {
198
+ bundle.putString(key, (String) value);
199
+ } else {
200
+ throw new IllegalArgumentException("Unsupported test value: " + value);
201
+ }
202
+ }
203
+ }