@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
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* END-TO-END TEST: Widget Lifecycle
|
|
3
|
+
*
|
|
4
|
+
* Tests complete user journeys from widget mount to close,
|
|
5
|
+
* simulating real-world scenarios that companies would encounter.
|
|
6
|
+
*
|
|
7
|
+
* Strategy: Minimal mocking, test full integration flow
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { render, waitFor, fireEvent } from '@testing-library/react-native';
|
|
12
|
+
import { SoluCXWidget } from '../../SoluCXWidget';
|
|
13
|
+
import type { WidgetCallbacks } from '../../interfaces';
|
|
14
|
+
|
|
15
|
+
// Mock storage
|
|
16
|
+
const mockStorage: Record<string, string> = {};
|
|
17
|
+
jest.mock('@react-native-async-storage/async-storage', () => ({
|
|
18
|
+
__esModule: true,
|
|
19
|
+
default: {
|
|
20
|
+
getItem: jest.fn((key: string) => Promise.resolve(mockStorage[key] || null)),
|
|
21
|
+
setItem: jest.fn((key: string, value: string) => {
|
|
22
|
+
mockStorage[key] = value;
|
|
23
|
+
return Promise.resolve();
|
|
24
|
+
}),
|
|
25
|
+
removeItem: jest.fn((key: string) => {
|
|
26
|
+
delete mockStorage[key];
|
|
27
|
+
return Promise.resolve();
|
|
28
|
+
}),
|
|
29
|
+
clear: jest.fn(() => {
|
|
30
|
+
Object.keys(mockStorage).forEach(key => delete mockStorage[key]);
|
|
31
|
+
return Promise.resolve();
|
|
32
|
+
}),
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Mock only what we absolutely must
|
|
37
|
+
jest.mock('react-native-webview', () => {
|
|
38
|
+
const React = require('react');
|
|
39
|
+
const { View, Text } = require('react-native');
|
|
40
|
+
return {
|
|
41
|
+
__esModule: true,
|
|
42
|
+
WebView: React.forwardRef((props: any, ref: any) => {
|
|
43
|
+
React.useImperativeHandle(ref, () => ({
|
|
44
|
+
injectJavaScript: jest.fn(),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<View testID="webview" {...props}>
|
|
49
|
+
<Text>WebView Content</Text>
|
|
50
|
+
</View>
|
|
51
|
+
);
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
jest.mock('../../utils/urlUtils', () => ({
|
|
57
|
+
buildWidgetURL: jest.fn().mockReturnValue('https://widget.solucx.com/form/123'),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
jest.mock('../../services/widgetBootstrapService', () => ({
|
|
61
|
+
requestWidgetUrl: jest.fn().mockResolvedValue('https://widget.solucx.com/survey/456'),
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
jest.mock('../../services/widgetValidationService', () => ({
|
|
65
|
+
WidgetValidationService: jest.fn().mockImplementation(() => ({
|
|
66
|
+
shouldDisplayWidget: jest.fn().mockResolvedValue({ canDisplay: true }),
|
|
67
|
+
})),
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
describe('E2E: Widget Lifecycle', () => {
|
|
71
|
+
beforeEach(async () => {
|
|
72
|
+
jest.clearAllMocks();
|
|
73
|
+
Object.keys(mockStorage).forEach(key => delete mockStorage[key]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('E-commerce Post-Purchase Survey Flow', () => {
|
|
77
|
+
it('should complete full flow: mount → display → interact → complete → close', async () => {
|
|
78
|
+
const analytics = {
|
|
79
|
+
track: jest.fn(),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const callbacks: WidgetCallbacks = {
|
|
83
|
+
onOpened: (userId) => analytics.track('widget_shown', { userId }),
|
|
84
|
+
onQuestionAnswered: () => analytics.track('question_answered'),
|
|
85
|
+
onPartialCompleted: (userId) => {
|
|
86
|
+
analytics.track('survey_completed', { userId });
|
|
87
|
+
},
|
|
88
|
+
onClosed: () => analytics.track('widget_closed'),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// 1. Mount widget after purchase
|
|
92
|
+
const { getByTestId } = render(
|
|
93
|
+
<SoluCXWidget
|
|
94
|
+
soluCXKey="ecommerce-post-purchase"
|
|
95
|
+
type="modal"
|
|
96
|
+
data={{
|
|
97
|
+
form_id: 'post-purchase-123',
|
|
98
|
+
customer_id: 'cust-789',
|
|
99
|
+
transaction_id: 'order-456',
|
|
100
|
+
amount: 199.99,
|
|
101
|
+
}}
|
|
102
|
+
options={{ height: 500 }}
|
|
103
|
+
callbacks={callbacks}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// 2. Widget should render
|
|
108
|
+
await waitFor(() => {
|
|
109
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const webview = getByTestId('webview');
|
|
113
|
+
|
|
114
|
+
// 3. First page loads, customer sees question
|
|
115
|
+
// (WebView automatically expands as content loads)
|
|
116
|
+
fireEvent(webview, 'message', {
|
|
117
|
+
nativeEvent: { data: 'FORM_RESIZE-400' },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await waitFor(() => {
|
|
121
|
+
expect(analytics.track).toHaveBeenCalledWith('question_answered', undefined);
|
|
122
|
+
}, { timeout: 100 }).catch(() => {}); // May not be called yet
|
|
123
|
+
|
|
124
|
+
// 4. Customer answers first question
|
|
125
|
+
fireEvent(webview, 'message', {
|
|
126
|
+
nativeEvent: { data: 'QUESTION_ANSWERED' },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
expect(analytics.track).toHaveBeenCalledWith('question_answered');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// 5. Customer goes to next page
|
|
134
|
+
fireEvent(webview, 'message', {
|
|
135
|
+
nativeEvent: { data: 'FORM_PAGECHANGED-2' },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// 6. Customer answers second question
|
|
139
|
+
fireEvent(webview, 'message', {
|
|
140
|
+
nativeEvent: { data: 'QUESTION_ANSWERED' },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await waitFor(() => {
|
|
144
|
+
expect(analytics.track).toHaveBeenCalledWith('question_answered');
|
|
145
|
+
expect(analytics.track).toHaveBeenCalledTimes(2); // Called twice
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// 7. Customer submits survey
|
|
149
|
+
fireEvent(webview, 'message', {
|
|
150
|
+
nativeEvent: { data: 'FORM_COMPLETED' },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await waitFor(() => {
|
|
154
|
+
expect(analytics.track).toHaveBeenCalledWith('survey_completed', {
|
|
155
|
+
userId: expect.any(String),
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// 8. Customer closes widget
|
|
160
|
+
fireEvent(webview, 'message', {
|
|
161
|
+
nativeEvent: { data: 'FORM_CLOSE' },
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await waitFor(() => {
|
|
165
|
+
expect(analytics.track).toHaveBeenCalledWith('widget_closed');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Verify complete tracking flow
|
|
169
|
+
expect(analytics.track).toHaveBeenCalledTimes(4);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('SaaS NPS Survey Flow with Error Recovery', () => {
|
|
174
|
+
it('should handle network error gracefully and still track partial completion', async () => {
|
|
175
|
+
const errorHandler = jest.fn();
|
|
176
|
+
const completionHandler = jest.fn();
|
|
177
|
+
|
|
178
|
+
const callbacks: WidgetCallbacks = {
|
|
179
|
+
onError: errorHandler,
|
|
180
|
+
onPartialCompleted: completionHandler,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const { getByTestId } = render(
|
|
184
|
+
<SoluCXWidget
|
|
185
|
+
soluCXKey="saas-nps"
|
|
186
|
+
type="bottom"
|
|
187
|
+
data={{
|
|
188
|
+
form_id: 'nps-survey',
|
|
189
|
+
customer_id: 'user-123',
|
|
190
|
+
plan: 'premium',
|
|
191
|
+
}}
|
|
192
|
+
options={{ height: 300 }}
|
|
193
|
+
callbacks={callbacks}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
await waitFor(() => {
|
|
198
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const webview = getByTestId('webview');
|
|
202
|
+
|
|
203
|
+
// User rates NPS
|
|
204
|
+
fireEvent(webview, 'message', {
|
|
205
|
+
nativeEvent: { data: 'FORM_PAGECHANGED-1' },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Network error occurs during submission
|
|
209
|
+
fireEvent(webview, 'message', {
|
|
210
|
+
nativeEvent: { data: 'FORM_ERROR-Network timeout occurred' },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await waitFor(() => {
|
|
214
|
+
expect(errorHandler).toHaveBeenCalledWith('Network timeout occurred');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// User tries again and succeeds
|
|
218
|
+
fireEvent(webview, 'message', {
|
|
219
|
+
nativeEvent: { data: 'FORM_COMPLETED' },
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await waitFor(() => {
|
|
223
|
+
expect(completionHandler).toHaveBeenCalledWith(expect.any(String));
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('Banking Compliance Survey with Dynamic Resizing', () => {
|
|
229
|
+
it('should handle multiple resize events as form expands', async () => {
|
|
230
|
+
const resizeHandler = jest.fn();
|
|
231
|
+
|
|
232
|
+
const { getByTestId } = render(
|
|
233
|
+
<SoluCXWidget
|
|
234
|
+
soluCXKey="banking-compliance"
|
|
235
|
+
type="inline"
|
|
236
|
+
data={{
|
|
237
|
+
form_id: 'compliance-check',
|
|
238
|
+
customer_id: 'bank-cust-456',
|
|
239
|
+
}}
|
|
240
|
+
options={{ height: 400 }}
|
|
241
|
+
callbacks={{ onResize: resizeHandler }}
|
|
242
|
+
/>
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
await waitFor(() => {
|
|
246
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const webview = getByTestId('webview');
|
|
250
|
+
|
|
251
|
+
// Form starts small
|
|
252
|
+
fireEvent(webview, 'message', {
|
|
253
|
+
nativeEvent: { data: 'FORM_RESIZE-300' },
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// User expands section, form grows
|
|
257
|
+
fireEvent(webview, 'message', {
|
|
258
|
+
nativeEvent: { data: 'FORM_RESIZE-500' },
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// User expands another section
|
|
262
|
+
fireEvent(webview, 'message', {
|
|
263
|
+
nativeEvent: { data: 'FORM_RESIZE-700' },
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// User collapses section
|
|
267
|
+
fireEvent(webview, 'message', {
|
|
268
|
+
nativeEvent: { data: 'FORM_RESIZE-600' },
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await waitFor(() => {
|
|
272
|
+
expect(resizeHandler).toHaveBeenCalledTimes(4);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Verify resize values
|
|
276
|
+
expect(resizeHandler).toHaveBeenNthCalledWith(1, '300');
|
|
277
|
+
expect(resizeHandler).toHaveBeenNthCalledWith(2, '500');
|
|
278
|
+
expect(resizeHandler).toHaveBeenNthCalledWith(3, '700');
|
|
279
|
+
expect(resizeHandler).toHaveBeenNthCalledWith(4, '600');
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe('Multi-Step Survey with Page Tracking', () => {
|
|
284
|
+
it('should track progress through multi-step survey', async () => {
|
|
285
|
+
const pageChangeHandler = jest.fn();
|
|
286
|
+
const questionHandler = jest.fn();
|
|
287
|
+
const completionHandler = jest.fn();
|
|
288
|
+
|
|
289
|
+
const { getByTestId } = render(
|
|
290
|
+
<SoluCXWidget
|
|
291
|
+
soluCXKey="multi-step-survey"
|
|
292
|
+
type="modal"
|
|
293
|
+
data={{
|
|
294
|
+
form_id: 'multi-step',
|
|
295
|
+
customer_id: 'user-999',
|
|
296
|
+
}}
|
|
297
|
+
options={{ height: 450 }}
|
|
298
|
+
callbacks={{
|
|
299
|
+
onPageChanged: pageChangeHandler,
|
|
300
|
+
onQuestionAnswered: questionHandler,
|
|
301
|
+
onPartialCompleted: completionHandler,
|
|
302
|
+
}}
|
|
303
|
+
/>
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
await waitFor(() => {
|
|
307
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const webview = getByTestId('webview');
|
|
311
|
+
|
|
312
|
+
// Page 1: Answer question
|
|
313
|
+
fireEvent(webview, 'message', {
|
|
314
|
+
nativeEvent: { data: 'QUESTION_ANSWERED' },
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Go to page 2
|
|
318
|
+
fireEvent(webview, 'message', {
|
|
319
|
+
nativeEvent: { data: 'FORM_PAGECHANGED-2' },
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Page 2: Answer question
|
|
323
|
+
fireEvent(webview, 'message', {
|
|
324
|
+
nativeEvent: { data: 'QUESTION_ANSWERED' },
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Go to page 3
|
|
328
|
+
fireEvent(webview, 'message', {
|
|
329
|
+
nativeEvent: { data: 'FORM_PAGECHANGED-3' },
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Page 3: Answer final question
|
|
333
|
+
fireEvent(webview, 'message', {
|
|
334
|
+
nativeEvent: { data: 'QUESTION_ANSWERED' },
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Complete survey
|
|
338
|
+
fireEvent(webview, 'message', {
|
|
339
|
+
nativeEvent: { data: 'FORM_COMPLETED' },
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
await waitFor(() => {
|
|
343
|
+
expect(pageChangeHandler).toHaveBeenCalledTimes(2);
|
|
344
|
+
expect(questionHandler).toHaveBeenCalledTimes(3);
|
|
345
|
+
expect(completionHandler).toHaveBeenCalledTimes(1);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Verify page progression
|
|
349
|
+
expect(pageChangeHandler).toHaveBeenNthCalledWith(1, '2');
|
|
350
|
+
expect(pageChangeHandler).toHaveBeenNthCalledWith(2, '3');
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIMPLIFIED INTEGRATION TEST: WebView Communication
|
|
3
|
+
* Starting with minimal test to understand the flow
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { render, waitFor, fireEvent } from '@testing-library/react-native';
|
|
8
|
+
import { SoluCXWidget } from '../../SoluCXWidget';
|
|
9
|
+
|
|
10
|
+
// Mock only external dependencies
|
|
11
|
+
jest.mock('react-native-webview', () => {
|
|
12
|
+
const React = require('react');
|
|
13
|
+
const { View, Text } = require('react-native');
|
|
14
|
+
return {
|
|
15
|
+
__esModule: true,
|
|
16
|
+
WebView: React.forwardRef((props: any, ref: any) => {
|
|
17
|
+
React.useImperativeHandle(ref, () => ({
|
|
18
|
+
injectJavaScript: jest.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<View testID="webview" {...props}>
|
|
23
|
+
<Text>WebView Mock</Text>
|
|
24
|
+
</View>
|
|
25
|
+
);
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
jest.mock('../../utils/urlUtils', () => ({
|
|
31
|
+
buildWidgetURL: jest.fn().mockReturnValue('https://form.url/test'),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
jest.mock('../../services/widgetBootstrapService', () => ({
|
|
35
|
+
requestWidgetUrl: jest.fn().mockResolvedValue('https://survey.url/test'),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
jest.mock('../../services/widgetValidationService', () => ({
|
|
39
|
+
WidgetValidationService: jest.fn().mockImplementation(() => ({
|
|
40
|
+
shouldDisplayWidget: jest.fn().mockResolvedValue({ canDisplay: true }),
|
|
41
|
+
})),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const mockStorage: Record<string, string> = {};
|
|
45
|
+
jest.mock('@react-native-async-storage/async-storage', () => ({
|
|
46
|
+
__esModule: true,
|
|
47
|
+
default: {
|
|
48
|
+
getItem: jest.fn((key: string) => Promise.resolve(mockStorage[key] || null)),
|
|
49
|
+
setItem: jest.fn((key: string, value: string) => {
|
|
50
|
+
mockStorage[key] = value;
|
|
51
|
+
return Promise.resolve();
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
describe('Integration: WebView Communication - Simplified', () => {
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
jest.clearAllMocks();
|
|
59
|
+
Object.keys(mockStorage).forEach(key => delete mockStorage[key]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should render WebView in form mode', async () => {
|
|
63
|
+
const { getByTestId, debug } = render(
|
|
64
|
+
<SoluCXWidget
|
|
65
|
+
soluCXKey="test-key"
|
|
66
|
+
type="modal"
|
|
67
|
+
data={{ form_id: 'form123', customer_id: 'user1' }}
|
|
68
|
+
options={{ height: 400 }}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
debug(); // Log component tree
|
|
73
|
+
|
|
74
|
+
await waitFor(() => {
|
|
75
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
76
|
+
}, { timeout: 3000 });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle FORM_CLOSE message in form mode', async () => {
|
|
80
|
+
const mockOnClosed = jest.fn();
|
|
81
|
+
|
|
82
|
+
const { getByTestId } = render(
|
|
83
|
+
<SoluCXWidget
|
|
84
|
+
soluCXKey="test-key"
|
|
85
|
+
type="modal"
|
|
86
|
+
data={{ form_id: 'form123', customer_id: 'user1' }}
|
|
87
|
+
options={{ height: 400 }}
|
|
88
|
+
callbacks={{ onClosed: mockOnClosed }}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
await waitFor(() => {
|
|
93
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
94
|
+
}, { timeout: 3000 });
|
|
95
|
+
|
|
96
|
+
// Simulate message
|
|
97
|
+
const webview = getByTestId('webview');
|
|
98
|
+
fireEvent(webview, 'message', {
|
|
99
|
+
nativeEvent: { data: 'FORM_CLOSE' },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await waitFor(() => {
|
|
103
|
+
expect(mockOnClosed).toHaveBeenCalledTimes(1);}, { timeout: 1000 });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should render WebView in survey mode', async () => {
|
|
107
|
+
const { getByTestId } = render(
|
|
108
|
+
<SoluCXWidget
|
|
109
|
+
soluCXKey="test-key"
|
|
110
|
+
type="modal"
|
|
111
|
+
data={{ customer_id: 'user1' }} // No form_id = survey
|
|
112
|
+
options={{ height: 400 }}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
await waitFor(() => {
|
|
117
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
118
|
+
}, { timeout: 3000 });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should handle closeSoluCXWidget message in survey mode', async () => {
|
|
122
|
+
const mockOnClosed = jest.fn();
|
|
123
|
+
|
|
124
|
+
const { getByTestId } = render(
|
|
125
|
+
<SoluCXWidget
|
|
126
|
+
soluCXKey="test-key"
|
|
127
|
+
type="modal"
|
|
128
|
+
data={{ customer_id: 'user1' }}
|
|
129
|
+
options={{ height: 400 }}
|
|
130
|
+
callbacks={{ onClosed: mockOnClosed }}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
await waitFor(() => {
|
|
135
|
+
expect(getByTestId('webview')).toBeTruthy();
|
|
136
|
+
}, { timeout: 3000 });
|
|
137
|
+
|
|
138
|
+
const webview = getByTestId('webview');
|
|
139
|
+
fireEvent(webview, 'message', {
|
|
140
|
+
nativeEvent: { data: 'closeSoluCXWidget' },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await waitFor(() => {
|
|
144
|
+
expect(mockOnClosed).toHaveBeenCalledTimes(1);
|
|
145
|
+
}, { timeout: 1000 });
|
|
146
|
+
});
|
|
147
|
+
});
|