@thoughtspot/visual-embed-sdk 1.49.0 → 1.49.2

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 (143) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/css-variables.d.ts +17 -0
  3. package/cjs/src/css-variables.d.ts.map +1 -1
  4. package/cjs/src/embed/app.d.ts.map +1 -1
  5. package/cjs/src/embed/app.js +4 -1
  6. package/cjs/src/embed/app.js.map +1 -1
  7. package/cjs/src/embed/app.spec.js +97 -0
  8. package/cjs/src/embed/app.spec.js.map +1 -1
  9. package/cjs/src/embed/conversation.d.ts +8 -0
  10. package/cjs/src/embed/conversation.d.ts.map +1 -1
  11. package/cjs/src/embed/conversation.js +2 -1
  12. package/cjs/src/embed/conversation.js.map +1 -1
  13. package/cjs/src/embed/conversation.spec.js +12 -0
  14. package/cjs/src/embed/conversation.spec.js.map +1 -1
  15. package/cjs/src/embed/events.spec.js +5 -0
  16. package/cjs/src/embed/events.spec.js.map +1 -1
  17. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  18. package/cjs/src/embed/liveboard.js +4 -1
  19. package/cjs/src/embed/liveboard.js.map +1 -1
  20. package/cjs/src/embed/liveboard.spec.js +11 -0
  21. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  22. package/cjs/src/embed/ts-embed.d.ts +38 -0
  23. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  24. package/cjs/src/embed/ts-embed.js +168 -11
  25. package/cjs/src/embed/ts-embed.js.map +1 -1
  26. package/cjs/src/embed/ts-embed.spec.js +237 -0
  27. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  28. package/cjs/src/mixpanel-service.js +2 -2
  29. package/cjs/src/mixpanel-service.js.map +1 -1
  30. package/cjs/src/types.d.ts +87 -4
  31. package/cjs/src/types.d.ts.map +1 -1
  32. package/cjs/src/types.js +47 -3
  33. package/cjs/src/types.js.map +1 -1
  34. package/cjs/src/utils/logger.js +2 -2
  35. package/cjs/src/utils/logger.js.map +1 -1
  36. package/cjs/src/utils/processTrigger.d.ts.map +1 -1
  37. package/cjs/src/utils/processTrigger.js +6 -5
  38. package/cjs/src/utils/processTrigger.js.map +1 -1
  39. package/cjs/src/utils/processTrigger.spec.js +13 -0
  40. package/cjs/src/utils/processTrigger.spec.js.map +1 -1
  41. package/cjs/src/utils/sdk-version.d.ts +2 -0
  42. package/cjs/src/utils/sdk-version.d.ts.map +1 -0
  43. package/cjs/src/utils/sdk-version.js +7 -0
  44. package/cjs/src/utils/sdk-version.js.map +1 -0
  45. package/cjs/src/utils.d.ts +10 -0
  46. package/cjs/src/utils.d.ts.map +1 -1
  47. package/cjs/src/utils.js +23 -1
  48. package/cjs/src/utils.js.map +1 -1
  49. package/cjs/src/utils.spec.js +48 -0
  50. package/cjs/src/utils.spec.js.map +1 -1
  51. package/dist/{index-_UGCSSDR.js → index-CS7yJ2Jj.js} +1 -1
  52. package/dist/src/css-variables.d.ts +17 -0
  53. package/dist/src/css-variables.d.ts.map +1 -1
  54. package/dist/src/embed/app.d.ts.map +1 -1
  55. package/dist/src/embed/conversation.d.ts +8 -0
  56. package/dist/src/embed/conversation.d.ts.map +1 -1
  57. package/dist/src/embed/liveboard.d.ts.map +1 -1
  58. package/dist/src/embed/ts-embed.d.ts +38 -0
  59. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  60. package/dist/src/types.d.ts +87 -4
  61. package/dist/src/types.d.ts.map +1 -1
  62. package/dist/src/utils/processTrigger.d.ts.map +1 -1
  63. package/dist/src/utils/sdk-version.d.ts +2 -0
  64. package/dist/src/utils/sdk-version.d.ts.map +1 -0
  65. package/dist/src/utils.d.ts +10 -0
  66. package/dist/src/utils.d.ts.map +1 -1
  67. package/dist/tsembed-react.es.js +255 -23
  68. package/dist/tsembed-react.js +254 -22
  69. package/dist/tsembed.es.js +255 -23
  70. package/dist/tsembed.js +254 -22
  71. package/dist/visual-embed-sdk-react-full.d.ts +148 -3
  72. package/dist/visual-embed-sdk-react.d.ts +148 -3
  73. package/dist/visual-embed-sdk.d.ts +148 -3
  74. package/lib/package.json +1 -1
  75. package/lib/src/css-variables.d.ts +17 -0
  76. package/lib/src/css-variables.d.ts.map +1 -1
  77. package/lib/src/embed/app.d.ts.map +1 -1
  78. package/lib/src/embed/app.js +4 -1
  79. package/lib/src/embed/app.js.map +1 -1
  80. package/lib/src/embed/app.spec.js +97 -0
  81. package/lib/src/embed/app.spec.js.map +1 -1
  82. package/lib/src/embed/conversation.d.ts +8 -0
  83. package/lib/src/embed/conversation.d.ts.map +1 -1
  84. package/lib/src/embed/conversation.js +2 -1
  85. package/lib/src/embed/conversation.js.map +1 -1
  86. package/lib/src/embed/conversation.spec.js +12 -0
  87. package/lib/src/embed/conversation.spec.js.map +1 -1
  88. package/lib/src/embed/events.spec.js +5 -0
  89. package/lib/src/embed/events.spec.js.map +1 -1
  90. package/lib/src/embed/liveboard.d.ts.map +1 -1
  91. package/lib/src/embed/liveboard.js +4 -1
  92. package/lib/src/embed/liveboard.js.map +1 -1
  93. package/lib/src/embed/liveboard.spec.js +11 -0
  94. package/lib/src/embed/liveboard.spec.js.map +1 -1
  95. package/lib/src/embed/ts-embed.d.ts +38 -0
  96. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  97. package/lib/src/embed/ts-embed.js +168 -11
  98. package/lib/src/embed/ts-embed.js.map +1 -1
  99. package/lib/src/embed/ts-embed.spec.js +237 -0
  100. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  101. package/lib/src/mixpanel-service.js +1 -1
  102. package/lib/src/mixpanel-service.js.map +1 -1
  103. package/lib/src/types.d.ts +87 -4
  104. package/lib/src/types.d.ts.map +1 -1
  105. package/lib/src/types.js +47 -3
  106. package/lib/src/types.js.map +1 -1
  107. package/lib/src/utils/logger.js +1 -1
  108. package/lib/src/utils/logger.js.map +1 -1
  109. package/lib/src/utils/processTrigger.d.ts.map +1 -1
  110. package/lib/src/utils/processTrigger.js +6 -5
  111. package/lib/src/utils/processTrigger.js.map +1 -1
  112. package/lib/src/utils/processTrigger.spec.js +13 -0
  113. package/lib/src/utils/processTrigger.spec.js.map +1 -1
  114. package/lib/src/utils/sdk-version.d.ts +2 -0
  115. package/lib/src/utils/sdk-version.d.ts.map +1 -0
  116. package/lib/src/utils/sdk-version.js +3 -0
  117. package/lib/src/utils/sdk-version.js.map +1 -0
  118. package/lib/src/utils.d.ts +10 -0
  119. package/lib/src/utils.d.ts.map +1 -1
  120. package/lib/src/utils.js +21 -0
  121. package/lib/src/utils.js.map +1 -1
  122. package/lib/src/utils.spec.js +49 -1
  123. package/lib/src/utils.spec.js.map +1 -1
  124. package/lib/src/visual-embed-sdk.d.ts +148 -3
  125. package/package.json +1 -1
  126. package/src/css-variables.ts +20 -0
  127. package/src/embed/app.spec.ts +109 -0
  128. package/src/embed/app.ts +4 -0
  129. package/src/embed/conversation.spec.ts +17 -0
  130. package/src/embed/conversation.ts +10 -0
  131. package/src/embed/events.spec.ts +7 -0
  132. package/src/embed/liveboard.spec.ts +15 -0
  133. package/src/embed/liveboard.ts +4 -0
  134. package/src/embed/ts-embed.spec.ts +294 -0
  135. package/src/embed/ts-embed.ts +181 -10
  136. package/src/mixpanel-service.ts +1 -1
  137. package/src/types.ts +86 -3
  138. package/src/utils/logger.ts +1 -1
  139. package/src/utils/processTrigger.spec.ts +23 -0
  140. package/src/utils/processTrigger.ts +8 -6
  141. package/src/utils/sdk-version.ts +3 -0
  142. package/src/utils.spec.ts +63 -0
  143. package/src/utils.ts +24 -0
@@ -1165,4 +1165,24 @@ export interface CustomCssVariables {
1165
1165
  * header underline, left panel border, thinking step connector and dots.
1166
1166
  */
1167
1167
  '--ts-var-spotterviz-border-color'?: string;
1168
+
1169
+ /**
1170
+ * Background color of the reference-mode toggle button in the SpotterViz
1171
+ * chat input when it is unselected and the user hovers over it.
1172
+ */
1173
+ '--ts-var-spotterviz-reference-icon-hover-background'?: string;
1174
+
1175
+ /**
1176
+ * Icon color for the reference-mode selected state — applies to
1177
+ * both the toggle button when active and the icon badge on each
1178
+ * referenced-entity chip in the chat input.
1179
+ */
1180
+ '--ts-var-spotterviz-reference-icon-selected-color'?: string;
1181
+
1182
+ /**
1183
+ * Background color for the reference-mode selected state — applies to
1184
+ * both the reference mode toggle button when active and the icon badge
1185
+ * on each referenced-entity chip in the chat input.
1186
+ */
1187
+ '--ts-var-spotterviz-reference-icon-selected-background'?: string;
1168
1188
  }
@@ -757,6 +757,20 @@ describe('App embed tests', () => {
757
757
  });
758
758
  });
759
759
 
760
+ test('should set enableStarterPrompts to true in url', async () => {
761
+ const appEmbed = new AppEmbed(getRootEl(), {
762
+ ...defaultViewConfig,
763
+ spotterChatConfig: { enableStarterPrompts: true },
764
+ } as AppViewConfig);
765
+ appEmbed.render();
766
+ await executeAfterWait(() => {
767
+ expectUrlMatchesWithParams(
768
+ getIFrameSrc(),
769
+ `http://${thoughtSpotHost}/?embedApp=true&profileAndHelpInNavBarHidden=false&enableStarterPrompts=true${defaultParamsPost}#/home`,
770
+ );
771
+ });
772
+ });
773
+
760
774
  test('should set spotterFileUploadFileTypes in url', async () => {
761
775
  const appEmbed = new AppEmbed(getRootEl(), {
762
776
  ...defaultViewConfig,
@@ -2120,6 +2134,101 @@ describe('App Embed Default Height and Minimum Height Handling', () => {
2120
2134
  });
2121
2135
  });
2122
2136
 
2137
+ describe('AppEmbed uncovered branch tests', () => {
2138
+ beforeEach(() => {
2139
+ document.body.innerHTML = getDocumentBody();
2140
+ });
2141
+
2142
+ test('should set enableStopAnswerGenerationEmbed param when provided', async () => {
2143
+ const appEmbed = new AppEmbed(getRootEl(), {
2144
+ ...defaultViewConfig,
2145
+ enableStopAnswerGenerationEmbed: true,
2146
+ } as AppViewConfig);
2147
+ appEmbed.render();
2148
+ await executeAfterWait(() => {
2149
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
2150
+ enableStopAnswerGenerationEmbed: 'true',
2151
+ });
2152
+ });
2153
+ });
2154
+
2155
+ test('should set enableStopAnswerGenerationEmbed=false when set to false', async () => {
2156
+ const appEmbed = new AppEmbed(getRootEl(), {
2157
+ ...defaultViewConfig,
2158
+ enableStopAnswerGenerationEmbed: false,
2159
+ } as AppViewConfig);
2160
+ appEmbed.render();
2161
+ await executeAfterWait(() => {
2162
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
2163
+ enableStopAnswerGenerationEmbed: 'false',
2164
+ });
2165
+ });
2166
+ });
2167
+
2168
+ test('should set hideObjects param when non-empty array is provided', async () => {
2169
+ const appEmbed = new AppEmbed(getRootEl(), {
2170
+ ...defaultViewConfig,
2171
+ hideObjects: ['guid-123', 'guid-456'],
2172
+ } as AppViewConfig);
2173
+ appEmbed.render();
2174
+ await executeAfterWait(() => {
2175
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
2176
+ hideObjects: JSON.stringify(['guid-123', 'guid-456']),
2177
+ });
2178
+ });
2179
+ });
2180
+
2181
+ test('should not set hideObjects param when array is empty', async () => {
2182
+ const appEmbed = new AppEmbed(getRootEl(), {
2183
+ ...defaultViewConfig,
2184
+ hideObjects: [],
2185
+ } as AppViewConfig);
2186
+ appEmbed.render();
2187
+ await executeAfterWait(() => {
2188
+ const src = getIFrameSrc();
2189
+ expect(src).not.toContain('hideObjects');
2190
+ });
2191
+ });
2192
+
2193
+ test('should set liveboardV2 param when provided', async () => {
2194
+ const appEmbed = new AppEmbed(getRootEl(), {
2195
+ ...defaultViewConfig,
2196
+ liveboardV2: true,
2197
+ } as AppViewConfig);
2198
+ appEmbed.render();
2199
+ await executeAfterWait(() => {
2200
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
2201
+ isPinboardV2Enabled: 'true',
2202
+ });
2203
+ });
2204
+ });
2205
+
2206
+ test('should strip leading slash from path in getIFrameSrc', async () => {
2207
+ const appEmbed = new AppEmbed(getRootEl(), {
2208
+ ...defaultViewConfig,
2209
+ path: '/pinboards',
2210
+ } as AppViewConfig);
2211
+ appEmbed.render();
2212
+ await executeAfterWait(() => {
2213
+ const src = getIFrameSrc();
2214
+ expect(src).toContain('#/pinboards');
2215
+ expect(src).not.toContain('#//pinboards');
2216
+ });
2217
+ });
2218
+
2219
+ test('registerLazyLoadEvents should return early when iFrame is not set', () => {
2220
+ const appEmbed = new AppEmbed(getRootEl(), {
2221
+ ...defaultViewConfig,
2222
+ fullHeight: true,
2223
+ lazyLoadingForFullHeight: true,
2224
+ } as AppViewConfig);
2225
+ // iFrame is not set (render not called), should not throw
2226
+ expect(() => {
2227
+ (appEmbed as any).registerLazyLoadEvents();
2228
+ }).not.toThrow();
2229
+ });
2230
+ });
2231
+
2123
2232
  describe('AppEmbed visualOverrides tests', () => {
2124
2233
  test('should include visualOverridesParams in APP_INIT when visualOverrides config is provided', async () => {
2125
2234
  const visualOverrides = {
package/src/embed/app.ts CHANGED
@@ -1030,6 +1030,7 @@ export class AppEmbed extends V1Embed {
1030
1030
  toolResponseCardBrandingLabel,
1031
1031
  spotterFileUploadEnabled,
1032
1032
  spotterFileUploadFileTypes,
1033
+ enableStarterPrompts,
1033
1034
  } = spotterChatConfig;
1034
1035
 
1035
1036
  setParamIfDefined(params, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true);
@@ -1037,6 +1038,9 @@ export class AppEmbed extends V1Embed {
1037
1038
  if (spotterFileUploadEnabled !== undefined) {
1038
1039
  params[Param.SpotterFileUploadEnabled] = spotterFileUploadEnabled;
1039
1040
  }
1041
+ if (enableStarterPrompts !== undefined) {
1042
+ params[Param.IsStarterPromptsEnabled] = enableStarterPrompts;
1043
+ }
1040
1044
  if (spotterFileUploadFileTypes !== undefined) {
1041
1045
  params[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes);
1042
1046
  }
@@ -369,6 +369,23 @@ describe('ConversationEmbed', () => {
369
369
  );
370
370
  });
371
371
 
372
+ it('should render the conversation embed with enableStarterPrompts', async () => {
373
+ const viewConfig: SpotterEmbedViewConfig = {
374
+ worksheetId: 'worksheetId',
375
+ searchOptions: {
376
+ searchQuery: 'searchQuery',
377
+ },
378
+ spotterChatConfig: { enableStarterPrompts: true },
379
+ };
380
+
381
+ const conversationEmbed = new SpotterEmbed(getRootEl(), viewConfig);
382
+ await conversationEmbed.render();
383
+ expectUrlMatchesWithParams(
384
+ getIFrameSrc(),
385
+ `http://${thoughtSpotHost}/v2/?${defaultParams}&isSpotterExperienceEnabled=true&enableStarterPrompts=true#/embed/insights/conv-assist?worksheet=worksheetId&query=searchQuery`,
386
+ );
387
+ });
388
+
372
389
  it('should render the conversation embed with spotterFileUploadFileTypes', async () => {
373
390
  const viewConfig: SpotterEmbedViewConfig = {
374
391
  worksheetId: 'worksheetId',
@@ -128,6 +128,14 @@ export interface SpotterChatViewConfig {
128
128
  * @version SDK: 1.49.0 | ThoughtSpot: 26.6.0.cl
129
129
  */
130
130
  spotterFileUploadFileTypes?: SpotterFileUploadFileTypes;
131
+ /**
132
+ * Enables starter prompts in the Spotter chat interface.
133
+ *
134
+ * Supported embed types: SpotterEmbed, LiveboardEmbed, AppEmbed
135
+ * @version SDK: 1.51.0 | ThoughtSpot: 26.8.0.cl
136
+ * @default false
137
+ */
138
+ enableStarterPrompts?: boolean;
131
139
  }
132
140
 
133
141
  /**
@@ -467,11 +475,13 @@ export class SpotterEmbed extends TsEmbed {
467
475
  toolResponseCardBrandingLabel,
468
476
  spotterFileUploadEnabled,
469
477
  spotterFileUploadFileTypes,
478
+ enableStarterPrompts,
470
479
  } = spotterChatConfig;
471
480
 
472
481
  setParamIfDefined(queryParams, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true);
473
482
  setParamIfDefined(queryParams, Param.ToolResponseCardBrandingLabel, toolResponseCardBrandingLabel);
474
483
  setParamIfDefined(queryParams, Param.SpotterFileUploadEnabled, spotterFileUploadEnabled, true);
484
+ setParamIfDefined(queryParams, Param.IsStarterPromptsEnabled, enableStarterPrompts, true);
475
485
  if (spotterFileUploadFileTypes !== undefined) {
476
486
  queryParams[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes);
477
487
  }
@@ -374,3 +374,10 @@ describe('Americanized enum alias values', () => {
374
374
  expect(Action.OrganizeFavorites).toBe(Action.OrganiseFavourites);
375
375
  });
376
376
  });
377
+
378
+ describe('Action enum values', () => {
379
+
380
+ test('RemoveFromFavorites should resolve to its string value', () => {
381
+ expect(Action.RemoveFromFavorites).toBe('removeFromFavorites');
382
+ });
383
+ });
@@ -1281,6 +1281,21 @@ describe('Liveboard/viz embed tests', () => {
1281
1281
  });
1282
1282
  });
1283
1283
 
1284
+ test('should set enableStarterPrompts parameter in url params', async () => {
1285
+ const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1286
+ ...defaultViewConfig,
1287
+ liveboardId,
1288
+ spotterChatConfig: { enableStarterPrompts: true },
1289
+ } as LiveboardViewConfig);
1290
+ await liveboardEmbed.render();
1291
+ await executeAfterWait(() => {
1292
+ expectUrlMatchesWithParams(
1293
+ getIFrameSrc(),
1294
+ `http://${thoughtSpotHost}/?embedApp=true${defaultParams}${prefixParams}&enableStarterPrompts=true#/embed/viz/${liveboardId}`,
1295
+ );
1296
+ });
1297
+ });
1298
+
1284
1299
  test('should set spotterFileUploadFileTypes parameter in url params', async () => {
1285
1300
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
1286
1301
  ...defaultViewConfig,
@@ -791,6 +791,7 @@ export class LiveboardEmbed extends V1Embed {
791
791
  toolResponseCardBrandingLabel,
792
792
  spotterFileUploadEnabled,
793
793
  spotterFileUploadFileTypes,
794
+ enableStarterPrompts,
794
795
  } = spotterChatConfig;
795
796
 
796
797
  setParamIfDefined(params, Param.HideToolResponseCardBranding, hideToolResponseCardBranding, true);
@@ -798,6 +799,9 @@ export class LiveboardEmbed extends V1Embed {
798
799
  if (spotterFileUploadEnabled !== undefined) {
799
800
  params[Param.SpotterFileUploadEnabled] = spotterFileUploadEnabled;
800
801
  }
802
+ if (enableStarterPrompts !== undefined) {
803
+ params[Param.IsStarterPromptsEnabled] = enableStarterPrompts;
804
+ }
801
805
  if (spotterFileUploadFileTypes !== undefined) {
802
806
  params[Param.SpotterFileUploadFileTypes] = JSON.stringify(spotterFileUploadFileTypes);
803
807
  }
@@ -2479,6 +2479,10 @@ describe('Unit test case for ts embed', () => {
2479
2479
  // The ResizeObserver now tracks the placeholder inside this.el,
2480
2480
  // not this.el itself, so pass it as the target.
2481
2481
  const preRenderPlaceholder = tsEmbedDiv.firstElementChild as HTMLElement;
2482
+ preRenderPlaceholder.getBoundingClientRect = jest.fn().mockReturnValue({
2483
+ x: 0, y: 0, width: 987, height: 297,
2484
+ top: 0, left: 0, bottom: 297, right: 987,
2485
+ });
2482
2486
  resizeObserverCb([
2483
2487
  {
2484
2488
  target: preRenderPlaceholder,
@@ -2660,6 +2664,296 @@ describe('Unit test case for ts embed', () => {
2660
2664
  );
2661
2665
  (logger.error as any).mockClear();
2662
2666
  });
2667
+
2668
+ describe('preRenderContainer', () => {
2669
+ it('should append preRenderWrapper to document.body when preRenderContainer is not set', async () => {
2670
+ createRootEleForEmbed();
2671
+
2672
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2673
+ preRenderId: 'container-default',
2674
+ liveboardId: 'myLiveboardId',
2675
+ });
2676
+ await libEmbed.preRender();
2677
+
2678
+ const preRenderIds = libEmbed.getPreRenderIds();
2679
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
2680
+ expect(document.body.contains(preRenderWrapper)).toBe(true);
2681
+ });
2682
+
2683
+ it('should append preRenderWrapper to the element matching a CSS selector string', async () => {
2684
+ createRootEleForEmbed();
2685
+ const customContainer = document.createElement('div');
2686
+ customContainer.id = 'custom-pre-render-container';
2687
+ document.body.appendChild(customContainer);
2688
+
2689
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2690
+ preRenderId: 'container-selector',
2691
+ liveboardId: 'myLiveboardId',
2692
+ preRenderContainer: '#custom-pre-render-container',
2693
+ });
2694
+ await libEmbed.preRender();
2695
+
2696
+ const preRenderIds = libEmbed.getPreRenderIds();
2697
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
2698
+ expect(customContainer.contains(preRenderWrapper)).toBe(true);
2699
+
2700
+ customContainer.remove();
2701
+ });
2702
+
2703
+ it('should fall back to document.body when the CSS selector string matches nothing', async () => {
2704
+ createRootEleForEmbed();
2705
+
2706
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2707
+ preRenderId: 'container-selector-miss',
2708
+ liveboardId: 'myLiveboardId',
2709
+ preRenderContainer: '#does-not-exist',
2710
+ });
2711
+ await libEmbed.preRender();
2712
+
2713
+ const preRenderIds = libEmbed.getPreRenderIds();
2714
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
2715
+ expect(document.body.contains(preRenderWrapper)).toBe(true);
2716
+ });
2717
+
2718
+ it('should fall back to document.body and log an error when the CSS selector is invalid', async () => {
2719
+ createRootEleForEmbed();
2720
+ jest.spyOn(logger, 'error').mockImplementation(jest.fn());
2721
+
2722
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2723
+ preRenderId: 'container-selector-invalid',
2724
+ liveboardId: 'myLiveboardId',
2725
+ preRenderContainer: '###invalid###',
2726
+ });
2727
+ await libEmbed.preRender();
2728
+
2729
+ const preRenderIds = libEmbed.getPreRenderIds();
2730
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
2731
+ expect(document.body.contains(preRenderWrapper)).toBe(true);
2732
+ expect(logger.error).toHaveBeenCalledWith(
2733
+ expect.stringContaining('Invalid CSS selector for preRenderContainer'),
2734
+ expect.any(Error),
2735
+ );
2736
+ (logger.error as any).mockClear();
2737
+ });
2738
+
2739
+ it('should append preRenderWrapper to an Element passed directly as preRenderContainer', async () => {
2740
+ createRootEleForEmbed();
2741
+ const customContainer = document.createElement('div');
2742
+ customContainer.id = 'custom-pre-render-element';
2743
+ document.body.appendChild(customContainer);
2744
+
2745
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2746
+ preRenderId: 'container-element',
2747
+ liveboardId: 'myLiveboardId',
2748
+ preRenderContainer: customContainer,
2749
+ });
2750
+ await libEmbed.preRender();
2751
+
2752
+ const preRenderIds = libEmbed.getPreRenderIds();
2753
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
2754
+ expect(customContainer.contains(preRenderWrapper)).toBe(true);
2755
+
2756
+ customContainer.remove();
2757
+ });
2758
+
2759
+ it('should set the container position to relative when it is static', async () => {
2760
+ createRootEleForEmbed();
2761
+ const customContainer = document.createElement('div');
2762
+ customContainer.id = 'custom-pre-render-static';
2763
+ customContainer.style.position = 'static';
2764
+ document.body.appendChild(customContainer);
2765
+
2766
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2767
+ preRenderId: 'container-position-static',
2768
+ liveboardId: 'myLiveboardId',
2769
+ preRenderContainer: customContainer,
2770
+ });
2771
+ await libEmbed.preRender();
2772
+
2773
+ expect(customContainer.style.position).toBe('relative');
2774
+
2775
+ customContainer.remove();
2776
+ });
2777
+
2778
+ it('should restore the original container position on destroy', async () => {
2779
+ createRootEleForEmbed();
2780
+ const customContainer = document.createElement('div');
2781
+ customContainer.id = 'custom-pre-render-restore';
2782
+ customContainer.style.position = 'static';
2783
+ document.body.appendChild(customContainer);
2784
+
2785
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2786
+ preRenderId: 'container-position-restore',
2787
+ liveboardId: 'myLiveboardId',
2788
+ preRenderContainer: customContainer,
2789
+ });
2790
+ await libEmbed.preRender();
2791
+ expect(customContainer.style.position).toBe('relative');
2792
+
2793
+ libEmbed.destroy();
2794
+
2795
+ // Reverts to the exact value the container had before.
2796
+ expect(customContainer.style.position).toBe('static');
2797
+ // The container reference is dropped so a destroyed embed does
2798
+ // not pin the (possibly detached) element in memory.
2799
+ expect((libEmbed as any).preRenderContainerEl).toBeNull();
2800
+
2801
+ customContainer.remove();
2802
+ });
2803
+
2804
+ it('should not touch the container position when it is already positioned', async () => {
2805
+ createRootEleForEmbed();
2806
+ const customContainer = document.createElement('div');
2807
+ customContainer.id = 'custom-pre-render-positioned';
2808
+ customContainer.style.position = 'absolute';
2809
+ document.body.appendChild(customContainer);
2810
+
2811
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2812
+ preRenderId: 'container-position-positioned',
2813
+ liveboardId: 'myLiveboardId',
2814
+ preRenderContainer: customContainer,
2815
+ });
2816
+ await libEmbed.preRender();
2817
+ expect(customContainer.style.position).toBe('absolute');
2818
+
2819
+ libEmbed.destroy();
2820
+ expect(customContainer.style.position).toBe('absolute');
2821
+
2822
+ customContainer.remove();
2823
+ });
2824
+
2825
+ it('should keep the container positioned on destroy while another preRender wrapper remains', async () => {
2826
+ createRootEleForEmbed();
2827
+ const customContainer = document.createElement('div');
2828
+ customContainer.id = 'custom-pre-render-shared';
2829
+ customContainer.style.position = 'static';
2830
+ document.body.appendChild(customContainer);
2831
+
2832
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2833
+ preRenderId: 'container-shared-first',
2834
+ liveboardId: 'myLiveboardId',
2835
+ preRenderContainer: customContainer,
2836
+ });
2837
+ await firstEmbed.preRender();
2838
+
2839
+ const secondEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2840
+ preRenderId: 'container-shared-second',
2841
+ liveboardId: 'myLiveboardId',
2842
+ preRenderContainer: customContainer,
2843
+ });
2844
+ await secondEmbed.preRender();
2845
+
2846
+ expect(customContainer.style.position).toBe('relative');
2847
+
2848
+ // Destroying the first embed must not strip the positioning
2849
+ // context the second embed still relies on.
2850
+ firstEmbed.destroy();
2851
+ expect(customContainer.style.position).toBe('relative');
2852
+
2853
+ // Once the last embed is destroyed, the override is reverted.
2854
+ secondEmbed.destroy();
2855
+ expect(customContainer.style.position).toBe('static');
2856
+
2857
+ customContainer.remove();
2858
+ });
2859
+
2860
+ it('should resolve a selector container inside the host shadow root', async () => {
2861
+ const host = document.createElement('div');
2862
+ document.body.appendChild(host);
2863
+ const shadow = host.attachShadow({ mode: 'open' });
2864
+ const hostEl = document.createElement('div');
2865
+ const shadowContainer = document.createElement('div');
2866
+ shadowContainer.id = 'shadow-pre-render-container';
2867
+ shadow.appendChild(hostEl);
2868
+ shadow.appendChild(shadowContainer);
2869
+
2870
+ const libEmbed = new LiveboardEmbed(hostEl, {
2871
+ preRenderId: 'container-shadow',
2872
+ liveboardId: 'myLiveboardId',
2873
+ preRenderContainer: '#shadow-pre-render-container',
2874
+ });
2875
+ await libEmbed.preRender();
2876
+
2877
+ const preRenderIds = libEmbed.getPreRenderIds();
2878
+ const preRenderWrapper = shadow.querySelector(`#${preRenderIds.wrapper}`);
2879
+ // The wrapper lands in the shadow container, not the body.
2880
+ expect(shadowContainer.contains(preRenderWrapper)).toBe(true);
2881
+ expect(document.getElementById(preRenderIds.wrapper)).toBeNull();
2882
+
2883
+ host.remove();
2884
+ });
2885
+
2886
+ it('should re-attach the wrapper when a selector container is remounted', async () => {
2887
+ createRootEleForEmbed();
2888
+ const oldContainer = document.createElement('div');
2889
+ oldContainer.id = 'remount-container';
2890
+ document.body.appendChild(oldContainer);
2891
+
2892
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2893
+ preRenderId: 'container-remount',
2894
+ liveboardId: 'myLiveboardId',
2895
+ preRenderContainer: '#remount-container',
2896
+ doNotTrackPreRenderSize: true,
2897
+ });
2898
+ libEmbed.preRender();
2899
+ await waitFor(() => !!getIFrameEl());
2900
+ await libEmbed.showPreRender();
2901
+
2902
+ const preRenderIds = libEmbed.getPreRenderIds();
2903
+ const wrapper = document.getElementById(preRenderIds.wrapper);
2904
+ expect(oldContainer.contains(wrapper)).toBe(true);
2905
+
2906
+ // Simulate React remounting the container: the old node (with
2907
+ // our wrapper) is detached and a fresh node takes its place.
2908
+ const removeSpy = jest.spyOn(oldContainer, 'removeEventListener');
2909
+ oldContainer.remove();
2910
+ const newContainer = document.createElement('div');
2911
+ newContainer.id = 'remount-container';
2912
+ document.body.appendChild(newContainer);
2913
+ const addSpy = jest.spyOn(newContainer, 'addEventListener');
2914
+
2915
+ // A reposition (scroll/resize/re-show) heals the stale ref.
2916
+ libEmbed.syncPreRenderStyle();
2917
+
2918
+ expect(newContainer.contains(wrapper)).toBe(true);
2919
+ expect(oldContainer.contains(wrapper)).toBe(false);
2920
+ // The scroll listener is migrated to the live container.
2921
+ expect(removeSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
2922
+ expect(addSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
2923
+
2924
+ libEmbed.destroy();
2925
+ newContainer.remove();
2926
+ });
2927
+
2928
+ it('should leave an element-passed container untouched when it is detached', async () => {
2929
+ createRootEleForEmbed();
2930
+ const container = document.createElement('div');
2931
+ document.body.appendChild(container);
2932
+
2933
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2934
+ preRenderId: 'container-element-remount',
2935
+ liveboardId: 'myLiveboardId',
2936
+ preRenderContainer: container,
2937
+ doNotTrackPreRenderSize: true,
2938
+ });
2939
+ libEmbed.preRender();
2940
+ await waitFor(() => !!getIFrameEl());
2941
+ await libEmbed.showPreRender();
2942
+
2943
+ const preRenderIds = libEmbed.getPreRenderIds();
2944
+ const wrapper = document.getElementById(preRenderIds.wrapper);
2945
+ expect(container.contains(wrapper)).toBe(true);
2946
+
2947
+ // Detach the element container; it cannot be re-resolved, so
2948
+ // the wrapper stays with it (no crash, no body fallback).
2949
+ container.remove();
2950
+ libEmbed.syncPreRenderStyle();
2951
+
2952
+ expect(container.contains(wrapper)).toBe(true);
2953
+
2954
+ libEmbed.destroy();
2955
+ });
2956
+ });
2663
2957
  });
2664
2958
 
2665
2959
  describe('IdleSessionTimeout embedEvent for TrustedAuthTokenCookieless authType with autoLogin true', () => {