@dashx/react-native 1.5.0 → 1.5.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 (85) hide show
  1. package/README.md +1 -1
  2. package/ios/DashXReactNative.mm +7 -7
  3. package/ios/DashXReactNative.swift +15 -11
  4. package/package.json +13 -11
  5. package/.claude/settings.local.json +0 -7
  6. package/.claude/worktrees/autocapture-conventions-review-78f665/.gitattributes +0 -1
  7. package/.claude/worktrees/autocapture-conventions-review-78f665/.github/workflows/ci.yml +0 -67
  8. package/.claude/worktrees/autocapture-conventions-review-78f665/.gitignore +0 -49
  9. package/.claude/worktrees/autocapture-conventions-review-78f665/.nvmrc +0 -1
  10. package/.claude/worktrees/autocapture-conventions-review-78f665/CHANGELOG.md +0 -144
  11. package/.claude/worktrees/autocapture-conventions-review-78f665/CONTRIBUTING.md +0 -190
  12. package/.claude/worktrees/autocapture-conventions-review-78f665/DashXReactNative.podspec +0 -38
  13. package/.claude/worktrees/autocapture-conventions-review-78f665/LICENSE +0 -21
  14. package/.claude/worktrees/autocapture-conventions-review-78f665/README.md +0 -97
  15. package/.claude/worktrees/autocapture-conventions-review-78f665/__tests__/fallback.test.js +0 -72
  16. package/.claude/worktrees/autocapture-conventions-review-78f665/__tests__/index.test.js +0 -702
  17. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gitignore +0 -10
  18. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/checksums/checksums.lock +0 -0
  19. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/fileChanges/last-build.bin +0 -0
  20. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/fileHashes/fileHashes.lock +0 -0
  21. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/gc.properties +0 -0
  22. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  23. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  24. package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/vcs-1/gc.properties +0 -0
  25. package/.claude/worktrees/autocapture-conventions-review-78f665/android/build/reports/problems/problems-report.html +0 -663
  26. package/.claude/worktrees/autocapture-conventions-review-78f665/android/build.gradle +0 -121
  27. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  28. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradle/wrapper/gradle-wrapper.properties +0 -6
  29. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradle.properties +0 -1
  30. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradlew +0 -185
  31. package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradlew.bat +0 -89
  32. package/.claude/worktrees/autocapture-conventions-review-78f665/android/settings.gradle +0 -27
  33. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/AndroidManifest.xml +0 -19
  34. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/DashXNotificationClickedListener.kt +0 -113
  35. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/DashXReactNativeFirebaseMessagingService.kt +0 -72
  36. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/DashXReactNativeModuleImpl.kt +0 -445
  37. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/MessageReceivedEventHolder.kt +0 -14
  38. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/ReactNativeLibraryInfoProvider.kt +0 -8
  39. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/util/Utils.kt +0 -208
  40. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/resources/META-INF/services/com.dashx.android.DashXLibraryInfoProvider +0 -1
  41. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/newarch/java/com/dashx/rn/sdk/DashXReactNativeModule.kt +0 -85
  42. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/newarch/java/com/dashx/rn/sdk/DashXReactNativePackage.kt +0 -33
  43. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/oldarch/java/com/dashx/rn/sdk/DashXReactNativeModule.kt +0 -106
  44. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/oldarch/java/com/dashx/rn/sdk/DashXReactNativePackage.kt +0 -33
  45. package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/test/java/com/dashx/rn/sdk/util/UtilsTest.kt +0 -305
  46. package/.claude/worktrees/autocapture-conventions-review-78f665/babel.config.js +0 -3
  47. package/.claude/worktrees/autocapture-conventions-review-78f665/index.d.ts +0 -321
  48. package/.claude/worktrees/autocapture-conventions-review-78f665/index.js +0 -300
  49. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXEventEmitter.swift +0 -15
  50. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXNotificationHandler.swift +0 -301
  51. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative-Bridging-Header.h +0 -3
  52. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.mm +0 -77
  53. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.swift +0 -244
  54. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.xcodeproj/project.pbxproj +0 -280
  55. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.xcworkspace/contents.xcworkspacedata +0 -7
  56. package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  57. package/.claude/worktrees/autocapture-conventions-review-78f665/package.json +0 -123
  58. package/.claude/worktrees/autocapture-conventions-review-78f665/scripts/generate-bridge-shim.js +0 -259
  59. package/.claude/worktrees/autocapture-conventions-review-78f665/src/NativeDashXReactNative.ts +0 -42
  60. package/.claude/worktrees/autocapture-conventions-review-78f665/yarn.lock +0 -6244
  61. package/.gitattributes +0 -1
  62. package/.github/workflows/ci.yml +0 -67
  63. package/.gitignore +0 -49
  64. package/.nvmrc +0 -1
  65. package/CHANGELOG.md +0 -144
  66. package/CONTRIBUTING.md +0 -190
  67. package/__tests__/fallback.test.js +0 -72
  68. package/__tests__/index.test.js +0 -702
  69. package/android/.gitignore +0 -10
  70. package/android/.gradle/8.14.3/checksums/checksums.lock +0 -0
  71. package/android/.gradle/8.14.3/fileChanges/last-build.bin +0 -0
  72. package/android/.gradle/8.14.3/fileHashes/fileHashes.lock +0 -0
  73. package/android/.gradle/8.14.3/gc.properties +0 -0
  74. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  75. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  76. package/android/.gradle/vcs-1/gc.properties +0 -0
  77. package/android/build/reports/problems/problems-report.html +0 -663
  78. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  79. package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
  80. package/android/gradlew +0 -185
  81. package/android/gradlew.bat +0 -89
  82. package/android/src/test/java/com/dashx/rn/sdk/util/UtilsTest.kt +0 -305
  83. package/babel.config.js +0 -3
  84. package/scripts/generate-bridge-shim.js +0 -259
  85. package/yarn.lock +0 -6244
@@ -1,702 +0,0 @@
1
- const mockNativeModule = {
2
- configure: jest.fn(),
3
- identify: jest.fn(),
4
- setIdentity: jest.fn(),
5
- reset: jest.fn(),
6
- track: jest.fn(),
7
- screen: jest.fn(),
8
- fetchRecord: jest.fn(),
9
- searchRecords: jest.fn(),
10
- fetchStoredPreferences: jest.fn(),
11
- saveStoredPreferences: jest.fn(),
12
- subscribe: jest.fn(),
13
- unsubscribe: jest.fn(),
14
- setLogLevel: jest.fn(),
15
- uploadAsset: jest.fn(),
16
- fetchAsset: jest.fn(),
17
- enableLifecycleTracking: jest.fn(),
18
- enableAdTracking: jest.fn(),
19
- processURL: jest.fn(),
20
- trackNotificationNavigation: jest.fn(),
21
- requestNotificationPermission: jest.fn(),
22
- getNotificationPermissionStatus: jest.fn(),
23
- addListener: jest.fn(),
24
- removeListeners: jest.fn(),
25
- };
26
-
27
- const mockAddListener = jest.fn(() => ({ remove: jest.fn() }));
28
-
29
- jest.mock('react-native', () => ({
30
- NativeModules: {
31
- DashXReactNative: mockNativeModule,
32
- },
33
- NativeEventEmitter: jest.fn().mockImplementation(() => ({
34
- addListener: mockAddListener,
35
- })),
36
- Platform: {
37
- OS: 'ios',
38
- select: jest.fn((obj) => obj.default || ''),
39
- },
40
- TurboModuleRegistry: {
41
- get: jest.fn(() => mockNativeModule),
42
- getEnforcing: jest.fn(() => mockNativeModule),
43
- },
44
- }));
45
-
46
- jest.mock('../src/NativeDashXReactNative', () => ({
47
- __esModule: true,
48
- default: mockNativeModule,
49
- }));
50
-
51
- const DashX = require('../index').default;
52
-
53
- beforeEach(() => {
54
- jest.clearAllMocks();
55
- });
56
-
57
- // --- configure ---
58
-
59
- describe('configure', () => {
60
- it('calls native configure with options', () => {
61
- DashX.configure({ publicKey: 'pk_test' });
62
- expect(mockNativeModule.configure).toHaveBeenCalledWith({
63
- publicKey: 'pk_test',
64
- });
65
- });
66
-
67
- it('throws if options is not an object', () => {
68
- expect(() => DashX.configure(null)).toThrow('options must be an object');
69
- expect(() => DashX.configure('string')).toThrow('options must be an object');
70
- });
71
-
72
- it('throws if publicKey is missing', () => {
73
- expect(() => DashX.configure({})).toThrow('publicKey is required');
74
- });
75
-
76
- it('throws if publicKey is not a string', () => {
77
- expect(() => DashX.configure({ publicKey: 123 })).toThrow(
78
- 'publicKey is required and must be a string'
79
- );
80
- });
81
- });
82
-
83
- // --- identify ---
84
-
85
- describe('identify', () => {
86
- it('calls native identify with options', () => {
87
- DashX.identify({ email: 'test@example.com' });
88
- expect(mockNativeModule.identify).toHaveBeenCalledWith({
89
- email: 'test@example.com',
90
- });
91
- });
92
-
93
- it('throws if options is not an object', () => {
94
- expect(() => DashX.identify(null)).toThrow('options must be an object');
95
- });
96
- });
97
-
98
- // --- setIdentity ---
99
-
100
- describe('setIdentity', () => {
101
- it('calls native setIdentity with uid and token', () => {
102
- DashX.setIdentity('user-1', 'token-abc');
103
- expect(mockNativeModule.setIdentity).toHaveBeenCalledWith(
104
- 'user-1',
105
- 'token-abc'
106
- );
107
- });
108
-
109
- it('passes null uid through to native', () => {
110
- DashX.setIdentity(null, 'token');
111
- expect(mockNativeModule.setIdentity).toHaveBeenCalledWith(null, 'token');
112
- });
113
-
114
- it('normalizes undefined uid to null', () => {
115
- DashX.setIdentity(undefined, 'token');
116
- expect(mockNativeModule.setIdentity).toHaveBeenCalledWith(null, 'token');
117
- });
118
-
119
- it('passes empty-string uid through to native without coercing to null', () => {
120
- DashX.setIdentity('', 'token');
121
- expect(mockNativeModule.setIdentity).toHaveBeenCalledWith('', 'token');
122
- });
123
-
124
- it('throws if uid is not a string or null', () => {
125
- expect(() => DashX.setIdentity(42, 'token')).toThrow(
126
- 'uid must be a string or null'
127
- );
128
- });
129
-
130
- it('passes null token through to native', () => {
131
- DashX.setIdentity('uid', null);
132
- expect(mockNativeModule.setIdentity).toHaveBeenCalledWith('uid', null);
133
- });
134
-
135
- it('passes empty-string token through to native without coercing to null', () => {
136
- DashX.setIdentity('uid', '');
137
- expect(mockNativeModule.setIdentity).toHaveBeenCalledWith('uid', '');
138
- });
139
-
140
- it('passes undefined token as null to native', () => {
141
- DashX.setIdentity('uid');
142
- expect(mockNativeModule.setIdentity).toHaveBeenCalledWith('uid', null);
143
- });
144
-
145
- it('throws if token is not a string or null', () => {
146
- expect(() => DashX.setIdentity('uid', 42)).toThrow(
147
- 'token must be a string or null'
148
- );
149
- });
150
- });
151
-
152
- // --- reset ---
153
-
154
- describe('reset', () => {
155
- it('calls native reset', () => {
156
- DashX.reset();
157
- expect(mockNativeModule.reset).toHaveBeenCalled();
158
- });
159
- });
160
-
161
- // --- track ---
162
-
163
- describe('track', () => {
164
- it('calls native track with event and data', () => {
165
- DashX.track('purchase', { amount: 100 });
166
- expect(mockNativeModule.track).toHaveBeenCalledWith('purchase', {
167
- amount: 100,
168
- });
169
- });
170
-
171
- it('calls native track with null data when omitted', () => {
172
- DashX.track('page_view');
173
- expect(mockNativeModule.track).toHaveBeenCalledWith('page_view', null);
174
- });
175
-
176
- it('throws if event is missing', () => {
177
- expect(() => DashX.track(null)).toThrow('event is required');
178
- });
179
-
180
- it('throws if event is not a string', () => {
181
- expect(() => DashX.track(42)).toThrow(
182
- 'event is required and must be a string'
183
- );
184
- });
185
- });
186
-
187
- // --- screen ---
188
-
189
- describe('screen', () => {
190
- it('calls native screen with name and data', () => {
191
- DashX.screen('Home', { section: 'top' });
192
- expect(mockNativeModule.screen).toHaveBeenCalledWith('Home', {
193
- section: 'top',
194
- });
195
- });
196
-
197
- it('calls native screen with null data when omitted', () => {
198
- DashX.screen('Settings');
199
- expect(mockNativeModule.screen).toHaveBeenCalledWith('Settings', null);
200
- });
201
-
202
- it('throws if screenName is missing', () => {
203
- expect(() => DashX.screen(null)).toThrow('screenName is required');
204
- });
205
- });
206
-
207
- // --- fetchRecord ---
208
-
209
- describe('fetchRecord', () => {
210
- it('calls native fetchRecord with urn and options', () => {
211
- mockNativeModule.fetchRecord.mockResolvedValue({ id: '123' });
212
- const result = DashX.fetchRecord('article/123', { preview: true });
213
- expect(mockNativeModule.fetchRecord).toHaveBeenCalledWith('article/123', {
214
- preview: true,
215
- });
216
- return expect(result).resolves.toEqual({ id: '123' });
217
- });
218
-
219
- it('calls native fetchRecord with empty options when omitted', () => {
220
- mockNativeModule.fetchRecord.mockResolvedValue({});
221
- DashX.fetchRecord('article/456');
222
- expect(mockNativeModule.fetchRecord).toHaveBeenCalledWith(
223
- 'article/456',
224
- {}
225
- );
226
- });
227
-
228
- it('throws if urn is missing', () => {
229
- expect(() => DashX.fetchRecord(null)).toThrow('urn is required');
230
- });
231
- });
232
-
233
- // --- searchRecords ---
234
-
235
- describe('searchRecords', () => {
236
- it('calls native searchRecords with resource and options', () => {
237
- mockNativeModule.searchRecords.mockResolvedValue([{ id: '1' }]);
238
- const result = DashX.searchRecords('articles', { limit: 10 });
239
- expect(mockNativeModule.searchRecords).toHaveBeenCalledWith('articles', {
240
- limit: 10,
241
- });
242
- return expect(result).resolves.toEqual([{ id: '1' }]);
243
- });
244
-
245
- it('throws if resource is missing', () => {
246
- expect(() => DashX.searchRecords(null)).toThrow('resource is required');
247
- });
248
- });
249
-
250
- // --- fetchStoredPreferences ---
251
-
252
- describe('fetchStoredPreferences', () => {
253
- it('calls native fetchStoredPreferences', () => {
254
- mockNativeModule.fetchStoredPreferences.mockResolvedValue({
255
- email: true,
256
- });
257
- const result = DashX.fetchStoredPreferences();
258
- expect(mockNativeModule.fetchStoredPreferences).toHaveBeenCalled();
259
- return expect(result).resolves.toEqual({ email: true });
260
- });
261
- });
262
-
263
- // --- saveStoredPreferences ---
264
-
265
- describe('saveStoredPreferences', () => {
266
- it('calls native saveStoredPreferences with data', () => {
267
- mockNativeModule.saveStoredPreferences.mockResolvedValue({ success: true });
268
- const result = DashX.saveStoredPreferences({ email: false });
269
- expect(mockNativeModule.saveStoredPreferences).toHaveBeenCalledWith({
270
- email: false,
271
- });
272
- return expect(result).resolves.toEqual({ success: true });
273
- });
274
-
275
- it('throws if preferenceData is not an object', () => {
276
- expect(() => DashX.saveStoredPreferences(null)).toThrow(
277
- 'preferenceData is required'
278
- );
279
- });
280
- });
281
-
282
- // --- subscribe / unsubscribe ---
283
-
284
- describe('subscribe', () => {
285
- it('calls native subscribe', () => {
286
- DashX.subscribe();
287
- expect(mockNativeModule.subscribe).toHaveBeenCalled();
288
- });
289
- });
290
-
291
- describe('unsubscribe', () => {
292
- it('calls native unsubscribe and forwards the resolved success payload', async () => {
293
- mockNativeModule.unsubscribe.mockResolvedValue({ success: true });
294
- const result = await DashX.unsubscribe();
295
- expect(mockNativeModule.unsubscribe).toHaveBeenCalled();
296
- expect(result).toEqual({ success: true });
297
- });
298
-
299
- it('forwards `success: false` (the no-matching-contact non-error outcome)', async () => {
300
- mockNativeModule.unsubscribe.mockResolvedValue({ success: false });
301
- const result = await DashX.unsubscribe();
302
- expect(result).toEqual({ success: false });
303
- });
304
-
305
- it('propagates the native rejection to the caller', async () => {
306
- mockNativeModule.unsubscribe.mockRejectedValue(new Error('EUNSPECIFIED'));
307
- await expect(DashX.unsubscribe()).rejects.toThrow('EUNSPECIFIED');
308
- });
309
- });
310
-
311
- // --- setLogLevel ---
312
-
313
- describe('setLogLevel', () => {
314
- it('calls native setLogLevel with level', () => {
315
- DashX.setLogLevel(2);
316
- expect(mockNativeModule.setLogLevel).toHaveBeenCalledWith(2);
317
- });
318
-
319
- it('throws if level is not a number', () => {
320
- expect(() => DashX.setLogLevel('debug')).toThrow(
321
- 'level must be a number'
322
- );
323
- });
324
- });
325
-
326
- // --- onPushNotificationReceived ---
327
-
328
- describe('onPushNotificationReceived', () => {
329
- it('throws if callback is not a function', () => {
330
- expect(() => DashX.onPushNotificationReceived('not a function')).toThrow(
331
- 'callback must be a function'
332
- );
333
- });
334
-
335
- it('registers a listener for messageReceived events', () => {
336
- const callback = jest.fn();
337
- const subscription = DashX.onPushNotificationReceived(callback);
338
- expect(mockAddListener).toHaveBeenCalledWith('messageReceived', callback);
339
- expect(subscription).toBeDefined();
340
- });
341
- });
342
-
343
- // --- onMessageReceived (deprecated alias) ---
344
-
345
- describe('onMessageReceived (deprecated)', () => {
346
- let warnSpy;
347
-
348
- beforeEach(() => {
349
- warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
350
- });
351
-
352
- afterEach(() => {
353
- warnSpy.mockRestore();
354
- });
355
-
356
- it('registers a listener and emits a deprecation warning on first call', () => {
357
- const callback = jest.fn();
358
- const subscription = DashX.onMessageReceived(callback);
359
- expect(mockAddListener).toHaveBeenCalledWith('messageReceived', callback);
360
- expect(subscription).toBeDefined();
361
- expect(warnSpy).toHaveBeenCalledWith(
362
- expect.stringContaining('onMessageReceived is deprecated')
363
- );
364
- });
365
-
366
- it('warns only once across repeated calls', () => {
367
- warnSpy.mockClear();
368
- DashX.onMessageReceived(jest.fn());
369
- DashX.onMessageReceived(jest.fn());
370
- DashX.onMessageReceived(jest.fn());
371
- // The first call across the whole test run already consumed the one-shot
372
- // warning (from the previous test). No new warnings should fire.
373
- expect(warnSpy).not.toHaveBeenCalled();
374
- });
375
- });
376
-
377
- // --- uploadAsset ---
378
-
379
- describe('uploadAsset', () => {
380
- it('calls native uploadAsset with filePath, resource, attribute', () => {
381
- mockNativeModule.uploadAsset.mockResolvedValue({ status: 'ready' });
382
- const result = DashX.uploadAsset('/path/to/file.jpg', 'products', 'image');
383
- expect(mockNativeModule.uploadAsset).toHaveBeenCalledWith(
384
- '/path/to/file.jpg',
385
- 'products',
386
- 'image'
387
- );
388
- return expect(result).resolves.toEqual({ status: 'ready' });
389
- });
390
-
391
- it('throws if filePath is missing', () => {
392
- expect(() => DashX.uploadAsset(null, 'r', 'a')).toThrow('filePath must be a string');
393
- });
394
-
395
- it('throws if resource is missing', () => {
396
- expect(() => DashX.uploadAsset('/path', null, 'a')).toThrow('resource must be a string');
397
- });
398
-
399
- it('throws if attribute is missing', () => {
400
- expect(() => DashX.uploadAsset('/path', 'r', null)).toThrow('attribute must be a string');
401
- });
402
- });
403
-
404
- // --- fetchAsset ---
405
-
406
- describe('fetchAsset', () => {
407
- it('calls native fetchAsset with assetId', () => {
408
- mockNativeModule.fetchAsset.mockResolvedValue({ status: 'ready' });
409
- const result = DashX.fetchAsset('asset-123');
410
- expect(mockNativeModule.fetchAsset).toHaveBeenCalledWith('asset-123');
411
- return expect(result).resolves.toEqual({ status: 'ready' });
412
- });
413
-
414
- it('throws if assetId is missing', () => {
415
- expect(() => DashX.fetchAsset(null)).toThrow('assetId must be a string');
416
- });
417
- });
418
-
419
- // --- processURL ---
420
-
421
- describe('processURL', () => {
422
- it('calls native processURL with defaults', () => {
423
- DashX.processURL('https://example.com/path');
424
- expect(mockNativeModule.processURL).toHaveBeenCalledWith(
425
- 'https://example.com/path',
426
- null,
427
- true
428
- );
429
- });
430
-
431
- it('passes source option', () => {
432
- DashX.processURL('https://example.com', { source: 'universal_link' });
433
- expect(mockNativeModule.processURL).toHaveBeenCalledWith(
434
- 'https://example.com',
435
- 'universal_link',
436
- true
437
- );
438
- });
439
-
440
- it('passes forwardToLinkHandler as false', () => {
441
- DashX.processURL('https://example.com', { forwardToLinkHandler: false });
442
- expect(mockNativeModule.processURL).toHaveBeenCalledWith(
443
- 'https://example.com',
444
- null,
445
- false
446
- );
447
- });
448
-
449
- it('throws if url is missing', () => {
450
- expect(() => DashX.processURL(null)).toThrow('url is required');
451
- });
452
-
453
- it('throws if url is not a string', () => {
454
- expect(() => DashX.processURL(123)).toThrow(
455
- 'url is required and must be a string'
456
- );
457
- });
458
-
459
- it('no-ops on Android', () => {
460
- const { Platform } = require('react-native');
461
- Platform.OS = 'android';
462
- DashX.processURL('https://example.com');
463
- expect(mockNativeModule.processURL).not.toHaveBeenCalled();
464
- Platform.OS = 'ios';
465
- });
466
- });
467
-
468
- // --- trackNotificationNavigation ---
469
-
470
- describe('trackNotificationNavigation', () => {
471
- it('calls native with deepLink action', () => {
472
- DashX.trackNotificationNavigation(
473
- { type: 'deepLink', url: 'https://example.com' },
474
- 'notif-1'
475
- );
476
- expect(mockNativeModule.trackNotificationNavigation).toHaveBeenCalledWith(
477
- { type: 'deepLink', url: 'https://example.com' },
478
- 'notif-1'
479
- );
480
- });
481
-
482
- it('calls native with screen action', () => {
483
- DashX.trackNotificationNavigation(
484
- { type: 'screen', name: 'Home', data: { ref: 'banner' } },
485
- 'notif-2'
486
- );
487
- expect(mockNativeModule.trackNotificationNavigation).toHaveBeenCalledWith(
488
- { type: 'screen', name: 'Home', data: { ref: 'banner' } },
489
- 'notif-2'
490
- );
491
- });
492
-
493
- it('calls native with richLanding action', () => {
494
- DashX.trackNotificationNavigation(
495
- { type: 'richLanding', url: 'https://promo.example.com' },
496
- 'notif-3'
497
- );
498
- expect(mockNativeModule.trackNotificationNavigation).toHaveBeenCalledWith(
499
- { type: 'richLanding', url: 'https://promo.example.com' },
500
- 'notif-3'
501
- );
502
- });
503
-
504
- it('calls native with clickAction action', () => {
505
- DashX.trackNotificationNavigation(
506
- { type: 'clickAction', action: 'OPEN_SETTINGS' },
507
- 'notif-4'
508
- );
509
- expect(mockNativeModule.trackNotificationNavigation).toHaveBeenCalledWith(
510
- { type: 'clickAction', action: 'OPEN_SETTINGS' },
511
- 'notif-4'
512
- );
513
- });
514
-
515
- it('calls native with null action when omitted', () => {
516
- DashX.trackNotificationNavigation(null, 'notif-5');
517
- expect(mockNativeModule.trackNotificationNavigation).toHaveBeenCalledWith(
518
- null,
519
- 'notif-5'
520
- );
521
- });
522
-
523
- it('calls native with null for both when both omitted', () => {
524
- DashX.trackNotificationNavigation();
525
- expect(mockNativeModule.trackNotificationNavigation).toHaveBeenCalledWith(
526
- null,
527
- null
528
- );
529
- });
530
-
531
- it('throws if action is not an object', () => {
532
- expect(() => DashX.trackNotificationNavigation('bad')).toThrow(
533
- 'action must be an object or null'
534
- );
535
- });
536
-
537
- it('no-ops on Android', () => {
538
- const { Platform } = require('react-native');
539
- Platform.OS = 'android';
540
- DashX.trackNotificationNavigation({ type: 'deepLink', url: 'https://example.com' });
541
- expect(mockNativeModule.trackNotificationNavigation).not.toHaveBeenCalled();
542
- Platform.OS = 'ios';
543
- });
544
- });
545
-
546
- // --- requestNotificationPermission / getNotificationPermissionStatus ---
547
-
548
- describe('requestNotificationPermission', () => {
549
- it('calls native requestNotificationPermission', () => {
550
- mockNativeModule.requestNotificationPermission.mockResolvedValue(2);
551
- const result = DashX.requestNotificationPermission();
552
- expect(mockNativeModule.requestNotificationPermission).toHaveBeenCalled();
553
- return expect(result).resolves.toBe(2);
554
- });
555
- });
556
-
557
- describe('getNotificationPermissionStatus', () => {
558
- it('calls native getNotificationPermissionStatus', () => {
559
- mockNativeModule.getNotificationPermissionStatus.mockResolvedValue(1);
560
- const result = DashX.getNotificationPermissionStatus();
561
- expect(mockNativeModule.getNotificationPermissionStatus).toHaveBeenCalled();
562
- return expect(result).resolves.toBe(1);
563
- });
564
- });
565
-
566
- // --- onLinkReceived ---
567
-
568
- describe('onLinkReceived', () => {
569
- it('throws if callback is not a function', () => {
570
- expect(() => DashX.onLinkReceived('not a function')).toThrow(
571
- 'callback must be a function'
572
- );
573
- });
574
-
575
- it('registers a listener for linkReceived events', () => {
576
- const callback = jest.fn();
577
- const subscription = DashX.onLinkReceived(callback);
578
- expect(mockAddListener).toHaveBeenCalledWith('linkReceived', callback);
579
- expect(subscription).toBeDefined();
580
- });
581
- });
582
-
583
- // --- enableLifecycleTracking ---
584
-
585
- describe('enableLifecycleTracking', () => {
586
- it.each(['ios', 'android'])('calls native on %s', (os) => {
587
- const { Platform } = require('react-native');
588
- Platform.OS = os;
589
- DashX.enableLifecycleTracking();
590
- expect(mockNativeModule.enableLifecycleTracking).toHaveBeenCalledTimes(1);
591
- Platform.OS = 'ios';
592
- });
593
- });
594
-
595
- // --- trackNavigation ---
596
-
597
- function createNavigationRef({ ready = true, routeName = 'Home' } = {}) {
598
- const listeners = [];
599
- const ref = {
600
- ready,
601
- route: routeName ? { name: routeName } : undefined,
602
- addListener: jest.fn((event, callback) => {
603
- listeners.push({ event, callback });
604
- return jest.fn(() => {
605
- const index = listeners.findIndex((l) => l.callback === callback);
606
- if (index !== -1) listeners.splice(index, 1);
607
- });
608
- }),
609
- getCurrentRoute: jest.fn(() => ref.route),
610
- isReady: jest.fn(() => ref.ready),
611
- navigate(name, params) {
612
- ref.route = { name, params };
613
- ref.emitState();
614
- },
615
- emitState() {
616
- listeners
617
- .filter((l) => l.event === 'state')
618
- .forEach((l) => l.callback({ data: { state: {} } }));
619
- },
620
- };
621
- return ref;
622
- }
623
-
624
- describe('trackNavigation', () => {
625
- it('sends the current route once when the ref is ready', () => {
626
- const ref = createNavigationRef();
627
- DashX.trackNavigation(ref);
628
- expect(mockNativeModule.screen).toHaveBeenCalledTimes(1);
629
- expect(mockNativeModule.screen).toHaveBeenCalledWith('Home', null);
630
- expect(ref.addListener).toHaveBeenCalledWith('state', expect.any(Function));
631
- });
632
-
633
- it('waits for readiness before sending the first screen', () => {
634
- const ref = createNavigationRef({ ready: false });
635
- DashX.trackNavigation(ref);
636
- expect(ref.getCurrentRoute).not.toHaveBeenCalled();
637
- expect(mockNativeModule.screen).not.toHaveBeenCalled();
638
-
639
- ref.ready = true;
640
- ref.emitState();
641
- expect(mockNativeModule.screen).toHaveBeenCalledTimes(1);
642
- expect(mockNativeModule.screen).toHaveBeenCalledWith('Home', null);
643
- });
644
-
645
- it('does not resend when the route name is unchanged', () => {
646
- const ref = createNavigationRef();
647
- DashX.trackNavigation(ref);
648
- ref.emitState();
649
- ref.navigate('Home', { tab: 'feed' });
650
- expect(mockNativeModule.screen).toHaveBeenCalledTimes(1);
651
- });
652
-
653
- it('sends the new route name on a route change, without params', () => {
654
- const ref = createNavigationRef();
655
- DashX.trackNavigation(ref);
656
- ref.navigate('Profile', { userId: 'u_1' });
657
- ref.navigate('Home');
658
- expect(mockNativeModule.screen.mock.calls).toEqual([
659
- ['Home', null],
660
- ['Profile', null],
661
- ['Home', null],
662
- ]);
663
- });
664
-
665
- it('stops sending after unsubscribe', () => {
666
- const ref = createNavigationRef();
667
- const stop = DashX.trackNavigation(ref);
668
- stop();
669
- ref.navigate('Profile');
670
- expect(mockNativeModule.screen).toHaveBeenCalledTimes(1);
671
- });
672
-
673
- it('resolves a plain React ref through current', () => {
674
- const navigation = createNavigationRef();
675
- DashX.trackNavigation({ current: navigation });
676
- navigation.navigate('Profile');
677
- expect(mockNativeModule.screen.mock.calls).toEqual([
678
- ['Home', null],
679
- ['Profile', null],
680
- ]);
681
- });
682
-
683
- it('warns and returns a no-op when a plain ref has a null current', () => {
684
- const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
685
- const stop = DashX.trackNavigation({ current: null });
686
- expect(warn).toHaveBeenCalledWith(
687
- expect.stringContaining('navigationRef.current is null')
688
- );
689
- expect(() => stop()).not.toThrow();
690
- expect(mockNativeModule.screen).not.toHaveBeenCalled();
691
- warn.mockRestore();
692
- });
693
-
694
- it('throws on a value that is not a navigation ref', () => {
695
- expect(() => DashX.trackNavigation(null)).toThrow(
696
- 'navigationRef must be a NavigationContainer ref'
697
- );
698
- expect(() => DashX.trackNavigation({ current: {} })).toThrow(
699
- 'navigationRef must be a NavigationContainer ref'
700
- );
701
- });
702
- });
@@ -1,10 +0,0 @@
1
- # Build outputs
2
- build/
3
-
4
- # Gradle
5
- .gradle/
6
- local.properties
7
-
8
- # IDE
9
- .idea/
10
- *.iml