@thoughtspot/visual-embed-sdk 1.49.3 → 1.50.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 (241) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/authToken.spec.js +5 -0
  3. package/cjs/src/authToken.spec.js.map +1 -1
  4. package/cjs/src/css-variables.d.ts +59 -0
  5. package/cjs/src/css-variables.d.ts.map +1 -1
  6. package/cjs/src/embed/app.d.ts +31 -2
  7. package/cjs/src/embed/app.d.ts.map +1 -1
  8. package/cjs/src/embed/app.js +19 -2
  9. package/cjs/src/embed/app.js.map +1 -1
  10. package/cjs/src/embed/app.spec.js +112 -2
  11. package/cjs/src/embed/app.spec.js.map +1 -1
  12. package/cjs/src/embed/conversation.d.ts +38 -0
  13. package/cjs/src/embed/conversation.d.ts.map +1 -1
  14. package/cjs/src/embed/conversation.js +12 -2
  15. package/cjs/src/embed/conversation.js.map +1 -1
  16. package/cjs/src/embed/conversation.spec.js +53 -0
  17. package/cjs/src/embed/conversation.spec.js.map +1 -1
  18. package/cjs/src/embed/embedConfig.spec.d.ts +2 -0
  19. package/cjs/src/embed/embedConfig.spec.d.ts.map +1 -0
  20. package/cjs/src/embed/embedConfig.spec.js +74 -0
  21. package/cjs/src/embed/embedConfig.spec.js.map +1 -0
  22. package/cjs/src/embed/events.spec.js +593 -0
  23. package/cjs/src/embed/events.spec.js.map +1 -1
  24. package/cjs/src/embed/host-events.spec.d.ts +2 -0
  25. package/cjs/src/embed/host-events.spec.d.ts.map +1 -0
  26. package/cjs/src/embed/host-events.spec.js +1325 -0
  27. package/cjs/src/embed/host-events.spec.js.map +1 -0
  28. package/cjs/src/embed/hostEventClient/contracts.d.ts +36 -14
  29. package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  30. package/cjs/src/embed/hostEventClient/contracts.js +10 -1
  31. package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
  32. package/cjs/src/embed/hostEventClient/host-event-client.d.ts +1 -0
  33. package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  34. package/cjs/src/embed/hostEventClient/host-event-client.js +8 -0
  35. package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
  36. package/cjs/src/embed/hostEventClient/host-event-client.spec.js +209 -0
  37. package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  38. package/cjs/src/embed/hostEventClient/utils.d.ts +2 -0
  39. package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -1
  40. package/cjs/src/embed/hostEventClient/utils.js +32 -2
  41. package/cjs/src/embed/hostEventClient/utils.js.map +1 -1
  42. package/cjs/src/embed/hostEventClient/utils.spec.js +178 -0
  43. package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -1
  44. package/cjs/src/embed/liveboard.d.ts +6 -0
  45. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  46. package/cjs/src/embed/liveboard.js.map +1 -1
  47. package/cjs/src/embed/spotter-viz-utils.d.ts +34 -0
  48. package/cjs/src/embed/spotter-viz-utils.d.ts.map +1 -1
  49. package/cjs/src/embed/spotter-viz-utils.js.map +1 -1
  50. package/cjs/src/embed/spotter-viz-utils.spec.js +17 -0
  51. package/cjs/src/embed/spotter-viz-utils.spec.js.map +1 -1
  52. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  53. package/cjs/src/embed/ts-embed.js +14 -7
  54. package/cjs/src/embed/ts-embed.js.map +1 -1
  55. package/cjs/src/embed/ts-embed.spec.js +146 -0
  56. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  57. package/cjs/src/errors.d.ts +1 -0
  58. package/cjs/src/errors.d.ts.map +1 -1
  59. package/cjs/src/errors.js +1 -0
  60. package/cjs/src/errors.js.map +1 -1
  61. package/cjs/src/index.d.ts +2 -2
  62. package/cjs/src/index.d.ts.map +1 -1
  63. package/cjs/src/index.js +3 -2
  64. package/cjs/src/index.js.map +1 -1
  65. package/cjs/src/mixpanel-service.spec.js +14 -0
  66. package/cjs/src/mixpanel-service.spec.js.map +1 -1
  67. package/cjs/src/react/util.spec.js +251 -0
  68. package/cjs/src/react/util.spec.js.map +1 -1
  69. package/cjs/src/types.d.ts +212 -8
  70. package/cjs/src/types.d.ts.map +1 -1
  71. package/cjs/src/types.js +185 -3
  72. package/cjs/src/types.js.map +1 -1
  73. package/cjs/src/utils/custom-actions.spec.js +26 -0
  74. package/cjs/src/utils/custom-actions.spec.js.map +1 -1
  75. package/cjs/src/utils/processData.spec.js +123 -0
  76. package/cjs/src/utils/processData.spec.js.map +1 -1
  77. package/cjs/src/utils/processTrigger.spec.js +61 -0
  78. package/cjs/src/utils/processTrigger.spec.js.map +1 -1
  79. package/cjs/src/utils/reporting.spec.js +21 -0
  80. package/cjs/src/utils/reporting.spec.js.map +1 -1
  81. package/cjs/src/utils/sessionInfoService.spec.d.ts +2 -0
  82. package/cjs/src/utils/sessionInfoService.spec.d.ts.map +1 -0
  83. package/cjs/src/utils/sessionInfoService.spec.js +130 -0
  84. package/cjs/src/utils/sessionInfoService.spec.js.map +1 -0
  85. package/cjs/src/utils.spec.js +158 -0
  86. package/cjs/src/utils.spec.js.map +1 -1
  87. package/dist/{index-DsSQndIB.js → index-fCne7kmU.js} +1 -1
  88. package/dist/src/css-variables.d.ts +59 -0
  89. package/dist/src/css-variables.d.ts.map +1 -1
  90. package/dist/src/embed/app.d.ts +31 -2
  91. package/dist/src/embed/app.d.ts.map +1 -1
  92. package/dist/src/embed/conversation.d.ts +38 -0
  93. package/dist/src/embed/conversation.d.ts.map +1 -1
  94. package/dist/src/embed/embedConfig.spec.d.ts +2 -0
  95. package/dist/src/embed/embedConfig.spec.d.ts.map +1 -0
  96. package/dist/src/embed/host-events.spec.d.ts +2 -0
  97. package/dist/src/embed/host-events.spec.d.ts.map +1 -0
  98. package/dist/src/embed/hostEventClient/contracts.d.ts +36 -14
  99. package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  100. package/dist/src/embed/hostEventClient/host-event-client.d.ts +1 -0
  101. package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  102. package/dist/src/embed/hostEventClient/utils.d.ts +2 -0
  103. package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -1
  104. package/dist/src/embed/liveboard.d.ts +6 -0
  105. package/dist/src/embed/liveboard.d.ts.map +1 -1
  106. package/dist/src/embed/spotter-viz-utils.d.ts +34 -0
  107. package/dist/src/embed/spotter-viz-utils.d.ts.map +1 -1
  108. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  109. package/dist/src/errors.d.ts +1 -0
  110. package/dist/src/errors.d.ts.map +1 -1
  111. package/dist/src/index.d.ts +2 -2
  112. package/dist/src/index.d.ts.map +1 -1
  113. package/dist/src/types.d.ts +212 -8
  114. package/dist/src/types.d.ts.map +1 -1
  115. package/dist/src/utils/sessionInfoService.spec.d.ts +2 -0
  116. package/dist/src/utils/sessionInfoService.spec.d.ts.map +1 -0
  117. package/dist/tsembed-react.es.js +482 -86
  118. package/dist/tsembed-react.js +481 -85
  119. package/dist/tsembed.es.js +483 -87
  120. package/dist/tsembed.js +481 -85
  121. package/dist/visual-embed-sdk-react-full.d.ts +407 -23
  122. package/dist/visual-embed-sdk-react.d.ts +407 -23
  123. package/dist/visual-embed-sdk.d.ts +411 -23
  124. package/lib/package.json +1 -1
  125. package/lib/src/authToken.spec.js +5 -0
  126. package/lib/src/authToken.spec.js.map +1 -1
  127. package/lib/src/css-variables.d.ts +59 -0
  128. package/lib/src/css-variables.d.ts.map +1 -1
  129. package/lib/src/embed/app.d.ts +31 -2
  130. package/lib/src/embed/app.d.ts.map +1 -1
  131. package/lib/src/embed/app.js +19 -2
  132. package/lib/src/embed/app.js.map +1 -1
  133. package/lib/src/embed/app.spec.js +112 -2
  134. package/lib/src/embed/app.spec.js.map +1 -1
  135. package/lib/src/embed/conversation.d.ts +38 -0
  136. package/lib/src/embed/conversation.d.ts.map +1 -1
  137. package/lib/src/embed/conversation.js +11 -1
  138. package/lib/src/embed/conversation.js.map +1 -1
  139. package/lib/src/embed/conversation.spec.js +54 -1
  140. package/lib/src/embed/conversation.spec.js.map +1 -1
  141. package/lib/src/embed/embedConfig.spec.d.ts +2 -0
  142. package/lib/src/embed/embedConfig.spec.d.ts.map +1 -0
  143. package/lib/src/embed/embedConfig.spec.js +72 -0
  144. package/lib/src/embed/embedConfig.spec.js.map +1 -0
  145. package/lib/src/embed/events.spec.js +593 -0
  146. package/lib/src/embed/events.spec.js.map +1 -1
  147. package/lib/src/embed/host-events.spec.d.ts +2 -0
  148. package/lib/src/embed/host-events.spec.d.ts.map +1 -0
  149. package/lib/src/embed/host-events.spec.js +1322 -0
  150. package/lib/src/embed/host-events.spec.js.map +1 -0
  151. package/lib/src/embed/hostEventClient/contracts.d.ts +36 -14
  152. package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  153. package/lib/src/embed/hostEventClient/contracts.js +9 -0
  154. package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
  155. package/lib/src/embed/hostEventClient/host-event-client.d.ts +1 -0
  156. package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  157. package/lib/src/embed/hostEventClient/host-event-client.js +9 -1
  158. package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
  159. package/lib/src/embed/hostEventClient/host-event-client.spec.js +209 -0
  160. package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  161. package/lib/src/embed/hostEventClient/utils.d.ts +2 -0
  162. package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -1
  163. package/lib/src/embed/hostEventClient/utils.js +28 -1
  164. package/lib/src/embed/hostEventClient/utils.js.map +1 -1
  165. package/lib/src/embed/hostEventClient/utils.spec.js +179 -1
  166. package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -1
  167. package/lib/src/embed/liveboard.d.ts +6 -0
  168. package/lib/src/embed/liveboard.d.ts.map +1 -1
  169. package/lib/src/embed/liveboard.js.map +1 -1
  170. package/lib/src/embed/spotter-viz-utils.d.ts +34 -0
  171. package/lib/src/embed/spotter-viz-utils.d.ts.map +1 -1
  172. package/lib/src/embed/spotter-viz-utils.js.map +1 -1
  173. package/lib/src/embed/spotter-viz-utils.spec.js +17 -0
  174. package/lib/src/embed/spotter-viz-utils.spec.js.map +1 -1
  175. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  176. package/lib/src/embed/ts-embed.js +14 -7
  177. package/lib/src/embed/ts-embed.js.map +1 -1
  178. package/lib/src/embed/ts-embed.spec.js +146 -0
  179. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  180. package/lib/src/errors.d.ts +1 -0
  181. package/lib/src/errors.d.ts.map +1 -1
  182. package/lib/src/errors.js +1 -0
  183. package/lib/src/errors.js.map +1 -1
  184. package/lib/src/index.d.ts +2 -2
  185. package/lib/src/index.d.ts.map +1 -1
  186. package/lib/src/index.js +2 -2
  187. package/lib/src/index.js.map +1 -1
  188. package/lib/src/mixpanel-service.spec.js +14 -0
  189. package/lib/src/mixpanel-service.spec.js.map +1 -1
  190. package/lib/src/react/util.spec.js +251 -0
  191. package/lib/src/react/util.spec.js.map +1 -1
  192. package/lib/src/types.d.ts +212 -8
  193. package/lib/src/types.d.ts.map +1 -1
  194. package/lib/src/types.js +185 -3
  195. package/lib/src/types.js.map +1 -1
  196. package/lib/src/utils/custom-actions.spec.js +26 -0
  197. package/lib/src/utils/custom-actions.spec.js.map +1 -1
  198. package/lib/src/utils/processData.spec.js +123 -0
  199. package/lib/src/utils/processData.spec.js.map +1 -1
  200. package/lib/src/utils/processTrigger.spec.js +61 -0
  201. package/lib/src/utils/processTrigger.spec.js.map +1 -1
  202. package/lib/src/utils/reporting.spec.js +21 -0
  203. package/lib/src/utils/reporting.spec.js.map +1 -1
  204. package/lib/src/utils/sessionInfoService.spec.d.ts +2 -0
  205. package/lib/src/utils/sessionInfoService.spec.d.ts.map +1 -0
  206. package/lib/src/utils/sessionInfoService.spec.js +127 -0
  207. package/lib/src/utils/sessionInfoService.spec.js.map +1 -0
  208. package/lib/src/utils.spec.js +159 -1
  209. package/lib/src/utils.spec.js.map +1 -1
  210. package/lib/src/visual-embed-sdk.d.ts +411 -23
  211. package/package.json +1 -1
  212. package/src/authToken.spec.ts +6 -0
  213. package/src/css-variables.ts +73 -0
  214. package/src/embed/app.spec.ts +155 -2
  215. package/src/embed/app.ts +44 -2
  216. package/src/embed/conversation.spec.ts +64 -1
  217. package/src/embed/conversation.ts +41 -0
  218. package/src/embed/embedConfig.spec.ts +79 -0
  219. package/src/embed/events.spec.ts +705 -1
  220. package/src/embed/host-events.spec.ts +1759 -0
  221. package/src/embed/hostEventClient/contracts.ts +41 -14
  222. package/src/embed/hostEventClient/host-event-client.spec.ts +326 -0
  223. package/src/embed/hostEventClient/host-event-client.ts +15 -0
  224. package/src/embed/hostEventClient/utils.spec.ts +204 -0
  225. package/src/embed/hostEventClient/utils.ts +37 -10
  226. package/src/embed/liveboard.ts +6 -0
  227. package/src/embed/spotter-viz-utils.spec.ts +17 -0
  228. package/src/embed/spotter-viz-utils.ts +34 -0
  229. package/src/embed/ts-embed.spec.ts +186 -0
  230. package/src/embed/ts-embed.ts +12 -4
  231. package/src/errors.ts +1 -0
  232. package/src/index.ts +2 -1
  233. package/src/mixpanel-service.spec.ts +15 -0
  234. package/src/react/util.spec.tsx +255 -0
  235. package/src/types.ts +215 -6
  236. package/src/utils/custom-actions.spec.ts +28 -0
  237. package/src/utils/processData.spec.ts +129 -0
  238. package/src/utils/processTrigger.spec.ts +91 -0
  239. package/src/utils/reporting.spec.ts +27 -0
  240. package/src/utils/sessionInfoService.spec.ts +151 -0
  241. package/src/utils.spec.ts +195 -0
@@ -80,6 +80,11 @@ describe('Unit test for process data', () => {
80
80
  );
81
81
  });
82
82
 
83
+ test('ProcessData QueryChanged default branch returns payload unchanged', () => {
84
+ const e = { type: EmbedEvent.QueryChanged, data: { query: 'revenue by region' } };
85
+ expect(processDataInstance.processEventData(EmbedEvent.QueryChanged, e, thoughtSpotHost, null)).toEqual(e);
86
+ });
87
+
83
88
  test('ProcessData, when Action is non CustomAction', () => {
84
89
  const processedData = { type: EmbedEvent.Data };
85
90
  jest.spyOn(processDataInstance, 'processCustomAction').mockImplementation(async () => ({}));
@@ -279,6 +284,130 @@ describe('Unit test for process data', () => {
279
284
  mockHandleExitPresentMode.mockReset();
280
285
  });
281
286
 
287
+ test('AuthInit with payload.userGUID fallback when data.userGUID is absent', () => {
288
+ const e = {
289
+ type: EmbedEvent.AuthInit,
290
+ payload: { userGUID: 'payloadGUID' },
291
+ };
292
+ jest.spyOn(base, 'notifyAuthSuccess');
293
+ expect(processDataInstance.processEventData(e.type, e, '', null)).toEqual({
294
+ type: e.type,
295
+ payload: { userGUID: 'payloadGUID' },
296
+ data: { userGUID: 'payloadGUID' },
297
+ });
298
+ expect(base.notifyAuthSuccess).toHaveBeenCalled();
299
+ });
300
+
301
+ test('process authFailure EmbeddedSSO with UNAUTHENTICATED_FAILURE suppresses login page', () => {
302
+ const e = {
303
+ type: EmbedEvent.AuthFailure,
304
+ data: { type: auth.AuthFailureType.UNAUTHENTICATED_FAILURE },
305
+ };
306
+ jest.spyOn(base, 'notifyAuthFailure');
307
+ jest.spyOn(embedConfigInstance, 'getEmbedConfig').mockReturnValue({
308
+ loginFailedMessage: 'Hello',
309
+ authType: AuthType.EmbeddedSSO,
310
+ disableLoginFailurePage: false,
311
+ } as any);
312
+ const el: any = {};
313
+ processDataInstance.processEventData(e.type, e, '', el);
314
+ // isEmbeddedSSOInfoFailure=true so neither branch fires, innerHTML stays unset
315
+ expect(el.innerHTML).toBeUndefined();
316
+ expect(base.notifyAuthFailure).not.toHaveBeenCalled();
317
+ });
318
+
319
+ test('processEventData default branch returns payload unchanged for non-special events', () => {
320
+ const events = [
321
+ EmbedEvent.Drilldown,
322
+ EmbedEvent.DataSourceSelected,
323
+ EmbedEvent.AddRemoveColumns,
324
+ EmbedEvent.VizPointDoubleClick,
325
+ EmbedEvent.VizPointClick,
326
+ EmbedEvent.Alert,
327
+ EmbedEvent.GetDataClick,
328
+ EmbedEvent.DialogClose,
329
+ EmbedEvent.Download,
330
+ EmbedEvent.DownloadAsPng,
331
+ EmbedEvent.DownloadAsPdf,
332
+ EmbedEvent.DownloadAsCsv,
333
+ EmbedEvent.DownloadAsXlsx,
334
+ EmbedEvent.DownloadLiveboardAsContinuousPDF,
335
+ EmbedEvent.AnswerDelete,
336
+ EmbedEvent.AIHighlights,
337
+ EmbedEvent.Pin,
338
+ EmbedEvent.SpotIQAnalyze,
339
+ EmbedEvent.Share,
340
+ EmbedEvent.DrillInclude,
341
+ EmbedEvent.DrillExclude,
342
+ EmbedEvent.CopyToClipboard,
343
+ EmbedEvent.UpdateTML,
344
+ EmbedEvent.EditTML,
345
+ EmbedEvent.ExportTML,
346
+ EmbedEvent.SaveAsView,
347
+ EmbedEvent.CopyAEdit,
348
+ EmbedEvent.ShowUnderlyingData,
349
+ EmbedEvent.AnswerChartSwitcher,
350
+ EmbedEvent.LiveboardInfo,
351
+ EmbedEvent.AddToFavorites,
352
+ EmbedEvent.Schedule,
353
+ EmbedEvent.Edit,
354
+ EmbedEvent.MakeACopy,
355
+ EmbedEvent.Present,
356
+ EmbedEvent.Delete,
357
+ EmbedEvent.SchedulesList,
358
+ EmbedEvent.Cancel,
359
+ EmbedEvent.Explore,
360
+ EmbedEvent.CopyLink,
361
+ EmbedEvent.CrossFilterChanged,
362
+ EmbedEvent.VizPointRightClick,
363
+ EmbedEvent.InsertIntoSlide,
364
+ EmbedEvent.FilterChanged,
365
+ EmbedEvent.UpdateConnection,
366
+ EmbedEvent.CreateConnection,
367
+ EmbedEvent.ResetLiveboard,
368
+ EmbedEvent.ChangePersonalizedView,
369
+ EmbedEvent.CreateWorksheet,
370
+ EmbedEvent.AskSageInit,
371
+ EmbedEvent.Rename,
372
+ EmbedEvent.ParameterChanged,
373
+ EmbedEvent.TableVizRendered,
374
+ EmbedEvent.CreateLiveboard,
375
+ EmbedEvent.CreateModel,
376
+ EmbedEvent.SpotterData,
377
+ EmbedEvent.PreviewSpotterData,
378
+ EmbedEvent.AddToCoaching,
379
+ EmbedEvent.DataModelInstructions,
380
+ EmbedEvent.SpotterQueryTriggered,
381
+ EmbedEvent.LastPromptEdited,
382
+ EmbedEvent.LastPromptDeleted,
383
+ EmbedEvent.ResetSpotterConversation,
384
+ EmbedEvent.SpotterInit,
385
+ EmbedEvent.SpotterLoadComplete,
386
+ EmbedEvent.OrgSwitched,
387
+ EmbedEvent.SpotterConversationRenamed,
388
+ EmbedEvent.SpotterConversationDeleted,
389
+ EmbedEvent.SpotterConversationSelected,
390
+ EmbedEvent.EmbedPageContextChanged,
391
+ EmbedEvent.Subscribed,
392
+ EmbedEvent.SendTestScheduleEmail,
393
+ EmbedEvent.SpotterVizInit,
394
+ EmbedEvent.SpotterVizQueryTriggered,
395
+ EmbedEvent.SpotterVizResponseComplete,
396
+ EmbedEvent.SpotterVizCheckpointCreated,
397
+ EmbedEvent.SpotterVizCheckpointRestored,
398
+ EmbedEvent.SpotterVizError,
399
+ EmbedEvent.SpotterVizClosed,
400
+ EmbedEvent.RefreshLiveboardBrowserCache,
401
+ EmbedEvent.V1Data,
402
+ ];
403
+ events.forEach((eventType) => {
404
+ const e = { type: eventType, data: 'test-payload' };
405
+ expect(
406
+ processDataInstance.processEventData(eventType, e, thoughtSpotHost, null),
407
+ ).toEqual(e);
408
+ });
409
+ });
410
+
282
411
  test('should handle ClearInfoCache', () => {
283
412
  const mockResetCachedPreauthInfo = jest.spyOn(sessionInfoService, 'resetCachedPreauthInfo').mockImplementation(() => {});
284
413
  const mockResetCachedSessionInfo = jest.spyOn(sessionInfoService, 'resetCachedSessionInfo').mockImplementation(() => {});
@@ -216,5 +216,96 @@ describe('Unit test for processTrigger', () => {
216
216
  'Fullscreen presentation mode is disabled. Set disableFullscreenPresentation: false to enable this feature.',
217
217
  );
218
218
  });
219
+
220
+ test('postMessage is called with type=present when disableFullscreenPresentation is false', () => {
221
+ mockGetEmbedConfig.mockReturnValue({ disableFullscreenPresentation: false });
222
+ const thoughtSpotHost = 'https://example.thoughtspot.com';
223
+ _processTriggerInstance.processTrigger(iFrame, HostEvent.Present, thoughtSpotHost, {});
224
+ expect(iFrame.contentWindow.postMessage).toHaveBeenCalledWith(
225
+ expect.objectContaining({ type: HostEvent.Present }),
226
+ thoughtSpotHost,
227
+ expect.anything(),
228
+ );
229
+ });
230
+
231
+ test('postMessage is still called with type=present when disableFullscreenPresentation is true', () => {
232
+ mockGetEmbedConfig.mockReturnValue({ disableFullscreenPresentation: true });
233
+ const thoughtSpotHost = 'https://example.thoughtspot.com';
234
+ _processTriggerInstance.processTrigger(iFrame, HostEvent.Present, thoughtSpotHost, {});
235
+ expect(iFrame.contentWindow.postMessage).toHaveBeenCalledWith(
236
+ expect.objectContaining({ type: HostEvent.Present }),
237
+ thoughtSpotHost,
238
+ expect.anything(),
239
+ );
240
+ });
241
+
242
+ test('handlePresentEvent receives the exact iFrame reference passed to processTrigger', () => {
243
+ mockGetEmbedConfig.mockReturnValue({ disableFullscreenPresentation: false });
244
+ _processTriggerInstance.processTrigger(
245
+ iFrame, HostEvent.Present, 'https://example.thoughtspot.com', {},
246
+ );
247
+ expect(mockHandlePresentEvent).toHaveBeenCalledWith(iFrame);
248
+ });
249
+ });
250
+
251
+ test('nested responseData.data.error causes promise rejection', async () => {
252
+ const messageType = HostEvent.Search;
253
+ const thoughtSpotHost = 'http://localhost:3000';
254
+ mockMessageChannel();
255
+ const triggerPromise = _processTriggerInstance.processTrigger(
256
+ iFrame, messageType, thoughtSpotHost, {},
257
+ );
258
+ messageChannelMock.port1.onmessage({ data: { data: { error: 'nested-error' } } });
259
+ await expect(triggerPromise).rejects.toEqual('nested-error');
260
+ });
261
+
262
+ test('context parameter is forwarded as context field in the postMessage payload', async () => {
263
+ const messageType = HostEvent.Search;
264
+ const thoughtSpotHost = 'http://localhost:3000';
265
+ const context = 'Liveboard' as any;
266
+ mockMessageChannel();
267
+ _processTriggerInstance.processTrigger(iFrame, messageType, thoughtSpotHost, {}, context);
268
+ expect(iFrame.contentWindow.postMessage).toHaveBeenCalledWith(
269
+ expect.objectContaining({ context }),
270
+ thoughtSpotHost,
271
+ expect.anything(),
272
+ );
273
+ });
274
+
275
+ describe('Reload timing and side-effects', () => {
276
+ test('src is cleared immediately but not restored until exactly 100ms', () => {
277
+ jest.useFakeTimers();
278
+ const iFrameElement = document.createElement('iframe');
279
+ iFrameElement.src = 'http://localhost:3000';
280
+ const originalSrc = iFrameElement.src; // normalized by jsdom
281
+
282
+ _processTriggerInstance.reload(iFrameElement);
283
+
284
+ expect(iFrameElement.src).not.toBe(originalSrc); // cleared
285
+ jest.advanceTimersByTime(99);
286
+ expect(iFrameElement.src).not.toBe(originalSrc); // not yet restored
287
+ jest.advanceTimersByTime(1);
288
+ expect(iFrameElement.src).toBe(originalSrc); // restored at 100ms
289
+ jest.useRealTimers();
290
+ });
291
+
292
+ test('processTrigger(Reload) does not call iFrame.contentWindow.postMessage', () => {
293
+ jest.useFakeTimers();
294
+ // Use the outer iFrame mock which has a mockable contentWindow
295
+ jest.clearAllMocks();
296
+ _processTriggerInstance.processTrigger(
297
+ iFrame, HostEvent.Reload, 'http://localhost:3000', {},
298
+ );
299
+ jest.advanceTimersByTime(200);
300
+ expect(iFrame.contentWindow.postMessage).not.toHaveBeenCalled();
301
+ jest.useRealTimers();
302
+ });
303
+
304
+ test('processTrigger(Reload) resolves with null', async () => {
305
+ const result = await _processTriggerInstance.processTrigger(
306
+ iFrame, HostEvent.Reload, 'http://localhost:3000', {},
307
+ );
308
+ expect(result).toBeNull();
309
+ });
219
310
  });
220
311
  });
@@ -88,4 +88,31 @@ describe('ReportingObserver', () => {
88
88
 
89
89
  (window as any).ReportingObserver = currentObserver;
90
90
  });
91
+
92
+ test('should suppress alert but still log error when suppressErrorAlerts is true', () => {
93
+ let callBackPassed: any;
94
+ const NewMockRO = jest.fn().mockImplementation((callback: any) => {
95
+ callBackPassed = callback;
96
+ return ({ observe: jest.fn(), disconnect: jest.fn(), takeRecords: jest.fn() });
97
+ });
98
+ const currentObserver = (window as any).ReportingObserver;
99
+ (window as any).ReportingObserver = NewMockRO;
100
+
101
+ setEmbedConfig({
102
+ thoughtSpotHost: 'testHost',
103
+ authType: AuthType.None,
104
+ suppressErrorAlerts: true,
105
+ } as any);
106
+
107
+ const mockAlert = jest.fn();
108
+ window.alert = mockAlert;
109
+
110
+ registerReportingObserver(true);
111
+ callBackPassed([{ type: 'csp-violation', url: 'testHost', body: { effectiveDirective: 'frame-ancestors' } }]);
112
+
113
+ expect(mockAlert).not.toHaveBeenCalled();
114
+ expect(logger.error).toHaveBeenCalledWith(ERROR_MESSAGE.CSP_FRAME_HOST_VIOLATION_LOG_MESSAGE);
115
+
116
+ (window as any).ReportingObserver = currentObserver;
117
+ });
91
118
  });
@@ -0,0 +1,151 @@
1
+ import * as authServiceModule from './authService';
2
+ import * as embedConfigModule from '../embed/embedConfig';
3
+ import {
4
+ formatPreauthInfo,
5
+ getPreauthInfo,
6
+ getSessionInfo,
7
+ resetCachedPreauthInfo,
8
+ resetCachedSessionInfo,
9
+ getCachedSessionInfo,
10
+ getSessionDetails,
11
+ } from './sessionInfoService';
12
+
13
+ describe('sessionInfoService', () => {
14
+ beforeEach(() => {
15
+ resetCachedPreauthInfo();
16
+ resetCachedSessionInfo();
17
+ jest.clearAllMocks();
18
+ });
19
+
20
+ describe('formatPreauthInfo', () => {
21
+ it('parses headers and JSON body from a response', async () => {
22
+ const headers = new Headers({ 'content-type': 'application/json' });
23
+ const mockResp = {
24
+ headers,
25
+ json: jest.fn().mockResolvedValue({ info: { userGUID: 'u1' } }),
26
+ };
27
+ const result = await formatPreauthInfo(mockResp);
28
+ expect(result.info.userGUID).toBe('u1');
29
+ expect(result.status).toBe(200);
30
+ expect(result.headers['content-type']).toBe('application/json');
31
+ });
32
+
33
+ it('returns null when response.json() throws (covers catch block)', async () => {
34
+ const mockResp = {
35
+ headers: null as any,
36
+ json: jest.fn().mockRejectedValue(new Error('parse error')),
37
+ };
38
+ const result = await formatPreauthInfo(mockResp);
39
+ expect(result).toBeNull();
40
+ });
41
+
42
+ it('handles response where headers is null (covers headers?.forEach branch)', async () => {
43
+ const mockResp = {
44
+ headers: null as any,
45
+ json: jest.fn().mockResolvedValue({ info: {} }),
46
+ };
47
+ const result = await formatPreauthInfo(mockResp);
48
+ expect(result.headers).toEqual({});
49
+ });
50
+ });
51
+
52
+ describe('getPreauthInfo', () => {
53
+ it('fetches and caches preauthInfo on first call', async () => {
54
+ const mockResp = {
55
+ headers: new Headers({ 'x-caller': 'test' }),
56
+ json: jest.fn().mockResolvedValue({ info: { userGUID: 'u1' } }),
57
+ };
58
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
59
+ jest.spyOn(authServiceModule, 'fetchPreauthInfoService').mockResolvedValue(mockResp as any);
60
+
61
+ const result = await getPreauthInfo();
62
+ expect(result.info.userGUID).toBe('u1');
63
+ });
64
+
65
+ it('returns cached preauthInfo on subsequent calls (covers allowCache=true && preauthInfo cached branch)', async () => {
66
+ const mockResp = {
67
+ headers: new Headers(),
68
+ json: jest.fn().mockResolvedValue({ info: { userGUID: 'cached-user' } }),
69
+ };
70
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
71
+ const fetchSpy = jest.spyOn(authServiceModule, 'fetchPreauthInfoService').mockResolvedValue(mockResp as any);
72
+
73
+ await getPreauthInfo(); // first call — fetches and caches
74
+ await getPreauthInfo(); // second call — should use cache
75
+
76
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
77
+ });
78
+
79
+ it('returns null when fetch throws (covers catch block)', async () => {
80
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
81
+ jest.spyOn(authServiceModule, 'fetchPreauthInfoService').mockRejectedValue(new Error('network error'));
82
+
83
+ const result = await getPreauthInfo();
84
+ expect(result).toBeNull();
85
+ });
86
+ });
87
+
88
+ describe('getSessionInfo', () => {
89
+ it('fetches and returns session info', async () => {
90
+ const sessionInfoResp = {
91
+ userGUID: 'u1',
92
+ releaseVersion: '1.0',
93
+ configInfo: {
94
+ mixpanelConfig: { devSdkKey: 'dev', prodSdkKey: 'prod', production: false },
95
+ isPublicUser: false,
96
+ selfClusterId: 'c1',
97
+ selfClusterName: 'cluster1',
98
+ },
99
+ };
100
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
101
+ jest.spyOn(authServiceModule, 'fetchSessionInfoService').mockResolvedValue(sessionInfoResp as any);
102
+
103
+ const result = await getSessionInfo();
104
+ expect(result.userGUID).toBe('u1');
105
+ expect(result.mixpanelToken).toBe('dev');
106
+ });
107
+
108
+ it('returns cached session info on second call', async () => {
109
+ const sessionInfoResp = {
110
+ userGUID: 'u2',
111
+ releaseVersion: '2.0',
112
+ configInfo: {
113
+ mixpanelConfig: { devSdkKey: 'dev2', prodSdkKey: 'prod2', production: true },
114
+ isPublicUser: false,
115
+ selfClusterId: 'c2',
116
+ selfClusterName: 'cluster2',
117
+ },
118
+ };
119
+ jest.spyOn(embedConfigModule, 'getEmbedConfig').mockReturnValue({ thoughtSpotHost: 'https://ts' } as any);
120
+ const fetchSpy = jest.spyOn(authServiceModule, 'fetchSessionInfoService').mockResolvedValue(sessionInfoResp as any);
121
+
122
+ await getSessionInfo();
123
+ await getSessionInfo();
124
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
125
+ });
126
+ });
127
+
128
+ describe('getCachedSessionInfo', () => {
129
+ it('returns null before any fetch', () => {
130
+ expect(getCachedSessionInfo()).toBeNull();
131
+ });
132
+ });
133
+
134
+ describe('getSessionDetails', () => {
135
+ it('uses prodSdkKey when production flag is true', () => {
136
+ const resp = {
137
+ userGUID: 'u3',
138
+ releaseVersion: '3.0',
139
+ configInfo: {
140
+ mixpanelConfig: { devSdkKey: 'dev', prodSdkKey: 'prod', production: true },
141
+ isPublicUser: true,
142
+ selfClusterId: 'c3',
143
+ selfClusterName: 'cluster3',
144
+ },
145
+ };
146
+ const result = getSessionDetails(resp);
147
+ expect(result.mixpanelToken).toBe('prod');
148
+ expect(result.isPublicUser).toBe(true);
149
+ });
150
+ });
151
+ });
package/src/utils.spec.ts CHANGED
@@ -27,6 +27,13 @@ import {
27
27
  validateHttpUrl,
28
28
  setParamIfDefined,
29
29
  querySelectorAcrossShadowRoot,
30
+ getSSOMarker,
31
+ deepMerge,
32
+ getHostEventsConfig,
33
+ isWindowUndefined,
34
+ getOffsetTop,
35
+ getDOMNode,
36
+ getOperationNameFromQuery,
30
37
  } from './utils';
31
38
  import { RuntimeFilterOp } from './types';
32
39
  import { logger } from './utils/logger';
@@ -388,6 +395,18 @@ describe('Fullscreen Utility Functions', () => {
388
395
  expect(logger.error).toHaveBeenCalledWith('Fullscreen API is not supported by this browser.');
389
396
  });
390
397
 
398
+ it('should catch error and log warning when requestFullscreen throws (covers line 506)', async () => {
399
+ // Covers line 506: catch block inside fullscreen method attempt
400
+ (mockIframe.requestFullscreen as jest.Mock).mockRejectedValue(new Error('permission denied'));
401
+
402
+ await handlePresentEvent(mockIframe);
403
+
404
+ expect(logger.warn).toHaveBeenCalledWith(
405
+ expect.stringContaining('Failed to enter fullscreen'),
406
+ expect.any(Error),
407
+ );
408
+ });
409
+
391
410
  it('should not attempt fullscreen when already in fullscreen mode', () => {
392
411
  Object.defineProperty(document, 'fullscreenElement', {
393
412
  writable: true,
@@ -1009,6 +1028,24 @@ describe('isValidCssMargin', () => {
1009
1028
  expect(isValidCssMargin('invalid')).toBe(false);
1010
1029
  expect(isValidCssMargin('10')).toBe(false); // missing unit
1011
1030
  });
1031
+
1032
+ it('should return false and log error when value is not a string (non-string type)', () => {
1033
+ // Covers line 147-148: typeof value !== 'string' branch
1034
+ expect(isValidCssMargin(42 as any)).toBe(false);
1035
+ expect(logger.error).toHaveBeenCalledWith('Please provide a valid lazyLoadingMargin value (e.g., "10px")');
1036
+ });
1037
+
1038
+ it('should return false when more than 4 space-separated parts are given', () => {
1039
+ // Covers line 157-158: parts.length > 4 branch
1040
+ expect(isValidCssMargin('10px 20px 30px 40px 50px')).toBe(false);
1041
+ expect(logger.error).toHaveBeenCalledWith('Please provide a valid lazyLoadingMargin value (e.g., "10px")');
1042
+ });
1043
+
1044
+ it('should return true for valid multi-part margin (up to 4 parts)', () => {
1045
+ expect(isValidCssMargin('10px 20px')).toBe(true);
1046
+ expect(isValidCssMargin('10px 20px 30px 40px')).toBe(true);
1047
+ expect(isValidCssMargin('auto')).toBe(true);
1048
+ });
1012
1049
  });
1013
1050
 
1014
1051
  describe('getValueFromWindow and storeValueInWindow', () => {
@@ -1140,3 +1177,161 @@ describe('getValueFromWindow and storeValueInWindow', () => {
1140
1177
  });
1141
1178
  });
1142
1179
  });
1180
+
1181
+ // ---------------------------------------------------------------------------
1182
+ // getSSOMarker
1183
+ // ---------------------------------------------------------------------------
1184
+ describe('getSSOMarker', () => {
1185
+ test('returns tsSSOMarker query string with encoded markerId', () => {
1186
+ expect(getSSOMarker('abc123')).toBe('tsSSOMarker=abc123');
1187
+ });
1188
+
1189
+ test('URL-encodes special characters in markerId', () => {
1190
+ expect(getSSOMarker('hello world')).toBe('tsSSOMarker=hello%20world');
1191
+ expect(getSSOMarker('foo=bar&baz')).toBe('tsSSOMarker=foo%3Dbar%26baz');
1192
+ });
1193
+
1194
+ test('handles empty string markerId', () => {
1195
+ expect(getSSOMarker('')).toBe('tsSSOMarker=');
1196
+ });
1197
+ });
1198
+
1199
+ // ---------------------------------------------------------------------------
1200
+ // deepMerge
1201
+ // ---------------------------------------------------------------------------
1202
+ describe('deepMerge', () => {
1203
+ test('merges two flat objects', () => {
1204
+ const result = deepMerge({ a: 1 }, { b: 2 });
1205
+ expect(result).toEqual({ a: 1, b: 2 });
1206
+ });
1207
+
1208
+ test('source overrides target for same keys', () => {
1209
+ const result = deepMerge({ a: 1, b: 2 }, { b: 99 });
1210
+ expect(result).toEqual({ a: 1, b: 99 });
1211
+ });
1212
+
1213
+ test('deep merges nested objects', () => {
1214
+ const result = deepMerge({ a: { x: 1, y: 2 } }, { a: { y: 99, z: 3 } });
1215
+ expect(result).toEqual({ a: { x: 1, y: 99, z: 3 } });
1216
+ });
1217
+
1218
+ test('handles empty source', () => {
1219
+ const result = deepMerge({ a: 1 }, {});
1220
+ expect(result).toEqual({ a: 1 });
1221
+ });
1222
+
1223
+ test('handles empty target', () => {
1224
+ const result = deepMerge({}, { b: 2 });
1225
+ expect(result).toEqual({ b: 2 });
1226
+ });
1227
+ });
1228
+
1229
+ // ---------------------------------------------------------------------------
1230
+ // getHostEventsConfig
1231
+ // ---------------------------------------------------------------------------
1232
+ describe('getHostEventsConfig', () => {
1233
+ test('returns shouldBypassPayloadValidation and useHostEventsV2 from viewConfig', () => {
1234
+ const viewConfig: any = { shouldBypassPayloadValidation: true, useHostEventsV2: false };
1235
+ expect(getHostEventsConfig(viewConfig)).toEqual({
1236
+ shouldBypassPayloadValidation: true,
1237
+ useHostEventsV2: false,
1238
+ });
1239
+ });
1240
+
1241
+ test('returns undefined for missing keys', () => {
1242
+ const result = getHostEventsConfig({} as any);
1243
+ expect(result.shouldBypassPayloadValidation).toBeUndefined();
1244
+ expect(result.useHostEventsV2).toBeUndefined();
1245
+ });
1246
+
1247
+ test('ignores unrelated config properties', () => {
1248
+ const viewConfig: any = {
1249
+ shouldBypassPayloadValidation: false,
1250
+ useHostEventsV2: true,
1251
+ liveboardId: 'lb-1',
1252
+ };
1253
+ expect(getHostEventsConfig(viewConfig)).toEqual({
1254
+ shouldBypassPayloadValidation: false,
1255
+ useHostEventsV2: true,
1256
+ });
1257
+ });
1258
+ });
1259
+
1260
+ // ---------------------------------------------------------------------------
1261
+ // isWindowUndefined
1262
+ // ---------------------------------------------------------------------------
1263
+ describe('isWindowUndefined', () => {
1264
+ test('returns false when window is defined (browser/jsdom env)', () => {
1265
+ // In jest/jsdom, window is always defined
1266
+ expect(isWindowUndefined()).toBe(false);
1267
+ });
1268
+ });
1269
+
1270
+ // ---------------------------------------------------------------------------
1271
+ // getOffsetTop
1272
+ // ---------------------------------------------------------------------------
1273
+ describe('getOffsetTop', () => {
1274
+ test('returns rect.top + window.scrollY', () => {
1275
+ const mockElement = {
1276
+ getBoundingClientRect: () => ({ top: 50 }),
1277
+ };
1278
+ Object.defineProperty(window, 'scrollY', { value: 100, configurable: true });
1279
+ expect(getOffsetTop(mockElement)).toBe(150);
1280
+ });
1281
+
1282
+ test('returns rect.top when scrollY is 0', () => {
1283
+ const mockElement = {
1284
+ getBoundingClientRect: () => ({ top: 30 }),
1285
+ };
1286
+ Object.defineProperty(window, 'scrollY', { value: 0, configurable: true });
1287
+ expect(getOffsetTop(mockElement)).toBe(30);
1288
+ });
1289
+ });
1290
+
1291
+ // ---------------------------------------------------------------------------
1292
+ // getDOMNode
1293
+ // ---------------------------------------------------------------------------
1294
+ describe('getDOMNode', () => {
1295
+ test('returns the element from document.querySelector when passed a string selector', () => {
1296
+ const div = document.createElement('div');
1297
+ div.id = 'test-dom-node';
1298
+ document.body.appendChild(div);
1299
+ expect(getDOMNode('#test-dom-node')).toBe(div);
1300
+ document.body.removeChild(div);
1301
+ });
1302
+
1303
+ test('returns the element directly when passed an HTMLElement', () => {
1304
+ const div = document.createElement('div');
1305
+ expect(getDOMNode(div)).toBe(div);
1306
+ });
1307
+
1308
+ test('returns null for a selector that matches nothing', () => {
1309
+ expect(getDOMNode('#nonexistent-element-xyz')).toBeNull();
1310
+ });
1311
+ });
1312
+
1313
+ // ---------------------------------------------------------------------------
1314
+ // getOperationNameFromQuery
1315
+ // ---------------------------------------------------------------------------
1316
+ describe('getOperationNameFromQuery', () => {
1317
+ test('extracts operation name from a query string', () => {
1318
+ expect(getOperationNameFromQuery('query GetUser { user { id } }')).toBe('GetUser');
1319
+ });
1320
+
1321
+ test('extracts operation name from a mutation string', () => {
1322
+ expect(getOperationNameFromQuery('mutation CreateUser { createUser { id } }')).toBe('CreateUser');
1323
+ });
1324
+
1325
+ test('returns undefined when no operation name is present', () => {
1326
+ expect(getOperationNameFromQuery('{ user { id } }')).toBeUndefined();
1327
+ });
1328
+
1329
+ test('handles multiline query strings', () => {
1330
+ const query = `
1331
+ query FetchData {
1332
+ data { id }
1333
+ }
1334
+ `;
1335
+ expect(getOperationNameFromQuery(query)).toBe('FetchData');
1336
+ });
1337
+ });