mixpanel-react-native 2.4.0 → 3.0.0-beta.1

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/.vscode/settings.json +4 -0
  2. package/CHANGELOG.md +31 -0
  3. package/README.md +2 -2
  4. package/Samples/MixpanelExpo/App.js +340 -0
  5. package/Samples/MixpanelExpo/app.json +30 -0
  6. package/Samples/MixpanelExpo/assets/adaptive-icon.png +0 -0
  7. package/Samples/MixpanelExpo/assets/favicon.png +0 -0
  8. package/Samples/MixpanelExpo/assets/icon.png +0 -0
  9. package/Samples/MixpanelExpo/assets/splash.png +0 -0
  10. package/Samples/MixpanelExpo/babel.config.js +6 -0
  11. package/Samples/MixpanelExpo/package.json +25 -0
  12. package/__mocks__/@react-native-async-storage/async-storage.js +1 -0
  13. package/__tests__/core.test.js +135 -0
  14. package/__tests__/index.test.js +257 -106
  15. package/__tests__/jest_setup.js +23 -4
  16. package/__tests__/main.test.js +788 -0
  17. package/__tests__/network.test.js +72 -0
  18. package/__tests__/persistent.test.js +161 -0
  19. package/__tests__/queue.test.js +65 -0
  20. package/android/bin/.settings/org.eclipse.buildship.core.prefs +1 -1
  21. package/android/bin/build.gradle +8 -1
  22. package/docs/Mixpanel.html +42 -44
  23. package/docs/MixpanelGroup.html +7 -7
  24. package/docs/People.html +12 -12
  25. package/docs/index.html +3 -3
  26. package/docs/index.js.html +873 -791
  27. package/index.d.ts +53 -53
  28. package/index.js +917 -835
  29. package/javascript/mixpanel-config.js +102 -0
  30. package/javascript/mixpanel-constants.js +22 -0
  31. package/javascript/mixpanel-core.js +164 -0
  32. package/javascript/mixpanel-logger.js +35 -0
  33. package/javascript/mixpanel-main.js +548 -0
  34. package/javascript/mixpanel-network.js +86 -0
  35. package/javascript/mixpanel-persistent.js +297 -0
  36. package/javascript/mixpanel-queue.js +65 -0
  37. package/javascript/mixpanel-storage.js +43 -0
  38. package/javascript/mixpanel-utils.js +38 -0
  39. package/logs/.b11bf985d66a037ca5688a574653f3bf76a28dfa-audit.json +49 -0
  40. package/logs/.c366df74eeb671df60a57a2075ae40a3dae2af25-audit.json +49 -0
  41. package/package.json +61 -56
  42. package/release.py +2 -1
@@ -0,0 +1,4 @@
1
+ {
2
+ "java.compile.nullAnalysis.mode": "automatic",
3
+ "files.trimTrailingWhitespace": true
4
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  #
2
2
 
3
+ ## [v3.0.0-beta.1](https://github.com/mixpanel/mixpanel-react-native/tree/v3.0.0-beta.1) (2024-02-29)
4
+
5
+ ### Expo and React Native Web support
6
+ This version(PR [\#223](https://github.com/mixpanel/mixpanel-react-native/pull/223)) introduces support for Expo, React Native Web, and any platform using React Native that does not support iOS and Android. To activate this feature, initialize Mixpanel with an additional parameter `useNative` set to false, which will enable JavaScript mode. Currently in beta, we plan to iterate on this to address any issues and add more features. We welcome your feedback.
7
+
8
+ ```
9
+ const trackAutomaticEvents = false;
10
+ const useNative = false;
11
+ const mixpanel = new Mixpanel(
12
+ "YOUR_MIXPANEL_TOKEN",
13
+ trackAutomaticEvents,
14
+ useNative
15
+ );
16
+ mixpanel.init();
17
+ ```
18
+
19
+
20
+ To try the Expo sample app, navigate to `Samples/MixpanelExpo`, run `npm install`, and then execute `npm run ios` or `npm run android`
21
+
22
+ Known limitations and differences compared to the native mode (iOS/Android):
23
+ - Automatic Events are currently not supported in this mode. Setting 'trackAutomaticEvents' to 'true' will have no effect.
24
+ - Certain Mixpanel Properties are unavailable in Javascript mode, including detailed information about the device and screen.
25
+ - The default flush interval is set to 10 seconds. The data will not flush automatically when the app moves to the background. We recommend flushing more frequently for key events.
26
+
27
+
28
+ #
29
+
3
30
  ## [v2.4.0](https://github.com/mixpanel/mixpanel-react-native/tree/v2.4.0) (2023-12-02)
4
31
 
5
32
  ### Enhancements
@@ -342,6 +369,10 @@ Report issues or give us any feedback is appreciated!
342
369
 
343
370
 
344
371
 
372
+
373
+
374
+
375
+
345
376
 
346
377
 
347
378
 
package/README.md CHANGED
@@ -55,7 +55,7 @@ To start tracking with the library you must first initialize with your project t
55
55
  ```js
56
56
  import { Mixpanel } from 'mixpanel-react-native';
57
57
 
58
- const trackAutomaticEvents = true;
58
+ const trackAutomaticEvents = false;
59
59
  const mixpanel = new Mixpanel("Your Project Token", trackAutomaticEvents);
60
60
  mixpanel.init();
61
61
 
@@ -82,7 +82,7 @@ import React from 'react';
82
82
  import { Button, SafeAreaView } from "react-native";
83
83
  import { Mixpanel } from 'mixpanel-react-native';
84
84
 
85
- const trackAutomaticEvents = true;
85
+ const trackAutomaticEvents = false;
86
86
  const mixpanel = new Mixpanel("Your Project Token", trackAutomaticEvents);
87
87
  mixpanel.init();
88
88
 
@@ -0,0 +1,340 @@
1
+ import React from "react";
2
+ import {
3
+ SectionList,
4
+ Text,
5
+ View,
6
+ Button,
7
+ StyleSheet,
8
+ SafeAreaView,
9
+ } from "react-native";
10
+
11
+ import {Mixpanel} from "mixpanel-react-native";
12
+
13
+ const App = () => {
14
+ const trackAutomaticEvents = false;
15
+ const useNative = false;
16
+ const mixpanel = new Mixpanel(
17
+ "YOUR_MIXPANEL_TOKEN",
18
+ trackAutomaticEvents,
19
+ useNative
20
+ );
21
+ mixpanel.init();
22
+ mixpanel.setLoggingEnabled(true);
23
+
24
+ const group = mixpanel.getGroup("company_id", 111);
25
+ const track = async () => {
26
+ await mixpanel.track("Track Event!");
27
+ };
28
+
29
+ const identify = async () => {
30
+ await mixpanel.identify("testDistinctId");
31
+ };
32
+
33
+ const timeEvent = () => {
34
+ const eventName = "Timed Event";
35
+ mixpanel.timeEvent(eventName);
36
+ setTimeout(async () => {
37
+ await mixpanel.track(eventName);
38
+ }, 2000);
39
+ };
40
+
41
+ const createAlias = async () => {
42
+ const distinctId = await mixpanel.getDistinctId();
43
+ alert(distinctId);
44
+ await mixpanel.alias("New Alias", distinctId);
45
+ };
46
+
47
+ const trackWProperties = async () => {
48
+ const properties = {"Cool Property": "Property Value"};
49
+ await mixpanel.track("Track event with property", properties);
50
+ };
51
+
52
+ /**
53
+ registerSuperProperties will store a new superProperty and possibly overwriting any existing superProperty with the same name.
54
+ */
55
+ const registerSuperProperties = () => {
56
+ mixpanel.registerSuperProperties({
57
+ "super property": "super property value",
58
+ "super property1": "super property value1",
59
+ });
60
+ };
61
+ /**
62
+ Erase all currently registered superProperties.
63
+ */
64
+ const clearSuperProperties = () => {
65
+ mixpanel.clearSuperProperties();
66
+ };
67
+
68
+ const unregisterSuperProperty = () => {
69
+ mixpanel.unregisterSuperProperty("super property");
70
+ };
71
+ /**
72
+ Returns a json object of the user's current super properties.
73
+ */
74
+ const getSuperProperties = () => {
75
+ mixpanel.getSuperProperties().then((t) => {
76
+ alert(JSON.stringify(t));
77
+ });
78
+ };
79
+
80
+ const registerSuperPropertiesOnce = () => {
81
+ mixpanel.registerSuperPropertiesOnce({
82
+ "super property": "super property value1",
83
+ });
84
+ };
85
+
86
+ const flush = () => {
87
+ mixpanel.flush();
88
+ };
89
+
90
+ const optIn = () => {
91
+ mixpanel.optInTracking(mixpanel.getDistinctId());
92
+ };
93
+
94
+ const optOut = () => {
95
+ mixpanel.optOutTracking();
96
+ };
97
+
98
+ const reset = () => {
99
+ mixpanel.reset();
100
+ };
101
+
102
+ const setProperty = () => {
103
+ mixpanel.getPeople().set({
104
+ a: 1,
105
+ b: 2.3,
106
+ c: ["4", 5],
107
+ });
108
+ };
109
+
110
+ const setOneProperty = () => {
111
+ mixpanel.getPeople().set("d", "yo");
112
+ };
113
+
114
+ const setOnePropertyOnce = () => {
115
+ mixpanel.getPeople().setOnce("c", "just once");
116
+ };
117
+
118
+ const unsetProperties = () => {
119
+ mixpanel.getPeople().unset("a");
120
+ };
121
+
122
+ const incrementProperty = () => {
123
+ mixpanel.getPeople().increment("a", 2.2);
124
+ };
125
+
126
+ const removePropertyValue = () => {
127
+ mixpanel.getPeople().remove("c", 5);
128
+ };
129
+
130
+ const appendProperties = () => {
131
+ mixpanel.getPeople().append("a", "Hello");
132
+ };
133
+
134
+ const unionProperties = () => {
135
+ mixpanel.getPeople().union("a", ["goodbye", "hi"]);
136
+ };
137
+
138
+ const trackChargeWithoutProperties = () => {
139
+ mixpanel.getPeople().trackCharge(22.8);
140
+ };
141
+
142
+ const trackCharge = () => {
143
+ mixpanel.getPeople().trackCharge(12.8, {sandwich: 1});
144
+ };
145
+
146
+ const clearCharges = () => {
147
+ mixpanel.getPeople().clearCharges();
148
+ };
149
+
150
+ const deleteUser = () => {
151
+ mixpanel.getPeople().deleteUser();
152
+ };
153
+
154
+ // ----------------- Group API -----------------
155
+ const addGroup = () => {
156
+ mixpanel.addGroup("company_id", 111);
157
+ };
158
+
159
+ const deleteGroup = () => {
160
+ mixpanel.deleteGroup("company_id", 111);
161
+ };
162
+
163
+ const setGroup = () => {
164
+ mixpanel.setGroup("company_id", 3233);
165
+ };
166
+
167
+ const removeGroup = () => {
168
+ mixpanel.removeGroup("company_id", 3233);
169
+ };
170
+
171
+ const setGroupProperty = () => {
172
+ group.set("prop_key", "prop_value1");
173
+ group.set("prop_key1", ["prop_value11", "prop_value12"]);
174
+ };
175
+
176
+ const setGroupPropertyOnce = () => {
177
+ group.setOnce("prop_key", "prop_value222");
178
+ };
179
+
180
+ const unsetGroupProperty = () => {
181
+ group.unset("aaa");
182
+ };
183
+
184
+ const removeGroupProperty = () => {
185
+ group.remove("prop_key1", "prop_value11");
186
+ };
187
+
188
+ const unionGroupProperty = () => {
189
+ group.union("prop_key", ["prop_value_a", "prop_value_b"]);
190
+ };
191
+
192
+ const trackWithGroups = () => {
193
+ mixpanel.trackWithGroups(
194
+ "tracked with groups",
195
+ {a: 1, b: 2.3},
196
+ {company_id: 111}
197
+ );
198
+ };
199
+
200
+ const DATA = [
201
+ {
202
+ title: "Events and Properties",
203
+ data: [
204
+ {id: "1", label: "Track Event", onPress: track},
205
+ {id: "2", label: "Identify", onPress: identify},
206
+ {id: "3", label: "Time Event for 2 secs", onPress: timeEvent},
207
+ {
208
+ id: "4",
209
+ label: "Track Event with Properties",
210
+ onPress: trackWProperties,
211
+ },
212
+ {
213
+ id: "5",
214
+ label: "Register Super Properties",
215
+ onPress: registerSuperProperties,
216
+ },
217
+ {
218
+ id: "6",
219
+ label: "Clear Super Properties",
220
+ onPress: clearSuperProperties,
221
+ },
222
+ {
223
+ id: "7",
224
+ label: "Unregister Super Property",
225
+ onPress: unregisterSuperProperty,
226
+ },
227
+ {
228
+ id: "8",
229
+ label: "Get Super Properties",
230
+ onPress: getSuperProperties,
231
+ },
232
+ {
233
+ id: "9",
234
+ label: "Register Super Properties Once",
235
+ onPress: registerSuperPropertiesOnce,
236
+ },
237
+ {id: "10", label: "Create Alias", onPress: createAlias},
238
+ {id: "11", label: "Flush", onPress: flush},
239
+ ],
240
+ },
241
+ {
242
+ title: "GDPR",
243
+ data: [
244
+ {id: "1", label: "Opt In", onPress: optIn},
245
+ {id: "2", label: "Opt Out", onPress: optOut},
246
+ ],
247
+ },
248
+ {
249
+ title: "Profile",
250
+ data: [
251
+ {id: "2", label: "Set Property", onPress: setProperty},
252
+ {id: "3", label: "Set One Property", onPress: setOneProperty},
253
+ {
254
+ id: "4",
255
+ label: "Set One Property Once",
256
+ onPress: setOnePropertyOnce,
257
+ },
258
+ {id: "5", label: "Unset Properties", onPress: unsetProperties},
259
+ {id: "6", label: "Increment Property", onPress: incrementProperty},
260
+ {
261
+ id: "7",
262
+ label: "Remove Property Value",
263
+ onPress: removePropertyValue,
264
+ },
265
+ {id: "8", label: "Append Properties", onPress: appendProperties},
266
+ {id: "9", label: "Union Properties", onPress: unionProperties},
267
+ {
268
+ id: "10",
269
+ label: "Track Charge",
270
+ onPress: trackChargeWithoutProperties,
271
+ },
272
+ {
273
+ id: "11",
274
+ label: "Track Charge with Properties",
275
+ onPress: trackCharge,
276
+ },
277
+ {id: "12", label: "Clear Charges", onPress: clearCharges},
278
+ {id: "1", label: "Reset", onPress: reset},
279
+ {id: "13", label: "Delete User", onPress: deleteUser},
280
+ {id: "14", label: "Flush", onPress: flush},
281
+ ],
282
+ },
283
+ {
284
+ title: "Group",
285
+ data: [
286
+ {id: "1", label: "Add Group", onPress: addGroup},
287
+ {id: "2", label: "Set Group", onPress: setGroup},
288
+ {id: "3", label: "Remove Group", onPress: removeGroup},
289
+ {id: "4", label: "Delete Group", onPress: deleteGroup},
290
+ {id: "5", label: "Track With Groups", onPress: trackWithGroups},
291
+ {id: "6", label: "Set Group Property", onPress: setGroupProperty},
292
+ {id: "7", label: "Set Property Once", onPress: setGroupPropertyOnce},
293
+ {id: "8", label: "Unset Property", onPress: unsetGroupProperty},
294
+ {id: "9", label: "Remove Property", onPress: removeGroupProperty},
295
+ {id: "10", label: "Union Property", onPress: unionGroupProperty},
296
+ {id: "11", label: "Flush", onPress: flush},
297
+ ],
298
+ },
299
+ ];
300
+
301
+ const renderItem = ({item}) => (
302
+ <View style={styles.item}>
303
+ <Button title={item.label} onPress={item.onPress} color="#8A2BE2" />
304
+ </View>
305
+ );
306
+
307
+ const renderSectionHeader = ({section: {title}}) => (
308
+ <Text style={styles.header}>{title}</Text>
309
+ );
310
+
311
+ return (
312
+ <SafeAreaView style={styles.container}>
313
+ <SectionList
314
+ sections={DATA}
315
+ keyExtractor={(item, index) => item.id}
316
+ renderItem={renderItem}
317
+ renderSectionHeader={renderSectionHeader}
318
+ />
319
+ </SafeAreaView>
320
+ );
321
+ };
322
+
323
+ const styles = StyleSheet.create({
324
+ container: {
325
+ flex: 1,
326
+ },
327
+ header: {
328
+ fontSize: 20,
329
+ backgroundColor: "#f4f4f4",
330
+ padding: 10,
331
+ },
332
+ item: {
333
+ backgroundColor: "#ffffff",
334
+ padding: 10,
335
+ borderBottomWidth: 1,
336
+ borderBottomColor: "#eeeeee",
337
+ },
338
+ });
339
+
340
+ export default App;
@@ -0,0 +1,30 @@
1
+ {
2
+ "expo": {
3
+ "name": "MixpanelExpo",
4
+ "slug": "MixpanelExpo",
5
+ "version": "1.0.0",
6
+ "orientation": "portrait",
7
+ "icon": "./assets/icon.png",
8
+ "userInterfaceStyle": "light",
9
+ "splash": {
10
+ "image": "./assets/splash.png",
11
+ "resizeMode": "contain",
12
+ "backgroundColor": "#ffffff"
13
+ },
14
+ "assetBundlePatterns": [
15
+ "**/*"
16
+ ],
17
+ "ios": {
18
+ "supportsTablet": true
19
+ },
20
+ "android": {
21
+ "adaptiveIcon": {
22
+ "foregroundImage": "./assets/adaptive-icon.png",
23
+ "backgroundColor": "#ffffff"
24
+ }
25
+ },
26
+ "web": {
27
+ "favicon": "./assets/favicon.png"
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = function(api) {
2
+ api.cache(true);
3
+ return {
4
+ presets: ['babel-preset-expo'],
5
+ };
6
+ };
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "mixpanelexpo",
3
+ "version": "1.0.0",
4
+ "main": "node_modules/expo/AppEntry.js",
5
+ "scripts": {
6
+ "start": "expo start",
7
+ "android": "expo start --android",
8
+ "ios": "expo start --ios",
9
+ "web": "expo start --web"
10
+ },
11
+ "dependencies": {
12
+ "expo": "~50.0.8",
13
+ "expo-status-bar": "~1.11.1",
14
+ "mixpanel-react-native": "github:mixpanel/mixpanel-react-native#master",
15
+ "react": "18.2.0",
16
+ "react-native": "0.73.4",
17
+ "react-native-web": "~0.19.6",
18
+ "react-dom": "18.2.0",
19
+ "@expo/metro-runtime": "~3.1.3"
20
+ },
21
+ "devDependencies": {
22
+ "@babel/core": "^7.20.0"
23
+ },
24
+ "private": true
25
+ }
@@ -0,0 +1 @@
1
+ export * from "@react-native-async-storage/async-storage/jest/async-storage-mock";
@@ -0,0 +1,135 @@
1
+ import { MixpanelType } from "mixpanel-react-native/javascript/mixpanel-constants";
2
+
3
+ jest.mock("mixpanel-react-native/javascript/mixpanel-queue", () => ({
4
+ MixpanelQueueManager: {
5
+ initialize: jest.fn(),
6
+ enqueue: jest.fn(),
7
+ getQueue: jest.fn(),
8
+ spliceQueue: jest.fn(),
9
+ clearQueue: jest.fn(),
10
+ },
11
+ }));
12
+
13
+ jest.mock("mixpanel-react-native/javascript/mixpanel-persistent", () => ({
14
+ MixpanelPersistent: {
15
+ getInstance: jest.fn().mockReturnValue({
16
+ getOptedOut: jest.fn(),
17
+ }),
18
+ },
19
+ }));
20
+
21
+ jest.mock("mixpanel-react-native/javascript/mixpanel-network", () => ({
22
+ MixpanelNetwork: {
23
+ sendRequest: jest.fn(),
24
+ },
25
+ }));
26
+
27
+ jest.mock("mixpanel-react-native/javascript/mixpanel-config", () => ({
28
+ MixpanelConfig: {
29
+ getInstance: jest.fn().mockReturnValue({
30
+ getFlushInterval: jest.fn().mockReturnValue(1000),
31
+ getFlushBatchSize: jest.fn().mockReturnValue(50),
32
+ getServerURL: jest.fn(),
33
+ getUseIpAddressForGeolocation: jest.fn(),
34
+ }),
35
+ },
36
+ }));
37
+
38
+ jest.mock("mixpanel-react-native/javascript/mixpanel-logger", () => ({
39
+ MixpanelLogger: {
40
+ log: jest.fn(),
41
+ error: jest.fn(),
42
+ },
43
+ }));
44
+
45
+ const {
46
+ MixpanelCore,
47
+ } = require("mixpanel-react-native/javascript/mixpanel-core");
48
+
49
+ const {
50
+ MixpanelQueueManager,
51
+ } = require("mixpanel-react-native/javascript/mixpanel-queue");
52
+
53
+ const {
54
+ MixpanelPersistent,
55
+ } = require("mixpanel-react-native/javascript/mixpanel-persistent");
56
+
57
+ const {
58
+ MixpanelNetwork,
59
+ } = require("mixpanel-react-native/javascript/mixpanel-network");
60
+
61
+ describe("MixpanelQueueManager", () => {
62
+ let mixpanelPersistent;
63
+ const token = "test-token";
64
+ const type = MixpanelType.EVENTS;
65
+ const data = { event: "testEvent" };
66
+
67
+ beforeEach(() => {
68
+ jest.clearAllMocks();
69
+ jest.isolateModules(() => {
70
+ MixpanelPersistent.getInstance().getOptedOut.mockReturnValue(false);
71
+ MixpanelQueueManager.getQueue.mockReturnValue([]);
72
+ });
73
+ });
74
+
75
+ it("initializes the Mixpanel queue for events", async () => {
76
+ await MixpanelCore().initialize(token);
77
+ expect(MixpanelQueueManager.initialize).toHaveBeenCalledWith(
78
+ token,
79
+ expect.any(String)
80
+ );
81
+ });
82
+
83
+ it("adds data to the Mixpanel queue if not opted out and data is valid", async () => {
84
+ MixpanelPersistent.getInstance().getOptedOut.mockReturnValueOnce(false);
85
+ await MixpanelCore().addToMixpanelQueue(token, type, data);
86
+ expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
87
+ token,
88
+ type,
89
+ expect.any(Object)
90
+ );
91
+ });
92
+
93
+ it("do not add data to the Mixpanel queue if opted out", async () => {
94
+ MixpanelPersistent.getInstance().getOptedOut.mockReturnValueOnce(true);
95
+ await MixpanelCore().addToMixpanelQueue(token, type, data);
96
+ expect(MixpanelQueueManager.enqueue).toHaveBeenCalledTimes(0);
97
+ });
98
+
99
+ it("do not add data to the Mixpanel queue if data is not valid", async () => {
100
+ MixpanelPersistent.getInstance().getOptedOut.mockReturnValueOnce(false);
101
+ // mock JSON.stringify to throw an error
102
+ jest.spyOn(JSON, "stringify").mockImplementationOnce(() => {
103
+ throw new Error("mock error");
104
+ });
105
+ await MixpanelCore().addToMixpanelQueue(token, type, data);
106
+ expect(MixpanelQueueManager.enqueue).toHaveBeenCalledTimes(0);
107
+ });
108
+
109
+ it("flushes the queue", async () => {
110
+ MixpanelPersistent.getInstance().getOptedOut.mockReturnValueOnce(false);
111
+ MixpanelQueueManager.getQueue.mockImplementation((token, type) => {
112
+ return [data];
113
+ });
114
+ await MixpanelCore().flush(token);
115
+ expect(MixpanelNetwork.sendRequest).toHaveBeenCalled();
116
+ });
117
+
118
+ it("do not flush the queue if opted out", async () => {
119
+ MixpanelPersistent.getInstance().getOptedOut.mockReturnValueOnce(true);
120
+ MixpanelQueueManager.getQueue.mockImplementation((token, type) => {
121
+ return [data];
122
+ });
123
+ await MixpanelCore().flush(token);
124
+ expect(MixpanelNetwork.sendRequest).toHaveBeenCalledTimes(0);
125
+ });
126
+
127
+ it("not flushes the queue if there is no data", async () => {
128
+ MixpanelPersistent.getInstance().getOptedOut.mockReturnValueOnce(false);
129
+ MixpanelQueueManager.getQueue.mockImplementation((token, type) => {
130
+ return [];
131
+ });
132
+ await MixpanelCore().flush(token);
133
+ expect(MixpanelNetwork.sendRequest).toHaveBeenCalledTimes(0);
134
+ });
135
+ });