react-native-notify-kit 10.4.8 → 10.4.9
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 +2 -0
- package/android/src/main/kotlin/io/invertase/notifee/NotifeeReactUtils.kt +679 -7
- package/android/src/test/java/io/invertase/notifee/NotifeeReactUtilsPendingEventsTest.java +1025 -0
- package/dist/NotifeeApiModule.js +4 -0
- package/dist/NotifeeApiModule.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/NotifeeApiModule.ts +5 -0
- package/src/version.ts +1 -1
- package/server/dist/android.d.ts +0 -8
- package/server/dist/android.d.ts.map +0 -1
- package/server/dist/android.js +0 -20
- package/server/dist/android.js.map +0 -1
- package/server/dist/buildPayload.d.ts +0 -3
- package/server/dist/buildPayload.d.ts.map +0 -1
- package/server/dist/buildPayload.js +0 -82
- package/server/dist/buildPayload.js.map +0 -1
- package/server/dist/index.d.ts +0 -6
- package/server/dist/index.d.ts.map +0 -1
- package/server/dist/index.js +0 -12
- package/server/dist/index.js.map +0 -1
- package/server/dist/ios.d.ts +0 -11
- package/server/dist/ios.d.ts.map +0 -1
- package/server/dist/ios.js +0 -61
- package/server/dist/ios.js.map +0 -1
- package/server/dist/serialize.d.ts +0 -11
- package/server/dist/serialize.d.ts.map +0 -1
- package/server/dist/serialize.js +0 -32
- package/server/dist/serialize.js.map +0 -1
- package/server/dist/types.d.ts +0 -2
- package/server/dist/types.d.ts.map +0 -1
- package/server/dist/types.js +0 -3
- package/server/dist/types.js.map +0 -1
- package/server/dist/validation.d.ts +0 -3
- package/server/dist/validation.d.ts.map +0 -1
- package/server/dist/validation.js +0 -101
- package/server/dist/validation.js.map +0 -1
|
@@ -0,0 +1,1025 @@
|
|
|
1
|
+
package io.invertase.notifee;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.assertEquals;
|
|
4
|
+
import static org.junit.Assert.assertFalse;
|
|
5
|
+
import static org.junit.Assert.assertNotNull;
|
|
6
|
+
import static org.junit.Assert.assertNull;
|
|
7
|
+
import static org.junit.Assert.assertSame;
|
|
8
|
+
import static org.junit.Assert.assertTrue;
|
|
9
|
+
import static org.mockito.ArgumentMatchers.any;
|
|
10
|
+
import static org.mockito.ArgumentMatchers.eq;
|
|
11
|
+
import static org.mockito.ArgumentMatchers.same;
|
|
12
|
+
import static org.mockito.Mockito.doThrow;
|
|
13
|
+
import static org.mockito.Mockito.mock;
|
|
14
|
+
import static org.mockito.Mockito.never;
|
|
15
|
+
import static org.mockito.Mockito.times;
|
|
16
|
+
import static org.mockito.Mockito.verify;
|
|
17
|
+
import static org.mockito.Mockito.when;
|
|
18
|
+
|
|
19
|
+
import android.app.Application;
|
|
20
|
+
import android.content.Context;
|
|
21
|
+
import android.content.pm.ApplicationInfo;
|
|
22
|
+
import android.content.pm.PackageManager;
|
|
23
|
+
import android.os.Bundle;
|
|
24
|
+
import app.notifee.core.ContextHolder;
|
|
25
|
+
import app.notifee.core.event.NotificationEvent;
|
|
26
|
+
import com.facebook.react.bridge.JavaOnlyMap;
|
|
27
|
+
import com.facebook.react.bridge.ReactContext;
|
|
28
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
29
|
+
import com.facebook.react.bridge.WritableMap;
|
|
30
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
31
|
+
import java.lang.reflect.Field;
|
|
32
|
+
import java.lang.reflect.Method;
|
|
33
|
+
import java.util.ArrayList;
|
|
34
|
+
import java.util.Arrays;
|
|
35
|
+
import java.util.List;
|
|
36
|
+
import java.util.concurrent.atomic.AtomicLong;
|
|
37
|
+
import kotlin.Pair;
|
|
38
|
+
import org.junit.After;
|
|
39
|
+
import org.junit.Before;
|
|
40
|
+
import org.junit.Test;
|
|
41
|
+
import org.junit.runner.RunWith;
|
|
42
|
+
import org.robolectric.RobolectricTestRunner;
|
|
43
|
+
import org.robolectric.RuntimeEnvironment;
|
|
44
|
+
import org.robolectric.annotation.Config;
|
|
45
|
+
import org.robolectric.shadows.ShadowLog;
|
|
46
|
+
|
|
47
|
+
@RunWith(RobolectricTestRunner.class)
|
|
48
|
+
@Config(application = NotifeeReactUtilsPendingEventsTest.TestApplication.class, sdk = 34)
|
|
49
|
+
public class NotifeeReactUtilsPendingEventsTest {
|
|
50
|
+
|
|
51
|
+
private static final String DIAGNOSTICS_TAG = "NOTIFEE_PENDING_DIAG";
|
|
52
|
+
private static final String NOTIFICATION_EVENT_NAME = "app.notifee.notification-event";
|
|
53
|
+
private static final String DIAGNOSTIC_SCENARIO_ID_KEY = "__notifeePendingDiagScenarioId";
|
|
54
|
+
private static final String DIAGNOSTIC_EVENT_ID_KEY = "__notifeePendingDiagEventId";
|
|
55
|
+
|
|
56
|
+
private TestApplication application;
|
|
57
|
+
|
|
58
|
+
@Before
|
|
59
|
+
public void setUp() throws Exception {
|
|
60
|
+
application = (TestApplication) RuntimeEnvironment.getApplication();
|
|
61
|
+
application.reactHost.reset();
|
|
62
|
+
ContextHolder.setApplicationContext(application);
|
|
63
|
+
resetState();
|
|
64
|
+
ShadowLog.clear();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@After
|
|
68
|
+
public void tearDown() throws Exception {
|
|
69
|
+
resetState();
|
|
70
|
+
ShadowLog.clear();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Test
|
|
74
|
+
public void sendEvent_whenContextUnavailable_enqueuesInFifoOrder() throws Exception {
|
|
75
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
76
|
+
WritableMap eventB = mock(WritableMap.class);
|
|
77
|
+
WritableMap eventC = mock(WritableMap.class);
|
|
78
|
+
|
|
79
|
+
setReactContexts(null, null, null);
|
|
80
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
81
|
+
NotifeeReactUtils.INSTANCE.sendEvent("B", eventB);
|
|
82
|
+
NotifeeReactUtils.INSTANCE.sendEvent("C", eventC);
|
|
83
|
+
|
|
84
|
+
List<?> queued = pendingEvents();
|
|
85
|
+
assertEquals(Arrays.asList("A", "B", "C"), pendingEventNames(queued));
|
|
86
|
+
assertSame(eventA, pendingEventBody(queued.get(0)));
|
|
87
|
+
assertSame(eventB, pendingEventBody(queued.get(1)));
|
|
88
|
+
assertSame(eventC, pendingEventBody(queued.get(2)));
|
|
89
|
+
assertEquals(Pair.class, queued.get(0).getClass());
|
|
90
|
+
assertNull(pendingDiagnosticsState());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Test
|
|
94
|
+
public void sendEvent_whenElevenEventsPending_dropsOldestAndKeepsTen() throws Exception {
|
|
95
|
+
List<String> names = Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K");
|
|
96
|
+
|
|
97
|
+
setReactContexts((ReactContext) null);
|
|
98
|
+
for (String name : names) {
|
|
99
|
+
NotifeeReactUtils.INSTANCE.sendEvent(name, mock(WritableMap.class));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
List<?> queued = pendingEvents();
|
|
103
|
+
assertEquals(10, queued.size());
|
|
104
|
+
assertEquals(names.subList(1, names.size()), pendingEventNames(queued));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@Test
|
|
108
|
+
public void snapshotAndClear_separatesBatchAndClearsCurrentQueue() throws Exception {
|
|
109
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
110
|
+
WritableMap eventB = mock(WritableMap.class);
|
|
111
|
+
WritableMap eventC = mock(WritableMap.class);
|
|
112
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
113
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
114
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
115
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
116
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
117
|
+
.thenReturn(emitter);
|
|
118
|
+
|
|
119
|
+
setReactContexts(null, null, null, activeContext);
|
|
120
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
121
|
+
NotifeeReactUtils.INSTANCE.sendEvent("B", eventB);
|
|
122
|
+
NotifeeReactUtils.INSTANCE.sendEvent("C", eventC);
|
|
123
|
+
|
|
124
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
125
|
+
|
|
126
|
+
assertEquals(0, pendingEvents().size());
|
|
127
|
+
org.mockito.InOrder emissionOrder = org.mockito.Mockito.inOrder(emitter);
|
|
128
|
+
emissionOrder.verify(emitter).emit("A", eventA);
|
|
129
|
+
emissionOrder.verify(emitter).emit("B", eventB);
|
|
130
|
+
emissionOrder.verify(emitter).emit("C", eventC);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Test
|
|
134
|
+
public void sendEvent_afterSnapshot_staysInCurrentQueueAndOutsidePreviousBatch()
|
|
135
|
+
throws Exception {
|
|
136
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
137
|
+
WritableMap eventB = mock(WritableMap.class);
|
|
138
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
139
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
140
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
141
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
142
|
+
.thenReturn(emitter);
|
|
143
|
+
setReactContexts((ReactContext) null);
|
|
144
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
145
|
+
|
|
146
|
+
setReactContexts(activeContext, null);
|
|
147
|
+
when(activeContext.hasActiveReactInstance())
|
|
148
|
+
.thenAnswer(
|
|
149
|
+
invocation -> {
|
|
150
|
+
NotifeeReactUtils.INSTANCE.sendEvent("B", eventB);
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
154
|
+
|
|
155
|
+
verify(emitter).emit("A", eventA);
|
|
156
|
+
assertEquals(Arrays.asList("B"), pendingEventNames(pendingEvents()));
|
|
157
|
+
assertSame(eventB, pendingEventBody(pendingEvents().get(0)));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@Test
|
|
161
|
+
public void flushPendingEvents_whenContextNull_requeuesEvent() throws Exception {
|
|
162
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
163
|
+
setReactContexts(null, null);
|
|
164
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
165
|
+
assertEquals(1, pendingEvents().size());
|
|
166
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
167
|
+
|
|
168
|
+
assertEquals(Arrays.asList("A"), pendingEventNames(pendingEvents()));
|
|
169
|
+
assertSame(eventA, pendingEventBody(pendingEvents().get(0)));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@Test
|
|
173
|
+
public void flushPendingEvents_whenContextInactive_requeuesEvent() throws Exception {
|
|
174
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
175
|
+
ReactContext inactiveContext = mock(ReactContext.class);
|
|
176
|
+
when(inactiveContext.hasActiveReactInstance()).thenReturn(false);
|
|
177
|
+
|
|
178
|
+
setReactContexts(null, inactiveContext);
|
|
179
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
180
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
181
|
+
|
|
182
|
+
assertEquals(Arrays.asList("A"), pendingEventNames(pendingEvents()));
|
|
183
|
+
assertSame(eventA, pendingEventBody(pendingEvents().get(0)));
|
|
184
|
+
verify(inactiveContext).hasActiveReactInstance();
|
|
185
|
+
verify(inactiveContext, never()).getJSModule(any());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@Test
|
|
189
|
+
public void flushPendingEvents_requeuedEventRecoversExactlyOnceOnNextFlush() throws Exception {
|
|
190
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
191
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
192
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
193
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
194
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
195
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
196
|
+
.thenReturn(emitter);
|
|
197
|
+
|
|
198
|
+
setReactContexts(null, null);
|
|
199
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
200
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
201
|
+
assertEquals(Arrays.asList("A"), pendingEventNames(pendingEvents()));
|
|
202
|
+
|
|
203
|
+
setReactContexts(activeContext);
|
|
204
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
205
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
206
|
+
|
|
207
|
+
assertTrue(pendingEvents().isEmpty());
|
|
208
|
+
verify(emitter, times(1)).emit("A", eventA);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@Test
|
|
212
|
+
public void flushPendingEvents_whenAllContextsUnavailable_preservesSnapshotFifo()
|
|
213
|
+
throws Exception {
|
|
214
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
215
|
+
WritableMap eventB = mock(WritableMap.class);
|
|
216
|
+
WritableMap eventC = mock(WritableMap.class);
|
|
217
|
+
|
|
218
|
+
setReactContexts(null, null, null);
|
|
219
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
220
|
+
NotifeeReactUtils.INSTANCE.sendEvent("B", eventB);
|
|
221
|
+
NotifeeReactUtils.INSTANCE.sendEvent("C", eventC);
|
|
222
|
+
setReactContexts((ReactContext) null);
|
|
223
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
224
|
+
|
|
225
|
+
assertEquals(Arrays.asList("A", "B", "C"), pendingEventNames(pendingEvents()));
|
|
226
|
+
assertSame(eventA, pendingEventBody(pendingEvents().get(0)));
|
|
227
|
+
assertSame(eventB, pendingEventBody(pendingEvents().get(1)));
|
|
228
|
+
assertSame(eventC, pendingEventBody(pendingEvents().get(2)));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@Test
|
|
232
|
+
public void flushPendingEvents_newArrivalDuringFlush_isMergedAfterRequeuedSnapshot()
|
|
233
|
+
throws Exception {
|
|
234
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
235
|
+
WritableMap eventB = mock(WritableMap.class);
|
|
236
|
+
WritableMap eventC = mock(WritableMap.class);
|
|
237
|
+
ReactContext inactiveContext = mock(ReactContext.class);
|
|
238
|
+
|
|
239
|
+
setReactContexts(null, null);
|
|
240
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
241
|
+
NotifeeReactUtils.INSTANCE.sendEvent("B", eventB);
|
|
242
|
+
setReactContexts(inactiveContext, null);
|
|
243
|
+
when(inactiveContext.hasActiveReactInstance())
|
|
244
|
+
.thenAnswer(
|
|
245
|
+
invocation -> {
|
|
246
|
+
NotifeeReactUtils.INSTANCE.sendEvent("C", eventC);
|
|
247
|
+
return false;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
251
|
+
|
|
252
|
+
assertEquals(Arrays.asList("A", "B", "C"), pendingEventNames(pendingEvents()));
|
|
253
|
+
assertSame(eventA, pendingEventBody(pendingEvents().get(0)));
|
|
254
|
+
assertSame(eventB, pendingEventBody(pendingEvents().get(1)));
|
|
255
|
+
assertSame(eventC, pendingEventBody(pendingEvents().get(2)));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@Test
|
|
259
|
+
public void flushPendingEvents_partialDelivery_requeuesOnlyUndeliveredEvents() throws Exception {
|
|
260
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
261
|
+
WritableMap eventB = mock(WritableMap.class);
|
|
262
|
+
WritableMap eventC = mock(WritableMap.class);
|
|
263
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
264
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
265
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
266
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
267
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
268
|
+
.thenReturn(emitter);
|
|
269
|
+
|
|
270
|
+
setReactContexts(null, null, null);
|
|
271
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
272
|
+
NotifeeReactUtils.INSTANCE.sendEvent("B", eventB);
|
|
273
|
+
NotifeeReactUtils.INSTANCE.sendEvent("C", eventC);
|
|
274
|
+
setReactContexts(activeContext, null);
|
|
275
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
276
|
+
|
|
277
|
+
verify(emitter, times(1)).emit("A", eventA);
|
|
278
|
+
verify(emitter, never()).emit("B", eventB);
|
|
279
|
+
verify(emitter, never()).emit("C", eventC);
|
|
280
|
+
assertEquals(Arrays.asList("B", "C"), pendingEventNames(pendingEvents()));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@Test
|
|
284
|
+
public void flushPendingEvents_requeueMergeOverCapacity_dropsGlobalOldestAndKeepsFifo()
|
|
285
|
+
throws Exception {
|
|
286
|
+
List<String> snapshotNames = Arrays.asList("A", "B", "C");
|
|
287
|
+
List<String> newNames = Arrays.asList("D", "E", "F", "G", "H", "I", "J", "K", "L");
|
|
288
|
+
ReactContext inactiveContext = mock(ReactContext.class);
|
|
289
|
+
|
|
290
|
+
setReactContexts((ReactContext) null);
|
|
291
|
+
for (String name : snapshotNames) {
|
|
292
|
+
NotifeeReactUtils.INSTANCE.sendEvent(name, mock(WritableMap.class));
|
|
293
|
+
}
|
|
294
|
+
setReactContexts(inactiveContext, null);
|
|
295
|
+
when(inactiveContext.hasActiveReactInstance())
|
|
296
|
+
.thenAnswer(
|
|
297
|
+
invocation -> {
|
|
298
|
+
for (String name : newNames) {
|
|
299
|
+
NotifeeReactUtils.INSTANCE.sendEvent(name, mock(WritableMap.class));
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
305
|
+
|
|
306
|
+
assertEquals(10, pendingEvents().size());
|
|
307
|
+
assertEquals(
|
|
308
|
+
Arrays.asList("C", "D", "E", "F", "G", "H", "I", "J", "K", "L"),
|
|
309
|
+
pendingEventNames(pendingEvents()));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@Test
|
|
313
|
+
public void sendEvent_directDuringFlush_bypassesRequeueAndCanPrecedeRecoveredEvent()
|
|
314
|
+
throws Exception {
|
|
315
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
316
|
+
WritableMap eventB = mock(WritableMap.class);
|
|
317
|
+
ReactContext inactiveContext = mock(ReactContext.class);
|
|
318
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
319
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
320
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
321
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
322
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
323
|
+
.thenReturn(emitter);
|
|
324
|
+
|
|
325
|
+
setReactContexts((ReactContext) null);
|
|
326
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
327
|
+
setReactContexts(inactiveContext, activeContext);
|
|
328
|
+
when(inactiveContext.hasActiveReactInstance())
|
|
329
|
+
.thenAnswer(
|
|
330
|
+
invocation -> {
|
|
331
|
+
NotifeeReactUtils.INSTANCE.sendEvent("B", eventB);
|
|
332
|
+
return false;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
336
|
+
assertEquals(Arrays.asList("A"), pendingEventNames(pendingEvents()));
|
|
337
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
338
|
+
|
|
339
|
+
assertTrue(pendingEvents().isEmpty());
|
|
340
|
+
org.mockito.InOrder emissionOrder = org.mockito.Mockito.inOrder(emitter);
|
|
341
|
+
emissionOrder.verify(emitter).emit("B", eventB);
|
|
342
|
+
emissionOrder.verify(emitter).emit("A", eventA);
|
|
343
|
+
verify(emitter, times(1)).emit("B", eventB);
|
|
344
|
+
verify(emitter, times(1)).emit("A", eventA);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
@Test
|
|
348
|
+
public void flushPendingEvents_whenFirstEmitThrows_doesNotRequeueAndAttemptsNext()
|
|
349
|
+
throws Exception {
|
|
350
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
351
|
+
WritableMap eventB = mock(WritableMap.class);
|
|
352
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
353
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
354
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
355
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
356
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
357
|
+
.thenReturn(emitter);
|
|
358
|
+
doThrow(new IllegalStateException("characterization emit failure"))
|
|
359
|
+
.when(emitter)
|
|
360
|
+
.emit(eq("A"), same(eventA));
|
|
361
|
+
|
|
362
|
+
setReactContexts(null, null, activeContext, activeContext);
|
|
363
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
364
|
+
NotifeeReactUtils.INSTANCE.sendEvent("B", eventB);
|
|
365
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
366
|
+
|
|
367
|
+
assertEquals(0, pendingEvents().size());
|
|
368
|
+
verify(emitter).emit("A", eventA);
|
|
369
|
+
verify(emitter).emit("B", eventB);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
@Test
|
|
373
|
+
public void diagnostics_withoutMetadata_areDisabledWithZeroDelayAndNoMarkers() throws Exception {
|
|
374
|
+
Object config = loadPendingDiagnosticsConfig(application);
|
|
375
|
+
assertFalse((boolean) privateField(config, "enabled"));
|
|
376
|
+
assertEquals(0L, ((Number) privateField(config, "delayAfterSnapshotMs")).longValue());
|
|
377
|
+
|
|
378
|
+
WritableMap eventA = mock(WritableMap.class);
|
|
379
|
+
setReactContexts(null, null);
|
|
380
|
+
int packageManagerRequestsBeforeEvent = application.packageManagerRequests;
|
|
381
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
382
|
+
List<?> queued = pendingEvents();
|
|
383
|
+
assertEquals(1, queued.size());
|
|
384
|
+
assertEquals(Pair.class, queued.get(0).getClass());
|
|
385
|
+
assertSame(eventA, pendingEventBody(queued.get(0)));
|
|
386
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
387
|
+
|
|
388
|
+
assertEquals(1, pendingEvents().size());
|
|
389
|
+
assertSame(eventA, pendingEventBody(pendingEvents().get(0)));
|
|
390
|
+
assertEquals(packageManagerRequestsBeforeEvent, application.packageManagerRequests);
|
|
391
|
+
assertNull(pendingDiagnosticsState());
|
|
392
|
+
assertTrue(ShadowLog.getLogsForTag(DIAGNOSTICS_TAG).isEmpty());
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@Test
|
|
396
|
+
public void diagnosticIdentity_validFixturePayload_extractsControlledIdentity() throws Exception {
|
|
397
|
+
JavaOnlyMap eventMap = diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A");
|
|
398
|
+
|
|
399
|
+
Object identity = extractPendingDiagnosticIdentity(eventMap);
|
|
400
|
+
|
|
401
|
+
assertNotNull(identity);
|
|
402
|
+
assertEquals("scenario-1", privateField(identity, "scenarioId"));
|
|
403
|
+
assertEquals("pending-diag:scenario-1:A", privateField(identity, "eventId"));
|
|
404
|
+
assertEquals(NotificationEvent.TYPE_DELIVERED, privateField(identity, "eventType"));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@Test
|
|
408
|
+
public void diagnosticIdentity_missingPayload_returnsUnavailableWithoutException()
|
|
409
|
+
throws Exception {
|
|
410
|
+
assertNull(extractPendingDiagnosticIdentity(new JavaOnlyMap()));
|
|
411
|
+
|
|
412
|
+
JavaOnlyMap eventMap = JavaOnlyMap.of("detail", JavaOnlyMap.of("notification", null));
|
|
413
|
+
assertNull(extractPendingDiagnosticIdentity(eventMap));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@Test
|
|
417
|
+
public void diagnosticIdentity_wrongFieldType_returnsUnavailableWithoutException()
|
|
418
|
+
throws Exception {
|
|
419
|
+
JavaOnlyMap eventMap = diagnosticEventMap("scenario-1", 7);
|
|
420
|
+
|
|
421
|
+
assertNull(extractPendingDiagnosticIdentity(eventMap));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
@Test
|
|
425
|
+
public void diagnosticIdentity_eventTypeMustBePresentNumericAndIntegerValued() throws Exception {
|
|
426
|
+
JavaOnlyMap missingType =
|
|
427
|
+
diagnosticEventMapWithoutType("scenario-1", "pending-diag:scenario-1:A");
|
|
428
|
+
JavaOnlyMap wrongType = diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A", "3");
|
|
429
|
+
JavaOnlyMap fractionalType =
|
|
430
|
+
diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A", 3.5d);
|
|
431
|
+
JavaOnlyMap outOfRangeType =
|
|
432
|
+
diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A", 2147483648d);
|
|
433
|
+
JavaOnlyMap integerValuedNumber =
|
|
434
|
+
diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A", 3.0d);
|
|
435
|
+
|
|
436
|
+
Object missingTypeIdentity = extractPendingDiagnosticIdentity(missingType);
|
|
437
|
+
Object wrongTypeIdentity = extractPendingDiagnosticIdentity(wrongType);
|
|
438
|
+
Object fractionalTypeIdentity = extractPendingDiagnosticIdentity(fractionalType);
|
|
439
|
+
Object outOfRangeTypeIdentity = extractPendingDiagnosticIdentity(outOfRangeType);
|
|
440
|
+
Object integerValuedIdentity = extractPendingDiagnosticIdentity(integerValuedNumber);
|
|
441
|
+
assertNotNull(missingTypeIdentity);
|
|
442
|
+
assertNotNull(wrongTypeIdentity);
|
|
443
|
+
assertNotNull(fractionalTypeIdentity);
|
|
444
|
+
assertNotNull(outOfRangeTypeIdentity);
|
|
445
|
+
assertNotNull(integerValuedIdentity);
|
|
446
|
+
assertNull(privateField(missingTypeIdentity, "eventType"));
|
|
447
|
+
assertNull(privateField(wrongTypeIdentity, "eventType"));
|
|
448
|
+
assertNull(privateField(fractionalTypeIdentity, "eventType"));
|
|
449
|
+
assertNull(privateField(outOfRangeTypeIdentity, "eventType"));
|
|
450
|
+
assertEquals(
|
|
451
|
+
NotificationEvent.TYPE_DELIVERED, privateField(integerValuedIdentity, "eventType"));
|
|
452
|
+
|
|
453
|
+
assertTrue(initializePendingDiagnostics(diagnosticsContext(true, true, 0).context));
|
|
454
|
+
setReactContexts((ReactContext) null);
|
|
455
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, missingType);
|
|
456
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, wrongType);
|
|
457
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, fractionalType);
|
|
458
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, outOfRangeType);
|
|
459
|
+
|
|
460
|
+
assertEquals(
|
|
461
|
+
Arrays.asList("unavailable", "unavailable", "unavailable", "unavailable"),
|
|
462
|
+
markerFieldValues("EVENT_RECEIVED", "eventType"));
|
|
463
|
+
assertEquals(
|
|
464
|
+
Arrays.asList(
|
|
465
|
+
"pending-diag:scenario-1:A",
|
|
466
|
+
"pending-diag:scenario-1:A",
|
|
467
|
+
"pending-diag:scenario-1:A",
|
|
468
|
+
"pending-diag:scenario-1:A"),
|
|
469
|
+
markerFieldValues("EVENT_RECEIVED", "diagEventId"));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
@Test
|
|
473
|
+
public void diagnosticIdentity_nonConformingValue_isUnavailableAndNeverLogged() throws Exception {
|
|
474
|
+
JavaOnlyMap missingPrefix = diagnosticEventMap("scenario-1", "consumer-value");
|
|
475
|
+
JavaOnlyMap forbiddenCharacters =
|
|
476
|
+
diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A secret");
|
|
477
|
+
JavaOnlyMap wrongScenario = diagnosticEventMap("scenario-1", "pending-diag:scenario-2:A");
|
|
478
|
+
|
|
479
|
+
assertNull(extractPendingDiagnosticIdentity(missingPrefix));
|
|
480
|
+
assertNull(extractPendingDiagnosticIdentity(forbiddenCharacters));
|
|
481
|
+
assertNull(extractPendingDiagnosticIdentity(wrongScenario));
|
|
482
|
+
|
|
483
|
+
assertTrue(initializePendingDiagnostics(diagnosticsContext(true, true, 0).context));
|
|
484
|
+
setReactContexts((ReactContext) null);
|
|
485
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", forbiddenCharacters);
|
|
486
|
+
|
|
487
|
+
String received = markerMessage("EVENT_RECEIVED");
|
|
488
|
+
assertTrue(received.contains("diagScenarioId=unavailable"));
|
|
489
|
+
assertTrue(received.contains("diagEventId=unavailable"));
|
|
490
|
+
assertTrue(received.contains("eventType=unavailable"));
|
|
491
|
+
assertFalse(received.contains("A secret"));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
@Test
|
|
495
|
+
public void diagnostics_sameIdDifferentTypes_keepSeparateNativeReceipts() throws Exception {
|
|
496
|
+
assertTrue(initializePendingDiagnostics(diagnosticsContext(true, true, 0).context));
|
|
497
|
+
String eventId = "pending-diag:scenario-1:A";
|
|
498
|
+
JavaOnlyMap triggerCreated =
|
|
499
|
+
diagnosticEventMap(
|
|
500
|
+
"scenario-1", eventId, NotificationEvent.TYPE_TRIGGER_NOTIFICATION_CREATED);
|
|
501
|
+
JavaOnlyMap delivered =
|
|
502
|
+
diagnosticEventMap("scenario-1", eventId, NotificationEvent.TYPE_DELIVERED);
|
|
503
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
504
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
505
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
506
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
507
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
508
|
+
.thenReturn(emitter);
|
|
509
|
+
|
|
510
|
+
setReactContexts(activeContext, null);
|
|
511
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, triggerCreated);
|
|
512
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, delivered);
|
|
513
|
+
|
|
514
|
+
verify(emitter).emit(NOTIFICATION_EVENT_NAME, triggerCreated);
|
|
515
|
+
assertEquals(
|
|
516
|
+
Arrays.asList(eventId, eventId), markerFieldValues("EVENT_RECEIVED", "diagEventId"));
|
|
517
|
+
assertEquals(Arrays.asList("1", "2"), markerFieldValues("EVENT_RECEIVED", "eventSeq"));
|
|
518
|
+
assertEquals(Arrays.asList("7", "3"), markerFieldValues("EVENT_RECEIVED", "eventType"));
|
|
519
|
+
assertEquals(Arrays.asList("7"), markerFieldValues("EVENT_EMIT_DIRECT_BEGIN", "eventType"));
|
|
520
|
+
assertEquals(Arrays.asList("3"), markerFieldValues("EVENT_ENQUEUED", "eventType"));
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
@Test
|
|
524
|
+
public void diagnostics_sameIdAndType_keepDistinctReceiptSequences() throws Exception {
|
|
525
|
+
assertTrue(initializePendingDiagnostics(diagnosticsContext(true, true, 0).context));
|
|
526
|
+
String eventId = "pending-diag:scenario-1:A";
|
|
527
|
+
JavaOnlyMap deliveredOne =
|
|
528
|
+
diagnosticEventMap("scenario-1", eventId, NotificationEvent.TYPE_DELIVERED);
|
|
529
|
+
JavaOnlyMap deliveredTwo =
|
|
530
|
+
diagnosticEventMap("scenario-1", eventId, NotificationEvent.TYPE_DELIVERED);
|
|
531
|
+
|
|
532
|
+
setReactContexts((ReactContext) null);
|
|
533
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, deliveredOne);
|
|
534
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, deliveredTwo);
|
|
535
|
+
|
|
536
|
+
assertEquals(
|
|
537
|
+
Arrays.asList(eventId, eventId), markerFieldValues("EVENT_RECEIVED", "diagEventId"));
|
|
538
|
+
assertEquals(Arrays.asList("3", "3"), markerFieldValues("EVENT_RECEIVED", "eventType"));
|
|
539
|
+
assertEquals(Arrays.asList("1", "2"), markerFieldValues("EVENT_RECEIVED", "eventSeq"));
|
|
540
|
+
assertEquals(Arrays.asList("1", "2"), markerFieldValues("EVENT_ENQUEUED", "eventSeq"));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
@Test
|
|
544
|
+
public void diagnostics_enabled_emitMarkersAndDoNotModifyPublicPayload() throws Exception {
|
|
545
|
+
DiagnosticsContext diagnostics = diagnosticsContext(true, true, 1);
|
|
546
|
+
assertTrue(initializePendingDiagnostics(diagnostics.context));
|
|
547
|
+
|
|
548
|
+
JavaOnlyMap eventA = diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A");
|
|
549
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
550
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
551
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
552
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
553
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
554
|
+
.thenReturn(emitter);
|
|
555
|
+
|
|
556
|
+
setReactContexts(null, activeContext);
|
|
557
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
558
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
559
|
+
|
|
560
|
+
verify(emitter).emit(eq("A"), same(eventA));
|
|
561
|
+
ReadableMap detail = eventA.getMap("detail");
|
|
562
|
+
assertNotNull(detail);
|
|
563
|
+
ReadableMap notification = detail.getMap("notification");
|
|
564
|
+
assertNotNull(notification);
|
|
565
|
+
ReadableMap data = notification.getMap("data");
|
|
566
|
+
assertNotNull(data);
|
|
567
|
+
assertEquals("consumer-value", data.getString("consumerField"));
|
|
568
|
+
assertEquals("scenario-1", data.getString(DIAGNOSTIC_SCENARIO_ID_KEY));
|
|
569
|
+
assertEquals("pending-diag:scenario-1:A", data.getString(DIAGNOSTIC_EVENT_ID_KEY));
|
|
570
|
+
assertEquals(NotificationEvent.TYPE_DELIVERED, eventA.getInt("type"));
|
|
571
|
+
assertFalse(eventA.hasKey("eventSeq"));
|
|
572
|
+
assertFalse(data.hasKey("eventSeq"));
|
|
573
|
+
assertTrue(hasMarker("EVENT_RECEIVED"));
|
|
574
|
+
assertTrue(hasMarker("EVENT_ENQUEUED"));
|
|
575
|
+
assertTrue(hasMarker("FLUSH_REQUESTED"));
|
|
576
|
+
assertTrue(hasMarker("FLUSH_SNAPSHOT"));
|
|
577
|
+
assertTrue(hasMarker("FLUSH_SNAPSHOT_ITEM"));
|
|
578
|
+
assertTrue(hasMarker("FLUSH_QUEUE_CLEARED"));
|
|
579
|
+
assertTrue(hasMarker("FLUSH_DIAGNOSTIC_DELAY_BEGIN"));
|
|
580
|
+
assertTrue(hasMarker("FLUSH_DIAGNOSTIC_DELAY_END"));
|
|
581
|
+
assertTrue(hasMarker("FLUSH_EVENT_CONTEXT_CHECK"));
|
|
582
|
+
assertTrue(hasMarker("FLUSH_EVENT_EMIT_BEGIN"));
|
|
583
|
+
assertTrue(hasMarker("FLUSH_EVENT_EMIT_RETURN"));
|
|
584
|
+
assertTrue(hasMarker("FLUSH_COMPLETED"));
|
|
585
|
+
assertTrue(markerMessage("EVENT_RECEIVED").contains("eventSeq=1"));
|
|
586
|
+
assertTrue(markerMessage("EVENT_RECEIVED").contains("diagScenarioId=scenario-1"));
|
|
587
|
+
assertTrue(markerMessage("EVENT_RECEIVED").contains("diagEventId=pending-diag:scenario-1:A"));
|
|
588
|
+
assertEquals(Arrays.asList("3"), markerFieldValues("EVENT_RECEIVED", "eventType"));
|
|
589
|
+
assertEquals(Arrays.asList("3"), markerFieldValues("EVENT_ENQUEUED", "eventType"));
|
|
590
|
+
assertEquals(Arrays.asList("3"), markerFieldValues("FLUSH_SNAPSHOT_ITEM", "eventType"));
|
|
591
|
+
assertEquals(Arrays.asList("3"), markerFieldValues("FLUSH_EVENT_CONTEXT_CHECK", "eventType"));
|
|
592
|
+
assertEquals(Arrays.asList("3"), markerFieldValues("FLUSH_EVENT_EMIT_RETURN", "eventType"));
|
|
593
|
+
assertTrue(markerMessage("FLUSH_REQUESTED").contains("flushId=1"));
|
|
594
|
+
|
|
595
|
+
Object state = pendingDiagnosticsState();
|
|
596
|
+
assertNotNull(state);
|
|
597
|
+
assertEquals(1L, atomicLongField(state, "eventSequence").get());
|
|
598
|
+
assertEquals(1L, atomicLongField(state, "flushSequence").get());
|
|
599
|
+
assertTrue(((java.util.Map<?, ?>) privateField(state, "queuedEventDiagnostics")).isEmpty());
|
|
600
|
+
verify(diagnostics.packageManager, times(1))
|
|
601
|
+
.getApplicationInfo("com.notifeeexample", PackageManager.GET_META_DATA);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
@Test
|
|
605
|
+
public void diagnostics_requeuedEvent_keepsIdentityAndRecoversExactlyOnce() throws Exception {
|
|
606
|
+
assertTrue(initializePendingDiagnostics(diagnosticsContext(true, true, 0).context));
|
|
607
|
+
JavaOnlyMap eventA = diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A");
|
|
608
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
609
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
610
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
611
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
612
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
613
|
+
.thenReturn(emitter);
|
|
614
|
+
|
|
615
|
+
setReactContexts(null, null);
|
|
616
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, eventA);
|
|
617
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
618
|
+
|
|
619
|
+
assertEquals(Arrays.asList(NOTIFICATION_EVENT_NAME), pendingEventNames(pendingEvents()));
|
|
620
|
+
assertSame(eventA, pendingEventBody(pendingEvents().get(0)));
|
|
621
|
+
assertEquals(
|
|
622
|
+
1,
|
|
623
|
+
((java.util.Map<?, ?>) privateField(pendingDiagnosticsState(), "queuedEventDiagnostics"))
|
|
624
|
+
.size());
|
|
625
|
+
assertEquals(Arrays.asList("1"), markerFieldValues("FLUSH_EVENT_REQUEUED", "eventSeq"));
|
|
626
|
+
assertEquals(Arrays.asList("1"), markerFieldValues("FLUSH_EVENT_REQUEUED", "flushId"));
|
|
627
|
+
assertEquals(Arrays.asList("3"), markerFieldValues("FLUSH_EVENT_REQUEUED", "eventType"));
|
|
628
|
+
assertEquals(
|
|
629
|
+
Arrays.asList("pending-diag:scenario-1:A"),
|
|
630
|
+
markerFieldValues("FLUSH_EVENT_REQUEUED", "diagEventId"));
|
|
631
|
+
assertEquals(
|
|
632
|
+
Arrays.asList("context_null"), markerFieldValues("FLUSH_EVENT_REQUEUED", "reason"));
|
|
633
|
+
assertEquals(Arrays.asList("1"), markerFieldValues("FLUSH_REQUEUE_MERGE", "requeuedCount"));
|
|
634
|
+
assertEquals(Arrays.asList("0"), markerFieldValues("FLUSH_REQUEUE_MERGE", "newArrivalsCount"));
|
|
635
|
+
assertEquals(Arrays.asList("0"), markerFieldValues("FLUSH_REQUEUE_MERGE", "droppedCount"));
|
|
636
|
+
|
|
637
|
+
setReactContexts(activeContext);
|
|
638
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
639
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
640
|
+
|
|
641
|
+
verify(emitter, times(1)).emit(NOTIFICATION_EVENT_NAME, eventA);
|
|
642
|
+
assertTrue(pendingEvents().isEmpty());
|
|
643
|
+
assertTrue(
|
|
644
|
+
((java.util.Map<?, ?>) privateField(pendingDiagnosticsState(), "queuedEventDiagnostics"))
|
|
645
|
+
.isEmpty());
|
|
646
|
+
assertEquals(Arrays.asList("1", "1"), markerFieldValues("FLUSH_SNAPSHOT_ITEM", "eventSeq"));
|
|
647
|
+
assertEquals(Arrays.asList("1", "2"), markerFieldValues("FLUSH_SNAPSHOT_ITEM", "flushId"));
|
|
648
|
+
assertEquals(Arrays.asList("1"), markerFieldValues("FLUSH_EVENT_EMIT_RETURN", "eventSeq"));
|
|
649
|
+
assertEquals(Arrays.asList("2"), markerFieldValues("FLUSH_EVENT_EMIT_RETURN", "flushId"));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
@Test
|
|
653
|
+
public void diagnostics_emitException_isNotRequeuedAndNextEventIsAttempted() throws Exception {
|
|
654
|
+
assertTrue(initializePendingDiagnostics(diagnosticsContext(true, true, 0).context));
|
|
655
|
+
JavaOnlyMap eventA = diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A");
|
|
656
|
+
JavaOnlyMap eventB = diagnosticEventMap("scenario-1", "pending-diag:scenario-1:B");
|
|
657
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
658
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
659
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
660
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
661
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
662
|
+
.thenReturn(emitter);
|
|
663
|
+
doThrow(new IllegalStateException("diagnostic emit failure"))
|
|
664
|
+
.when(emitter)
|
|
665
|
+
.emit(eq(NOTIFICATION_EVENT_NAME), same(eventA));
|
|
666
|
+
|
|
667
|
+
setReactContexts(null, null, activeContext, activeContext);
|
|
668
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, eventA);
|
|
669
|
+
NotifeeReactUtils.INSTANCE.sendEvent(NOTIFICATION_EVENT_NAME, eventB);
|
|
670
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
671
|
+
|
|
672
|
+
assertTrue(pendingEvents().isEmpty());
|
|
673
|
+
verify(emitter).emit(NOTIFICATION_EVENT_NAME, eventA);
|
|
674
|
+
verify(emitter).emit(NOTIFICATION_EVENT_NAME, eventB);
|
|
675
|
+
assertEquals(
|
|
676
|
+
Arrays.asList("pending-diag:scenario-1:A"),
|
|
677
|
+
markerFieldValues("FLUSH_EVENT_EMIT_EXCEPTION", "diagEventId"));
|
|
678
|
+
assertFalse(hasMarker("FLUSH_EVENT_REQUEUED"));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
@Test
|
|
682
|
+
public void diagnostics_requeueMergeOverCapacity_preservesSideMapAndGlobalFifo()
|
|
683
|
+
throws Exception {
|
|
684
|
+
assertTrue(initializePendingDiagnostics(diagnosticsContext(true, true, 0).context));
|
|
685
|
+
List<String> expectedEventIds = new ArrayList<>();
|
|
686
|
+
for (int index = 1; index <= 11; index += 1) {
|
|
687
|
+
expectedEventIds.add(String.format("pending-diag:scenario-1:overflow:%02d", index));
|
|
688
|
+
}
|
|
689
|
+
ReactContext inactiveContext = mock(ReactContext.class);
|
|
690
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
691
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
692
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
693
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
694
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
695
|
+
.thenReturn(emitter);
|
|
696
|
+
|
|
697
|
+
setReactContexts((ReactContext) null);
|
|
698
|
+
for (int index = 0; index < 2; index += 1) {
|
|
699
|
+
NotifeeReactUtils.INSTANCE.sendEvent(
|
|
700
|
+
"overflow", diagnosticEventMap("scenario-1", expectedEventIds.get(index)));
|
|
701
|
+
}
|
|
702
|
+
setReactContexts(inactiveContext, null);
|
|
703
|
+
when(inactiveContext.hasActiveReactInstance())
|
|
704
|
+
.thenAnswer(
|
|
705
|
+
invocation -> {
|
|
706
|
+
for (int index = 2; index < expectedEventIds.size(); index += 1) {
|
|
707
|
+
NotifeeReactUtils.INSTANCE.sendEvent(
|
|
708
|
+
"overflow", diagnosticEventMap("scenario-1", expectedEventIds.get(index)));
|
|
709
|
+
}
|
|
710
|
+
return false;
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
714
|
+
|
|
715
|
+
assertEquals(10, pendingEvents().size());
|
|
716
|
+
assertEquals(
|
|
717
|
+
10,
|
|
718
|
+
((java.util.Map<?, ?>) privateField(pendingDiagnosticsState(), "queuedEventDiagnostics"))
|
|
719
|
+
.size());
|
|
720
|
+
assertEquals(
|
|
721
|
+
Arrays.asList(expectedEventIds.get(0)),
|
|
722
|
+
markerFieldValues("FLUSH_REQUEUE_DROP", "diagEventId"));
|
|
723
|
+
assertEquals(Arrays.asList("2"), markerFieldValues("FLUSH_REQUEUE_MERGE", "requeuedCount"));
|
|
724
|
+
assertEquals(Arrays.asList("9"), markerFieldValues("FLUSH_REQUEUE_MERGE", "newArrivalsCount"));
|
|
725
|
+
assertEquals(Arrays.asList("1"), markerFieldValues("FLUSH_REQUEUE_MERGE", "droppedCount"));
|
|
726
|
+
|
|
727
|
+
setReactContexts(activeContext);
|
|
728
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
729
|
+
|
|
730
|
+
List<String> allSnapshotIds = markerFieldValues("FLUSH_SNAPSHOT_ITEM", "diagEventId");
|
|
731
|
+
assertEquals(
|
|
732
|
+
expectedEventIds.subList(1, expectedEventIds.size()), allSnapshotIds.subList(2, 12));
|
|
733
|
+
assertTrue(pendingEvents().isEmpty());
|
|
734
|
+
assertTrue(
|
|
735
|
+
((java.util.Map<?, ?>) privateField(pendingDiagnosticsState(), "queuedEventDiagnostics"))
|
|
736
|
+
.isEmpty());
|
|
737
|
+
verify(emitter, times(10)).emit(eq("overflow"), any(WritableMap.class));
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
@Test
|
|
741
|
+
public void diagnostics_similarEvents_keepDistinctControlledIdentities() throws Exception {
|
|
742
|
+
assertTrue(initializePendingDiagnostics(diagnosticsContext(true, true, 0).context));
|
|
743
|
+
JavaOnlyMap eventA = diagnosticEventMap("scenario-1", "pending-diag:scenario-1:A");
|
|
744
|
+
JavaOnlyMap eventB = diagnosticEventMap("scenario-1", "pending-diag:scenario-1:B");
|
|
745
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
746
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
747
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
748
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
749
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
750
|
+
.thenReturn(emitter);
|
|
751
|
+
|
|
752
|
+
setReactContexts(null, null, activeContext, activeContext);
|
|
753
|
+
NotifeeReactUtils.INSTANCE.sendEvent("A", eventA);
|
|
754
|
+
NotifeeReactUtils.INSTANCE.sendEvent("B", eventB);
|
|
755
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
756
|
+
|
|
757
|
+
verify(emitter).emit("A", eventA);
|
|
758
|
+
verify(emitter).emit("B", eventB);
|
|
759
|
+
assertEquals(
|
|
760
|
+
Arrays.asList("pending-diag:scenario-1:A", "pending-diag:scenario-1:B"),
|
|
761
|
+
markerFieldValues("EVENT_ENQUEUED", "diagEventId"));
|
|
762
|
+
assertEquals(
|
|
763
|
+
Arrays.asList("pending-diag:scenario-1:A", "pending-diag:scenario-1:B"),
|
|
764
|
+
markerFieldValues("FLUSH_SNAPSHOT_ITEM", "diagEventId"));
|
|
765
|
+
assertTrue(
|
|
766
|
+
((java.util.Map<?, ?>) privateField(pendingDiagnosticsState(), "queuedEventDiagnostics"))
|
|
767
|
+
.isEmpty());
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
@Test
|
|
771
|
+
public void diagnostics_overflow_logsDroppedIdentityAndCleansSideMap() throws Exception {
|
|
772
|
+
assertTrue(initializePendingDiagnostics(diagnosticsContext(true, true, 0).context));
|
|
773
|
+
setReactContexts((ReactContext) null);
|
|
774
|
+
List<String> expectedEventIds = new ArrayList<>();
|
|
775
|
+
|
|
776
|
+
for (int index = 1; index <= 11; index += 1) {
|
|
777
|
+
String eventId = String.format("pending-diag:scenario-1:overflow:%02d", index);
|
|
778
|
+
expectedEventIds.add(eventId);
|
|
779
|
+
NotifeeReactUtils.INSTANCE.sendEvent("overflow", diagnosticEventMap("scenario-1", eventId));
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
Object state = pendingDiagnosticsState();
|
|
783
|
+
assertEquals(10, ((java.util.Map<?, ?>) privateField(state, "queuedEventDiagnostics")).size());
|
|
784
|
+
assertTrue(
|
|
785
|
+
markerMessage("EVENT_OVERFLOW_DROPPED")
|
|
786
|
+
.contains("diagEventId=pending-diag:scenario-1:overflow:01"));
|
|
787
|
+
|
|
788
|
+
ReactContext activeContext = mock(ReactContext.class);
|
|
789
|
+
DeviceEventManagerModule.RCTDeviceEventEmitter emitter =
|
|
790
|
+
mock(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
|
|
791
|
+
when(activeContext.hasActiveReactInstance()).thenReturn(true);
|
|
792
|
+
when(activeContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class))
|
|
793
|
+
.thenReturn(emitter);
|
|
794
|
+
setReactContexts(activeContext);
|
|
795
|
+
NotifeeReactUtils.INSTANCE.flushPendingEvents();
|
|
796
|
+
|
|
797
|
+
assertEquals(
|
|
798
|
+
expectedEventIds.subList(1, expectedEventIds.size()),
|
|
799
|
+
markerFieldValues("FLUSH_SNAPSHOT_ITEM", "diagEventId"));
|
|
800
|
+
assertEquals(
|
|
801
|
+
expectedEventIds.subList(1, expectedEventIds.size()),
|
|
802
|
+
markerFieldValues("FLUSH_EVENT_CONTEXT_CHECK", "diagEventId"));
|
|
803
|
+
assertTrue(((java.util.Map<?, ?>) privateField(state, "queuedEventDiagnostics")).isEmpty());
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
@Test
|
|
807
|
+
public void diagnostics_configuration_isDebugGatedClampedAndInitializedOnce() throws Exception {
|
|
808
|
+
DiagnosticsContext releaseContext = diagnosticsContext(false, true, 3000);
|
|
809
|
+
assertFalse(initializePendingDiagnostics(releaseContext.context));
|
|
810
|
+
assertNull(pendingDiagnosticsState());
|
|
811
|
+
|
|
812
|
+
Object negativeDelayConfig =
|
|
813
|
+
loadPendingDiagnosticsConfig(diagnosticsContext(true, true, -1).context);
|
|
814
|
+
assertEquals(
|
|
815
|
+
0L, ((Number) privateField(negativeDelayConfig, "delayAfterSnapshotMs")).longValue());
|
|
816
|
+
|
|
817
|
+
DiagnosticsContext clampedContext = diagnosticsContext(true, true, 15000);
|
|
818
|
+
assertTrue(initializePendingDiagnostics(clampedContext.context));
|
|
819
|
+
assertEquals(
|
|
820
|
+
10000L,
|
|
821
|
+
((Number) privateField(pendingDiagnosticsState(), "delayAfterSnapshotMs")).longValue());
|
|
822
|
+
assertTrue(initializePendingDiagnostics(clampedContext.context));
|
|
823
|
+
verify(clampedContext.packageManager, times(1))
|
|
824
|
+
.getApplicationInfo("com.notifeeexample", PackageManager.GET_META_DATA);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
private void setReactContexts(ReactContext... contexts) {
|
|
828
|
+
if (contexts.length == 0) {
|
|
829
|
+
throw new IllegalArgumentException("At least one context result is required");
|
|
830
|
+
}
|
|
831
|
+
application.reactHost.setResults(contexts);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
@SuppressWarnings("unchecked")
|
|
835
|
+
private static List<?> pendingEvents() throws Exception {
|
|
836
|
+
Field field = NotifeeReactUtils.class.getDeclaredField("pendingEvents");
|
|
837
|
+
field.setAccessible(true);
|
|
838
|
+
return (List<?>) field.get(NotifeeReactUtils.INSTANCE);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
private static Object loadPendingDiagnosticsConfig(Context context) throws Exception {
|
|
842
|
+
Method method =
|
|
843
|
+
NotifeeReactUtils.class.getDeclaredMethod("loadPendingDiagnosticsConfig", Context.class);
|
|
844
|
+
method.setAccessible(true);
|
|
845
|
+
return method.invoke(NotifeeReactUtils.INSTANCE, context);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
private static boolean initializePendingDiagnostics(Context context) throws Exception {
|
|
849
|
+
Method method =
|
|
850
|
+
NotifeeReactUtils.class.getDeclaredMethod("initializePendingDiagnostics", Context.class);
|
|
851
|
+
method.setAccessible(true);
|
|
852
|
+
return (boolean) method.invoke(NotifeeReactUtils.INSTANCE, context);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
private static Object extractPendingDiagnosticIdentity(WritableMap eventMap) throws Exception {
|
|
856
|
+
Method method =
|
|
857
|
+
NotifeeReactUtils.class.getDeclaredMethod(
|
|
858
|
+
"extractPendingDiagnosticIdentity", WritableMap.class);
|
|
859
|
+
method.setAccessible(true);
|
|
860
|
+
return method.invoke(NotifeeReactUtils.INSTANCE, eventMap);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
private static JavaOnlyMap diagnosticEventMap(String scenarioId, Object eventId) {
|
|
864
|
+
return diagnosticEventMap(scenarioId, eventId, NotificationEvent.TYPE_DELIVERED);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
private static JavaOnlyMap diagnosticEventMap(
|
|
868
|
+
String scenarioId, Object eventId, Object eventType) {
|
|
869
|
+
JavaOnlyMap data =
|
|
870
|
+
JavaOnlyMap.of(
|
|
871
|
+
DIAGNOSTIC_SCENARIO_ID_KEY,
|
|
872
|
+
scenarioId,
|
|
873
|
+
DIAGNOSTIC_EVENT_ID_KEY,
|
|
874
|
+
eventId,
|
|
875
|
+
"consumerField",
|
|
876
|
+
"consumer-value");
|
|
877
|
+
JavaOnlyMap notification = JavaOnlyMap.of("id", "controlled-fixture", "data", data);
|
|
878
|
+
JavaOnlyMap detail = JavaOnlyMap.of("notification", notification);
|
|
879
|
+
return JavaOnlyMap.of("type", eventType, "detail", detail, "headless", false);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
private static JavaOnlyMap diagnosticEventMapWithoutType(String scenarioId, Object eventId) {
|
|
883
|
+
JavaOnlyMap data =
|
|
884
|
+
JavaOnlyMap.of(
|
|
885
|
+
DIAGNOSTIC_SCENARIO_ID_KEY,
|
|
886
|
+
scenarioId,
|
|
887
|
+
DIAGNOSTIC_EVENT_ID_KEY,
|
|
888
|
+
eventId,
|
|
889
|
+
"consumerField",
|
|
890
|
+
"consumer-value");
|
|
891
|
+
JavaOnlyMap notification = JavaOnlyMap.of("id", "controlled-fixture", "data", data);
|
|
892
|
+
JavaOnlyMap detail = JavaOnlyMap.of("notification", notification);
|
|
893
|
+
return JavaOnlyMap.of("detail", detail, "headless", false);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
private static List<String> pendingEventNames(List<?> pendingEvents) throws Exception {
|
|
897
|
+
List<String> names = new ArrayList<>();
|
|
898
|
+
for (Object pendingEvent : pendingEvents) {
|
|
899
|
+
names.add((String) ((Pair<?, ?>) pendingEvent).getFirst());
|
|
900
|
+
}
|
|
901
|
+
return names;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
private static WritableMap pendingEventBody(Object pendingEvent) throws Exception {
|
|
905
|
+
return (WritableMap) ((Pair<?, ?>) pendingEvent).getSecond();
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
private static Object pendingDiagnosticsState() throws Exception {
|
|
909
|
+
Field field = NotifeeReactUtils.class.getDeclaredField("pendingDiagnosticsState");
|
|
910
|
+
field.setAccessible(true);
|
|
911
|
+
return field.get(NotifeeReactUtils.INSTANCE);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
private static AtomicLong atomicLongField(Object target, String name) throws Exception {
|
|
915
|
+
return (AtomicLong) privateField(target, name);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
private static boolean hasMarker(String marker) {
|
|
919
|
+
return ShadowLog.getLogsForTag(DIAGNOSTICS_TAG).stream()
|
|
920
|
+
.anyMatch(item -> item.msg.contains("marker=" + marker + " "));
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
private static String markerMessage(String marker) {
|
|
924
|
+
return ShadowLog.getLogsForTag(DIAGNOSTICS_TAG).stream()
|
|
925
|
+
.map(item -> item.msg)
|
|
926
|
+
.filter(message -> message.contains("marker=" + marker + " "))
|
|
927
|
+
.findFirst()
|
|
928
|
+
.orElse("");
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
private static List<String> markerFieldValues(String marker, String field) {
|
|
932
|
+
List<String> values = new ArrayList<>();
|
|
933
|
+
String fieldPrefix = field + "=";
|
|
934
|
+
for (ShadowLog.LogItem item : ShadowLog.getLogsForTag(DIAGNOSTICS_TAG)) {
|
|
935
|
+
if (!item.msg.contains("marker=" + marker + " ")) continue;
|
|
936
|
+
for (String token : item.msg.split(" ")) {
|
|
937
|
+
if (token.startsWith(fieldPrefix)) {
|
|
938
|
+
values.add(token.substring(fieldPrefix.length()));
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return values;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
private static DiagnosticsContext diagnosticsContext(
|
|
947
|
+
boolean debuggable, boolean enabled, int delayMs) throws Exception {
|
|
948
|
+
Context context = mock(Context.class);
|
|
949
|
+
PackageManager packageManager = mock(PackageManager.class);
|
|
950
|
+
ApplicationInfo applicationInfo = new ApplicationInfo();
|
|
951
|
+
applicationInfo.flags = debuggable ? ApplicationInfo.FLAG_DEBUGGABLE : 0;
|
|
952
|
+
applicationInfo.metaData = new Bundle();
|
|
953
|
+
applicationInfo.metaData.putBoolean("notifee_pending_events_diagnostics_enabled", enabled);
|
|
954
|
+
applicationInfo.metaData.putInt("notifee_pending_events_delay_after_snapshot_ms", delayMs);
|
|
955
|
+
when(context.getPackageManager()).thenReturn(packageManager);
|
|
956
|
+
when(context.getPackageName()).thenReturn("com.notifeeexample");
|
|
957
|
+
when(packageManager.getApplicationInfo("com.notifeeexample", PackageManager.GET_META_DATA))
|
|
958
|
+
.thenReturn(applicationInfo);
|
|
959
|
+
return new DiagnosticsContext(context, packageManager);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
private static Object privateField(Object target, String name) throws Exception {
|
|
963
|
+
Field field = target.getClass().getDeclaredField(name);
|
|
964
|
+
field.setAccessible(true);
|
|
965
|
+
return field.get(target);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
private static void resetState() throws Exception {
|
|
969
|
+
List<?> events = pendingEvents();
|
|
970
|
+
synchronized (events) {
|
|
971
|
+
events.clear();
|
|
972
|
+
}
|
|
973
|
+
Field stateField = NotifeeReactUtils.class.getDeclaredField("pendingDiagnosticsState");
|
|
974
|
+
stateField.setAccessible(true);
|
|
975
|
+
stateField.set(NotifeeReactUtils.INSTANCE, null);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
public static class TestApplication extends Application {
|
|
979
|
+
final TestReactHost reactHost = new TestReactHost();
|
|
980
|
+
int packageManagerRequests;
|
|
981
|
+
|
|
982
|
+
@Override
|
|
983
|
+
public PackageManager getPackageManager() {
|
|
984
|
+
packageManagerRequests += 1;
|
|
985
|
+
return super.getPackageManager();
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
public TestReactHost getReactHost() {
|
|
989
|
+
return reactHost;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
public static class TestReactHost {
|
|
994
|
+
private final List<ReactContext> results = new ArrayList<>();
|
|
995
|
+
private int invocation;
|
|
996
|
+
|
|
997
|
+
public ReactContext getCurrentReactContext() {
|
|
998
|
+
if (results.isEmpty()) return null;
|
|
999
|
+
ReactContext result = results.get(Math.min(invocation, results.size() - 1));
|
|
1000
|
+
invocation += 1;
|
|
1001
|
+
return result;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
void setResults(ReactContext... contexts) {
|
|
1005
|
+
results.clear();
|
|
1006
|
+
results.addAll(Arrays.asList(contexts));
|
|
1007
|
+
invocation = 0;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
void reset() {
|
|
1011
|
+
results.clear();
|
|
1012
|
+
invocation = 0;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
private static final class DiagnosticsContext {
|
|
1017
|
+
final Context context;
|
|
1018
|
+
final PackageManager packageManager;
|
|
1019
|
+
|
|
1020
|
+
DiagnosticsContext(Context context, PackageManager packageManager) {
|
|
1021
|
+
this.context = context;
|
|
1022
|
+
this.packageManager = packageManager;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|