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