react-native-notify-kit 10.2.0 → 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.
- package/README.md +6 -6
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/app/notifee/core/ForegroundService.java +3 -1
- package/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java +4 -2
- package/android/src/main/java/app/notifee/core/NotificationManager.java +43 -43
- package/android/src/main/java/app/notifee/core/ReceiverService.java +18 -13
- package/android/src/main/java/app/notifee/core/database/NotifeeCoreDatabase.java +1 -4
- package/android/src/main/java/app/notifee/core/model/ChannelModel.java +8 -13
- package/android/src/main/java/app/notifee/core/model/IntervalTriggerModel.java +2 -6
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidActionModel.java +1 -2
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidModel.java +30 -33
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidPressActionModel.java +3 -9
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidStyleModel.java +6 -4
- package/android/src/main/java/app/notifee/core/model/TimestampTriggerModel.java +7 -4
- package/android/src/main/java/app/notifee/core/utility/BundleValueReader.java +65 -0
- package/android/src/main/java/app/notifee/core/utility/IntentUtils.java +0 -28
- package/android/src/main/java/app/notifee/core/utility/ObjectUtils.java +1 -1
- package/android/src/main/java/app/notifee/core/utility/ParcelableCompatReader.java +64 -0
- package/android/src/main/java/app/notifee/core/utility/PowerManagerUtils.java +79 -41
- package/android/src/main/java/app/notifee/core/utility/ResourceUtils.java +3 -0
- package/android/src/main/kotlin/io/invertase/notifee/NotifeeApiModule.kt +1 -1
- package/android/src/main/kotlin/io/invertase/notifee/NotifeeInitProvider.kt +0 -26
- package/android/src/main/kotlin/io/invertase/notifee/NotifeeReactUtils.kt +1 -2
- package/android/src/test/java/app/notifee/core/NotifeeAlarmManagerCurrentBehaviorTest.java +446 -0
- package/android/src/test/java/app/notifee/core/NotificationManagerCreateTriggerNotificationCurrentBehaviorTest.java +203 -0
- package/android/src/test/java/app/notifee/core/NotificationManagerPressActionOptOutTest.java +180 -0
- package/android/src/test/java/app/notifee/core/NotificationReceiverHandlerInitialNotificationTest.java +157 -0
- package/android/src/test/java/app/notifee/core/ReceiverServiceInitialNotificationTest.java +169 -0
- package/android/src/test/java/app/notifee/core/model/ChannelModelTest.java +116 -0
- package/android/src/test/java/app/notifee/core/model/IntervalTriggerModelTest.java +71 -0
- package/android/src/test/java/app/notifee/core/model/NotificationAndroidModelTest.java +309 -0
- package/android/src/test/java/app/notifee/core/model/NotificationAndroidStyleModelTest.java +154 -0
- package/android/src/test/java/app/notifee/core/model/TimestampTriggerModelTest.java +296 -14
- package/android/src/test/java/app/notifee/core/utility/BundleValueReaderTest.java +131 -0
- package/android/src/test/java/app/notifee/core/utility/ObjectUtilsTest.java +142 -0
- package/android/src/test/java/app/notifee/core/utility/ParcelableCompatReaderTest.java +124 -0
- package/android/src/test/java/io/invertase/notifee/HeadlessTaskReactHostTest.java +136 -0
- package/android/src/test/java/io/invertase/notifee/NotifeeEventSubscriberRoutingTest.java +262 -0
- package/android/src/test/java/io/invertase/notifee/NotifeeReactUtilsDrawerTest.java +24 -0
- package/cli/dist/lib/patchPodfile.d.ts +3 -1
- package/cli/dist/lib/patchPodfile.js +125 -33
- package/cli/dist/lib/patchPodfile.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/types/Module.d.ts +20 -6
- package/dist/types/Notification.d.ts +5 -2
- package/dist/types/Notification.js +5 -2
- package/dist/types/Notification.js.map +1 -1
- package/dist/types/NotificationAndroid.d.ts +4 -0
- package/dist/types/NotificationAndroid.js +4 -0
- package/dist/types/NotificationAndroid.js.map +1 -1
- package/dist/types/PowerManagerInfo.d.ts +9 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/ios/NotifeeCore/NotifeeCore.m +101 -114
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/types/Module.ts +20 -6
- package/src/types/Notification.ts +5 -2
- package/src/types/NotificationAndroid.ts +4 -0
- package/src/types/PowerManagerInfo.ts +9 -4
- package/src/version.ts +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
package app.notifee.core.utility;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.assertEquals;
|
|
4
|
+
import static org.junit.Assert.assertNotNull;
|
|
5
|
+
import static org.junit.Assert.assertNull;
|
|
6
|
+
import static org.junit.Assert.assertThrows;
|
|
7
|
+
|
|
8
|
+
import android.app.Notification;
|
|
9
|
+
import android.os.Bundle;
|
|
10
|
+
import java.util.ArrayList;
|
|
11
|
+
import org.junit.Test;
|
|
12
|
+
import org.junit.runner.RunWith;
|
|
13
|
+
import org.robolectric.RobolectricTestRunner;
|
|
14
|
+
import org.robolectric.RuntimeEnvironment;
|
|
15
|
+
import org.robolectric.annotation.Config;
|
|
16
|
+
|
|
17
|
+
@RunWith(RobolectricTestRunner.class)
|
|
18
|
+
public class ParcelableCompatReaderTest {
|
|
19
|
+
|
|
20
|
+
@Test
|
|
21
|
+
@Config(sdk = 32)
|
|
22
|
+
public void getParcelable_legacyPath_returnsNotificationAndNullWhenMissing() {
|
|
23
|
+
Bundle bundle = new Bundle();
|
|
24
|
+
Notification notification = createNotification();
|
|
25
|
+
bundle.putParcelable("notification", notification);
|
|
26
|
+
bundle.putParcelable("explicitNull", (Notification) null);
|
|
27
|
+
|
|
28
|
+
assertEquals(
|
|
29
|
+
notification,
|
|
30
|
+
ParcelableCompatReader.getParcelable(bundle, "notification", Notification.class));
|
|
31
|
+
assertNull(ParcelableCompatReader.getParcelable(bundle, "missing", Notification.class));
|
|
32
|
+
assertNull(ParcelableCompatReader.getParcelable(bundle, "explicitNull", Notification.class));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
@Config(sdk = 33)
|
|
37
|
+
public void getParcelable_typedPath_returnsNotificationAndNullWhenMissing() {
|
|
38
|
+
Bundle bundle = new Bundle();
|
|
39
|
+
Notification notification = createNotification();
|
|
40
|
+
bundle.putParcelable("notification", notification);
|
|
41
|
+
bundle.putParcelable("explicitNull", (Notification) null);
|
|
42
|
+
|
|
43
|
+
assertEquals(
|
|
44
|
+
notification,
|
|
45
|
+
ParcelableCompatReader.getParcelable(bundle, "notification", Notification.class));
|
|
46
|
+
assertNull(ParcelableCompatReader.getParcelable(bundle, "missing", Notification.class));
|
|
47
|
+
assertNull(ParcelableCompatReader.getParcelable(bundle, "explicitNull", Notification.class));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Test
|
|
51
|
+
@Config(sdk = 32)
|
|
52
|
+
public void getParcelable_legacyPath_preservesWrongClassCast() {
|
|
53
|
+
Bundle bundle = new Bundle();
|
|
54
|
+
bundle.putParcelable("notification", createNotification());
|
|
55
|
+
|
|
56
|
+
assertThrows(
|
|
57
|
+
ClassCastException.class,
|
|
58
|
+
() -> {
|
|
59
|
+
Bundle ignored =
|
|
60
|
+
ParcelableCompatReader.getParcelable(bundle, "notification", Bundle.class);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Test
|
|
65
|
+
@Config(sdk = 33)
|
|
66
|
+
public void getParcelable_typedPath_returnsNullForWrongClass() {
|
|
67
|
+
Bundle bundle = new Bundle();
|
|
68
|
+
bundle.putParcelable("notification", createNotification());
|
|
69
|
+
|
|
70
|
+
assertNull(ParcelableCompatReader.getParcelable(bundle, "notification", Bundle.class));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Test
|
|
74
|
+
@Config(sdk = 32)
|
|
75
|
+
public void getParcelableArrayList_legacyPath_returnsBundleList() {
|
|
76
|
+
Bundle bundle = new Bundle();
|
|
77
|
+
ArrayList<Bundle> bundles = createBundleList();
|
|
78
|
+
bundle.putParcelableArrayList("bundles", bundles);
|
|
79
|
+
bundle.putParcelableArrayList("explicitNull", null);
|
|
80
|
+
|
|
81
|
+
ArrayList<Bundle> result =
|
|
82
|
+
ParcelableCompatReader.getParcelableArrayList(bundle, "bundles", Bundle.class);
|
|
83
|
+
|
|
84
|
+
assertNotNull(result);
|
|
85
|
+
assertEquals(1, result.size());
|
|
86
|
+
assertEquals("first", result.get(0).getString("name"));
|
|
87
|
+
assertNull(ParcelableCompatReader.getParcelableArrayList(bundle, "missing", Bundle.class));
|
|
88
|
+
assertNull(ParcelableCompatReader.getParcelableArrayList(bundle, "explicitNull", Bundle.class));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Test
|
|
92
|
+
@Config(sdk = 33)
|
|
93
|
+
public void getParcelableArrayList_typedPath_returnsBundleList() {
|
|
94
|
+
Bundle bundle = new Bundle();
|
|
95
|
+
ArrayList<Bundle> bundles = createBundleList();
|
|
96
|
+
bundle.putParcelableArrayList("bundles", bundles);
|
|
97
|
+
bundle.putParcelableArrayList("explicitNull", null);
|
|
98
|
+
|
|
99
|
+
ArrayList<Bundle> result =
|
|
100
|
+
ParcelableCompatReader.getParcelableArrayList(bundle, "bundles", Bundle.class);
|
|
101
|
+
|
|
102
|
+
assertNotNull(result);
|
|
103
|
+
assertEquals(1, result.size());
|
|
104
|
+
assertEquals("first", result.get(0).getString("name"));
|
|
105
|
+
assertNull(ParcelableCompatReader.getParcelableArrayList(bundle, "missing", Bundle.class));
|
|
106
|
+
assertNull(ParcelableCompatReader.getParcelableArrayList(bundle, "explicitNull", Bundle.class));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private static Notification createNotification() {
|
|
110
|
+
return new Notification.Builder(RuntimeEnvironment.getApplication(), "reader-test")
|
|
111
|
+
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
|
112
|
+
.setContentTitle("reader-test")
|
|
113
|
+
.build();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private static ArrayList<Bundle> createBundleList() {
|
|
117
|
+
Bundle nested = new Bundle();
|
|
118
|
+
nested.putString("name", "first");
|
|
119
|
+
|
|
120
|
+
ArrayList<Bundle> bundles = new ArrayList<>();
|
|
121
|
+
bundles.add(nested);
|
|
122
|
+
return bundles;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
package io.invertase.notifee;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.assertEquals;
|
|
4
|
+
import static org.mockito.ArgumentMatchers.any;
|
|
5
|
+
import static org.mockito.ArgumentMatchers.anyInt;
|
|
6
|
+
import static org.mockito.ArgumentMatchers.eq;
|
|
7
|
+
import static org.mockito.ArgumentMatchers.same;
|
|
8
|
+
import static org.mockito.Mockito.mock;
|
|
9
|
+
import static org.mockito.Mockito.never;
|
|
10
|
+
import static org.mockito.Mockito.verify;
|
|
11
|
+
import static org.mockito.Mockito.when;
|
|
12
|
+
|
|
13
|
+
import android.app.Application;
|
|
14
|
+
import android.os.Looper;
|
|
15
|
+
import app.notifee.core.ContextHolder;
|
|
16
|
+
import com.facebook.react.ReactInstanceEventListener;
|
|
17
|
+
import com.facebook.react.bridge.JavaOnlyMap;
|
|
18
|
+
import com.facebook.react.bridge.ReactContext;
|
|
19
|
+
import com.facebook.react.bridge.WritableMap;
|
|
20
|
+
import com.facebook.react.common.LifecycleState;
|
|
21
|
+
import com.facebook.react.modules.appregistry.AppRegistry;
|
|
22
|
+
import java.util.ArrayList;
|
|
23
|
+
import java.util.List;
|
|
24
|
+
import org.junit.Before;
|
|
25
|
+
import org.junit.Test;
|
|
26
|
+
import org.junit.runner.RunWith;
|
|
27
|
+
import org.mockito.ArgumentCaptor;
|
|
28
|
+
import org.robolectric.RuntimeEnvironment;
|
|
29
|
+
import org.robolectric.Shadows;
|
|
30
|
+
import org.robolectric.RobolectricTestRunner;
|
|
31
|
+
import org.robolectric.annotation.Config;
|
|
32
|
+
import org.robolectric.annotation.LooperMode;
|
|
33
|
+
|
|
34
|
+
@RunWith(RobolectricTestRunner.class)
|
|
35
|
+
@Config(application = HeadlessTaskReactHostTest.TestApplication.class, sdk = 34)
|
|
36
|
+
@LooperMode(LooperMode.Mode.PAUSED)
|
|
37
|
+
public class HeadlessTaskReactHostTest {
|
|
38
|
+
|
|
39
|
+
private TestApplication application;
|
|
40
|
+
|
|
41
|
+
@Before
|
|
42
|
+
public void setUp() {
|
|
43
|
+
application = (TestApplication) RuntimeEnvironment.getApplication();
|
|
44
|
+
application.reactHost.reset();
|
|
45
|
+
ContextHolder.setApplicationContext(application);
|
|
46
|
+
Shadows.shadowOf(Looper.getMainLooper()).idle();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Test
|
|
50
|
+
public void startTask_whenReactContextInitializes_drainsQueuedTaskAfterCurrentDelay() {
|
|
51
|
+
JavaOnlyMap params = new JavaOnlyMap();
|
|
52
|
+
params.putString("source", "react-host-test");
|
|
53
|
+
|
|
54
|
+
HeadlessTask headlessTask = new HeadlessTask();
|
|
55
|
+
HeadlessTask.TaskConfig taskConfig =
|
|
56
|
+
new HeadlessTask.TaskConfig("test-headless-task", 60000L, params, null);
|
|
57
|
+
ReactContext reactContext = mock(ReactContext.class);
|
|
58
|
+
AppRegistry appRegistry = mock(AppRegistry.class);
|
|
59
|
+
when(reactContext.getLifecycleState()).thenReturn(LifecycleState.BEFORE_RESUME);
|
|
60
|
+
when(reactContext.hasActiveReactInstance()).thenReturn(true);
|
|
61
|
+
when(reactContext.getJSModule(AppRegistry.class)).thenReturn(appRegistry);
|
|
62
|
+
|
|
63
|
+
WritableMap copiedParams = taskConfig.getTaskConfig().getData();
|
|
64
|
+
|
|
65
|
+
headlessTask.startTask(application, taskConfig);
|
|
66
|
+
|
|
67
|
+
assertEquals(
|
|
68
|
+
"ReactHost should be started through reflection", 1, application.reactHost.startCalls);
|
|
69
|
+
assertEquals(
|
|
70
|
+
"ReactContext listener should be registered through reflection",
|
|
71
|
+
1,
|
|
72
|
+
application.reactHost.listeners.size());
|
|
73
|
+
verify(appRegistry, never())
|
|
74
|
+
.startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
|
|
75
|
+
|
|
76
|
+
ReactInstanceEventListener listener = application.reactHost.listeners.get(0);
|
|
77
|
+
listener.onReactContextInitialized(reactContext);
|
|
78
|
+
|
|
79
|
+
assertEquals(
|
|
80
|
+
"ReactContext listener should be removed after initialization",
|
|
81
|
+
0,
|
|
82
|
+
application.reactHost.listeners.size());
|
|
83
|
+
Shadows.shadowOf(Looper.getMainLooper()).idleFor(499, java.util.concurrent.TimeUnit.MILLISECONDS);
|
|
84
|
+
verify(appRegistry, never()).startHeadlessTask(anyInt(), any(String.class), any(WritableMap.class));
|
|
85
|
+
|
|
86
|
+
Shadows.shadowOf(Looper.getMainLooper()).idleFor(1, java.util.concurrent.TimeUnit.MILLISECONDS);
|
|
87
|
+
|
|
88
|
+
ArgumentCaptor<Integer> taskIdCaptor = ArgumentCaptor.forClass(Integer.class);
|
|
89
|
+
verify(appRegistry)
|
|
90
|
+
.startHeadlessTask(
|
|
91
|
+
taskIdCaptor.capture(), eq("test-headless-task"), same(copiedParams));
|
|
92
|
+
assertEquals(taskIdCaptor.getValue().intValue(), taskConfig.getReactTaskId());
|
|
93
|
+
assertEquals(
|
|
94
|
+
"source payload should be copied into the queued task",
|
|
95
|
+
"react-host-test",
|
|
96
|
+
copiedParams.getString("source"));
|
|
97
|
+
assertEquals(
|
|
98
|
+
"copied params should receive the native task id",
|
|
99
|
+
taskConfig.getTaskId(),
|
|
100
|
+
copiedParams.getInt("taskId"));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public static class TestApplication extends Application {
|
|
104
|
+
final TestReactHost reactHost = new TestReactHost();
|
|
105
|
+
|
|
106
|
+
public TestReactHost getReactHost() {
|
|
107
|
+
return reactHost;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public static class TestReactHost {
|
|
112
|
+
int startCalls;
|
|
113
|
+
final List<ReactInstanceEventListener> listeners = new ArrayList<>();
|
|
114
|
+
|
|
115
|
+
public ReactContext getCurrentReactContext() {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public void addReactInstanceEventListener(ReactInstanceEventListener listener) {
|
|
120
|
+
listeners.add(listener);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public void removeReactInstanceEventListener(ReactInstanceEventListener listener) {
|
|
124
|
+
listeners.remove(listener);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public void start() {
|
|
128
|
+
startCalls++;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
void reset() {
|
|
132
|
+
startCalls = 0;
|
|
133
|
+
listeners.clear();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
package io.invertase.notifee;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.assertEquals;
|
|
4
|
+
import static org.junit.Assert.assertSame;
|
|
5
|
+
import static org.mockito.ArgumentMatchers.any;
|
|
6
|
+
import static org.mockito.Mockito.mock;
|
|
7
|
+
import static org.mockito.Mockito.mockStatic;
|
|
8
|
+
import static org.mockito.Mockito.never;
|
|
9
|
+
import static org.mockito.Mockito.verify;
|
|
10
|
+
import static org.mockito.Mockito.when;
|
|
11
|
+
|
|
12
|
+
import android.app.Application;
|
|
13
|
+
import android.os.Bundle;
|
|
14
|
+
import android.os.Looper;
|
|
15
|
+
import androidx.lifecycle.ProcessLifecycleOwner;
|
|
16
|
+
import app.notifee.core.ContextHolder;
|
|
17
|
+
import app.notifee.core.event.NotificationEvent;
|
|
18
|
+
import app.notifee.core.model.NotificationModel;
|
|
19
|
+
import com.facebook.react.ReactInstanceEventListener;
|
|
20
|
+
import com.facebook.react.bridge.Arguments;
|
|
21
|
+
import com.facebook.react.bridge.ReactContext;
|
|
22
|
+
import com.facebook.react.bridge.WritableMap;
|
|
23
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
24
|
+
import java.lang.reflect.Field;
|
|
25
|
+
import java.util.ArrayList;
|
|
26
|
+
import java.util.List;
|
|
27
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
28
|
+
import org.junit.After;
|
|
29
|
+
import org.junit.Before;
|
|
30
|
+
import org.junit.Test;
|
|
31
|
+
import org.junit.runner.RunWith;
|
|
32
|
+
import org.mockito.MockedStatic;
|
|
33
|
+
import org.robolectric.RuntimeEnvironment;
|
|
34
|
+
import org.robolectric.Shadows;
|
|
35
|
+
import org.robolectric.RobolectricTestRunner;
|
|
36
|
+
import org.robolectric.annotation.Config;
|
|
37
|
+
|
|
38
|
+
@RunWith(RobolectricTestRunner.class)
|
|
39
|
+
@Config(application = NotifeeEventSubscriberRoutingTest.TestApplication.class, sdk = 34)
|
|
40
|
+
public class NotifeeEventSubscriberRoutingTest {
|
|
41
|
+
|
|
42
|
+
private TestApplication application;
|
|
43
|
+
private HeadlessTask headlessTask;
|
|
44
|
+
|
|
45
|
+
@Before
|
|
46
|
+
public void setUp() throws Exception {
|
|
47
|
+
application = (TestApplication) RuntimeEnvironment.getApplication();
|
|
48
|
+
application.reactHost.reset();
|
|
49
|
+
ContextHolder.setApplicationContext(application);
|
|
50
|
+
headlessTask = NotifeeReactUtils.INSTANCE.getHeadlessTaskManager();
|
|
51
|
+
resetHeadlessTask(headlessTask);
|
|
52
|
+
moveProcessToBackground();
|
|
53
|
+
Shadows.shadowOf(Looper.getMainLooper()).idle();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@After
|
|
57
|
+
public void tearDown() throws Exception {
|
|
58
|
+
moveProcessToBackground();
|
|
59
|
+
resetHeadlessTask(headlessTask);
|
|
60
|
+
application.reactHost.reset();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Test
|
|
64
|
+
public void onNotificationEvent_typePressWhenResumed_routesToForegroundEvent() throws Exception {
|
|
65
|
+
moveProcessToForeground();
|
|
66
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
67
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
68
|
+
ReactContext reactContext = mock(ReactContext.class);
|
|
69
|
+
when(reactContext.hasActiveReactInstance()).thenReturn(true);
|
|
70
|
+
when(reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
71
|
+
.thenReturn(emitter);
|
|
72
|
+
application.reactHost.currentReactContext = reactContext;
|
|
73
|
+
|
|
74
|
+
WritableMap eventMap = mock(WritableMap.class);
|
|
75
|
+
WritableMap detailMap = mock(WritableMap.class);
|
|
76
|
+
WritableMap notificationMap = mock(WritableMap.class);
|
|
77
|
+
WritableMap pressActionMap = mock(WritableMap.class);
|
|
78
|
+
|
|
79
|
+
try (MockedStatic<Arguments> arguments = mockArguments(
|
|
80
|
+
eventMap, detailMap, notificationMap, pressActionMap)) {
|
|
81
|
+
new NotifeeEventSubscriber().onNotificationEvent(buildPressEvent("routing-foreground"));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
verify(eventMap).putInt("type", NotificationEvent.TYPE_PRESS);
|
|
85
|
+
verify(detailMap).putMap("notification", notificationMap);
|
|
86
|
+
verify(detailMap).putMap("pressAction", pressActionMap);
|
|
87
|
+
verify(detailMap).putString("input", "typed reply");
|
|
88
|
+
verify(eventMap).putMap("detail", detailMap);
|
|
89
|
+
verify(eventMap).putBoolean("headless", false);
|
|
90
|
+
verify(emitter).emit(NotifeeEventSubscriber.NOTIFICATION_EVENT_KEY, eventMap);
|
|
91
|
+
verify(eventMap, never()).copy();
|
|
92
|
+
assertEquals("foreground routing must not queue a headless task", 0, taskQueueSize());
|
|
93
|
+
assertEquals("foreground routing must not start ReactHost", 0, application.reactHost.startCalls);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@Test
|
|
97
|
+
public void onNotificationEvent_typePressWhenNotResumed_routesToHeadlessTask() throws Exception {
|
|
98
|
+
moveProcessToBackground();
|
|
99
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
100
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
101
|
+
ReactContext reactContext = mock(ReactContext.class);
|
|
102
|
+
when(reactContext.hasActiveReactInstance()).thenReturn(true);
|
|
103
|
+
when(reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
104
|
+
.thenReturn(emitter);
|
|
105
|
+
application.reactHost.currentReactContext = null;
|
|
106
|
+
|
|
107
|
+
WritableMap eventMap = mock(WritableMap.class);
|
|
108
|
+
WritableMap queuedEventMap = mock(WritableMap.class);
|
|
109
|
+
WritableMap detailMap = mock(WritableMap.class);
|
|
110
|
+
WritableMap notificationMap = mock(WritableMap.class);
|
|
111
|
+
WritableMap pressActionMap = mock(WritableMap.class);
|
|
112
|
+
when(eventMap.copy()).thenReturn(queuedEventMap);
|
|
113
|
+
|
|
114
|
+
try (MockedStatic<Arguments> arguments = mockArguments(
|
|
115
|
+
eventMap, detailMap, notificationMap, pressActionMap)) {
|
|
116
|
+
new NotifeeEventSubscriber().onNotificationEvent(buildPressEvent("routing-background"));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
verify(eventMap).putInt("type", NotificationEvent.TYPE_PRESS);
|
|
120
|
+
verify(detailMap).putMap("notification", notificationMap);
|
|
121
|
+
verify(detailMap).putMap("pressAction", pressActionMap);
|
|
122
|
+
verify(detailMap).putString("input", "typed reply");
|
|
123
|
+
verify(eventMap).putMap("detail", detailMap);
|
|
124
|
+
verify(eventMap).putBoolean("headless", true);
|
|
125
|
+
verify(eventMap).copy();
|
|
126
|
+
verify(queuedEventMap).putInt("taskId", headlessTaskQueue().get(0).getTaskId());
|
|
127
|
+
verify(emitter, never()).emit(any(String.class), any(WritableMap.class));
|
|
128
|
+
|
|
129
|
+
assertEquals("background routing should queue exactly one headless task", 1, taskQueueSize());
|
|
130
|
+
HeadlessTask.TaskConfig queuedTask = headlessTaskQueue().get(0);
|
|
131
|
+
assertEquals(NotifeeEventSubscriber.NOTIFICATION_EVENT_KEY, queuedTask.getTaskConfig().getTaskKey());
|
|
132
|
+
assertEquals(60000L, queuedTask.getTaskConfig().getTimeout());
|
|
133
|
+
assertSame(queuedEventMap, queuedTask.getTaskConfig().getData());
|
|
134
|
+
assertEquals("background routing should initialize ReactHost via reflection", 1, application.reactHost.startCalls);
|
|
135
|
+
assertEquals("background routing should register one ReactContext listener", 1, application.reactHost.listeners.size());
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static MockedStatic<Arguments> mockArguments(
|
|
139
|
+
WritableMap eventMap,
|
|
140
|
+
WritableMap detailMap,
|
|
141
|
+
WritableMap notificationMap,
|
|
142
|
+
WritableMap pressActionMap) {
|
|
143
|
+
MockedStatic<Arguments> arguments = mockStatic(Arguments.class);
|
|
144
|
+
arguments.when(Arguments::createMap).thenReturn(eventMap, detailMap);
|
|
145
|
+
arguments.when(() -> Arguments.fromBundle(any(Bundle.class)))
|
|
146
|
+
.thenReturn(notificationMap, pressActionMap);
|
|
147
|
+
return arguments;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private static NotificationEvent buildPressEvent(String notificationId) {
|
|
151
|
+
Bundle notificationBundle = new Bundle();
|
|
152
|
+
notificationBundle.putString("id", notificationId);
|
|
153
|
+
notificationBundle.putString("title", "Routing test " + notificationId);
|
|
154
|
+
Bundle androidBundle = new Bundle();
|
|
155
|
+
androidBundle.putString("channelId", "routing-test-channel");
|
|
156
|
+
notificationBundle.putBundle("android", androidBundle);
|
|
157
|
+
|
|
158
|
+
Bundle pressActionBundle = new Bundle();
|
|
159
|
+
pressActionBundle.putString("id", "default");
|
|
160
|
+
pressActionBundle.putString("launchActivity", "default");
|
|
161
|
+
|
|
162
|
+
Bundle extras = new Bundle();
|
|
163
|
+
extras.putBundle("pressAction", pressActionBundle);
|
|
164
|
+
extras.putString("input", "typed reply");
|
|
165
|
+
|
|
166
|
+
return new NotificationEvent(
|
|
167
|
+
NotificationEvent.TYPE_PRESS, NotificationModel.fromBundle(notificationBundle), extras);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private static void moveProcessToForeground() {
|
|
171
|
+
ProcessLifecycleOwner owner = (ProcessLifecycleOwner) ProcessLifecycleOwner.get();
|
|
172
|
+
owner.activityStarted$lifecycle_process_release();
|
|
173
|
+
owner.activityResumed$lifecycle_process_release();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private static void moveProcessToBackground() throws Exception {
|
|
177
|
+
ProcessLifecycleOwner.init$lifecycle_process_release(RuntimeEnvironment.getApplication());
|
|
178
|
+
ProcessLifecycleOwner owner = (ProcessLifecycleOwner) ProcessLifecycleOwner.get();
|
|
179
|
+
int resumedCounter = getIntField(owner, "resumedCounter");
|
|
180
|
+
for (int i = 0; i < resumedCounter; i++) {
|
|
181
|
+
owner.activityPaused$lifecycle_process_release();
|
|
182
|
+
}
|
|
183
|
+
owner.dispatchPauseIfNeeded$lifecycle_process_release();
|
|
184
|
+
|
|
185
|
+
int startedCounter = getIntField(owner, "startedCounter");
|
|
186
|
+
for (int i = 0; i < startedCounter; i++) {
|
|
187
|
+
owner.activityStopped$lifecycle_process_release();
|
|
188
|
+
}
|
|
189
|
+
owner.dispatchStopIfNeeded$lifecycle_process_release();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private static int getIntField(Object target, String name) throws Exception {
|
|
193
|
+
Field field = target.getClass().getDeclaredField(name);
|
|
194
|
+
field.setAccessible(true);
|
|
195
|
+
return field.getInt(target);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private int taskQueueSize() throws Exception {
|
|
199
|
+
return headlessTaskQueue().size();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@SuppressWarnings("unchecked")
|
|
203
|
+
private List<HeadlessTask.TaskConfig> headlessTaskQueue() throws Exception {
|
|
204
|
+
Field queueField = HeadlessTask.class.getDeclaredField("mTaskQueue");
|
|
205
|
+
queueField.setAccessible(true);
|
|
206
|
+
return (List<HeadlessTask.TaskConfig>) queueField.get(headlessTask);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private static void resetHeadlessTask(HeadlessTask task) throws Exception {
|
|
210
|
+
Field queueField = HeadlessTask.class.getDeclaredField("mTaskQueue");
|
|
211
|
+
queueField.setAccessible(true);
|
|
212
|
+
((List<?>) queueField.get(task)).clear();
|
|
213
|
+
|
|
214
|
+
setAtomicBoolean(task, "mIsReactContextInitialized", false);
|
|
215
|
+
setAtomicBoolean(task, "mWillDrainTaskQueue", false);
|
|
216
|
+
setAtomicBoolean(task, "mIsInitializingReactContext", false);
|
|
217
|
+
setAtomicBoolean(task, "mIsHeadlessJsTaskListenerRegistered", false);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private static void setAtomicBoolean(HeadlessTask task, String name, boolean value)
|
|
221
|
+
throws Exception {
|
|
222
|
+
Field field = HeadlessTask.class.getDeclaredField(name);
|
|
223
|
+
field.setAccessible(true);
|
|
224
|
+
((AtomicBoolean) field.get(task)).set(value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
public static class TestApplication extends Application {
|
|
228
|
+
final TestReactHost reactHost = new TestReactHost();
|
|
229
|
+
|
|
230
|
+
public TestReactHost getReactHost() {
|
|
231
|
+
return reactHost;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public static class TestReactHost {
|
|
236
|
+
ReactContext currentReactContext;
|
|
237
|
+
int startCalls;
|
|
238
|
+
final List<ReactInstanceEventListener> listeners = new ArrayList<>();
|
|
239
|
+
|
|
240
|
+
public ReactContext getCurrentReactContext() {
|
|
241
|
+
return currentReactContext;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public void addReactInstanceEventListener(ReactInstanceEventListener listener) {
|
|
245
|
+
listeners.add(listener);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public void removeReactInstanceEventListener(ReactInstanceEventListener listener) {
|
|
249
|
+
listeners.remove(listener);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public void start() {
|
|
253
|
+
startCalls++;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
void reset() {
|
|
257
|
+
currentReactContext = null;
|
|
258
|
+
startCalls = 0;
|
|
259
|
+
listeners.clear();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package io.invertase.notifee;
|
|
2
|
+
|
|
3
|
+
import app.notifee.core.ContextHolder;
|
|
4
|
+
import org.junit.Before;
|
|
5
|
+
import org.junit.Test;
|
|
6
|
+
import org.junit.runner.RunWith;
|
|
7
|
+
import org.robolectric.RuntimeEnvironment;
|
|
8
|
+
import org.robolectric.RobolectricTestRunner;
|
|
9
|
+
import org.robolectric.annotation.Config;
|
|
10
|
+
|
|
11
|
+
@RunWith(RobolectricTestRunner.class)
|
|
12
|
+
@Config(sdk = 34)
|
|
13
|
+
public class NotifeeReactUtilsDrawerTest {
|
|
14
|
+
|
|
15
|
+
@Before
|
|
16
|
+
public void setUp() {
|
|
17
|
+
ContextHolder.setApplicationContext(RuntimeEnvironment.getApplication());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Test
|
|
21
|
+
public void hideNotificationDrawer_whenStatusBarReflectionFails_doesNotCrash() {
|
|
22
|
+
NotifeeReactUtils.INSTANCE.hideNotificationDrawer();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Patches the Podfile to add the NSE target with RNNotifeeCore pod.
|
|
3
3
|
* The NSE target is nested inside the main app target so CocoaPods can
|
|
4
4
|
* detect the host→extension relationship. Uses `inherit! :search_paths`.
|
|
5
|
-
*
|
|
5
|
+
* Also installs a post_install hook that keeps React Native Firebase's
|
|
6
|
+
* generated Info.plist input path from recreating a host-extension build cycle.
|
|
7
|
+
* Idempotent: returns false when no Podfile changes are needed.
|
|
6
8
|
*/
|
|
7
9
|
export declare function patchPodfile(podfilePath: string, targetName: string, dryRun: boolean): boolean;
|
|
8
10
|
/**
|