@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.
- package/README.md +1 -1
- package/ios/DashXReactNative.mm +7 -7
- package/ios/DashXReactNative.swift +15 -11
- package/package.json +13 -11
- package/.claude/settings.local.json +0 -7
- package/.claude/worktrees/autocapture-conventions-review-78f665/.gitattributes +0 -1
- package/.claude/worktrees/autocapture-conventions-review-78f665/.github/workflows/ci.yml +0 -67
- package/.claude/worktrees/autocapture-conventions-review-78f665/.gitignore +0 -49
- package/.claude/worktrees/autocapture-conventions-review-78f665/.nvmrc +0 -1
- package/.claude/worktrees/autocapture-conventions-review-78f665/CHANGELOG.md +0 -144
- package/.claude/worktrees/autocapture-conventions-review-78f665/CONTRIBUTING.md +0 -190
- package/.claude/worktrees/autocapture-conventions-review-78f665/DashXReactNative.podspec +0 -38
- package/.claude/worktrees/autocapture-conventions-review-78f665/LICENSE +0 -21
- package/.claude/worktrees/autocapture-conventions-review-78f665/README.md +0 -97
- package/.claude/worktrees/autocapture-conventions-review-78f665/__tests__/fallback.test.js +0 -72
- package/.claude/worktrees/autocapture-conventions-review-78f665/__tests__/index.test.js +0 -702
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gitignore +0 -10
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/checksums/checksums.lock +0 -0
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/fileChanges/last-build.bin +0 -0
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/fileHashes/fileHashes.lock +0 -0
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/8.14.3/gc.properties +0 -0
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/.gradle/vcs-1/gc.properties +0 -0
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/build/reports/problems/problems-report.html +0 -663
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/build.gradle +0 -121
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradle.properties +0 -1
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradlew +0 -185
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/gradlew.bat +0 -89
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/settings.gradle +0 -27
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/AndroidManifest.xml +0 -19
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/DashXNotificationClickedListener.kt +0 -113
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/DashXReactNativeFirebaseMessagingService.kt +0 -72
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/DashXReactNativeModuleImpl.kt +0 -445
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/MessageReceivedEventHolder.kt +0 -14
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/ReactNativeLibraryInfoProvider.kt +0 -8
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/java/com/dashx/rn/sdk/util/Utils.kt +0 -208
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/main/resources/META-INF/services/com.dashx.android.DashXLibraryInfoProvider +0 -1
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/newarch/java/com/dashx/rn/sdk/DashXReactNativeModule.kt +0 -85
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/newarch/java/com/dashx/rn/sdk/DashXReactNativePackage.kt +0 -33
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/oldarch/java/com/dashx/rn/sdk/DashXReactNativeModule.kt +0 -106
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/oldarch/java/com/dashx/rn/sdk/DashXReactNativePackage.kt +0 -33
- package/.claude/worktrees/autocapture-conventions-review-78f665/android/src/test/java/com/dashx/rn/sdk/util/UtilsTest.kt +0 -305
- package/.claude/worktrees/autocapture-conventions-review-78f665/babel.config.js +0 -3
- package/.claude/worktrees/autocapture-conventions-review-78f665/index.d.ts +0 -321
- package/.claude/worktrees/autocapture-conventions-review-78f665/index.js +0 -300
- package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXEventEmitter.swift +0 -15
- package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXNotificationHandler.swift +0 -301
- package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative-Bridging-Header.h +0 -3
- package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.mm +0 -77
- package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.swift +0 -244
- package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.xcodeproj/project.pbxproj +0 -280
- package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.xcworkspace/contents.xcworkspacedata +0 -7
- package/.claude/worktrees/autocapture-conventions-review-78f665/ios/DashXReactNative.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/.claude/worktrees/autocapture-conventions-review-78f665/package.json +0 -123
- package/.claude/worktrees/autocapture-conventions-review-78f665/scripts/generate-bridge-shim.js +0 -259
- package/.claude/worktrees/autocapture-conventions-review-78f665/src/NativeDashXReactNative.ts +0 -42
- package/.claude/worktrees/autocapture-conventions-review-78f665/yarn.lock +0 -6244
- package/.gitattributes +0 -1
- package/.github/workflows/ci.yml +0 -67
- package/.gitignore +0 -49
- package/.nvmrc +0 -1
- package/CHANGELOG.md +0 -144
- package/CONTRIBUTING.md +0 -190
- package/__tests__/fallback.test.js +0 -72
- package/__tests__/index.test.js +0 -702
- package/android/.gitignore +0 -10
- package/android/.gradle/8.14.3/checksums/checksums.lock +0 -0
- package/android/.gradle/8.14.3/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.14.3/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.14.3/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build/reports/problems/problems-report.html +0 -663
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/android/gradlew +0 -185
- package/android/gradlew.bat +0 -89
- package/android/src/test/java/com/dashx/rn/sdk/util/UtilsTest.kt +0 -305
- package/babel.config.js +0 -3
- package/scripts/generate-bridge-shim.js +0 -259
- 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
|
-
});
|