@solucx/react-native-solucx-widget 0.1.16 → 0.2.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/package.json +31 -4
- package/src/SoluCXWidget.tsx +108 -53
- package/src/__mocks__/expo-modules-core-web.js +16 -0
- package/src/__mocks__/expo-modules-core.js +33 -0
- package/src/__tests__/ClientVersionCollector.test.ts +55 -0
- package/src/__tests__/CloseButton.test.tsx +47 -0
- package/src/__tests__/Constants.test.ts +17 -0
- package/src/__tests__/InlineWidget.rendering.test.tsx +81 -0
- package/src/__tests__/ModalWidget.rendering.test.tsx +157 -0
- package/src/__tests__/OverlayWidget.rendering.test.tsx +123 -0
- package/src/__tests__/SoluCXWidget.rendering.test.tsx +504 -0
- package/src/__tests__/e2e/widget-lifecycle.test.tsx +352 -0
- package/src/__tests__/integration/webview-communication-simple.test.tsx +147 -0
- package/src/__tests__/integration/webview-communication.test.tsx +417 -0
- package/src/__tests__/useDeviceInfoCollector.test.ts +109 -0
- package/src/__tests__/useWidgetState.test.ts +76 -84
- package/src/__tests__/widgetBootstrapService.test.ts +182 -0
- package/src/components/ModalWidget.tsx +3 -5
- package/src/components/OverlayWidget.tsx +1 -1
- package/src/constants/Constants.ts +4 -0
- package/src/constants/webViewConstants.ts +1 -0
- package/src/hooks/useDeviceInfoCollector.ts +67 -0
- package/src/hooks/useWidgetState.ts +4 -4
- package/src/index.ts +4 -0
- package/src/interfaces/WidgetCallbacks.ts +14 -0
- package/src/interfaces/index.ts +3 -2
- package/src/services/ClientVersionCollector.ts +15 -0
- package/src/services/storage.ts +2 -2
- package/src/services/widgetBootstrapService.ts +67 -0
- package/src/services/widgetEventService.ts +14 -30
- package/src/services/widgetValidationService.ts +29 -13
- package/src/setupTests.js +43 -0
- package/src/styles/widgetStyles.ts +1 -1
- package/src/utils/urlUtils.ts +2 -2
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* INTEGRATION TEST: WebView Communication
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: Tests the core WebView message passing bridge between
|
|
5
|
+
* the embedded widget and React Native.
|
|
6
|
+
*
|
|
7
|
+
* Coverage Target: handleWebViewMessage, handleWebViewLoad, handleClose
|
|
8
|
+
* Previous Coverage: 0% (CRITICAL GAP)
|
|
9
|
+
*
|
|
10
|
+
* Strategy: Minimal mocking - use real WidgetEventService and components
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import React from 'react';
|
|
14
|
+
import { render, waitFor, fireEvent } from '@testing-library/react-native';
|
|
15
|
+
import { SoluCXWidget } from '../../SoluCXWidget';
|
|
16
|
+
import type { WidgetCallbacks } from '../../interfaces';
|
|
17
|
+
|
|
18
|
+
// Mock only external dependencies we can't control
|
|
19
|
+
jest.mock('react-native-webview', () => {
|
|
20
|
+
const React = require('react');
|
|
21
|
+
const { View } = require('react-native');
|
|
22
|
+
return {
|
|
23
|
+
__esModule: true,
|
|
24
|
+
WebView: React.forwardRef((props: any, ref: any) => {
|
|
25
|
+
// Store ref methods for testing
|
|
26
|
+
React.useImperativeHandle(ref, () => ({
|
|
27
|
+
injectJavaScript: jest.fn((script) => {
|
|
28
|
+
// Simulate successful injection
|
|
29
|
+
if (props.onLoadEnd) props.onLoadEnd();
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View testID="webview" {...props}>
|
|
35
|
+
{/* Expose onMessage handler for testing */}
|
|
36
|
+
{props.children}
|
|
37
|
+
</View>
|
|
38
|
+
);
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
jest.mock('../../services/widgetBootstrapService', () => ({
|
|
44
|
+
requestWidgetUrl: jest.fn().mockResolvedValue('https://mock.widget.url/form123'),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
jest.mock('../../utils/urlUtils', () => ({
|
|
48
|
+
buildWidgetURL: jest.fn().mockReturnValue('https://mock.widget.url/form123'),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
jest.mock('../../services/widgetValidationService', () => ({
|
|
52
|
+
WidgetValidationService: jest.fn().mockImplementation(() => ({
|
|
53
|
+
shouldDisplayWidget: jest.fn().mockResolvedValue({ canDisplay: true }),
|
|
54
|
+
})),
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
// Mock storage but keep it functional
|
|
58
|
+
const mockStorage: Record<string, string> = {};
|
|
59
|
+
jest.mock('@react-native-async-storage/async-storage', () => ({
|
|
60
|
+
__esModule: true,
|
|
61
|
+
default: {
|
|
62
|
+
getItem: jest.fn((key: string) => Promise.resolve(mockStorage[key] || null)),
|
|
63
|
+
setItem: jest.fn((key: string, value: string) => {
|
|
64
|
+
mockStorage[key] = value;
|
|
65
|
+
return Promise.resolve();
|
|
66
|
+
}),
|
|
67
|
+
removeItem: jest.fn((key: string) => {
|
|
68
|
+
delete mockStorage[key];
|
|
69
|
+
return Promise.resolve();
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
describe('Integration: WebView Communication', () => {
|
|
75
|
+
const baseProps = {
|
|
76
|
+
soluCXKey: 'test-key-123',
|
|
77
|
+
type: 'modal' as const,
|
|
78
|
+
data: {
|
|
79
|
+
customer_id: 'user-456',
|
|
80
|
+
form_id: 'form-789', // Required for form mode
|
|
81
|
+
},
|
|
82
|
+
options: {
|
|
83
|
+
height: 400,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
jest.clearAllMocks();
|
|
89
|
+
Object.keys(mockStorage).forEach(key => delete mockStorage[key]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Message Handling - Form Events', () => {
|
|
93
|
+
it('should handle FORM_CLOSE message and call onClosed callback', async () => {
|
|
94
|
+
const mockOnClosed = jest.fn();
|
|
95
|
+
const callbacks: WidgetCallbacks = {
|
|
96
|
+
onClosed: mockOnClosed,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const { getByTestId } = render(
|
|
100
|
+
<SoluCXWidget {...baseProps} callbacks={callbacks} />
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Wait for widget to be ready
|
|
104
|
+
await waitFor(() => {
|
|
105
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const webview = getByTestId('webview');
|
|
109
|
+
|
|
110
|
+
// Simulate WebView sending FORM_CLOSE message
|
|
111
|
+
fireEvent(webview, 'message', {
|
|
112
|
+
nativeEvent: { data: 'FORM_CLOSE' },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Verify callback was called
|
|
116
|
+
await waitFor(() => {
|
|
117
|
+
expect(mockOnClosed).toHaveBeenCalledTimes(1);
|
|
118
|
+
});
|
|
119
|
+
}, 15000); // 15 second timeout for slower CI environments
|
|
120
|
+
|
|
121
|
+
it('should handle FORM_ERROR message with error text', async () => {
|
|
122
|
+
const mockOnError = jest.fn();
|
|
123
|
+
const callbacks: WidgetCallbacks = {
|
|
124
|
+
onError: mockOnError,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const { getByTestId } = render(
|
|
128
|
+
<SoluCXWidget {...baseProps} callbacks={callbacks} />
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Simulate error message with details
|
|
136
|
+
fireEvent(getByTestId('webview'), 'message', {
|
|
137
|
+
nativeEvent: { data: 'FORM_ERROR-Network timeout occurred' },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await waitFor(() => {
|
|
141
|
+
expect(mockOnError).toHaveBeenCalledWith('Network timeout occurred');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should handle FORM_RESIZE message and update height', async () => {
|
|
146
|
+
const mockOnResize = jest.fn();
|
|
147
|
+
const callbacks: WidgetCallbacks = {
|
|
148
|
+
onResize: mockOnResize,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const { getByTestId } = render(
|
|
152
|
+
<SoluCXWidget {...baseProps} callbacks={callbacks} />
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
await waitFor(() => {
|
|
156
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Simulate resize message
|
|
160
|
+
fireEvent(getByTestId('webview'), 'message', {
|
|
161
|
+
nativeEvent: { data: 'FORM_RESIZE-650' },
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await waitFor(() => {
|
|
165
|
+
expect(mockOnResize).toHaveBeenCalledWith('650');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should handle FORM_COMPLETED message with userId', async () => {
|
|
170
|
+
const mockOnCompleted = jest.fn();
|
|
171
|
+
const callbacks: WidgetCallbacks = {
|
|
172
|
+
onCompleted: mockOnCompleted,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const { getByTestId } = render(
|
|
176
|
+
<SoluCXWidget {...baseProps} callbacks={callbacks} />
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
await waitFor(() => {
|
|
180
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Simulate form completion
|
|
184
|
+
fireEvent(getByTestId('webview'), 'message', {
|
|
185
|
+
nativeEvent: { data: 'FORM_COMPLETED' },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await waitFor(() => {
|
|
189
|
+
expect(mockOnCompleted).toHaveBeenCalledWith(expect.any(String));
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should handle FORM_PAGECHANGED message', async () => {
|
|
194
|
+
const mockOnPageChanged = jest.fn();
|
|
195
|
+
const callbacks: WidgetCallbacks = {
|
|
196
|
+
onPageChanged: mockOnPageChanged,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const { getByTestId } = render(
|
|
200
|
+
<SoluCXWidget {...baseProps} callbacks={callbacks} />
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Simulate page change
|
|
208
|
+
fireEvent(getByTestId('webview'), 'message', {
|
|
209
|
+
nativeEvent: { data: 'FORM_PAGECHANGED-2' },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await waitFor(() => {
|
|
213
|
+
expect(mockOnPageChanged).toHaveBeenCalledWith('2');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should handle QUESTION_ANSWERED message', async () => {
|
|
218
|
+
const mockOnQuestionAnswered = jest.fn();
|
|
219
|
+
const callbacks: WidgetCallbacks = {
|
|
220
|
+
onQuestionAnswered: mockOnQuestionAnswered,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const { getByTestId } = render(
|
|
224
|
+
<SoluCXWidget {...baseProps} callbacks={callbacks} />
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
await waitFor(() => {
|
|
228
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Simulate question answered
|
|
232
|
+
fireEvent(getByTestId('webview'), 'message', {
|
|
233
|
+
nativeEvent: { data: 'QUESTION_ANSWERED' },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(mockOnQuestionAnswered).toHaveBeenCalledTimes(1);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('Message Handling - Survey Events', () => {
|
|
243
|
+
const surveyProps = {
|
|
244
|
+
...baseProps,
|
|
245
|
+
data: {
|
|
246
|
+
customer_id: 'user-456',
|
|
247
|
+
// No form_id = survey mode
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
it('should handle closeSoluCXWidget survey event', async () => {
|
|
252
|
+
const mockOnClosed = jest.fn();
|
|
253
|
+
|
|
254
|
+
const { getByTestId } = render(
|
|
255
|
+
<SoluCXWidget {...surveyProps} callbacks={{ onClosed: mockOnClosed }} />
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
await waitFor(() => {
|
|
259
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Survey uses different event names
|
|
263
|
+
fireEvent(getByTestId('webview'), 'message', {
|
|
264
|
+
nativeEvent: { data: 'closeSoluCXWidget' },
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await waitFor(() => {
|
|
268
|
+
expect(mockOnClosed).toHaveBeenCalledTimes(1);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should handle resizeSoluCXWidget survey event', async () => {
|
|
273
|
+
const mockOnResize = jest.fn();
|
|
274
|
+
|
|
275
|
+
const { getByTestId } = render(
|
|
276
|
+
<SoluCXWidget {...surveyProps} callbacks={{ onResize: mockOnResize }} />
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
await waitFor(() => {
|
|
280
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
fireEvent(getByTestId('webview'), 'message', {
|
|
284
|
+
nativeEvent: { data: 'resizeSoluCXWidget-800' },
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await waitFor(() => {
|
|
288
|
+
expect(mockOnResize).toHaveBeenCalledWith('800');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should handle completeSoluCXWidget survey event', async () => {
|
|
293
|
+
const mockOnCompleted = jest.fn();
|
|
294
|
+
|
|
295
|
+
const { getByTestId } = render(
|
|
296
|
+
<SoluCXWidget {...surveyProps} callbacks={{ onCompleted: mockOnCompleted }} />
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
await waitFor(() => {
|
|
300
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
fireEvent(getByTestId('webview'), 'message', {
|
|
304
|
+
nativeEvent: { data: 'completeSoluCXWidget' },
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await waitFor(() => {
|
|
308
|
+
expect(mockOnCompleted).toHaveBeenCalled();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe('Edge Cases - Message Handling', () => {
|
|
314
|
+
it('should handle rapid successive messages', async () => {
|
|
315
|
+
const mockOnResize = jest.fn();
|
|
316
|
+
|
|
317
|
+
const { getByTestId } = render(
|
|
318
|
+
<SoluCXWidget {...baseProps} callbacks={{ onResize: mockOnResize }} />
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
await waitFor(() => {
|
|
322
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const webview = getByTestId('webview');
|
|
326
|
+
|
|
327
|
+
// Send 10 resize messages rapidly
|
|
328
|
+
for (let i = 0; i < 10; i++) {
|
|
329
|
+
fireEvent(webview, 'message', {
|
|
330
|
+
nativeEvent: { data: `FORM_RESIZE-${300 + i * 10}` },
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// All should be processed
|
|
335
|
+
await waitFor(() => {
|
|
336
|
+
expect(mockOnResize).toHaveBeenCalledTimes(10);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Verify last call had correct value
|
|
340
|
+
expect(mockOnResize).toHaveBeenLastCalledWith('390');
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('Close Button Integration', () => {
|
|
345
|
+
it('should render close button in modal', async () => {
|
|
346
|
+
const { getByText, getByTestId } = render(
|
|
347
|
+
<SoluCXWidget {...baseProps} type="modal" />
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
await waitFor(() => {
|
|
351
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
352
|
+
expect(getByText('✕')).toBeTruthy();
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('Multiple Callbacks in Single Flow', () => {
|
|
358
|
+
it('should call multiple callbacks during form interaction flow', async () => {
|
|
359
|
+
const mockOnPageChanged = jest.fn();
|
|
360
|
+
const mockOnQuestionAnswered = jest.fn();
|
|
361
|
+
const mockOnResize = jest.fn();
|
|
362
|
+
const mockOnCompleted = jest.fn();
|
|
363
|
+
const mockOnClosed = jest.fn();
|
|
364
|
+
|
|
365
|
+
const callbacks: WidgetCallbacks = {
|
|
366
|
+
onPageChanged: mockOnPageChanged,
|
|
367
|
+
onQuestionAnswered: mockOnQuestionAnswered,
|
|
368
|
+
onResize: mockOnResize,
|
|
369
|
+
onCompleted: mockOnCompleted,
|
|
370
|
+
onClosed: mockOnClosed,
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const { getByTestId } = render(
|
|
374
|
+
<SoluCXWidget {...baseProps} callbacks={callbacks} />
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
await waitFor(() => {
|
|
378
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const webview = getByTestId('webview');
|
|
382
|
+
|
|
383
|
+
// Simulate complete user flow
|
|
384
|
+
// 1. Form resizes
|
|
385
|
+
fireEvent(webview, 'message', { nativeEvent: { data: 'FORM_RESIZE-400' } });
|
|
386
|
+
|
|
387
|
+
// 2. User goes to page 2
|
|
388
|
+
fireEvent(webview, 'message', { nativeEvent: { data: 'FORM_PAGECHANGED-2' } });
|
|
389
|
+
|
|
390
|
+
// 3. User answers a question
|
|
391
|
+
fireEvent(webview, 'message', { nativeEvent: { data: 'QUESTION_ANSWERED' } });
|
|
392
|
+
|
|
393
|
+
// 4. Form resizes again
|
|
394
|
+
fireEvent(webview, 'message', { nativeEvent: { data: 'FORM_RESIZE-500' } });
|
|
395
|
+
|
|
396
|
+
// 5. User completes form
|
|
397
|
+
fireEvent(webview, 'message', { nativeEvent: { data: 'FORM_COMPLETED' } });
|
|
398
|
+
|
|
399
|
+
// 6. User closes
|
|
400
|
+
fireEvent(webview, 'message', { nativeEvent: { data: 'FORM_CLOSE' } });
|
|
401
|
+
|
|
402
|
+
// Verify all callbacks were called in order
|
|
403
|
+
await waitFor(() => {
|
|
404
|
+
expect(mockOnResize).toHaveBeenCalledTimes(2);
|
|
405
|
+
expect(mockOnPageChanged).toHaveBeenCalledTimes(1);
|
|
406
|
+
expect(mockOnQuestionAnswered).toHaveBeenCalledTimes(1);
|
|
407
|
+
expect(mockOnCompleted).toHaveBeenCalledTimes(1);
|
|
408
|
+
expect(mockOnClosed).toHaveBeenCalledTimes(1);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Verify correct arguments
|
|
412
|
+
expect(mockOnResize).toHaveBeenNthCalledWith(1, '400');
|
|
413
|
+
expect(mockOnResize).toHaveBeenNthCalledWith(2, '500');
|
|
414
|
+
expect(mockOnPageChanged).toHaveBeenCalledWith('2');
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Platform, Dimensions } from 'react-native';
|
|
2
|
+
import { getDeviceInfo } from '../hooks/useDeviceInfoCollector';
|
|
3
|
+
|
|
4
|
+
jest.mock('react-native', () => ({
|
|
5
|
+
Platform: {
|
|
6
|
+
OS: 'ios',
|
|
7
|
+
Version: '16.0',
|
|
8
|
+
constants: {},
|
|
9
|
+
},
|
|
10
|
+
Dimensions: {
|
|
11
|
+
get: jest.fn(),
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock('react-native-device-info', () => ({
|
|
16
|
+
getModel: () => 'iPhone 14',
|
|
17
|
+
}), { virtual: true });
|
|
18
|
+
|
|
19
|
+
const mockDimensionsGet = Dimensions.get as jest.MockedFunction<typeof Dimensions.get>;
|
|
20
|
+
|
|
21
|
+
describe('useDeviceInfoCollector', () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('getDeviceInfo', () => {
|
|
27
|
+
it('should return complete device information', () => {
|
|
28
|
+
mockDimensionsGet.mockImplementation((type) => {
|
|
29
|
+
if (type === 'screen') {
|
|
30
|
+
return { width: 390, height: 844, scale: 3, fontScale: 1 };
|
|
31
|
+
}
|
|
32
|
+
return { width: 390, height: 844, scale: 3, fontScale: 1 };
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const deviceInfo = getDeviceInfo();
|
|
36
|
+
|
|
37
|
+
expect(deviceInfo).toEqual({
|
|
38
|
+
platform: 'ios',
|
|
39
|
+
osVersion: '16.0',
|
|
40
|
+
screenWidth: 390,
|
|
41
|
+
screenHeight: 844,
|
|
42
|
+
windowWidth: 390,
|
|
43
|
+
windowHeight: 844,
|
|
44
|
+
scale: 3,
|
|
45
|
+
fontScale: 1,
|
|
46
|
+
deviceType: 'phone',
|
|
47
|
+
model: 'iPhone 14',
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should identify device as tablet when dimensions are large', () => {
|
|
52
|
+
mockDimensionsGet.mockImplementation((type) => {
|
|
53
|
+
if (type === 'screen') {
|
|
54
|
+
return { width: 768, height: 1024, scale: 2, fontScale: 1 };
|
|
55
|
+
}
|
|
56
|
+
return { width: 768, height: 1024, scale: 2, fontScale: 1 };
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const deviceInfo = getDeviceInfo();
|
|
60
|
+
|
|
61
|
+
expect(deviceInfo.deviceType).toBe('tablet');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should identify device as phone when dimensions are small', () => {
|
|
65
|
+
mockDimensionsGet.mockImplementation((type) => {
|
|
66
|
+
if (type === 'screen') {
|
|
67
|
+
return { width: 375, height: 812, scale: 3, fontScale: 1 };
|
|
68
|
+
}
|
|
69
|
+
return { width: 375, height: 812, scale: 3, fontScale: 1 };
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const deviceInfo = getDeviceInfo();
|
|
73
|
+
|
|
74
|
+
expect(deviceInfo.deviceType).toBe('phone');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return model from react-native-device-info', () => {
|
|
78
|
+
mockDimensionsGet.mockImplementation(() => ({
|
|
79
|
+
width: 390,
|
|
80
|
+
height: 844,
|
|
81
|
+
scale: 3,
|
|
82
|
+
fontScale: 1,
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
const deviceInfo = getDeviceInfo();
|
|
86
|
+
|
|
87
|
+
expect(deviceInfo.model).toBe('iPhone 14');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('platform detection', () => {
|
|
92
|
+
it('should detect Android platform', () => {
|
|
93
|
+
(Platform.OS as any) = 'android';
|
|
94
|
+
(Platform.Version as any) = 33;
|
|
95
|
+
|
|
96
|
+
mockDimensionsGet.mockImplementation(() => ({
|
|
97
|
+
width: 412,
|
|
98
|
+
height: 915,
|
|
99
|
+
scale: 2.625,
|
|
100
|
+
fontScale: 1,
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
const deviceInfo = getDeviceInfo();
|
|
104
|
+
|
|
105
|
+
expect(deviceInfo.platform).toBe('android');
|
|
106
|
+
expect(deviceInfo.osVersion).toBe('33');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|