@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.
Files changed (34) hide show
  1. package/package.json +31 -4
  2. package/src/SoluCXWidget.tsx +108 -53
  3. package/src/__mocks__/expo-modules-core-web.js +16 -0
  4. package/src/__mocks__/expo-modules-core.js +33 -0
  5. package/src/__tests__/ClientVersionCollector.test.ts +55 -0
  6. package/src/__tests__/CloseButton.test.tsx +47 -0
  7. package/src/__tests__/Constants.test.ts +17 -0
  8. package/src/__tests__/InlineWidget.rendering.test.tsx +81 -0
  9. package/src/__tests__/ModalWidget.rendering.test.tsx +157 -0
  10. package/src/__tests__/OverlayWidget.rendering.test.tsx +123 -0
  11. package/src/__tests__/SoluCXWidget.rendering.test.tsx +504 -0
  12. package/src/__tests__/e2e/widget-lifecycle.test.tsx +352 -0
  13. package/src/__tests__/integration/webview-communication-simple.test.tsx +147 -0
  14. package/src/__tests__/integration/webview-communication.test.tsx +417 -0
  15. package/src/__tests__/useDeviceInfoCollector.test.ts +109 -0
  16. package/src/__tests__/useWidgetState.test.ts +76 -84
  17. package/src/__tests__/widgetBootstrapService.test.ts +182 -0
  18. package/src/components/ModalWidget.tsx +3 -5
  19. package/src/components/OverlayWidget.tsx +1 -1
  20. package/src/constants/Constants.ts +4 -0
  21. package/src/constants/webViewConstants.ts +1 -0
  22. package/src/hooks/useDeviceInfoCollector.ts +67 -0
  23. package/src/hooks/useWidgetState.ts +4 -4
  24. package/src/index.ts +4 -0
  25. package/src/interfaces/WidgetCallbacks.ts +14 -0
  26. package/src/interfaces/index.ts +3 -2
  27. package/src/services/ClientVersionCollector.ts +15 -0
  28. package/src/services/storage.ts +2 -2
  29. package/src/services/widgetBootstrapService.ts +67 -0
  30. package/src/services/widgetEventService.ts +14 -30
  31. package/src/services/widgetValidationService.ts +29 -13
  32. package/src/setupTests.js +43 -0
  33. package/src/styles/widgetStyles.ts +1 -1
  34. package/src/utils/urlUtils.ts +2 -2
@@ -0,0 +1,157 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, within } from '@testing-library/react-native';
3
+ import { Text } from 'react-native';
4
+ import { ModalWidget } from '../components/ModalWidget';
5
+
6
+ describe('ModalWidget rendering', () => {
7
+ it('should render Modal component when visible is true', () => {
8
+ const { getByTestId, UNSAFE_getByType } = render(
9
+ <ModalWidget visible={true} height={400}>
10
+ <Text>Survey Content</Text>
11
+ </ModalWidget>
12
+ );
13
+
14
+ const modal = UNSAFE_getByType(require('react-native').Modal);
15
+ expect(modal).toBeTruthy();
16
+ });
17
+
18
+ it('should render children inside the modal', () => {
19
+ const { getByText } = render(
20
+ <ModalWidget visible={true} height={400}>
21
+ <Text>Survey Content</Text>
22
+ </ModalWidget>
23
+ );
24
+
25
+ expect(getByText('Survey Content')).toBeTruthy();
26
+ });
27
+
28
+ it('should render the close button when visible is true', () => {
29
+ const { getByText } = render(
30
+ <ModalWidget visible={true} height={400}>
31
+ <Text>Content</Text>
32
+ </ModalWidget>
33
+ );
34
+
35
+ expect(getByText('✕')).toBeTruthy();
36
+ });
37
+
38
+ it('should call onClose when close button is pressed', () => {
39
+ const mockOnClose = jest.fn();
40
+ const { getByText } = render(
41
+ <ModalWidget visible={true} height={400} onClose={mockOnClose}>
42
+ <Text>Content</Text>
43
+ </ModalWidget>
44
+ );
45
+
46
+ fireEvent.press(getByText('✕'));
47
+
48
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
49
+ });
50
+
51
+ it('should set Modal visible to true via internal state on initial render', () => {
52
+ const { UNSAFE_getByType } = render(
53
+ <ModalWidget visible={true} height={400}>
54
+ <Text>Content</Text>
55
+ </ModalWidget>
56
+ );
57
+
58
+ const modal = UNSAFE_getByType(require('react-native').Modal);
59
+ expect(modal.props.visible).toBe(true);
60
+ });
61
+
62
+ it('should use slide animation type', () => {
63
+ const { UNSAFE_getByType } = render(
64
+ <ModalWidget visible={true} height={400}>
65
+ <Text>Content</Text>
66
+ </ModalWidget>
67
+ );
68
+
69
+ const modal = UNSAFE_getByType(require('react-native').Modal);
70
+ expect(modal.props.animationType).toBe('slide');
71
+ });
72
+
73
+ it('should set Modal transparent prop to true', () => {
74
+ const { UNSAFE_getByType } = render(
75
+ <ModalWidget visible={true} height={400}>
76
+ <Text>Content</Text>
77
+ </ModalWidget>
78
+ );
79
+
80
+ const modal = UNSAFE_getByType(require('react-native').Modal);
81
+ expect(modal.props.transparent).toBe(true);
82
+ });
83
+
84
+ it('should set hardwareAccelerated prop to true', () => {
85
+ const { UNSAFE_getByType } = render(
86
+ <ModalWidget visible={true} height={400}>
87
+ <Text>Content</Text>
88
+ </ModalWidget>
89
+ );
90
+
91
+ const modal = UNSAFE_getByType(require('react-native').Modal);
92
+ expect(modal.props.hardwareAccelerated).toBe(true);
93
+ });
94
+
95
+ it('should pass visible=false to Modal when visible prop is false', () => {
96
+ const { UNSAFE_getByType } = render(
97
+ <ModalWidget visible={false} height={400}>
98
+ <Text>Content</Text>
99
+ </ModalWidget>
100
+ );
101
+
102
+ const modal = UNSAFE_getByType(require('react-native').Modal);
103
+ expect(modal.props.visible).toBe(false);
104
+ });
105
+
106
+ it('should call onClose when close button is pressed inside modal', () => {
107
+ const mockOnClose = jest.fn();
108
+ const { getByText } = render(
109
+ <ModalWidget visible={true} height={400} onClose={mockOnClose}>
110
+ <Text>Content</Text>
111
+ </ModalWidget>
112
+ );
113
+
114
+ fireEvent.press(getByText('✕'));
115
+
116
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
117
+ });
118
+
119
+ it('should reflect updated visible prop after rerender', () => {
120
+ const { UNSAFE_getByType, rerender } = render(
121
+ <ModalWidget visible={true} height={400} onClose={jest.fn()}>
122
+ <Text>Content</Text>
123
+ </ModalWidget>
124
+ );
125
+
126
+ rerender(
127
+ <ModalWidget visible={false} height={400} onClose={jest.fn()}>
128
+ <Text>Content</Text>
129
+ </ModalWidget>
130
+ );
131
+
132
+ const modal = UNSAFE_getByType(require('react-native').Modal);
133
+ expect(modal.props.visible).toBe(false);
134
+ });
135
+
136
+ it('should not call onClose if onClose prop is not provided', () => {
137
+ const { getByText } = render(
138
+ <ModalWidget visible={true} height={400}>
139
+ <Text>Content</Text>
140
+ </ModalWidget>
141
+ );
142
+
143
+ expect(() => fireEvent.press(getByText('✕'))).not.toThrow();
144
+ });
145
+
146
+ it('should render multiple children', () => {
147
+ const { getByText } = render(
148
+ <ModalWidget visible={true} height={400}>
149
+ <Text>First Child</Text>
150
+ <Text>Second Child</Text>
151
+ </ModalWidget>
152
+ );
153
+
154
+ expect(getByText('First Child')).toBeTruthy();
155
+ expect(getByText('Second Child')).toBeTruthy();
156
+ });
157
+ });
@@ -0,0 +1,123 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react-native';
3
+ import { Text } from 'react-native';
4
+ import { OverlayWidget } from '../components/OverlayWidget';
5
+
6
+ describe('OverlayWidget rendering', () => {
7
+ it('should render children when visible is true for bottom position', () => {
8
+ const { getByText } = render(
9
+ <OverlayWidget visible={true} width={390} height={300} position="bottom">
10
+ <Text>Bottom Widget</Text>
11
+ </OverlayWidget>
12
+ );
13
+
14
+ expect(getByText('Bottom Widget')).toBeTruthy();
15
+ });
16
+
17
+ it('should render children when visible is true for top position', () => {
18
+ const { getByText } = render(
19
+ <OverlayWidget visible={true} width={390} height={300} position="top">
20
+ <Text>Top Widget</Text>
21
+ </OverlayWidget>
22
+ );
23
+
24
+ expect(getByText('Top Widget')).toBeTruthy();
25
+ });
26
+
27
+ it('should render close button when visible is true', () => {
28
+ const { getByText } = render(
29
+ <OverlayWidget visible={true} width={390} height={300} position="bottom">
30
+ <Text>Content</Text>
31
+ </OverlayWidget>
32
+ );
33
+
34
+ expect(getByText('✕')).toBeTruthy();
35
+ });
36
+
37
+ it('should call onClose when close button is pressed', () => {
38
+ const mockOnClose = jest.fn();
39
+ const { getByText } = render(
40
+ <OverlayWidget visible={true} width={390} height={300} position="bottom" onClose={mockOnClose}>
41
+ <Text>Content</Text>
42
+ </OverlayWidget>
43
+ );
44
+
45
+ fireEvent.press(getByText('✕'));
46
+
47
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
48
+ });
49
+
50
+ it('should hide entirely after close button is pressed', () => {
51
+ const { getByText, queryByText } = render(
52
+ <OverlayWidget visible={true} width={390} height={300} position="bottom" onClose={jest.fn()}>
53
+ <Text>Dismissible Content</Text>
54
+ </OverlayWidget>
55
+ );
56
+
57
+ fireEvent.press(getByText('✕'));
58
+
59
+ expect(queryByText('Dismissible Content')).toBeNull();
60
+ });
61
+
62
+ it('should not render content when internal visibility is set to false after close', () => {
63
+ const { getByText, queryByText } = render(
64
+ <OverlayWidget visible={true} width={390} height={300} position="top" onClose={jest.fn()}>
65
+ <Text>Top Content</Text>
66
+ </OverlayWidget>
67
+ );
68
+
69
+ fireEvent.press(getByText('✕'));
70
+
71
+ expect(queryByText('✕')).toBeNull();
72
+ });
73
+
74
+ it('should not reopen after internal state is set to false even if rerendered', () => {
75
+ const { getByText, queryByText, rerender } = render(
76
+ <OverlayWidget visible={true} width={390} height={300} position="bottom" onClose={jest.fn()}>
77
+ <Text>Content</Text>
78
+ </OverlayWidget>
79
+ );
80
+
81
+ fireEvent.press(getByText('✕'));
82
+
83
+ rerender(
84
+ <OverlayWidget visible={true} width={390} height={300} position="bottom" onClose={jest.fn()}>
85
+ <Text>Content</Text>
86
+ </OverlayWidget>
87
+ );
88
+
89
+ expect(queryByText('Content')).toBeNull();
90
+ });
91
+
92
+ it('should not call onClose if onClose prop is not provided', () => {
93
+ const { getByText } = render(
94
+ <OverlayWidget visible={true} width={390} height={300} position="bottom">
95
+ <Text>Content</Text>
96
+ </OverlayWidget>
97
+ );
98
+
99
+ expect(() => fireEvent.press(getByText('✕'))).not.toThrow();
100
+ });
101
+
102
+ it('should hide close button when visible is false', () => {
103
+ const { queryByText } = render(
104
+ <OverlayWidget visible={false} width={390} height={300} position="bottom">
105
+ <Text>Content</Text>
106
+ </OverlayWidget>
107
+ );
108
+
109
+ expect(queryByText('✕')).toBeNull();
110
+ });
111
+
112
+ it('should render multiple children', () => {
113
+ const { getByText } = render(
114
+ <OverlayWidget visible={true} width={390} height={300} position="bottom">
115
+ <Text>First</Text>
116
+ <Text>Second</Text>
117
+ </OverlayWidget>
118
+ );
119
+
120
+ expect(getByText('First')).toBeTruthy();
121
+ expect(getByText('Second')).toBeTruthy();
122
+ });
123
+ });