@solucx/react-native-solucx-widget 0.1.15 → 0.2.0
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.intern.md +513 -513
- package/README.md +285 -285
- package/package.json +50 -23
- package/src/SoluCXWidget.tsx +172 -119
- 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 +315 -0
- package/src/__tests__/e2e/widget-lifecycle.test.tsx +353 -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__/urlUtils.test.ts +56 -56
- package/src/__tests__/useDeviceInfoCollector.test.ts +109 -0
- package/src/__tests__/useWidgetState.test.ts +181 -189
- package/src/__tests__/widgetBootstrapService.test.ts +182 -0
- package/src/components/CloseButton.tsx +36 -36
- package/src/components/InlineWidget.tsx +36 -36
- package/src/components/ModalWidget.tsx +57 -59
- package/src/components/OverlayWidget.tsx +88 -88
- package/src/constants/Constants.ts +4 -0
- package/src/constants/webViewConstants.ts +15 -14
- package/src/hooks/index.ts +2 -2
- package/src/hooks/useDeviceInfoCollector.ts +67 -0
- package/src/hooks/useHeightAnimation.ts +22 -22
- package/src/hooks/useWidgetHeight.ts +38 -38
- package/src/hooks/useWidgetState.ts +101 -101
- package/src/index.ts +12 -8
- package/src/interfaces/WidgetCallbacks.ts +15 -0
- package/src/interfaces/WidgetData.ts +19 -19
- package/src/interfaces/WidgetOptions.ts +7 -7
- package/src/interfaces/WidgetResponse.ts +15 -15
- package/src/interfaces/WidgetSamplerLog.ts +5 -5
- package/src/interfaces/index.ts +25 -24
- package/src/services/ClientVersionCollector.ts +15 -0
- package/src/services/storage.ts +21 -21
- package/src/services/widgetBootstrapService.ts +67 -0
- package/src/services/widgetEventService.ts +110 -111
- package/src/services/widgetValidationService.ts +102 -86
- package/src/setupTests.js +43 -0
- package/src/styles/widgetStyles.ts +58 -58
- package/src/utils/urlUtils.ts +13 -13
|
@@ -1,189 +1,181 @@
|
|
|
1
|
-
import { WidgetEventService } from '../services/widgetEventService';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
expect(
|
|
68
|
-
expect(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
expect(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
expect(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
expect(
|
|
130
|
-
expect(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
expect(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
expect(
|
|
163
|
-
expect(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const result = await
|
|
177
|
-
|
|
178
|
-
expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
|
|
179
|
-
expect(result).toEqual({ status: '
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('should adapt resizeSoluCXWidget to FORM_RESIZE', async () => {
|
|
183
|
-
const result = await service.handleMessage('resizeSoluCXWidget-400', false);
|
|
184
|
-
|
|
185
|
-
expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(true);
|
|
186
|
-
expect(mockResize).toHaveBeenCalledWith('400');
|
|
187
|
-
expect(result).toEqual({ status: 'success' });
|
|
188
|
-
});
|
|
189
|
-
});
|
|
1
|
+
import { WidgetEventService } from '../services/widgetEventService';
|
|
2
|
+
import type { WidgetCallbacks } from '../interfaces';
|
|
3
|
+
|
|
4
|
+
describe('WidgetEventService', () => {
|
|
5
|
+
let mockSetIsWidgetVisible: jest.Mock;
|
|
6
|
+
let mockResize: jest.Mock;
|
|
7
|
+
let mockUserId: string;
|
|
8
|
+
let mockCallbacks: WidgetCallbacks;
|
|
9
|
+
let service: WidgetEventService;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
|
|
14
|
+
mockSetIsWidgetVisible = jest.fn();
|
|
15
|
+
mockResize = jest.fn();
|
|
16
|
+
mockUserId = 'test-user-123';
|
|
17
|
+
mockCallbacks = {
|
|
18
|
+
onClosed: jest.fn(),
|
|
19
|
+
onError: jest.fn(),
|
|
20
|
+
onPageChanged: jest.fn(),
|
|
21
|
+
onQuestionAnswered: jest.fn(),
|
|
22
|
+
onCompleted: jest.fn(),
|
|
23
|
+
onPartialCompleted: jest.fn(),
|
|
24
|
+
onResize: jest.fn(),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
service = new WidgetEventService(
|
|
28
|
+
mockSetIsWidgetVisible,
|
|
29
|
+
mockResize,
|
|
30
|
+
mockUserId,
|
|
31
|
+
mockCallbacks,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
jest.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should handle FORM_CLOSE event correctly', async () => {
|
|
41
|
+
const result = await service.handleMessage('FORM_CLOSE', true);
|
|
42
|
+
|
|
43
|
+
expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
|
|
44
|
+
expect(mockCallbacks.onClosed).toHaveBeenCalledTimes(1);
|
|
45
|
+
expect(result).toEqual({ status: 'success' });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle FORM_RESIZE event correctly', async () => {
|
|
49
|
+
const result = await service.handleMessage('FORM_RESIZE-350', true);
|
|
50
|
+
|
|
51
|
+
expect(mockResize).toHaveBeenCalledWith('350');
|
|
52
|
+
expect(mockCallbacks.onResize).toHaveBeenCalledWith('350');
|
|
53
|
+
expect(result).toEqual({ status: 'success' });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle FORM_ERROR event correctly', async () => {
|
|
57
|
+
const result = await service.handleMessage('FORM_ERROR-Something went wrong', true);
|
|
58
|
+
|
|
59
|
+
expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
|
|
60
|
+
expect(mockCallbacks.onError).toHaveBeenCalledWith('Something went wrong');
|
|
61
|
+
expect(result).toEqual({ status: 'error', message: 'Something went wrong' });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle FORM_PAGECHANGED event correctly', async () => {
|
|
65
|
+
const result = await service.handleMessage('FORM_PAGECHANGED-page2', true);
|
|
66
|
+
|
|
67
|
+
expect(mockCallbacks.onPageChanged).toHaveBeenCalledWith('page2');
|
|
68
|
+
expect(result).toEqual({ status: 'success' });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle QUESTION_ANSWERED event correctly', async () => {
|
|
72
|
+
const result = await service.handleMessage('QUESTION_ANSWERED', true);
|
|
73
|
+
|
|
74
|
+
expect(mockCallbacks.onQuestionAnswered).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(result).toEqual({ status: 'success' });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should handle FORM_COMPLETED event correctly', async () => {
|
|
79
|
+
const result = await service.handleMessage('FORM_COMPLETED', true);
|
|
80
|
+
|
|
81
|
+
expect(mockCallbacks.onPartialCompleted).toHaveBeenCalledWith('test-user-123');
|
|
82
|
+
expect(result).toEqual({ status: 'success' });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should handle FORM_PARTIALCOMPLETED event correctly', async () => {
|
|
86
|
+
const result = await service.handleMessage('FORM_PARTIALCOMPLETED', true);
|
|
87
|
+
|
|
88
|
+
expect(mockCallbacks.onPartialCompleted).toHaveBeenCalledWith('test-user-123');
|
|
89
|
+
expect(result).toEqual({ status: 'success' });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return error for unknown events', async () => {
|
|
93
|
+
const result = await service.handleMessage('UNKNOWN_EVENT', true);
|
|
94
|
+
|
|
95
|
+
expect(result).toEqual({ status: 'error', message: 'Unknown event' });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should return error when FORM_OPENED is sent since it was removed from event handlers', async () => {
|
|
99
|
+
const result = await service.handleMessage('FORM_OPENED', true);
|
|
100
|
+
|
|
101
|
+
expect(result).toEqual({ status: 'error', message: 'Unknown event' });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should adapt closeSoluCXWidget to FORM_CLOSE', async () => {
|
|
105
|
+
const result = await service.handleMessage('closeSoluCXWidget', false);
|
|
106
|
+
|
|
107
|
+
expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
|
|
108
|
+
expect(mockCallbacks.onClosed).toHaveBeenCalledTimes(1);
|
|
109
|
+
expect(result).toEqual({ status: 'success' });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should adapt completeSoluCXWidget to FORM_COMPLETED', async () => {
|
|
113
|
+
const result = await service.handleMessage('completeSoluCXWidget', false);
|
|
114
|
+
|
|
115
|
+
expect(mockCallbacks.onPartialCompleted).toHaveBeenCalledWith('test-user-123');
|
|
116
|
+
expect(result).toEqual({ status: 'success' });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should adapt partialSoluCXWidget to FORM_PARTIALCOMPLETED', async () => {
|
|
120
|
+
const result = await service.handleMessage('partialSoluCXWidget', false);
|
|
121
|
+
|
|
122
|
+
expect(mockCallbacks.onPartialCompleted).toHaveBeenCalledWith('test-user-123');
|
|
123
|
+
expect(result).toEqual({ status: 'success' });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should adapt dismissSoluCXWidget to FORM_CLOSE', async () => {
|
|
127
|
+
const result = await service.handleMessage('dismissSoluCXWidget', false);
|
|
128
|
+
|
|
129
|
+
expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
|
|
130
|
+
expect(mockCallbacks.onClosed).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(result).toEqual({ status: 'success' });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should adapt errorSoluCXWidget to FORM_ERROR', async () => {
|
|
135
|
+
const result = await service.handleMessage('errorSoluCXWidget-Network error', false);
|
|
136
|
+
|
|
137
|
+
expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
|
|
138
|
+
expect(mockCallbacks.onError).toHaveBeenCalledWith('Network error');
|
|
139
|
+
expect(result).toEqual({ status: 'error', message: 'Network error' });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should adapt resizeSoluCXWidget to FORM_RESIZE', async () => {
|
|
143
|
+
const result = await service.handleMessage('resizeSoluCXWidget-400', false);
|
|
144
|
+
|
|
145
|
+
expect(mockResize).toHaveBeenCalledWith('400');
|
|
146
|
+
expect(mockCallbacks.onResize).toHaveBeenCalledWith('400');
|
|
147
|
+
expect(result).toEqual({ status: 'success' });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should adapt openSoluCXWidget to FORM_OPENED which returns unknown event', async () => {
|
|
151
|
+
const result = await service.handleMessage('openSoluCXWidget', false);
|
|
152
|
+
|
|
153
|
+
expect(result).toEqual({ status: 'error', message: 'Unknown event' });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should not call any callback when event is unknown', async () => {
|
|
157
|
+
await service.handleMessage('SOME_RANDOM_EVENT', true);
|
|
158
|
+
|
|
159
|
+
expect(mockSetIsWidgetVisible).not.toHaveBeenCalled();
|
|
160
|
+
expect(mockResize).not.toHaveBeenCalled();
|
|
161
|
+
expect(mockCallbacks.onClosed).not.toHaveBeenCalled();
|
|
162
|
+
expect(mockCallbacks.onError).not.toHaveBeenCalled();
|
|
163
|
+
expect(mockCallbacks.onPageChanged).not.toHaveBeenCalled();
|
|
164
|
+
expect(mockCallbacks.onQuestionAnswered).not.toHaveBeenCalled();
|
|
165
|
+
expect(mockCallbacks.onPartialCompleted).not.toHaveBeenCalled();
|
|
166
|
+
expect(mockCallbacks.onResize).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should work without callbacks when none are provided', async () => {
|
|
170
|
+
const serviceWithoutCallbacks = new WidgetEventService(
|
|
171
|
+
mockSetIsWidgetVisible,
|
|
172
|
+
mockResize,
|
|
173
|
+
mockUserId,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const result = await serviceWithoutCallbacks.handleMessage('FORM_CLOSE', true);
|
|
177
|
+
|
|
178
|
+
expect(mockSetIsWidgetVisible).toHaveBeenCalledWith(false);
|
|
179
|
+
expect(result).toEqual({ status: 'success' });
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { requestWidgetUrl, buildRequestParams } from '../services/widgetBootstrapService';
|
|
2
|
+
import { WidgetData } from '../interfaces';
|
|
3
|
+
|
|
4
|
+
// Mock das dependências
|
|
5
|
+
jest.mock('../constants/Constants', () => ({
|
|
6
|
+
SDK_NAME: 'rn-widget-sdk',
|
|
7
|
+
SDK_VERSION: '0.1.16',
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.mock('../services/ClientVersionCollector', () => ({
|
|
11
|
+
getClientVersion: jest.fn(() => '1.0.0'),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock('../hooks/useDeviceInfoCollector', () => ({
|
|
15
|
+
getDeviceInfo: jest.fn(() => ({
|
|
16
|
+
platform: 'ios',
|
|
17
|
+
osVersion: '16.0',
|
|
18
|
+
deviceType: 'phone',
|
|
19
|
+
model: 'iPhone 14 Pro',
|
|
20
|
+
})),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Mock global do fetch
|
|
24
|
+
const originalFetch = global.fetch;
|
|
25
|
+
|
|
26
|
+
describe('widgetBootstrapService', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
global.fetch = jest.fn();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
global.fetch = originalFetch;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('buildRequestParams', () => {
|
|
37
|
+
it('should build URL parameters from widget data', () => {
|
|
38
|
+
const data: WidgetData = {
|
|
39
|
+
customer_id: 'cust-123',
|
|
40
|
+
email: 'test@example.com',
|
|
41
|
+
journey: 'checkout',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const params = buildRequestParams(data);
|
|
45
|
+
|
|
46
|
+
expect(params.get('customer_id')).toBe('cust-123');
|
|
47
|
+
expect(params.get('email')).toBe('test@example.com');
|
|
48
|
+
expect(params.get('journey')).toBe('checkout');
|
|
49
|
+
expect(params.get('transactionId')).toBe('');
|
|
50
|
+
expect(params.get('attemptId')).toBe('');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should skip undefined and null values', () => {
|
|
54
|
+
const data: WidgetData = {
|
|
55
|
+
customer_id: 'cust-123',
|
|
56
|
+
email: undefined,
|
|
57
|
+
name: null as any,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const params = buildRequestParams(data);
|
|
61
|
+
|
|
62
|
+
expect(params.has('customer_id')).toBe(true);
|
|
63
|
+
expect(params.has('email')).toBe(false);
|
|
64
|
+
expect(params.has('name')).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should convert numbers to strings', () => {
|
|
68
|
+
const data: WidgetData = {
|
|
69
|
+
amount: 100.50,
|
|
70
|
+
score: 5,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const params = buildRequestParams(data);
|
|
74
|
+
|
|
75
|
+
expect(params.get('amount')).toBe('100.5');
|
|
76
|
+
expect(params.get('score')).toBe('5');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('requestWidgetUrl', () => {
|
|
81
|
+
it('should send request with correct Sec-CH-UA headers', async () => {
|
|
82
|
+
const mockResponse = {
|
|
83
|
+
ok: true,
|
|
84
|
+
json: jest.fn().mockResolvedValue({ url: 'https://widget.url' }),
|
|
85
|
+
};
|
|
86
|
+
(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
|
|
87
|
+
|
|
88
|
+
const data: WidgetData = {
|
|
89
|
+
customer_id: 'cust-123',
|
|
90
|
+
journey: 'test',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
await requestWidgetUrl('api-key-123', data, 'user-id-456');
|
|
94
|
+
|
|
95
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
96
|
+
expect.stringContaining('customer_id=cust-123'),
|
|
97
|
+
expect.objectContaining({
|
|
98
|
+
method: 'GET',
|
|
99
|
+
headers: expect.objectContaining({
|
|
100
|
+
'x-solucx-api-key': 'api-key-123',
|
|
101
|
+
'x-solucx-device-id': 'user-id-456',
|
|
102
|
+
'Sec-CH-UA': '"rn-widget-sdk";v="0.1.16", "app";v="1.0.0"',
|
|
103
|
+
'Sec-CH-UA-Platform': '"ios"',
|
|
104
|
+
'Sec-CH-UA-Mobile': '?1',
|
|
105
|
+
'Sec-CH-UA-Platform-Version': '"16.0"',
|
|
106
|
+
'Sec-CH-UA-Model': '"iPhone 14 Pro"',
|
|
107
|
+
'User-Agent': 'rn-widget-sdk/0.1.16 (ios; OS 16.0; iPhone 14 Pro) App/1.0.0',
|
|
108
|
+
}),
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should set Sec-CH-UA-Mobile to ?0 for tablets', async () => {
|
|
114
|
+
const { getDeviceInfo } = require('../hooks/useDeviceInfoCollector');
|
|
115
|
+
getDeviceInfo.mockReturnValueOnce({
|
|
116
|
+
platform: 'android',
|
|
117
|
+
osVersion: '13.0',
|
|
118
|
+
deviceType: 'tablet',
|
|
119
|
+
model: 'Samsung Galaxy Tab S8',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const mockResponse = {
|
|
123
|
+
ok: true,
|
|
124
|
+
json: jest.fn().mockResolvedValue({ url: 'https://widget.url' }),
|
|
125
|
+
};
|
|
126
|
+
(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
|
|
127
|
+
|
|
128
|
+
const data: WidgetData = { customer_id: 'cust-123' };
|
|
129
|
+
|
|
130
|
+
await requestWidgetUrl('api-key-123', data, 'user-id-456');
|
|
131
|
+
|
|
132
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
133
|
+
expect.any(String),
|
|
134
|
+
expect.objectContaining({
|
|
135
|
+
headers: expect.objectContaining({
|
|
136
|
+
'Sec-CH-UA-Mobile': '?0',
|
|
137
|
+
'Sec-CH-UA-Platform': '"android"',
|
|
138
|
+
}),
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should return widget URL on success', async () => {
|
|
144
|
+
const mockResponse = {
|
|
145
|
+
ok: true,
|
|
146
|
+
json: jest.fn().mockResolvedValue({ url: 'https://widget.url/test' }),
|
|
147
|
+
};
|
|
148
|
+
(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
|
|
149
|
+
|
|
150
|
+
const result = await requestWidgetUrl(
|
|
151
|
+
'api-key',
|
|
152
|
+
{ customer_id: 'cust' },
|
|
153
|
+
'user-id'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(result).toBe('https://widget.url/test');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should return undefined when response is not ok', async () => {
|
|
160
|
+
const mockResponse = {
|
|
161
|
+
ok: false,
|
|
162
|
+
};
|
|
163
|
+
(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
|
|
164
|
+
|
|
165
|
+
const result = await requestWidgetUrl(
|
|
166
|
+
'api-key',
|
|
167
|
+
{ customer_id: 'cust' },
|
|
168
|
+
'user-id'
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
expect(result).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return undefined when fetch is not available', async () => {
|
|
175
|
+
(global as any).fetch = undefined;
|
|
176
|
+
|
|
177
|
+
const result = await requestWidgetUrl('api-key', { customer_id: 'cust' }, 'user-id');
|
|
178
|
+
|
|
179
|
+
expect(result).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
3
|
-
|
|
4
|
-
interface CloseButtonProps {
|
|
5
|
-
onPress: () => void;
|
|
6
|
-
visible?: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const CloseButton: React.FC<CloseButtonProps> = ({ onPress, visible = true }) => {
|
|
10
|
-
if (!visible) return null;
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<TouchableOpacity style={styles.closeButton} onPress={onPress}>
|
|
14
|
-
<Text style={styles.closeButtonText}>✕</Text>
|
|
15
|
-
</TouchableOpacity>
|
|
16
|
-
);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const styles = StyleSheet.create({
|
|
20
|
-
closeButton: {
|
|
21
|
-
position: 'absolute',
|
|
22
|
-
top: 10,
|
|
23
|
-
right: 10,
|
|
24
|
-
width: 30,
|
|
25
|
-
height: 30,
|
|
26
|
-
borderRadius: 15,
|
|
27
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
28
|
-
justifyContent: 'center',
|
|
29
|
-
alignItems: 'center',
|
|
30
|
-
zIndex: 10001,
|
|
31
|
-
},
|
|
32
|
-
closeButtonText: {
|
|
33
|
-
color: 'white',
|
|
34
|
-
fontSize: 16,
|
|
35
|
-
fontWeight: 'bold',
|
|
36
|
-
},
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface CloseButtonProps {
|
|
5
|
+
onPress: () => void;
|
|
6
|
+
visible?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const CloseButton: React.FC<CloseButtonProps> = ({ onPress, visible = true }) => {
|
|
10
|
+
if (!visible) return null;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<TouchableOpacity style={styles.closeButton} onPress={onPress}>
|
|
14
|
+
<Text style={styles.closeButtonText}>✕</Text>
|
|
15
|
+
</TouchableOpacity>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
closeButton: {
|
|
21
|
+
position: 'absolute',
|
|
22
|
+
top: 10,
|
|
23
|
+
right: 10,
|
|
24
|
+
width: 30,
|
|
25
|
+
height: 30,
|
|
26
|
+
borderRadius: 15,
|
|
27
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
28
|
+
justifyContent: 'center',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
zIndex: 10001,
|
|
31
|
+
},
|
|
32
|
+
closeButtonText: {
|
|
33
|
+
color: 'white',
|
|
34
|
+
fontSize: 16,
|
|
35
|
+
fontWeight: 'bold',
|
|
36
|
+
},
|
|
37
37
|
});
|