@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.
Files changed (256) hide show
  1. package/README.md +526 -182
  2. package/lib/SoluCXWidget.d.ts +50 -7
  3. package/lib/SoluCXWidget.d.ts.map +1 -1
  4. package/lib/SoluCXWidget.js +105 -101
  5. package/lib/SoluCXWidget.js.map +1 -1
  6. package/lib/SoluCXWidgetHost.d.ts +3 -0
  7. package/lib/SoluCXWidgetHost.d.ts.map +1 -0
  8. package/lib/SoluCXWidgetHost.js +34 -0
  9. package/lib/SoluCXWidgetHost.js.map +1 -0
  10. package/lib/SoluCXWidgetView.d.ts +12 -0
  11. package/lib/SoluCXWidgetView.d.ts.map +1 -0
  12. package/lib/SoluCXWidgetView.js +61 -0
  13. package/lib/SoluCXWidgetView.js.map +1 -0
  14. package/lib/components/CloseButton.d.ts +1 -1
  15. package/lib/components/CloseButton.d.ts.map +1 -1
  16. package/lib/components/CloseButton.js +4 -1
  17. package/lib/components/CloseButton.js.map +1 -1
  18. package/lib/components/InlineWidget.d.ts.map +1 -1
  19. package/lib/components/InlineWidget.js +2 -7
  20. package/lib/components/InlineWidget.js.map +1 -1
  21. package/lib/components/ModalWidget.d.ts +1 -1
  22. package/lib/components/ModalWidget.d.ts.map +1 -1
  23. package/lib/components/ModalWidget.js +3 -16
  24. package/lib/components/ModalWidget.js.map +1 -1
  25. package/lib/components/OverlayWidget.d.ts.map +1 -1
  26. package/lib/components/OverlayWidget.js +5 -15
  27. package/lib/components/OverlayWidget.js.map +1 -1
  28. package/lib/components/index.d.ts +5 -0
  29. package/lib/components/index.d.ts.map +1 -0
  30. package/lib/components/index.js +12 -0
  31. package/lib/components/index.js.map +1 -0
  32. package/lib/constants/Constants.d.ts +11 -0
  33. package/lib/constants/Constants.d.ts.map +1 -1
  34. package/lib/constants/Constants.js +16 -1
  35. package/lib/constants/Constants.js.map +1 -1
  36. package/lib/{interfaces → domain}/WidgetCallbacks.d.ts +2 -2
  37. package/lib/domain/WidgetCallbacks.d.ts.map +1 -0
  38. package/lib/domain/WidgetCallbacks.js.map +1 -0
  39. package/{src/interfaces/WidgetData.ts → lib/domain/WidgetData.d.ts} +5 -2
  40. package/lib/domain/WidgetData.d.ts.map +1 -0
  41. package/lib/{interfaces → domain}/WidgetData.js.map +1 -1
  42. package/lib/domain/WidgetDisplayResult.d.ts +6 -0
  43. package/lib/domain/WidgetDisplayResult.d.ts.map +1 -0
  44. package/lib/domain/WidgetDisplayResult.js +3 -0
  45. package/lib/domain/WidgetDisplayResult.js.map +1 -0
  46. package/lib/domain/WidgetOptions.d.ts +27 -0
  47. package/lib/domain/WidgetOptions.d.ts.map +1 -0
  48. package/lib/domain/WidgetOptions.js +30 -0
  49. package/lib/domain/WidgetOptions.js.map +1 -0
  50. package/lib/domain/WidgetResponse.d.ts +5 -0
  51. package/lib/domain/WidgetResponse.d.ts.map +1 -0
  52. package/lib/{interfaces/WidgetOptions.js → domain/WidgetResponse.js} +1 -1
  53. package/lib/domain/WidgetResponse.js.map +1 -0
  54. package/lib/domain/WidgetSamplerLog.d.ts +12 -0
  55. package/lib/domain/WidgetSamplerLog.d.ts.map +1 -0
  56. package/lib/domain/WidgetSamplerLog.js.map +1 -0
  57. package/lib/{interfaces → domain}/index.d.ts +1 -2
  58. package/lib/domain/index.d.ts.map +1 -0
  59. package/lib/{interfaces → domain}/index.js.map +1 -1
  60. package/lib/hooks/index.d.ts +2 -2
  61. package/lib/hooks/index.d.ts.map +1 -1
  62. package/lib/hooks/index.js +5 -5
  63. package/lib/hooks/index.js.map +1 -1
  64. package/lib/hooks/useClientVersionCollector.d.ts +3 -0
  65. package/lib/hooks/useClientVersionCollector.d.ts.map +1 -0
  66. package/lib/{services/ClientVersionCollector.js → hooks/useClientVersionCollector.js} +7 -2
  67. package/lib/hooks/useClientVersionCollector.js.map +1 -0
  68. package/lib/hooks/useHeightAnimation.d.ts +0 -1
  69. package/lib/hooks/useHeightAnimation.d.ts.map +1 -1
  70. package/lib/hooks/useHeightAnimation.js +4 -2
  71. package/lib/hooks/useHeightAnimation.js.map +1 -1
  72. package/lib/hooks/useWidget.d.ts +13 -0
  73. package/lib/hooks/useWidget.d.ts.map +1 -0
  74. package/lib/hooks/useWidget.js +44 -0
  75. package/lib/hooks/useWidget.js.map +1 -0
  76. package/lib/hooks/useWidgetBootstrap.d.ts +21 -0
  77. package/lib/hooks/useWidgetBootstrap.d.ts.map +1 -0
  78. package/lib/hooks/useWidgetBootstrap.js +87 -0
  79. package/lib/hooks/useWidgetBootstrap.js.map +1 -0
  80. package/lib/hooks/useWidgetServices.d.ts +19 -0
  81. package/lib/hooks/useWidgetServices.d.ts.map +1 -0
  82. package/lib/hooks/useWidgetServices.js +34 -0
  83. package/lib/hooks/useWidgetServices.js.map +1 -0
  84. package/lib/hooks/useWidgetUI.d.ts +9 -0
  85. package/lib/hooks/useWidgetUI.d.ts.map +1 -0
  86. package/lib/hooks/useWidgetUI.js +33 -0
  87. package/lib/hooks/useWidgetUI.js.map +1 -0
  88. package/lib/index.d.ts +10 -11
  89. package/lib/index.d.ts.map +1 -1
  90. package/lib/index.js +13 -38
  91. package/lib/index.js.map +1 -1
  92. package/lib/services/UserIdentificationService.d.ts +3 -0
  93. package/lib/services/UserIdentificationService.d.ts.map +1 -0
  94. package/lib/services/UserIdentificationService.js +17 -0
  95. package/lib/services/UserIdentificationService.js.map +1 -0
  96. package/lib/services/WidgetBootstrapService.d.ts +12 -0
  97. package/lib/services/WidgetBootstrapService.d.ts.map +1 -0
  98. package/lib/services/{widgetBootstrapService.js → WidgetBootstrapService.js} +36 -15
  99. package/lib/services/WidgetBootstrapService.js.map +1 -0
  100. package/lib/services/WidgetEventService.d.ts +8 -0
  101. package/lib/services/WidgetEventService.d.ts.map +1 -0
  102. package/lib/services/WidgetEventService.js +14 -0
  103. package/lib/services/WidgetEventService.js.map +1 -0
  104. package/lib/services/WidgetStateManager.d.ts +20 -0
  105. package/lib/services/WidgetStateManager.d.ts.map +1 -0
  106. package/lib/services/WidgetStateManager.js +93 -0
  107. package/lib/services/WidgetStateManager.js.map +1 -0
  108. package/lib/services/WidgetValidationService.d.ts +17 -0
  109. package/lib/services/WidgetValidationService.d.ts.map +1 -0
  110. package/lib/services/WidgetValidationService.js +132 -0
  111. package/lib/services/WidgetValidationService.js.map +1 -0
  112. package/lib/services/events/EventHandlerFactory.d.ts +18 -0
  113. package/lib/services/events/EventHandlerFactory.d.ts.map +1 -0
  114. package/lib/services/events/EventHandlerFactory.js +67 -0
  115. package/lib/services/events/EventHandlerFactory.js.map +1 -0
  116. package/lib/services/events/EventHandlers.d.ts +10 -0
  117. package/lib/services/events/EventHandlers.d.ts.map +1 -0
  118. package/lib/services/events/EventHandlers.js +72 -0
  119. package/lib/services/events/EventHandlers.js.map +1 -0
  120. package/lib/services/events/index.d.ts +3 -0
  121. package/lib/services/events/index.d.ts.map +1 -0
  122. package/lib/services/events/index.js +21 -0
  123. package/lib/services/events/index.js.map +1 -0
  124. package/lib/services/height/HeightStrategies.d.ts +3 -0
  125. package/lib/services/height/HeightStrategies.d.ts.map +1 -0
  126. package/lib/services/height/HeightStrategies.js +14 -0
  127. package/lib/services/height/HeightStrategies.js.map +1 -0
  128. package/lib/services/storage/AsyncStorageService.d.ts +13 -0
  129. package/lib/services/storage/AsyncStorageService.d.ts.map +1 -0
  130. package/lib/services/storage/AsyncStorageService.js +73 -0
  131. package/lib/services/storage/AsyncStorageService.js.map +1 -0
  132. package/lib/services/storage/IStorageService.d.ts +30 -0
  133. package/lib/services/storage/IStorageService.d.ts.map +1 -0
  134. package/lib/services/storage/IStorageService.js +3 -0
  135. package/lib/services/storage/IStorageService.js.map +1 -0
  136. package/lib/services/storage/StorageIdBuilder.d.ts +11 -0
  137. package/lib/services/storage/StorageIdBuilder.d.ts.map +1 -0
  138. package/lib/services/storage/StorageIdBuilder.js +17 -0
  139. package/lib/services/storage/StorageIdBuilder.js.map +1 -0
  140. package/lib/services/storage/index.d.ts +3 -0
  141. package/lib/services/storage/index.d.ts.map +1 -0
  142. package/lib/services/storage/index.js +6 -0
  143. package/lib/services/storage/index.js.map +1 -0
  144. package/lib/styles/widgetStyles.d.ts +1 -1
  145. package/lib/styles/widgetStyles.d.ts.map +1 -1
  146. package/package.json +8 -2
  147. package/src/SoluCXWidget.ts +144 -0
  148. package/src/SoluCXWidgetHost.tsx +44 -0
  149. package/src/SoluCXWidgetView.tsx +97 -0
  150. package/src/__tests__/ClientVersionCollector.test.ts +5 -5
  151. package/src/__tests__/OverlayWidget.rendering.test.tsx +12 -14
  152. package/src/__tests__/SoluCXWidget.rendering.test.tsx +103 -60
  153. package/src/__tests__/SoluCXWidget.test.ts +448 -0
  154. package/src/__tests__/WidgetValidationService.test.ts +408 -0
  155. package/src/__tests__/e2e/widget-lifecycle.test.tsx +14 -23
  156. package/src/__tests__/index.test.tsx +39 -0
  157. package/src/__tests__/integration/webview-communication-simple.test.tsx +8 -6
  158. package/src/__tests__/integration/webview-communication.test.tsx +127 -130
  159. package/src/__tests__/normalizeWidgetOptions.test.ts +80 -0
  160. package/src/__tests__/useWidgetBootstrap.test.ts +634 -0
  161. package/src/__tests__/useWidgetState.test.ts +56 -13
  162. package/src/__tests__/widgetBootstrapService.test.ts +15 -17
  163. package/src/components/CloseButton.tsx +6 -2
  164. package/src/components/InlineWidget.tsx +4 -9
  165. package/src/components/ModalWidget.tsx +15 -45
  166. package/src/components/OverlayWidget.tsx +5 -15
  167. package/src/components/index.ts +4 -0
  168. package/src/constants/Constants.ts +15 -0
  169. package/src/{interfaces → domain}/WidgetCallbacks.ts +2 -2
  170. package/{lib/interfaces/WidgetData.d.ts → src/domain/WidgetData.ts} +3 -2
  171. package/src/domain/WidgetDisplayResult.ts +16 -0
  172. package/src/domain/WidgetOptions.ts +53 -0
  173. package/src/domain/WidgetResponse.ts +5 -0
  174. package/src/domain/WidgetSamplerLog.ts +11 -0
  175. package/src/{interfaces → domain}/index.ts +1 -2
  176. package/src/hooks/index.ts +2 -2
  177. package/src/{services/ClientVersionCollector.ts → hooks/useClientVersionCollector.ts} +6 -0
  178. package/src/hooks/useHeightAnimation.ts +6 -3
  179. package/src/hooks/useWidget.ts +46 -0
  180. package/src/hooks/useWidgetBootstrap.ts +117 -0
  181. package/src/hooks/useWidgetServices.ts +44 -0
  182. package/src/hooks/useWidgetUI.ts +38 -0
  183. package/src/index.ts +16 -11
  184. package/src/services/UserIdentificationService.ts +14 -0
  185. package/src/services/{widgetBootstrapService.ts → WidgetBootstrapService.ts} +43 -19
  186. package/src/services/WidgetEventService.ts +15 -0
  187. package/src/services/WidgetStateManager.ts +115 -0
  188. package/src/services/WidgetValidationService.ts +149 -0
  189. package/src/services/events/EventHandlerFactory.ts +70 -0
  190. package/src/services/events/EventHandlers.ts +67 -0
  191. package/src/services/events/index.ts +2 -0
  192. package/src/services/height/HeightStrategies.ts +15 -0
  193. package/src/services/storage/AsyncStorageService.ts +74 -0
  194. package/src/services/storage/IStorageService.ts +32 -0
  195. package/src/services/storage/StorageIdBuilder.ts +15 -0
  196. package/src/services/storage/index.ts +2 -0
  197. package/src/styles/widgetStyles.ts +1 -1
  198. package/README.intern.md +0 -490
  199. package/lib/constants/webViewConstants.d.ts +0 -12
  200. package/lib/constants/webViewConstants.d.ts.map +0 -1
  201. package/lib/constants/webViewConstants.js +0 -19
  202. package/lib/constants/webViewConstants.js.map +0 -1
  203. package/lib/hooks/useWidgetHeight.d.ts +0 -13
  204. package/lib/hooks/useWidgetHeight.d.ts.map +0 -1
  205. package/lib/hooks/useWidgetHeight.js +0 -21
  206. package/lib/hooks/useWidgetHeight.js.map +0 -1
  207. package/lib/hooks/useWidgetState.d.ts +0 -15
  208. package/lib/hooks/useWidgetState.d.ts.map +0 -1
  209. package/lib/hooks/useWidgetState.js +0 -79
  210. package/lib/hooks/useWidgetState.js.map +0 -1
  211. package/lib/interfaces/WidgetCallbacks.d.ts.map +0 -1
  212. package/lib/interfaces/WidgetCallbacks.js.map +0 -1
  213. package/lib/interfaces/WidgetData.d.ts.map +0 -1
  214. package/lib/interfaces/WidgetOptions.d.ts +0 -9
  215. package/lib/interfaces/WidgetOptions.d.ts.map +0 -1
  216. package/lib/interfaces/WidgetOptions.js.map +0 -1
  217. package/lib/interfaces/WidgetResponse.d.ts +0 -10
  218. package/lib/interfaces/WidgetResponse.d.ts.map +0 -1
  219. package/lib/interfaces/WidgetResponse.js +0 -12
  220. package/lib/interfaces/WidgetResponse.js.map +0 -1
  221. package/lib/interfaces/WidgetSamplerLog.d.ts +0 -7
  222. package/lib/interfaces/WidgetSamplerLog.d.ts.map +0 -1
  223. package/lib/interfaces/WidgetSamplerLog.js.map +0 -1
  224. package/lib/interfaces/index.d.ts.map +0 -1
  225. package/lib/services/ClientVersionCollector.d.ts +0 -2
  226. package/lib/services/ClientVersionCollector.d.ts.map +0 -1
  227. package/lib/services/ClientVersionCollector.js.map +0 -1
  228. package/lib/services/storage.d.ts +0 -8
  229. package/lib/services/storage.d.ts.map +0 -1
  230. package/lib/services/storage.js +0 -23
  231. package/lib/services/storage.js.map +0 -1
  232. package/lib/services/widgetBootstrapService.d.ts +0 -6
  233. package/lib/services/widgetBootstrapService.d.ts.map +0 -1
  234. package/lib/services/widgetBootstrapService.js.map +0 -1
  235. package/lib/services/widgetEventService.d.ts +0 -19
  236. package/lib/services/widgetEventService.d.ts.map +0 -1
  237. package/lib/services/widgetEventService.js +0 -79
  238. package/lib/services/widgetEventService.js.map +0 -1
  239. package/lib/services/widgetValidationService.d.ts +0 -18
  240. package/lib/services/widgetValidationService.d.ts.map +0 -1
  241. package/lib/services/widgetValidationService.js +0 -71
  242. package/lib/services/widgetValidationService.js.map +0 -1
  243. package/src/SoluCXWidget.tsx +0 -179
  244. package/src/constants/webViewConstants.ts +0 -15
  245. package/src/hooks/useWidgetHeight.ts +0 -38
  246. package/src/hooks/useWidgetState.ts +0 -101
  247. package/src/interfaces/WidgetOptions.ts +0 -8
  248. package/src/interfaces/WidgetResponse.ts +0 -15
  249. package/src/interfaces/WidgetSamplerLog.ts +0 -6
  250. package/src/services/storage.ts +0 -21
  251. package/src/services/widgetEventService.ts +0 -110
  252. package/src/services/widgetValidationService.ts +0 -102
  253. /package/lib/{interfaces → domain}/WidgetCallbacks.js +0 -0
  254. /package/lib/{interfaces → domain}/WidgetData.js +0 -0
  255. /package/lib/{interfaces → domain}/WidgetSamplerLog.js +0 -0
  256. /package/lib/{interfaces → domain}/index.js +0 -0
@@ -0,0 +1,448 @@
1
+ import { SoluCXWidget, _registerHost, _unregisterHost } from '../SoluCXWidget';
2
+ import { WidgetStateManager } from '../services/WidgetStateManager';
3
+ import { AsyncStorageService } from '../services/storage/AsyncStorageService';
4
+ import type { WidgetSamplerLog } from '../domain';
5
+
6
+ // Mock do AsyncStorage
7
+ jest.mock('@react-native-async-storage/async-storage', () => ({
8
+ __esModule: true,
9
+ default: {
10
+ setItem: jest.fn(() => Promise.resolve()),
11
+ getItem: jest.fn(() => Promise.resolve(null)),
12
+ removeItem: jest.fn(() => Promise.resolve()),
13
+ clear: jest.fn(() => Promise.resolve()),
14
+ },
15
+ }));
16
+
17
+ // Mock dos serviços
18
+ jest.mock('../services/storage/AsyncStorageService');
19
+ jest.mock('../services/UserIdentificationService', () => ({
20
+ getUserId: jest.fn((data) => data?.user_id || 'default-user-id'),
21
+ }));
22
+
23
+ describe('SoluCXWidget - Log Management', () => {
24
+ let mockStorageService: jest.Mocked<AsyncStorageService>;
25
+ let widget: SoluCXWidget;
26
+
27
+ beforeEach(() => {
28
+ jest.clearAllMocks();
29
+
30
+ // Setup do mock do AsyncStorageService
31
+ mockStorageService = {
32
+ read: jest.fn(),
33
+ write: jest.fn(),
34
+ delete: jest.fn(),
35
+ exists: jest.fn(),
36
+ } as any;
37
+
38
+ (AsyncStorageService as jest.Mock).mockImplementation(() => mockStorageService);
39
+
40
+ // Criar widget
41
+ widget = SoluCXWidget.create('test-instance-key')
42
+ .setData({
43
+ journey: 'test-journey',
44
+ user_id: 'test-user-123',
45
+ });
46
+ });
47
+
48
+ describe('overrideTimestamp', () => {
49
+ it('should override timestamp when Date object is provided', async () => {
50
+ const mockLogs: WidgetSamplerLog = {
51
+ attempts: 1,
52
+ answeredTransactionIds: [],
53
+ lastDisplay: 1000,
54
+ };
55
+
56
+ mockStorageService.read.mockResolvedValue(mockLogs);
57
+ mockStorageService.write.mockResolvedValue();
58
+
59
+ const testDate = new Date('2026-03-10T10:00:00.000Z');
60
+ const result = await widget.overrideTimestamp('lastDisplay', testDate);
61
+
62
+ expect(result).toBe(widget); // Deve retornar this para chaining
63
+ expect(mockStorageService.write).toHaveBeenCalledWith(
64
+ 'test-instance-key:test-journey:test-user-123',
65
+ expect.objectContaining({
66
+ lastDisplay: testDate.getTime(),
67
+ })
68
+ );
69
+ });
70
+
71
+ it('should override timestamp when number in milliseconds is provided', async () => {
72
+ const mockLogs: WidgetSamplerLog = {
73
+ attempts: 2,
74
+ answeredTransactionIds: [],
75
+ };
76
+
77
+ mockStorageService.read.mockResolvedValue(mockLogs);
78
+ mockStorageService.write.mockResolvedValue();
79
+
80
+ const timestamp = 1678454400000;
81
+ await widget.overrideTimestamp('lastFirstAccess', timestamp);
82
+
83
+ expect(mockStorageService.write).toHaveBeenCalledWith(
84
+ 'test-instance-key:test-journey:test-user-123',
85
+ expect.objectContaining({
86
+ lastFirstAccess: timestamp,
87
+ })
88
+ );
89
+ });
90
+
91
+ it('should clear timestamp when 0 is provided', async () => {
92
+ const mockLogs: WidgetSamplerLog = {
93
+ attempts: 1,
94
+ answeredTransactionIds: [],
95
+ lastDismiss: 1000,
96
+ };
97
+
98
+ mockStorageService.read.mockResolvedValue(mockLogs);
99
+ mockStorageService.write.mockResolvedValue();
100
+
101
+ await widget.overrideTimestamp('lastDismiss', 0);
102
+
103
+ expect(mockStorageService.write).toHaveBeenCalledWith(
104
+ 'test-instance-key:test-journey:test-user-123',
105
+ expect.objectContaining({
106
+ lastDismiss: 0,
107
+ })
108
+ );
109
+ });
110
+
111
+ it('should return this for method chaining when overrideTimestamp is called', async () => {
112
+ mockStorageService.read.mockResolvedValue({
113
+ attempts: 0,
114
+ answeredTransactionIds: [],
115
+ });
116
+ mockStorageService.write.mockResolvedValue();
117
+
118
+ const result = await widget
119
+ .overrideTimestamp('lastDisplay', Date.now())
120
+ .then((w) => w.overrideTimestamp('lastDismiss', Date.now()));
121
+
122
+ expect(result).toBe(widget);
123
+ expect(mockStorageService.write).toHaveBeenCalledTimes(2);
124
+ });
125
+
126
+ it('should override timestamp when any timestamp field is specified', async () => {
127
+ mockStorageService.read.mockResolvedValue({
128
+ attempts: 0,
129
+ answeredTransactionIds: [],
130
+ });
131
+ mockStorageService.write.mockResolvedValue();
132
+
133
+ const timestamp = Date.now();
134
+ const fields = [
135
+ 'lastDisplayAttempt',
136
+ 'lastFirstAccess',
137
+ 'lastDisplay',
138
+ 'lastDismiss',
139
+ 'lastSubmit',
140
+ 'lastPartialSubmit',
141
+ ] as const;
142
+
143
+ for (const field of fields) {
144
+ await widget.overrideTimestamp(field, timestamp);
145
+ }
146
+
147
+ expect(mockStorageService.write).toHaveBeenCalledTimes(fields.length);
148
+ });
149
+ });
150
+
151
+ describe('getWidgetLogs', () => {
152
+ it('should return widget logs when getWidgetLogs is called', async () => {
153
+ const mockLogs: WidgetSamplerLog = {
154
+ attempts: 5,
155
+ answeredTransactionIds: ['tx1', 'tx2'],
156
+ lastDisplay: 1000,
157
+ lastDismiss: 2000,
158
+ lastFirstAccess: 500,
159
+ };
160
+
161
+ mockStorageService.read.mockResolvedValue(mockLogs);
162
+
163
+ const logs = await widget.getWidgetLogs();
164
+
165
+ expect(logs).toEqual(mockLogs);
166
+ expect(mockStorageService.read).toHaveBeenCalledWith(
167
+ 'test-instance-key:test-journey:test-user-123'
168
+ );
169
+ });
170
+
171
+ it('should return default logs when no logs exist in storage', async () => {
172
+ // Suprimir console.error esperado quando logs não são encontrados
173
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
174
+
175
+ mockStorageService.read.mockRejectedValue(new Error('Not found'));
176
+
177
+ const logs = await widget.getWidgetLogs();
178
+
179
+ expect(logs).toEqual({
180
+ attempts: 0,
181
+ answeredTransactionIds: [],
182
+ });
183
+
184
+ consoleErrorSpy.mockRestore();
185
+ });
186
+
187
+ it('should use correct storage ID when widget data contains instanceKey, journey and userId', async () => {
188
+ const widget2 = SoluCXWidget.create('instance2')
189
+ .setData({
190
+ form_id: 'form123',
191
+ user_id: 'user456',
192
+ });
193
+
194
+ mockStorageService.read.mockResolvedValue({
195
+ attempts: 0,
196
+ answeredTransactionIds: [],
197
+ });
198
+
199
+ await widget2.getWidgetLogs();
200
+
201
+ expect(mockStorageService.read).toHaveBeenCalledWith(
202
+ 'instance2:user456'
203
+ );
204
+ });
205
+ });
206
+
207
+ describe('clearWidgetLogs', () => {
208
+ it('should clear all widget logs when clearWidgetLogs is called', async () => {
209
+ mockStorageService.delete.mockResolvedValue();
210
+
211
+ const result = await widget.clearWidgetLogs();
212
+
213
+ expect(result).toBe(widget); // Deve retornar this para chaining
214
+ expect(mockStorageService.delete).toHaveBeenCalledWith(
215
+ 'test-instance-key:test-journey:test-user-123'
216
+ );
217
+ });
218
+
219
+ it('should return this for method chaining when clearWidgetLogs is called', async () => {
220
+ mockStorageService.delete.mockResolvedValue();
221
+ mockStorageService.read.mockResolvedValue({
222
+ attempts: 0,
223
+ answeredTransactionIds: [],
224
+ });
225
+
226
+ const result = await widget
227
+ .clearWidgetLogs()
228
+ .then((w) => w.getWidgetLogs());
229
+
230
+ expect(result).toBeDefined();
231
+ expect(mockStorageService.delete).toHaveBeenCalled();
232
+ });
233
+
234
+ it('should throw error when deletion fails', async () => {
235
+ mockStorageService.delete.mockRejectedValue(new Error('Delete failed'));
236
+
237
+ await expect(widget.clearWidgetLogs()).rejects.toThrow('Delete failed');
238
+ });
239
+ });
240
+
241
+ describe('Storage ID generation', () => {
242
+ it('should build storage ID when instanceKey, journey and userId are provided', async () => {
243
+ const widget = SoluCXWidget.create('my-app')
244
+ .setData({
245
+ journey: 'onboarding',
246
+ user_id: 'user123',
247
+ });
248
+
249
+ mockStorageService.read.mockResolvedValue({
250
+ attempts: 0,
251
+ answeredTransactionIds: [],
252
+ });
253
+
254
+ await widget.getWidgetLogs();
255
+
256
+ expect(mockStorageService.read).toHaveBeenCalledWith(
257
+ 'my-app:onboarding:user123'
258
+ );
259
+ });
260
+
261
+ it('should use form_id when journey is not provided', async () => {
262
+ const widget = SoluCXWidget.create('my-app')
263
+ .setData({
264
+ form_id: 'form456',
265
+ user_id: 'user123',
266
+ });
267
+
268
+ mockStorageService.read.mockResolvedValue({
269
+ attempts: 0,
270
+ answeredTransactionIds: [],
271
+ });
272
+
273
+ await widget.getWidgetLogs();
274
+
275
+ expect(mockStorageService.read).toHaveBeenCalledWith(
276
+ 'my-app:user123'
277
+ );
278
+ });
279
+
280
+ it('should use default userId when userId is missing', async () => {
281
+ const widget = SoluCXWidget.create('my-app')
282
+ .setData({
283
+ journey: 'test',
284
+ });
285
+
286
+ mockStorageService.read.mockResolvedValue({
287
+ attempts: 0,
288
+ answeredTransactionIds: [],
289
+ });
290
+
291
+ await widget.getWidgetLogs();
292
+
293
+ // getUserId mock retorna 'default-user-id' quando não há user_id
294
+ expect(mockStorageService.read).toHaveBeenCalledWith(
295
+ 'my-app:test:default-user-id'
296
+ );
297
+ });
298
+ });
299
+
300
+ describe('Builder pattern integration', () => {
301
+ it('should work with log management when full builder chain is used', async () => {
302
+ mockStorageService.read.mockResolvedValue({
303
+ attempts: 0,
304
+ answeredTransactionIds: [],
305
+ });
306
+ mockStorageService.write.mockResolvedValue();
307
+
308
+ const widget = SoluCXWidget.create('app1')
309
+ .setType('modal')
310
+ .setData({
311
+ journey: 'survey',
312
+ user_id: 'user999',
313
+ })
314
+ .setOptions({
315
+ maxAttemptsAfterDismiss: 1,
316
+ })
317
+ .setCallbacks({
318
+ onOpened: jest.fn(),
319
+ });
320
+
321
+ const timestamp = Date.now();
322
+ await widget.overrideTimestamp('lastDisplay', timestamp);
323
+ const logs = await widget.getWidgetLogs();
324
+
325
+ expect(logs).toBeDefined();
326
+ expect(mockStorageService.read).toHaveBeenCalledWith(
327
+ 'app1:survey:user999'
328
+ );
329
+ });
330
+ });
331
+ });
332
+
333
+ describe('SoluCXWidget - Host Registration', () => {
334
+ let showHandler: jest.Mock;
335
+ let dismissHandler: jest.Mock;
336
+
337
+ beforeEach(() => {
338
+ showHandler = jest.fn();
339
+ dismissHandler = jest.fn();
340
+ _unregisterHost(); // Limpar antes de cada teste
341
+ });
342
+
343
+ afterEach(() => {
344
+ _unregisterHost();
345
+ });
346
+
347
+ describe('show', () => {
348
+ it('should throw error when show is called without registered host', () => {
349
+ const widget = SoluCXWidget.create('test-key').setData({
350
+ journey: 'test',
351
+ });
352
+
353
+ expect(() => widget.show()).toThrow(
354
+ '[SoluCXWidget] Cannot show widget — no <SoluCXWidgetHost /> is mounted. Add <SoluCXWidgetHost /> at the root of your app.'
355
+ );
356
+ });
357
+
358
+ it('should call show handler with correct config when widget is shown', () => {
359
+ _registerHost(showHandler, dismissHandler);
360
+
361
+ const callbacks = {
362
+ onOpened: jest.fn(),
363
+ onClosed: jest.fn(),
364
+ };
365
+
366
+ const widget = SoluCXWidget.create('test-key')
367
+ .setType('modal')
368
+ .setData({
369
+ journey: 'test-journey',
370
+ user_id: 'user123',
371
+ })
372
+ .setOptions({
373
+ maxAttemptsAfterDismiss: 2,
374
+ })
375
+ .setCallbacks(callbacks);
376
+
377
+ widget.show();
378
+
379
+ expect(showHandler).toHaveBeenCalledWith({
380
+ instanceKey: 'test-key',
381
+ type: 'modal',
382
+ data: {
383
+ journey: 'test-journey',
384
+ user_id: 'user123',
385
+ },
386
+ options: {
387
+ maxAttemptsAfterDismiss: 2,
388
+ },
389
+ callbacks,
390
+ });
391
+ });
392
+
393
+ it('should cleanup widget state when show is called', () => {
394
+ _registerHost(showHandler, dismissHandler);
395
+
396
+ const widget = SoluCXWidget.create('test-key')
397
+ .setType('modal')
398
+ .setData({ journey: 'test' })
399
+ .setOptions({ maxAttemptsAfterDismiss: 1 })
400
+ .setCallbacks({ onOpened: jest.fn() });
401
+
402
+ widget.show();
403
+
404
+ // Chamar show novamente deve usar valores padrão
405
+ widget.show();
406
+
407
+ expect(showHandler).toHaveBeenCalledTimes(2);
408
+ expect(showHandler).toHaveBeenLastCalledWith({
409
+ instanceKey: 'test-key',
410
+ type: 'bottom', // Valor padrão após cleanup
411
+ data: {},
412
+ options: {},
413
+ callbacks: {},
414
+ });
415
+ });
416
+ });
417
+
418
+ describe('dismiss', () => {
419
+ it('should call dismiss handler when dismiss is called', () => {
420
+ _registerHost(showHandler, dismissHandler);
421
+
422
+ SoluCXWidget.dismiss();
423
+
424
+ expect(dismissHandler).toHaveBeenCalled();
425
+ });
426
+
427
+ it('should not throw when dismiss is called without registered handler', () => {
428
+ expect(() => SoluCXWidget.dismiss()).not.toThrow();
429
+ });
430
+ });
431
+
432
+ describe('_registerHost and _unregisterHost', () => {
433
+ it('should allow showing widget when new host is registered', () => {
434
+ _registerHost(showHandler, dismissHandler);
435
+
436
+ const widget = SoluCXWidget.create('test').setData({});
437
+ expect(() => widget.show()).not.toThrow();
438
+ });
439
+
440
+ it('should throw error when unregistered host is used', () => {
441
+ _registerHost(showHandler, dismissHandler);
442
+ _unregisterHost();
443
+
444
+ const widget = SoluCXWidget.create('test').setData({});
445
+ expect(() => widget.show()).toThrow();
446
+ });
447
+ });
448
+ });