@thoughtspot/visual-embed-sdk 1.43.0 → 1.43.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 (178) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/api-intercept.d.ts +51 -0
  3. package/cjs/src/api-intercept.d.ts.map +1 -0
  4. package/cjs/src/api-intercept.js +180 -0
  5. package/cjs/src/api-intercept.js.map +1 -0
  6. package/cjs/src/api-intercept.spec.d.ts +2 -0
  7. package/cjs/src/api-intercept.spec.d.ts.map +1 -0
  8. package/cjs/src/api-intercept.spec.js +672 -0
  9. package/cjs/src/api-intercept.spec.js.map +1 -0
  10. package/cjs/src/css-variables.d.ts +4 -0
  11. package/cjs/src/css-variables.d.ts.map +1 -1
  12. package/cjs/src/embed/app.d.ts +0 -5
  13. package/cjs/src/embed/app.d.ts.map +1 -1
  14. package/cjs/src/embed/app.js +4 -5
  15. package/cjs/src/embed/app.js.map +1 -1
  16. package/cjs/src/embed/app.spec.js +10 -0
  17. package/cjs/src/embed/app.spec.js.map +1 -1
  18. package/cjs/src/embed/conversation.d.ts +2 -2
  19. package/cjs/src/embed/conversation.js +1 -1
  20. package/cjs/src/embed/conversation.js.map +1 -1
  21. package/cjs/src/embed/conversation.spec.js +17 -0
  22. package/cjs/src/embed/conversation.spec.js.map +1 -1
  23. package/cjs/src/embed/hostEventClient/contracts.d.ts +11 -1
  24. package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  25. package/cjs/src/embed/hostEventClient/contracts.js +1 -0
  26. package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
  27. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  28. package/cjs/src/embed/liveboard.js +4 -2
  29. package/cjs/src/embed/liveboard.js.map +1 -1
  30. package/cjs/src/embed/liveboard.spec.js +11 -0
  31. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  32. package/cjs/src/embed/search.d.ts +0 -7
  33. package/cjs/src/embed/search.d.ts.map +1 -1
  34. package/cjs/src/embed/search.js +1 -4
  35. package/cjs/src/embed/search.js.map +1 -1
  36. package/cjs/src/embed/ts-embed.d.ts +10 -0
  37. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  38. package/cjs/src/embed/ts-embed.js +56 -15
  39. package/cjs/src/embed/ts-embed.js.map +1 -1
  40. package/cjs/src/embed/ts-embed.spec.d.ts.map +1 -1
  41. package/cjs/src/embed/ts-embed.spec.js +412 -238
  42. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  43. package/cjs/src/index.d.ts +2 -2
  44. package/cjs/src/index.d.ts.map +1 -1
  45. package/cjs/src/index.js +2 -1
  46. package/cjs/src/index.js.map +1 -1
  47. package/cjs/src/react/all-types-export.d.ts +1 -1
  48. package/cjs/src/react/all-types-export.d.ts.map +1 -1
  49. package/cjs/src/react/all-types-export.js +2 -1
  50. package/cjs/src/react/all-types-export.js.map +1 -1
  51. package/cjs/src/types.d.ts +244 -88
  52. package/cjs/src/types.d.ts.map +1 -1
  53. package/cjs/src/types.js +181 -70
  54. package/cjs/src/types.js.map +1 -1
  55. package/cjs/src/utils/logger.d.ts.map +1 -1
  56. package/cjs/src/utils/logger.js +1 -2
  57. package/cjs/src/utils/logger.js.map +1 -1
  58. package/cjs/src/utils/processData.d.ts +1 -1
  59. package/cjs/src/utils/processData.d.ts.map +1 -1
  60. package/cjs/src/utils/processData.js +8 -8
  61. package/cjs/src/utils/processData.js.map +1 -1
  62. package/cjs/src/utils/processData.spec.js.map +1 -1
  63. package/dist/{index-HZ94j9Ey.js → index-CpkMygsc.js} +1 -1
  64. package/dist/src/api-intercept.d.ts +51 -0
  65. package/dist/src/api-intercept.d.ts.map +1 -0
  66. package/dist/src/api-intercept.spec.d.ts +2 -0
  67. package/dist/src/api-intercept.spec.d.ts.map +1 -0
  68. package/dist/src/css-variables.d.ts +4 -0
  69. package/dist/src/css-variables.d.ts.map +1 -1
  70. package/dist/src/embed/app.d.ts +0 -5
  71. package/dist/src/embed/app.d.ts.map +1 -1
  72. package/dist/src/embed/conversation.d.ts +2 -2
  73. package/dist/src/embed/hostEventClient/contracts.d.ts +11 -1
  74. package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  75. package/dist/src/embed/liveboard.d.ts.map +1 -1
  76. package/dist/src/embed/search.d.ts +0 -7
  77. package/dist/src/embed/search.d.ts.map +1 -1
  78. package/dist/src/embed/ts-embed.d.ts +10 -0
  79. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  80. package/dist/src/embed/ts-embed.spec.d.ts.map +1 -1
  81. package/dist/src/index.d.ts +2 -2
  82. package/dist/src/index.d.ts.map +1 -1
  83. package/dist/src/react/all-types-export.d.ts +1 -1
  84. package/dist/src/react/all-types-export.d.ts.map +1 -1
  85. package/dist/src/types.d.ts +244 -88
  86. package/dist/src/types.d.ts.map +1 -1
  87. package/dist/src/utils/logger.d.ts.map +1 -1
  88. package/dist/src/utils/processData.d.ts +1 -1
  89. package/dist/src/utils/processData.d.ts.map +1 -1
  90. package/dist/tsembed-react.es.js +435 -118
  91. package/dist/tsembed-react.js +434 -117
  92. package/dist/tsembed.es.js +3577 -3260
  93. package/dist/tsembed.js +3575 -3258
  94. package/dist/visual-embed-sdk-react-full.d.ts +271 -103
  95. package/dist/visual-embed-sdk-react.d.ts +268 -103
  96. package/dist/visual-embed-sdk.d.ts +271 -103
  97. package/lib/package.json +1 -1
  98. package/lib/src/api-intercept.d.ts +51 -0
  99. package/lib/src/api-intercept.d.ts.map +1 -0
  100. package/lib/src/api-intercept.js +173 -0
  101. package/lib/src/api-intercept.js.map +1 -0
  102. package/lib/src/api-intercept.spec.d.ts +2 -0
  103. package/lib/src/api-intercept.spec.d.ts.map +1 -0
  104. package/lib/src/api-intercept.spec.js +669 -0
  105. package/lib/src/api-intercept.spec.js.map +1 -0
  106. package/lib/src/css-variables.d.ts +4 -0
  107. package/lib/src/css-variables.d.ts.map +1 -1
  108. package/lib/src/embed/app.d.ts +0 -5
  109. package/lib/src/embed/app.d.ts.map +1 -1
  110. package/lib/src/embed/app.js +4 -5
  111. package/lib/src/embed/app.js.map +1 -1
  112. package/lib/src/embed/app.spec.js +10 -0
  113. package/lib/src/embed/app.spec.js.map +1 -1
  114. package/lib/src/embed/conversation.d.ts +2 -2
  115. package/lib/src/embed/conversation.js +1 -1
  116. package/lib/src/embed/conversation.js.map +1 -1
  117. package/lib/src/embed/conversation.spec.js +17 -0
  118. package/lib/src/embed/conversation.spec.js.map +1 -1
  119. package/lib/src/embed/hostEventClient/contracts.d.ts +11 -1
  120. package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  121. package/lib/src/embed/hostEventClient/contracts.js +1 -0
  122. package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
  123. package/lib/src/embed/liveboard.d.ts.map +1 -1
  124. package/lib/src/embed/liveboard.js +4 -2
  125. package/lib/src/embed/liveboard.js.map +1 -1
  126. package/lib/src/embed/liveboard.spec.js +11 -0
  127. package/lib/src/embed/liveboard.spec.js.map +1 -1
  128. package/lib/src/embed/search.d.ts +0 -7
  129. package/lib/src/embed/search.d.ts.map +1 -1
  130. package/lib/src/embed/search.js +1 -4
  131. package/lib/src/embed/search.js.map +1 -1
  132. package/lib/src/embed/ts-embed.d.ts +10 -0
  133. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  134. package/lib/src/embed/ts-embed.js +56 -15
  135. package/lib/src/embed/ts-embed.js.map +1 -1
  136. package/lib/src/embed/ts-embed.spec.d.ts.map +1 -1
  137. package/lib/src/embed/ts-embed.spec.js +412 -238
  138. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  139. package/lib/src/index.d.ts +2 -2
  140. package/lib/src/index.d.ts.map +1 -1
  141. package/lib/src/index.js +2 -2
  142. package/lib/src/index.js.map +1 -1
  143. package/lib/src/react/all-types-export.d.ts +1 -1
  144. package/lib/src/react/all-types-export.d.ts.map +1 -1
  145. package/lib/src/react/all-types-export.js +1 -1
  146. package/lib/src/react/all-types-export.js.map +1 -1
  147. package/lib/src/types.d.ts +244 -88
  148. package/lib/src/types.d.ts.map +1 -1
  149. package/lib/src/types.js +180 -69
  150. package/lib/src/types.js.map +1 -1
  151. package/lib/src/utils/logger.d.ts.map +1 -1
  152. package/lib/src/utils/logger.js +1 -2
  153. package/lib/src/utils/logger.js.map +1 -1
  154. package/lib/src/utils/processData.d.ts +1 -1
  155. package/lib/src/utils/processData.d.ts.map +1 -1
  156. package/lib/src/utils/processData.js +8 -8
  157. package/lib/src/utils/processData.js.map +1 -1
  158. package/lib/src/utils/processData.spec.js.map +1 -1
  159. package/package.json +1 -1
  160. package/src/api-intercept.spec.ts +856 -0
  161. package/src/api-intercept.ts +204 -0
  162. package/src/css-variables.ts +5 -0
  163. package/src/embed/app.spec.ts +11 -0
  164. package/src/embed/app.ts +5 -16
  165. package/src/embed/conversation.spec.ts +22 -0
  166. package/src/embed/conversation.ts +3 -3
  167. package/src/embed/hostEventClient/contracts.ts +10 -0
  168. package/src/embed/liveboard.spec.ts +12 -0
  169. package/src/embed/liveboard.ts +6 -2
  170. package/src/embed/search.ts +1 -14
  171. package/src/embed/ts-embed.spec.ts +498 -250
  172. package/src/embed/ts-embed.ts +80 -32
  173. package/src/index.ts +2 -0
  174. package/src/react/all-types-export.ts +1 -0
  175. package/src/types.ts +327 -165
  176. package/src/utils/logger.ts +1 -2
  177. package/src/utils/processData.spec.ts +0 -1
  178. package/src/utils/processData.ts +10 -11
@@ -0,0 +1,856 @@
1
+ import * as apiIntercept from './api-intercept';
2
+ import * as config from './config';
3
+ import * as embedConfig from './embed/embedConfig';
4
+ import { InterceptedApiType, EmbedEvent, BaseViewConfig } from './types';
5
+ import { embedEventStatus } from './utils';
6
+ import { logger } from './utils/logger';
7
+
8
+ jest.mock('./config');
9
+ jest.mock('./embed/embedConfig');
10
+ jest.mock('./utils/logger');
11
+
12
+ const mockGetThoughtSpotHost = config.getThoughtSpotHost as jest.Mock;
13
+ const mockGetEmbedConfig = embedConfig.getEmbedConfig as jest.Mock;
14
+ const mockLogger = logger as jest.Mocked<typeof logger>;
15
+
16
+ describe('api-intercept', () => {
17
+ const thoughtSpotHost = 'https://test.thoughtspot.com';
18
+ let originalJsonParse: any;
19
+
20
+ beforeAll(() => {
21
+ originalJsonParse = JSON.parse;
22
+ });
23
+
24
+ beforeEach(() => {
25
+ jest.clearAllMocks();
26
+ mockGetThoughtSpotHost.mockReturnValue(thoughtSpotHost);
27
+ mockGetEmbedConfig.mockReturnValue({});
28
+ // Restore JSON.parse before each test
29
+ JSON.parse = originalJsonParse;
30
+ });
31
+
32
+ afterEach(() => {
33
+ // Ensure JSON.parse is restored after each test
34
+ JSON.parse = originalJsonParse;
35
+ });
36
+
37
+ describe('getInterceptInitData', () => {
38
+ it('should return default intercept flags when no intercepts are configured', () => {
39
+ const viewConfig: BaseViewConfig = {};
40
+
41
+ const result = apiIntercept.getInterceptInitData(viewConfig);
42
+
43
+ expect(result).toEqual({
44
+ interceptUrls: [],
45
+ interceptTimeout: undefined,
46
+ });
47
+ });
48
+
49
+ it('should expand InterceptedApiType.AnswerData to specific URLs', () => {
50
+ const viewConfig: BaseViewConfig = {
51
+ interceptUrls: [InterceptedApiType.AnswerData]
52
+ };
53
+
54
+ const result = apiIntercept.getInterceptInitData(viewConfig);
55
+
56
+ expect(result.interceptUrls).toEqual([
57
+ `${thoughtSpotHost}/prism/?op=GetChartWithData`,
58
+ `${thoughtSpotHost}/prism/?op=GetTableWithHeadlineData`,
59
+ `${thoughtSpotHost}/prism/?op=GetTableWithData`,
60
+ ]);
61
+ });
62
+
63
+ it('should expand InterceptedApiType.LiveboardData to specific URLs', () => {
64
+ const viewConfig: BaseViewConfig = {
65
+ interceptUrls: [InterceptedApiType.LiveboardData]
66
+ };
67
+
68
+ const result = apiIntercept.getInterceptInitData(viewConfig);
69
+
70
+ expect(result.interceptUrls).toEqual([
71
+ `${thoughtSpotHost}/prism/?op=LoadContextBook`
72
+ ]);
73
+ });
74
+
75
+ it('should handle multiple intercept types', () => {
76
+ const viewConfig: BaseViewConfig = {
77
+ interceptUrls: [
78
+ InterceptedApiType.AnswerData,
79
+ InterceptedApiType.LiveboardData
80
+ ]
81
+ };
82
+
83
+ const result = apiIntercept.getInterceptInitData(viewConfig);
84
+
85
+ expect(result.interceptUrls).toContain(`${thoughtSpotHost}/prism/?op=GetChartWithData`);
86
+ expect(result.interceptUrls).toContain(`${thoughtSpotHost}/prism/?op=LoadContextBook`);
87
+ expect(result.interceptUrls.length).toBe(4);
88
+ });
89
+
90
+ it('should handle custom URL strings', () => {
91
+ const customUrl = '/api/custom-endpoint';
92
+ const viewConfig: BaseViewConfig = {
93
+ interceptUrls: [customUrl]
94
+ };
95
+
96
+ const result = apiIntercept.getInterceptInitData(viewConfig);
97
+
98
+ expect(result.interceptUrls).toEqual([`${thoughtSpotHost}${customUrl}`]);
99
+ });
100
+
101
+ it('should handle full URL strings', () => {
102
+ const fullUrl = 'https://example.com/api/endpoint';
103
+ const viewConfig: BaseViewConfig = {
104
+ interceptUrls: [fullUrl]
105
+ };
106
+
107
+ const result = apiIntercept.getInterceptInitData(viewConfig);
108
+
109
+ expect(result.interceptUrls).toEqual([fullUrl]);
110
+ });
111
+
112
+ it('should handle InterceptedApiType.ALL', () => {
113
+ const viewConfig: BaseViewConfig = {
114
+ interceptUrls: [InterceptedApiType.ALL]
115
+ };
116
+
117
+ const result = apiIntercept.getInterceptInitData(viewConfig);
118
+
119
+ expect(result.interceptUrls).toEqual([InterceptedApiType.ALL]);
120
+ });
121
+
122
+ it('should prioritize ALL over other intercept types', () => {
123
+ const viewConfig: BaseViewConfig = {
124
+ interceptUrls: [
125
+ InterceptedApiType.AnswerData,
126
+ InterceptedApiType.ALL,
127
+ '/api/custom'
128
+ ]
129
+ };
130
+
131
+ const result = apiIntercept.getInterceptInitData(viewConfig);
132
+
133
+ expect(result.interceptUrls).toEqual([InterceptedApiType.ALL]);
134
+ });
135
+
136
+ it('should handle legacy isOnBeforeGetVizDataInterceptEnabled flag', () => {
137
+ const viewConfig: any = {
138
+ isOnBeforeGetVizDataInterceptEnabled: true
139
+ };
140
+
141
+ const result = apiIntercept.getInterceptInitData(viewConfig);
142
+
143
+ expect(result.interceptUrls).toContain(`${thoughtSpotHost}/prism/?op=GetChartWithData`);
144
+ });
145
+
146
+ it('should combine legacy flag with interceptUrls', () => {
147
+ const viewConfig: any = {
148
+ isOnBeforeGetVizDataInterceptEnabled: true,
149
+ interceptUrls: [InterceptedApiType.LiveboardData]
150
+ };
151
+
152
+ const result = apiIntercept.getInterceptInitData(viewConfig);
153
+
154
+ expect(result.interceptUrls).toContain(`${thoughtSpotHost}/prism/?op=GetChartWithData`);
155
+ expect(result.interceptUrls).toContain(`${thoughtSpotHost}/prism/?op=LoadContextBook`);
156
+ });
157
+
158
+ it('should pass through interceptTimeout', () => {
159
+ const viewConfig: BaseViewConfig = {
160
+ interceptUrls: [],
161
+ interceptTimeout: 5000
162
+ };
163
+
164
+ const result = apiIntercept.getInterceptInitData(viewConfig);
165
+
166
+ expect(result.interceptTimeout).toBe(5000);
167
+ });
168
+
169
+ it('should deduplicate URLs when same type is added multiple times', () => {
170
+ const viewConfig: BaseViewConfig = {
171
+ interceptUrls: [
172
+ InterceptedApiType.AnswerData,
173
+ InterceptedApiType.AnswerData
174
+ ]
175
+ };
176
+
177
+ const result = apiIntercept.getInterceptInitData(viewConfig);
178
+
179
+ expect(result.interceptUrls.length).toBe(3); // 3 answer data URLs
180
+ });
181
+ });
182
+
183
+ describe('handleInterceptEvent', () => {
184
+ let executeEvent: jest.Mock;
185
+ let getUnsavedAnswerTml: jest.Mock;
186
+ let viewConfig: BaseViewConfig;
187
+
188
+ beforeEach(() => {
189
+ executeEvent = jest.fn();
190
+ getUnsavedAnswerTml = jest.fn().mockResolvedValue({ answer: { tml: 'test-tml' } });
191
+ viewConfig = {};
192
+ });
193
+
194
+ it('should handle valid intercept data', async () => {
195
+ const eventData = {
196
+ data: JSON.stringify({
197
+ input: '/prism/?op=GetChartWithData',
198
+ init: {
199
+ method: 'POST',
200
+ body: JSON.stringify({
201
+ variables: {
202
+ session: { sessionId: 'session-123' },
203
+ contextBookId: 'viz-456'
204
+ }
205
+ })
206
+ }
207
+ })
208
+ };
209
+
210
+ await apiIntercept.handleInterceptEvent({
211
+ eventData,
212
+ executeEvent,
213
+ viewConfig,
214
+ getUnsavedAnswerTml
215
+ });
216
+
217
+ expect(executeEvent).toHaveBeenCalledWith(
218
+ EmbedEvent.ApiIntercept,
219
+ expect.objectContaining({
220
+ input: '/prism/?op=GetChartWithData',
221
+ urlType: InterceptedApiType.AnswerData
222
+ })
223
+ );
224
+ });
225
+
226
+ it('should trigger legacy OnBeforeGetVizDataIntercept for answer data URLs', async () => {
227
+ viewConfig.isOnBeforeGetVizDataInterceptEnabled = true;
228
+ const eventData = {
229
+ data: JSON.stringify({
230
+ input: '/prism/?op=GetChartWithData',
231
+ init: {
232
+ method: 'POST',
233
+ body: JSON.stringify({
234
+ variables: {
235
+ session: { sessionId: 'session-123' },
236
+ contextBookId: 'viz-456'
237
+ }
238
+ })
239
+ }
240
+ })
241
+ };
242
+
243
+ await apiIntercept.handleInterceptEvent({
244
+ eventData,
245
+ executeEvent,
246
+ viewConfig,
247
+ getUnsavedAnswerTml
248
+ });
249
+
250
+ expect(getUnsavedAnswerTml).toHaveBeenCalledWith({
251
+ sessionId: 'session-123',
252
+ vizId: 'viz-456'
253
+ });
254
+
255
+ expect(executeEvent).toHaveBeenCalledWith(
256
+ EmbedEvent.OnBeforeGetVizDataIntercept,
257
+ {
258
+ data: {
259
+ data: { answer: { tml: 'test-tml' } },
260
+ status: embedEventStatus.END,
261
+ type: EmbedEvent.OnBeforeGetVizDataIntercept
262
+ }
263
+ }
264
+ );
265
+
266
+ expect(executeEvent).toHaveBeenCalledWith(
267
+ EmbedEvent.ApiIntercept,
268
+ expect.any(Object)
269
+ );
270
+ });
271
+
272
+ it('should not trigger legacy intercept for non-answer data URLs', async () => {
273
+ viewConfig.isOnBeforeGetVizDataInterceptEnabled = true;
274
+ const eventData = {
275
+ data: JSON.stringify({
276
+ input: '/prism/?op=LoadContextBook',
277
+ init: {
278
+ method: 'POST',
279
+ body: '{}'
280
+ }
281
+ })
282
+ };
283
+
284
+ await apiIntercept.handleInterceptEvent({
285
+ eventData,
286
+ executeEvent,
287
+ viewConfig,
288
+ getUnsavedAnswerTml
289
+ });
290
+
291
+ expect(getUnsavedAnswerTml).not.toHaveBeenCalled();
292
+ expect(executeEvent).toHaveBeenCalledTimes(1);
293
+ expect(executeEvent).toHaveBeenCalledWith(
294
+ EmbedEvent.ApiIntercept,
295
+ expect.any(Object)
296
+ );
297
+ });
298
+
299
+ it('should handle GetTableWithHeadlineData URL as answer data', async () => {
300
+ viewConfig.isOnBeforeGetVizDataInterceptEnabled = true;
301
+ const eventData = {
302
+ data: JSON.stringify({
303
+ input: '/prism/?op=GetTableWithHeadlineData',
304
+ init: {
305
+ body: JSON.stringify({
306
+ variables: { session: { sessionId: 'test' } }
307
+ })
308
+ }
309
+ })
310
+ };
311
+
312
+ await apiIntercept.handleInterceptEvent({
313
+ eventData,
314
+ executeEvent,
315
+ viewConfig,
316
+ getUnsavedAnswerTml
317
+ });
318
+
319
+ expect(getUnsavedAnswerTml).toHaveBeenCalled();
320
+ });
321
+
322
+ it('should handle GetTableWithData URL as answer data', async () => {
323
+ viewConfig.isOnBeforeGetVizDataInterceptEnabled = true;
324
+ const eventData = {
325
+ data: JSON.stringify({
326
+ input: '/prism/?op=GetTableWithData',
327
+ init: {
328
+ body: JSON.stringify({
329
+ variables: { session: { sessionId: 'test' } }
330
+ })
331
+ }
332
+ })
333
+ };
334
+
335
+ await apiIntercept.handleInterceptEvent({
336
+ eventData,
337
+ executeEvent,
338
+ viewConfig,
339
+ getUnsavedAnswerTml
340
+ });
341
+
342
+ expect(getUnsavedAnswerTml).toHaveBeenCalled();
343
+ });
344
+
345
+ it('should handle invalid JSON in event data', async () => {
346
+ const eventData = {
347
+ data: 'invalid-json'
348
+ };
349
+
350
+ await apiIntercept.handleInterceptEvent({
351
+ eventData,
352
+ executeEvent,
353
+ viewConfig,
354
+ getUnsavedAnswerTml
355
+ });
356
+
357
+ expect(executeEvent).toHaveBeenCalledWith(
358
+ EmbedEvent.Error,
359
+ { error: 'Error parsing api intercept body' }
360
+ );
361
+ expect(mockLogger.error).toHaveBeenCalled();
362
+ });
363
+
364
+ it('should handle init with non-JSON body', async () => {
365
+ const eventData = {
366
+ data: JSON.stringify({
367
+ input: '/api/test',
368
+ init: {
369
+ method: 'POST',
370
+ body: 'plain-text-body'
371
+ }
372
+ })
373
+ };
374
+
375
+ await apiIntercept.handleInterceptEvent({
376
+ eventData,
377
+ executeEvent,
378
+ viewConfig,
379
+ getUnsavedAnswerTml
380
+ });
381
+
382
+ expect(executeEvent).toHaveBeenCalledWith(
383
+ EmbedEvent.ApiIntercept,
384
+ expect.objectContaining({
385
+ init: expect.objectContaining({
386
+ body: 'plain-text-body'
387
+ })
388
+ })
389
+ );
390
+ });
391
+
392
+ it('should handle malformed event data structure with property access error', async () => {
393
+ // Create an object with a getter that throws when accessing 'input'
394
+ global.JSON.parse = jest.fn().mockImplementationOnce((str) => {
395
+ // Return an object with a getter that throws
396
+ return new Proxy({}, {
397
+ get(target, prop) {
398
+ if (prop === 'input') {
399
+ throw new Error('Property access error');
400
+ }
401
+ return undefined;
402
+ }
403
+ });
404
+ });
405
+
406
+ const eventData = {
407
+ data: JSON.stringify({ input: '/test', init: {} })
408
+ };
409
+
410
+ await apiIntercept.handleInterceptEvent({
411
+ eventData,
412
+ executeEvent,
413
+ viewConfig,
414
+ getUnsavedAnswerTml
415
+ });
416
+
417
+ expect(executeEvent).toHaveBeenCalledWith(
418
+ EmbedEvent.Error,
419
+ { error: 'Error parsing api intercept body' }
420
+ );
421
+ expect(mockLogger.error).toHaveBeenCalled();
422
+
423
+ // Explicitly restore for this test
424
+ global.JSON.parse = originalJsonParse;
425
+ });
426
+
427
+ it('should determine urlType as ALL for unknown URLs', async () => {
428
+ const eventData = {
429
+ data: JSON.stringify({
430
+ input: '/unknown/endpoint',
431
+ init: { method: 'GET' }
432
+ })
433
+ };
434
+
435
+ await apiIntercept.handleInterceptEvent({
436
+ eventData,
437
+ executeEvent,
438
+ viewConfig,
439
+ getUnsavedAnswerTml
440
+ });
441
+
442
+ expect(executeEvent).toHaveBeenCalledWith(
443
+ EmbedEvent.ApiIntercept,
444
+ expect.objectContaining({
445
+ urlType: InterceptedApiType.ALL
446
+ })
447
+ );
448
+ });
449
+
450
+ it('should determine urlType as LiveboardData for liveboard URLs', async () => {
451
+ const eventData = {
452
+ data: JSON.stringify({
453
+ input: '/prism/?op=LoadContextBook',
454
+ init: { method: 'POST' }
455
+ })
456
+ };
457
+
458
+ await apiIntercept.handleInterceptEvent({
459
+ eventData,
460
+ executeEvent,
461
+ viewConfig,
462
+ getUnsavedAnswerTml
463
+ });
464
+
465
+ expect(executeEvent).toHaveBeenCalledWith(
466
+ EmbedEvent.ApiIntercept,
467
+ expect.objectContaining({
468
+ urlType: InterceptedApiType.LiveboardData
469
+ })
470
+ );
471
+ });
472
+
473
+ it('should handle event data with missing init', async () => {
474
+ const eventData = {
475
+ data: JSON.stringify({
476
+ input: '/prism/?op=GetChartWithData'
477
+ })
478
+ };
479
+
480
+ await apiIntercept.handleInterceptEvent({
481
+ eventData,
482
+ executeEvent,
483
+ viewConfig,
484
+ getUnsavedAnswerTml
485
+ });
486
+
487
+ // When init is missing, accessing init.body throws an error
488
+ expect(executeEvent).toHaveBeenCalledWith(
489
+ EmbedEvent.Error,
490
+ { error: 'Error parsing api intercept body' }
491
+ );
492
+ });
493
+
494
+ it('should handle event data with missing body', async () => {
495
+ const eventData = {
496
+ data: JSON.stringify({
497
+ input: '/prism/?op=GetChartWithData',
498
+ init: {}
499
+ })
500
+ };
501
+
502
+ await apiIntercept.handleInterceptEvent({
503
+ eventData,
504
+ executeEvent,
505
+ viewConfig,
506
+ getUnsavedAnswerTml
507
+ });
508
+
509
+ expect(executeEvent).toHaveBeenCalledWith(
510
+ EmbedEvent.ApiIntercept,
511
+ expect.any(Object)
512
+ );
513
+ });
514
+
515
+ it('should handle event data with missing variables in body', async () => {
516
+ const eventData = {
517
+ data: JSON.stringify({
518
+ input: '/prism/?op=GetChartWithData',
519
+ init: {
520
+ body: JSON.stringify({})
521
+ }
522
+ })
523
+ };
524
+
525
+ await apiIntercept.handleInterceptEvent({
526
+ eventData,
527
+ executeEvent,
528
+ viewConfig,
529
+ getUnsavedAnswerTml
530
+ });
531
+
532
+ expect(executeEvent).toHaveBeenCalledWith(
533
+ EmbedEvent.ApiIntercept,
534
+ expect.any(Object)
535
+ );
536
+ });
537
+
538
+ it('should handle event data with missing session in variables', async () => {
539
+ const eventData = {
540
+ data: JSON.stringify({
541
+ input: '/prism/?op=GetChartWithData',
542
+ init: {
543
+ body: JSON.stringify({
544
+ variables: {}
545
+ })
546
+ }
547
+ })
548
+ };
549
+
550
+ await apiIntercept.handleInterceptEvent({
551
+ eventData,
552
+ executeEvent,
553
+ viewConfig,
554
+ getUnsavedAnswerTml
555
+ });
556
+
557
+ expect(executeEvent).toHaveBeenCalledWith(
558
+ EmbedEvent.ApiIntercept,
559
+ expect.any(Object)
560
+ );
561
+ });
562
+ });
563
+
564
+ describe('processApiInterceptResponse', () => {
565
+ it('should process legacy format with error', () => {
566
+ const legacyPayload = {
567
+ data: {
568
+ error: {
569
+ errorText: 'Test Error',
570
+ errorDescription: 'Test Description'
571
+ },
572
+ execute: false
573
+ }
574
+ };
575
+
576
+ const result = apiIntercept.processApiInterceptResponse(legacyPayload);
577
+
578
+ expect(result).toEqual({
579
+ data: {
580
+ execute: false,
581
+ response: {
582
+ body: {
583
+ errors: [
584
+ {
585
+ title: 'Test Error',
586
+ description: 'Test Description',
587
+ isUserError: true,
588
+ },
589
+ ],
590
+ data: {},
591
+ },
592
+ },
593
+ }
594
+ });
595
+ });
596
+
597
+ it('should pass through new format unchanged', () => {
598
+ const newPayload = {
599
+ execute: true,
600
+ response: {
601
+ body: { data: 'test' }
602
+ }
603
+ };
604
+
605
+ const result = apiIntercept.processApiInterceptResponse(newPayload);
606
+
607
+ expect(result).toEqual(newPayload);
608
+ });
609
+
610
+ it('should handle payload without data property', () => {
611
+ const payload = {
612
+ execute: true
613
+ };
614
+
615
+ const result = apiIntercept.processApiInterceptResponse(payload);
616
+
617
+ expect(result).toEqual(payload);
618
+ });
619
+
620
+ it('should handle payload with data but no error', () => {
621
+ const payload = {
622
+ data: {
623
+ execute: true,
624
+ someOtherProperty: 'value'
625
+ }
626
+ };
627
+
628
+ const result = apiIntercept.processApiInterceptResponse(payload);
629
+
630
+ expect(result).toEqual(payload);
631
+ });
632
+
633
+ it('should handle null payload', () => {
634
+ const result = apiIntercept.processApiInterceptResponse(null);
635
+
636
+ expect(result).toBeNull();
637
+ });
638
+
639
+ it('should handle undefined payload', () => {
640
+ const result = apiIntercept.processApiInterceptResponse(undefined);
641
+
642
+ expect(result).toBeUndefined();
643
+ });
644
+
645
+ it('should handle payload with null data', () => {
646
+ const payload: any = {
647
+ data: null
648
+ };
649
+
650
+ const result = apiIntercept.processApiInterceptResponse(payload);
651
+
652
+ expect(result).toEqual(payload);
653
+ });
654
+
655
+ it('should handle payload with data.error set to null', () => {
656
+ const payload: any = {
657
+ data: {
658
+ error: null,
659
+ execute: true
660
+ }
661
+ };
662
+
663
+ const result = apiIntercept.processApiInterceptResponse(payload);
664
+
665
+ expect(result).toEqual(payload);
666
+ });
667
+
668
+ it('should handle payload with data.error set to undefined', () => {
669
+ const payload: any = {
670
+ data: {
671
+ error: undefined,
672
+ execute: true
673
+ }
674
+ };
675
+
676
+ const result = apiIntercept.processApiInterceptResponse(payload);
677
+
678
+ expect(result).toEqual(payload);
679
+ });
680
+
681
+ it('should handle payload with data.error set to false', () => {
682
+ const payload = {
683
+ data: {
684
+ error: false,
685
+ execute: true
686
+ }
687
+ };
688
+
689
+ const result = apiIntercept.processApiInterceptResponse(payload);
690
+
691
+ expect(result).toEqual(payload);
692
+ });
693
+
694
+ it('should handle payload with data.error set to 0', () => {
695
+ const payload = {
696
+ data: {
697
+ error: 0,
698
+ execute: true
699
+ }
700
+ };
701
+
702
+ const result = apiIntercept.processApiInterceptResponse(payload);
703
+
704
+ expect(result).toEqual(payload);
705
+ });
706
+
707
+ it('should handle payload with data.error set to empty string', () => {
708
+ const payload = {
709
+ data: {
710
+ error: '',
711
+ execute: true
712
+ }
713
+ };
714
+
715
+ const result = apiIntercept.processApiInterceptResponse(payload);
716
+
717
+ expect(result).toEqual(payload);
718
+ });
719
+ });
720
+
721
+ describe('processLegacyInterceptResponse', () => {
722
+ it('should convert legacy error format to new format', () => {
723
+ const legacyPayload = {
724
+ data: {
725
+ error: {
726
+ errorText: 'Custom Error',
727
+ errorDescription: 'Custom Description'
728
+ },
729
+ execute: false
730
+ }
731
+ };
732
+
733
+ const result = apiIntercept.processLegacyInterceptResponse(legacyPayload);
734
+
735
+ expect(result).toEqual({
736
+ data: {
737
+ execute: false,
738
+ response: {
739
+ body: {
740
+ errors: [
741
+ {
742
+ title: 'Custom Error',
743
+ description: 'Custom Description',
744
+ isUserError: true,
745
+ },
746
+ ],
747
+ data: {},
748
+ },
749
+ },
750
+ }
751
+ });
752
+ });
753
+
754
+ it('should handle missing error properties', () => {
755
+ const legacyPayload = {
756
+ data: {
757
+ error: {},
758
+ execute: true
759
+ }
760
+ };
761
+
762
+ const result = apiIntercept.processLegacyInterceptResponse(legacyPayload);
763
+
764
+ expect(result.data.response.body.errors[0]).toEqual({
765
+ title: undefined,
766
+ description: undefined,
767
+ isUserError: true,
768
+ });
769
+ });
770
+
771
+ it('should handle missing execute property', () => {
772
+ const legacyPayload = {
773
+ data: {
774
+ error: {
775
+ errorText: 'Error',
776
+ errorDescription: 'Description'
777
+ }
778
+ }
779
+ };
780
+
781
+ const result = apiIntercept.processLegacyInterceptResponse(legacyPayload);
782
+
783
+ expect(result.data.execute).toBeUndefined();
784
+ });
785
+
786
+ it('should always include empty data object', () => {
787
+ const legacyPayload = {
788
+ data: {
789
+ error: {
790
+ errorText: 'Error',
791
+ errorDescription: 'Description'
792
+ },
793
+ execute: false
794
+ }
795
+ };
796
+
797
+ const result = apiIntercept.processLegacyInterceptResponse(legacyPayload);
798
+
799
+ expect(result.data.response.body.data).toEqual({});
800
+ });
801
+
802
+ it('should always set isUserError to true', () => {
803
+ const legacyPayload = {
804
+ data: {
805
+ error: {
806
+ errorText: 'Error',
807
+ errorDescription: 'Description'
808
+ },
809
+ execute: false
810
+ }
811
+ };
812
+
813
+ const result = apiIntercept.processLegacyInterceptResponse(legacyPayload);
814
+
815
+ expect(result.data.response.body.errors[0].isUserError).toBe(true);
816
+ });
817
+
818
+ it('should handle payload with null data', () => {
819
+ const legacyPayload: any = {
820
+ data: null
821
+ };
822
+
823
+ const result = apiIntercept.processLegacyInterceptResponse(legacyPayload);
824
+
825
+ expect(result.data.execute).toBeUndefined();
826
+ expect(result.data.response.body.errors[0].title).toBeUndefined();
827
+ expect(result.data.response.body.errors[0].description).toBeUndefined();
828
+ });
829
+
830
+ it('should handle payload with null error', () => {
831
+ const legacyPayload: any = {
832
+ data: {
833
+ error: null,
834
+ execute: true
835
+ }
836
+ };
837
+
838
+ const result = apiIntercept.processLegacyInterceptResponse(legacyPayload);
839
+
840
+ expect(result.data.execute).toBe(true);
841
+ expect(result.data.response.body.errors[0].title).toBeUndefined();
842
+ expect(result.data.response.body.errors[0].description).toBeUndefined();
843
+ });
844
+
845
+ it('should handle payload with undefined properties', () => {
846
+ const legacyPayload = {};
847
+
848
+ const result = apiIntercept.processLegacyInterceptResponse(legacyPayload);
849
+
850
+ expect(result.data.execute).toBeUndefined();
851
+ expect(result.data.response.body.errors[0].title).toBeUndefined();
852
+ expect(result.data.response.body.errors[0].description).toBeUndefined();
853
+ });
854
+ });
855
+ });
856
+