@thoughtspot/visual-embed-sdk 1.50.0 → 1.51.0-test

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 (285) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/config.d.ts +1 -0
  3. package/cjs/src/config.d.ts.map +1 -1
  4. package/cjs/src/config.js +2 -1
  5. package/cjs/src/config.js.map +1 -1
  6. package/cjs/src/css-variables.d.ts +122 -0
  7. package/cjs/src/css-variables.d.ts.map +1 -1
  8. package/cjs/src/embed/app.d.ts +176 -24
  9. package/cjs/src/embed/app.d.ts.map +1 -1
  10. package/cjs/src/embed/app.js +86 -38
  11. package/cjs/src/embed/app.js.map +1 -1
  12. package/cjs/src/embed/app.spec.js +672 -150
  13. package/cjs/src/embed/app.spec.js.map +1 -1
  14. package/cjs/src/embed/base.d.ts +1 -1
  15. package/cjs/src/embed/base.js +1 -1
  16. package/cjs/src/embed/conversation.d.ts +401 -2
  17. package/cjs/src/embed/conversation.d.ts.map +1 -1
  18. package/cjs/src/embed/conversation.js +32 -16
  19. package/cjs/src/embed/conversation.js.map +1 -1
  20. package/cjs/src/embed/conversation.spec.js +346 -17
  21. package/cjs/src/embed/conversation.spec.js.map +1 -1
  22. package/cjs/src/embed/events.spec.js +638 -0
  23. package/cjs/src/embed/events.spec.js.map +1 -1
  24. package/cjs/src/embed/host-events.spec.js +187 -0
  25. package/cjs/src/embed/host-events.spec.js.map +1 -1
  26. package/cjs/src/embed/hostEventClient/contracts.d.ts +51 -14
  27. package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  28. package/cjs/src/embed/hostEventClient/contracts.js +11 -1
  29. package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
  30. package/cjs/src/embed/hostEventClient/host-event-client.d.ts +2 -1
  31. package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  32. package/cjs/src/embed/hostEventClient/host-event-client.js +9 -0
  33. package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
  34. package/cjs/src/embed/hostEventClient/host-event-client.spec.js +236 -0
  35. package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  36. package/cjs/src/embed/hostEventClient/utils.d.ts +2 -0
  37. package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -1
  38. package/cjs/src/embed/hostEventClient/utils.js +32 -2
  39. package/cjs/src/embed/hostEventClient/utils.js.map +1 -1
  40. package/cjs/src/embed/hostEventClient/utils.spec.js +178 -0
  41. package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -1
  42. package/cjs/src/embed/liveboard.d.ts +81 -13
  43. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  44. package/cjs/src/embed/liveboard.js +50 -9
  45. package/cjs/src/embed/liveboard.js.map +1 -1
  46. package/cjs/src/embed/liveboard.spec.js +432 -4
  47. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  48. package/cjs/src/embed/spotter-utils.d.ts +15 -1
  49. package/cjs/src/embed/spotter-utils.d.ts.map +1 -1
  50. package/cjs/src/embed/spotter-utils.js +28 -1
  51. package/cjs/src/embed/spotter-utils.js.map +1 -1
  52. package/cjs/src/embed/spotter-utils.spec.js +129 -0
  53. package/cjs/src/embed/spotter-utils.spec.js.map +1 -1
  54. package/cjs/src/embed/spotter-viz-utils.d.ts +37 -4
  55. package/cjs/src/embed/spotter-viz-utils.d.ts.map +1 -1
  56. package/cjs/src/embed/spotter-viz-utils.js.map +1 -1
  57. package/cjs/src/embed/spotter-viz-utils.spec.js +17 -0
  58. package/cjs/src/embed/spotter-viz-utils.spec.js.map +1 -1
  59. package/cjs/src/embed/ts-embed.d.ts +16 -8
  60. package/cjs/src/embed/ts-embed.d.ts.map +1 -1
  61. package/cjs/src/embed/ts-embed.js +96 -37
  62. package/cjs/src/embed/ts-embed.js.map +1 -1
  63. package/cjs/src/embed/ts-embed.spec.js +683 -3
  64. package/cjs/src/embed/ts-embed.spec.js.map +1 -1
  65. package/cjs/src/errors.d.ts +1 -0
  66. package/cjs/src/errors.d.ts.map +1 -1
  67. package/cjs/src/errors.js +1 -0
  68. package/cjs/src/errors.js.map +1 -1
  69. package/cjs/src/index.d.ts +3 -3
  70. package/cjs/src/index.d.ts.map +1 -1
  71. package/cjs/src/index.js +4 -2
  72. package/cjs/src/index.js.map +1 -1
  73. package/cjs/src/mixpanel-service.d.ts +4 -0
  74. package/cjs/src/mixpanel-service.d.ts.map +1 -1
  75. package/cjs/src/mixpanel-service.js +4 -0
  76. package/cjs/src/mixpanel-service.js.map +1 -1
  77. package/cjs/src/react/all-types-export.d.ts +1 -1
  78. package/cjs/src/react/all-types-export.d.ts.map +1 -1
  79. package/cjs/src/react/all-types-export.js +2 -1
  80. package/cjs/src/react/all-types-export.js.map +1 -1
  81. package/cjs/src/react/all-types-export.parity.spec.d.ts +2 -0
  82. package/cjs/src/react/all-types-export.parity.spec.d.ts.map +1 -0
  83. package/cjs/src/react/all-types-export.parity.spec.js +194 -0
  84. package/cjs/src/react/all-types-export.parity.spec.js.map +1 -0
  85. package/cjs/src/react/index.d.ts +66 -32
  86. package/cjs/src/react/index.d.ts.map +1 -1
  87. package/cjs/src/react/index.js +5 -3
  88. package/cjs/src/react/index.js.map +1 -1
  89. package/cjs/src/react/index.spec.js +101 -0
  90. package/cjs/src/react/index.spec.js.map +1 -1
  91. package/cjs/src/react/util.spec.js +251 -0
  92. package/cjs/src/react/util.spec.js.map +1 -1
  93. package/cjs/src/types.d.ts +902 -28
  94. package/cjs/src/types.d.ts.map +1 -1
  95. package/cjs/src/types.js +701 -14
  96. package/cjs/src/types.js.map +1 -1
  97. package/cjs/src/utils/processData.spec.js +93 -0
  98. package/cjs/src/utils/processData.spec.js.map +1 -1
  99. package/cjs/src/utils/processTrigger.spec.js +61 -0
  100. package/cjs/src/utils/processTrigger.spec.js.map +1 -1
  101. package/cjs/src/utils.spec.js +4 -0
  102. package/cjs/src/utils.spec.js.map +1 -1
  103. package/dist/{index-B5xoN6Cp.js → index-Bivg-mDx.js} +1 -1
  104. package/dist/src/config.d.ts +1 -0
  105. package/dist/src/config.d.ts.map +1 -1
  106. package/dist/src/css-variables.d.ts +122 -0
  107. package/dist/src/css-variables.d.ts.map +1 -1
  108. package/dist/src/embed/app.d.ts +176 -24
  109. package/dist/src/embed/app.d.ts.map +1 -1
  110. package/dist/src/embed/base.d.ts +1 -1
  111. package/dist/src/embed/conversation.d.ts +401 -2
  112. package/dist/src/embed/conversation.d.ts.map +1 -1
  113. package/dist/src/embed/hostEventClient/contracts.d.ts +51 -14
  114. package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  115. package/dist/src/embed/hostEventClient/host-event-client.d.ts +2 -1
  116. package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  117. package/dist/src/embed/hostEventClient/utils.d.ts +2 -0
  118. package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -1
  119. package/dist/src/embed/liveboard.d.ts +81 -13
  120. package/dist/src/embed/liveboard.d.ts.map +1 -1
  121. package/dist/src/embed/spotter-utils.d.ts +15 -1
  122. package/dist/src/embed/spotter-utils.d.ts.map +1 -1
  123. package/dist/src/embed/spotter-viz-utils.d.ts +37 -4
  124. package/dist/src/embed/spotter-viz-utils.d.ts.map +1 -1
  125. package/dist/src/embed/ts-embed.d.ts +16 -8
  126. package/dist/src/embed/ts-embed.d.ts.map +1 -1
  127. package/dist/src/errors.d.ts +1 -0
  128. package/dist/src/errors.d.ts.map +1 -1
  129. package/dist/src/index.d.ts +3 -3
  130. package/dist/src/index.d.ts.map +1 -1
  131. package/dist/src/mixpanel-service.d.ts +4 -0
  132. package/dist/src/mixpanel-service.d.ts.map +1 -1
  133. package/dist/src/react/all-types-export.d.ts +1 -1
  134. package/dist/src/react/all-types-export.d.ts.map +1 -1
  135. package/dist/src/react/all-types-export.parity.spec.d.ts +2 -0
  136. package/dist/src/react/all-types-export.parity.spec.d.ts.map +1 -0
  137. package/dist/src/react/index.d.ts +66 -32
  138. package/dist/src/react/index.d.ts.map +1 -1
  139. package/dist/src/types.d.ts +902 -28
  140. package/dist/src/types.d.ts.map +1 -1
  141. package/dist/tsembed-react.es.js +1269 -206
  142. package/dist/tsembed-react.js +1268 -205
  143. package/dist/tsembed.es.js +1364 -304
  144. package/dist/tsembed.js +1362 -302
  145. package/dist/visual-embed-sdk-react-full.d.ts +1867 -157
  146. package/dist/visual-embed-sdk-react.d.ts +1872 -161
  147. package/dist/visual-embed-sdk.d.ts +1815 -127
  148. package/lib/package.json +1 -1
  149. package/lib/src/config.d.ts +1 -0
  150. package/lib/src/config.d.ts.map +1 -1
  151. package/lib/src/config.js +1 -0
  152. package/lib/src/config.js.map +1 -1
  153. package/lib/src/css-variables.d.ts +122 -0
  154. package/lib/src/css-variables.d.ts.map +1 -1
  155. package/lib/src/embed/app.d.ts +176 -24
  156. package/lib/src/embed/app.d.ts.map +1 -1
  157. package/lib/src/embed/app.js +87 -39
  158. package/lib/src/embed/app.js.map +1 -1
  159. package/lib/src/embed/app.spec.js +673 -151
  160. package/lib/src/embed/app.spec.js.map +1 -1
  161. package/lib/src/embed/base.d.ts +1 -1
  162. package/lib/src/embed/base.js +1 -1
  163. package/lib/src/embed/conversation.d.ts +401 -2
  164. package/lib/src/embed/conversation.d.ts.map +1 -1
  165. package/lib/src/embed/conversation.js +33 -17
  166. package/lib/src/embed/conversation.js.map +1 -1
  167. package/lib/src/embed/conversation.spec.js +347 -18
  168. package/lib/src/embed/conversation.spec.js.map +1 -1
  169. package/lib/src/embed/events.spec.js +638 -0
  170. package/lib/src/embed/events.spec.js.map +1 -1
  171. package/lib/src/embed/host-events.spec.js +188 -1
  172. package/lib/src/embed/host-events.spec.js.map +1 -1
  173. package/lib/src/embed/hostEventClient/contracts.d.ts +51 -14
  174. package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
  175. package/lib/src/embed/hostEventClient/contracts.js +10 -0
  176. package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
  177. package/lib/src/embed/hostEventClient/host-event-client.d.ts +2 -1
  178. package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
  179. package/lib/src/embed/hostEventClient/host-event-client.js +10 -1
  180. package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
  181. package/lib/src/embed/hostEventClient/host-event-client.spec.js +236 -0
  182. package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
  183. package/lib/src/embed/hostEventClient/utils.d.ts +2 -0
  184. package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -1
  185. package/lib/src/embed/hostEventClient/utils.js +28 -1
  186. package/lib/src/embed/hostEventClient/utils.js.map +1 -1
  187. package/lib/src/embed/hostEventClient/utils.spec.js +179 -1
  188. package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -1
  189. package/lib/src/embed/liveboard.d.ts +81 -13
  190. package/lib/src/embed/liveboard.d.ts.map +1 -1
  191. package/lib/src/embed/liveboard.js +50 -9
  192. package/lib/src/embed/liveboard.js.map +1 -1
  193. package/lib/src/embed/liveboard.spec.js +432 -4
  194. package/lib/src/embed/liveboard.spec.js.map +1 -1
  195. package/lib/src/embed/spotter-utils.d.ts +15 -1
  196. package/lib/src/embed/spotter-utils.d.ts.map +1 -1
  197. package/lib/src/embed/spotter-utils.js +25 -0
  198. package/lib/src/embed/spotter-utils.js.map +1 -1
  199. package/lib/src/embed/spotter-utils.spec.js +130 -1
  200. package/lib/src/embed/spotter-utils.spec.js.map +1 -1
  201. package/lib/src/embed/spotter-viz-utils.d.ts +37 -4
  202. package/lib/src/embed/spotter-viz-utils.d.ts.map +1 -1
  203. package/lib/src/embed/spotter-viz-utils.js.map +1 -1
  204. package/lib/src/embed/spotter-viz-utils.spec.js +17 -0
  205. package/lib/src/embed/spotter-viz-utils.spec.js.map +1 -1
  206. package/lib/src/embed/ts-embed.d.ts +16 -8
  207. package/lib/src/embed/ts-embed.d.ts.map +1 -1
  208. package/lib/src/embed/ts-embed.js +96 -37
  209. package/lib/src/embed/ts-embed.js.map +1 -1
  210. package/lib/src/embed/ts-embed.spec.js +683 -3
  211. package/lib/src/embed/ts-embed.spec.js.map +1 -1
  212. package/lib/src/errors.d.ts +1 -0
  213. package/lib/src/errors.d.ts.map +1 -1
  214. package/lib/src/errors.js +1 -0
  215. package/lib/src/errors.js.map +1 -1
  216. package/lib/src/index.d.ts +3 -3
  217. package/lib/src/index.d.ts.map +1 -1
  218. package/lib/src/index.js +3 -3
  219. package/lib/src/index.js.map +1 -1
  220. package/lib/src/mixpanel-service.d.ts +4 -0
  221. package/lib/src/mixpanel-service.d.ts.map +1 -1
  222. package/lib/src/mixpanel-service.js +4 -0
  223. package/lib/src/mixpanel-service.js.map +1 -1
  224. package/lib/src/react/all-types-export.d.ts +1 -1
  225. package/lib/src/react/all-types-export.d.ts.map +1 -1
  226. package/lib/src/react/all-types-export.js +1 -1
  227. package/lib/src/react/all-types-export.js.map +1 -1
  228. package/lib/src/react/all-types-export.parity.spec.d.ts +2 -0
  229. package/lib/src/react/all-types-export.parity.spec.d.ts.map +1 -0
  230. package/lib/src/react/all-types-export.parity.spec.js +191 -0
  231. package/lib/src/react/all-types-export.parity.spec.js.map +1 -0
  232. package/lib/src/react/index.d.ts +66 -32
  233. package/lib/src/react/index.d.ts.map +1 -1
  234. package/lib/src/react/index.js +5 -3
  235. package/lib/src/react/index.js.map +1 -1
  236. package/lib/src/react/index.spec.js +102 -1
  237. package/lib/src/react/index.spec.js.map +1 -1
  238. package/lib/src/react/util.spec.js +251 -0
  239. package/lib/src/react/util.spec.js.map +1 -1
  240. package/lib/src/types.d.ts +902 -28
  241. package/lib/src/types.d.ts.map +1 -1
  242. package/lib/src/types.js +701 -14
  243. package/lib/src/types.js.map +1 -1
  244. package/lib/src/utils/processData.spec.js +93 -0
  245. package/lib/src/utils/processData.spec.js.map +1 -1
  246. package/lib/src/utils/processTrigger.spec.js +61 -0
  247. package/lib/src/utils/processTrigger.spec.js.map +1 -1
  248. package/lib/src/utils.spec.js +4 -0
  249. package/lib/src/utils.spec.js.map +1 -1
  250. package/lib/src/visual-embed-sdk.d.ts +1815 -127
  251. package/package.json +1 -1
  252. package/src/config.ts +1 -0
  253. package/src/css-variables.ts +150 -0
  254. package/src/embed/app.spec.ts +877 -196
  255. package/src/embed/app.ts +276 -75
  256. package/src/embed/base.ts +1 -1
  257. package/src/embed/conversation.spec.ts +411 -21
  258. package/src/embed/conversation.ts +441 -18
  259. package/src/embed/events.spec.ts +761 -1
  260. package/src/embed/host-events.spec.ts +242 -0
  261. package/src/embed/hostEventClient/contracts.ts +57 -14
  262. package/src/embed/hostEventClient/host-event-client.spec.ts +372 -0
  263. package/src/embed/hostEventClient/host-event-client.ts +18 -2
  264. package/src/embed/hostEventClient/utils.spec.ts +204 -0
  265. package/src/embed/hostEventClient/utils.ts +37 -10
  266. package/src/embed/liveboard.spec.ts +565 -4
  267. package/src/embed/liveboard.ts +155 -28
  268. package/src/embed/spotter-utils.spec.ts +140 -1
  269. package/src/embed/spotter-utils.ts +36 -1
  270. package/src/embed/spotter-viz-utils.spec.ts +17 -0
  271. package/src/embed/spotter-viz-utils.ts +37 -4
  272. package/src/embed/ts-embed.spec.ts +885 -3
  273. package/src/embed/ts-embed.ts +111 -39
  274. package/src/errors.ts +1 -0
  275. package/src/index.ts +45 -2
  276. package/src/mixpanel-service.ts +4 -0
  277. package/src/react/all-types-export.parity.spec.ts +208 -0
  278. package/src/react/all-types-export.ts +22 -0
  279. package/src/react/index.spec.tsx +158 -0
  280. package/src/react/index.tsx +70 -33
  281. package/src/react/util.spec.tsx +255 -0
  282. package/src/types.ts +904 -24
  283. package/src/utils/processData.spec.ts +97 -0
  284. package/src/utils/processTrigger.spec.ts +91 -0
  285. package/src/utils.spec.ts +5 -0
@@ -1409,6 +1409,40 @@ describe('Unit test case for ts embed', () => {
1409
1409
  const { mockGetPreauthInfo } = await setup(true, 123);
1410
1410
  expect(mockGetPreauthInfo).toHaveBeenCalledTimes(0);
1411
1411
  });
1412
+
1413
+ test('should NOT trigger InfoSuccess when getPreauthInfo resolves with no .info field', async () => {
1414
+ jest.spyOn(window, 'addEventListener').mockImplementationOnce(
1415
+ (event, handler) => {
1416
+ (handler as EventListener)({
1417
+ data: { type: 'xyz' },
1418
+ ports: [3000],
1419
+ source: null,
1420
+ } as any);
1421
+ },
1422
+ );
1423
+ mockProcessTrigger.mockResolvedValueOnce({ session: 'test' });
1424
+ jest.spyOn(authService, 'fetchPreauthInfoService').mockResolvedValueOnce({
1425
+ ok: true,
1426
+ headers: new Headers({ 'content-type': 'application/json' }),
1427
+ json: async () => ({}), // no .info field
1428
+ } as any);
1429
+ const iFrame: any = document.createElement('div');
1430
+ jest.spyOn(baseInstance, 'getAuthPromise').mockResolvedValueOnce(true as any);
1431
+ const tsEmbed = new SearchEmbed(getRootEl(), {});
1432
+ iFrame.contentWindow = { postMessage: jest.fn() };
1433
+ jest.spyOn(iFrame, 'addEventListener').mockImplementationOnce(
1434
+ (event, handler) => { (handler as EventListener)({} as Event); },
1435
+ );
1436
+ jest.spyOn(document, 'createElement').mockReturnValueOnce(iFrame);
1437
+ await tsEmbed.render();
1438
+
1439
+ await executeAfterWait(() => {
1440
+ const infoSuccessCall = mockProcessTrigger.mock.calls.find(
1441
+ (call) => call[1] === HostEvent.InfoSuccess,
1442
+ );
1443
+ expect(infoSuccessCall).toBeUndefined();
1444
+ });
1445
+ });
1412
1446
  });
1413
1447
 
1414
1448
  describe('Preauth Cache for FullAppEmbed with PrimaryNavBar', () => {
@@ -2281,6 +2315,46 @@ describe('Unit test case for ts embed', () => {
2281
2315
  });
2282
2316
  });
2283
2317
 
2318
+ it('Sets the overrideHistoryState param', async () => {
2319
+ const appEmbed = new AppEmbed(getRootEl(), {
2320
+ frameParams: {
2321
+ width: '100%',
2322
+ height: '100%',
2323
+ },
2324
+ overrideHistoryState: true,
2325
+ });
2326
+ await appEmbed.render();
2327
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
2328
+ overrideHistoryState: true,
2329
+ });
2330
+ });
2331
+
2332
+ it('Sets the overrideHistoryState param to false', async () => {
2333
+ const appEmbed = new AppEmbed(getRootEl(), {
2334
+ frameParams: {
2335
+ width: '100%',
2336
+ height: '100%',
2337
+ },
2338
+ overrideHistoryState: false,
2339
+ });
2340
+ await appEmbed.render();
2341
+ expectUrlToHaveParamsWithValues(getIFrameSrc(), {
2342
+ overrideHistoryState: false,
2343
+ });
2344
+ });
2345
+
2346
+ it('Should not add overrideHistoryState param when it is undefined', async () => {
2347
+ const appEmbed = new AppEmbed(getRootEl(), {
2348
+ frameParams: {
2349
+ width: '100%',
2350
+ height: '100%',
2351
+ },
2352
+ });
2353
+ await appEmbed.render();
2354
+ const url = getIFrameSrc();
2355
+ expect(url).not.toContain('overrideHistoryState');
2356
+ });
2357
+
2284
2358
  it('Should not add contextMenuEnabledOnWhichClick flag to the iframe src when it is not passed', async () => {
2285
2359
  const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
2286
2360
  ...defaultViewConfig,
@@ -2665,6 +2739,45 @@ describe('Unit test case for ts embed', () => {
2665
2739
  (logger.error as any).mockClear();
2666
2740
  });
2667
2741
 
2742
+ it('should skip syncing styles when the placeholder is detached', async () => {
2743
+ createRootEleForEmbed();
2744
+ jest.spyOn(logger, 'debug').mockImplementation(jest.fn());
2745
+
2746
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2747
+ liveboardId: 'myLiveboardId',
2748
+ preRenderId: 'placeholder-detached',
2749
+ doNotTrackPreRenderSize: true,
2750
+ });
2751
+ libEmbed.preRender();
2752
+ await waitFor(() => !!getIFrameEl());
2753
+ await libEmbed.showPreRender();
2754
+
2755
+ const preRenderIds = libEmbed.getPreRenderIds();
2756
+ const wrapper = document.getElementById(preRenderIds.wrapper);
2757
+ const placeholder = document.getElementById(preRenderIds.placeHolder);
2758
+ const rectSpy = jest.spyOn(placeholder, 'getBoundingClientRect');
2759
+ wrapper.style.top = '42px';
2760
+ wrapper.style.height = '111px';
2761
+ (logger.debug as any).mockClear();
2762
+
2763
+ // Simulate the host subtree being unmounted: the placeholder is no
2764
+ // longer connected to the document.
2765
+ placeholder.remove();
2766
+ libEmbed.syncPreRenderStyle();
2767
+
2768
+ expect(logger.debug).toHaveBeenCalledWith(
2769
+ 'syncPreRenderStyle skipped: placeholder is detached',
2770
+ );
2771
+ // The detached node is never measured, so the wrapper keeps its
2772
+ // last known geometry instead of collapsing to a stale rect.
2773
+ expect(rectSpy).not.toHaveBeenCalled();
2774
+ expect(wrapper.style.top).toBe('42px');
2775
+ expect(wrapper.style.height).toBe('111px');
2776
+
2777
+ libEmbed.destroy();
2778
+ (logger.debug as any).mockRestore();
2779
+ });
2780
+
2668
2781
  describe('preRenderContainer', () => {
2669
2782
  it('should append preRenderWrapper to document.body when preRenderContainer is not set', async () => {
2670
2783
  createRootEleForEmbed();
@@ -2794,9 +2907,10 @@ describe('Unit test case for ts embed', () => {
2794
2907
 
2795
2908
  // Reverts to the exact value the container had before.
2796
2909
  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();
2910
+ // The custom container reference is dropped (back to the default
2911
+ // body) so a destroyed embed does not pin the (possibly
2912
+ // detached) element in memory.
2913
+ expect((libEmbed as any).preRenderContainerEl).toBe(document.body);
2800
2914
 
2801
2915
  customContainer.remove();
2802
2916
  });
@@ -2822,6 +2936,38 @@ describe('Unit test case for ts embed', () => {
2822
2936
  customContainer.remove();
2823
2937
  });
2824
2938
 
2939
+ it('should default preRenderContainerEl to document.body before rendering', () => {
2940
+ createRootEleForEmbed();
2941
+
2942
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2943
+ liveboardId: 'myLiveboardId',
2944
+ });
2945
+
2946
+ // document.body is the "no custom container" sentinel, so the
2947
+ // field is never null and needs no null-guarding downstream.
2948
+ expect((libEmbed as any).preRenderContainerEl).toBe(document.body);
2949
+ });
2950
+
2951
+ it('should leave document.body untouched on destroy when no custom container is set', async () => {
2952
+ createRootEleForEmbed();
2953
+ const originalBodyPosition = document.body.style.position;
2954
+
2955
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
2956
+ preRenderId: 'container-body-destroy',
2957
+ liveboardId: 'myLiveboardId',
2958
+ });
2959
+ await libEmbed.preRender();
2960
+
2961
+ expect((libEmbed as any).preRenderContainerEl).toBe(document.body);
2962
+
2963
+ libEmbed.destroy();
2964
+
2965
+ // The body sentinel must never be treated as a custom container:
2966
+ // no position override to apply and none to restore.
2967
+ expect(document.body.style.position).toBe(originalBodyPosition);
2968
+ expect(document.body.dataset.tsEmbedOriginalPosition).toBeUndefined();
2969
+ });
2970
+
2825
2971
  it('should keep the container positioned on destroy while another preRender wrapper remains', async () => {
2826
2972
  createRootEleForEmbed();
2827
2973
  const customContainer = document.createElement('div');
@@ -2925,6 +3071,45 @@ describe('Unit test case for ts embed', () => {
2925
3071
  newContainer.remove();
2926
3072
  });
2927
3073
 
3074
+ it('should position the newly resolved container after a remount, not the stale one', async () => {
3075
+ createRootEleForEmbed();
3076
+ const oldContainer = document.createElement('div');
3077
+ oldContainer.id = 'reposition-remount-container';
3078
+ oldContainer.style.position = 'static';
3079
+ document.body.appendChild(oldContainer);
3080
+
3081
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3082
+ preRenderId: 'container-remount-position',
3083
+ liveboardId: 'myLiveboardId',
3084
+ preRenderContainer: '#reposition-remount-container',
3085
+ doNotTrackPreRenderSize: true,
3086
+ });
3087
+ libEmbed.preRender();
3088
+ await waitFor(() => !!getIFrameEl());
3089
+ await libEmbed.showPreRender();
3090
+
3091
+ expect(oldContainer.style.position).toBe('relative');
3092
+
3093
+ oldContainer.remove();
3094
+ const newContainer = document.createElement('div');
3095
+ newContainer.id = 'reposition-remount-container';
3096
+ newContainer.style.position = 'static';
3097
+ document.body.appendChild(newContainer);
3098
+
3099
+ libEmbed.syncPreRenderStyle();
3100
+
3101
+ // applyPreRenderContainerPositioning() reads preRenderContainerEl
3102
+ // instead of taking the container as an argument, so reconcile has
3103
+ // to assign the field before calling it — otherwise the override
3104
+ // lands on the detached node and the fresh one stays static.
3105
+ expect((libEmbed as any).preRenderContainerEl).toBe(newContainer);
3106
+ expect(newContainer.style.position).toBe('relative');
3107
+ expect(newContainer.dataset.tsEmbedOriginalPosition).toBe('static');
3108
+
3109
+ libEmbed.destroy();
3110
+ newContainer.remove();
3111
+ });
3112
+
2928
3113
  it('should leave an element-passed container untouched when it is detached', async () => {
2929
3114
  createRootEleForEmbed();
2930
3115
  const container = document.createElement('div');
@@ -2953,6 +3138,557 @@ describe('Unit test case for ts embed', () => {
2953
3138
 
2954
3139
  libEmbed.destroy();
2955
3140
  });
3141
+
3142
+ describe('connectPreRendered container init', () => {
3143
+ beforeEach(() => {
3144
+ (window as any).ResizeObserver = jest.fn().mockImplementation(() => ({
3145
+ disconnect: jest.fn(),
3146
+ observe: jest.fn(),
3147
+ unobserve: jest.fn(),
3148
+ }));
3149
+ });
3150
+
3151
+ it('should resolve preRenderContainerEl when connecting to an existing pre-render', async () => {
3152
+ createRootEleForEmbed();
3153
+
3154
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3155
+ preRenderId: 'connect-init-default',
3156
+ liveboardId: 'myLiveboardId',
3157
+ });
3158
+ await firstEmbed.preRender();
3159
+ await waitFor(() => !!getIFrameEl());
3160
+
3161
+ const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3162
+ preRenderId: 'connect-init-default',
3163
+ liveboardId: 'myLiveboardId',
3164
+ });
3165
+ await connectingEmbed.showPreRender();
3166
+
3167
+ expect((connectingEmbed as any).preRenderContainerEl).not.toBeNull();
3168
+
3169
+ firstEmbed.destroy();
3170
+ });
3171
+
3172
+ it('should fall back to document.body when no preRenderContainer is configured on the connecting embed', async () => {
3173
+ createRootEleForEmbed();
3174
+
3175
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3176
+ preRenderId: 'connect-init-body',
3177
+ liveboardId: 'myLiveboardId',
3178
+ });
3179
+ await firstEmbed.preRender();
3180
+ await waitFor(() => !!getIFrameEl());
3181
+
3182
+ const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3183
+ preRenderId: 'connect-init-body',
3184
+ liveboardId: 'myLiveboardId',
3185
+ });
3186
+ await connectingEmbed.showPreRender();
3187
+
3188
+ expect((connectingEmbed as any).preRenderContainerEl).toBe(document.body);
3189
+
3190
+ firstEmbed.destroy();
3191
+ });
3192
+
3193
+ it('should resolve a custom preRenderContainer on the connecting embed', async () => {
3194
+ createRootEleForEmbed();
3195
+ const customContainer = document.createElement('div');
3196
+ customContainer.id = 'connect-custom-container';
3197
+ document.body.appendChild(customContainer);
3198
+
3199
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3200
+ preRenderId: 'connect-init-custom',
3201
+ liveboardId: 'myLiveboardId',
3202
+ preRenderContainer: customContainer,
3203
+ });
3204
+ await firstEmbed.preRender();
3205
+ await waitFor(() => !!getIFrameEl());
3206
+
3207
+ const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3208
+ preRenderId: 'connect-init-custom',
3209
+ liveboardId: 'myLiveboardId',
3210
+ preRenderContainer: customContainer,
3211
+ });
3212
+ await connectingEmbed.showPreRender();
3213
+
3214
+ expect((connectingEmbed as any).preRenderContainerEl).toBe(customContainer);
3215
+
3216
+ firstEmbed.destroy();
3217
+ customContainer.remove();
3218
+ });
3219
+
3220
+ it('should apply container positioning when the connecting embed resolves a static container', async () => {
3221
+ createRootEleForEmbed();
3222
+ const customContainer = document.createElement('div');
3223
+ customContainer.id = 'connect-static-container';
3224
+ customContainer.style.position = 'static';
3225
+ document.body.appendChild(customContainer);
3226
+
3227
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3228
+ preRenderId: 'connect-init-static',
3229
+ liveboardId: 'myLiveboardId',
3230
+ preRenderContainer: customContainer,
3231
+ });
3232
+ await firstEmbed.preRender();
3233
+ await waitFor(() => !!getIFrameEl());
3234
+
3235
+ // Reset position to static so the connecting embed also triggers the guard.
3236
+ customContainer.style.position = 'static';
3237
+ delete customContainer.dataset['preRenderContainerOriginalPosition'];
3238
+
3239
+ const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3240
+ preRenderId: 'connect-init-static',
3241
+ liveboardId: 'myLiveboardId',
3242
+ preRenderContainer: customContainer,
3243
+ });
3244
+ await connectingEmbed.showPreRender();
3245
+
3246
+ expect(customContainer.style.position).toBe('relative');
3247
+
3248
+ firstEmbed.destroy();
3249
+ connectingEmbed.destroy();
3250
+ customContainer.remove();
3251
+ });
3252
+
3253
+ it('should inherit the container from the pre-rendering embed instead of re-resolving it', async () => {
3254
+ createRootEleForEmbed();
3255
+ const customContainer = document.createElement('div');
3256
+ customContainer.id = 'connect-inherit-container';
3257
+ document.body.appendChild(customContainer);
3258
+
3259
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3260
+ preRenderId: 'connect-no-re-resolve',
3261
+ liveboardId: 'myLiveboardId',
3262
+ preRenderContainer: customContainer,
3263
+ });
3264
+ await firstEmbed.preRender();
3265
+ await waitFor(() => !!getIFrameEl());
3266
+
3267
+ // No preRenderContainer here: the container belongs to the
3268
+ // PreRender component and is adopted as-is.
3269
+ const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3270
+ preRenderId: 'connect-no-re-resolve',
3271
+ liveboardId: 'myLiveboardId',
3272
+ });
3273
+
3274
+ const resolveSpy = jest.spyOn(
3275
+ connectingEmbed as any,
3276
+ 'resolvePreRenderContainerTarget',
3277
+ );
3278
+
3279
+ (connectingEmbed as any).connectPreRendered();
3280
+
3281
+ expect((connectingEmbed as any).preRenderContainerEl).toBe(customContainer);
3282
+ expect(resolveSpy).not.toHaveBeenCalled();
3283
+
3284
+ firstEmbed.destroy();
3285
+ customContainer.remove();
3286
+ });
3287
+
3288
+ it('should warn and ignore a preRenderContainer passed on the connecting embed', async () => {
3289
+ createRootEleForEmbed();
3290
+ const ownerContainer = document.createElement('div');
3291
+ ownerContainer.id = 'connect-owner-container';
3292
+ const otherContainer = document.createElement('div');
3293
+ otherContainer.id = 'connect-other-container';
3294
+ document.body.appendChild(ownerContainer);
3295
+ document.body.appendChild(otherContainer);
3296
+ const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
3297
+
3298
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3299
+ preRenderId: 'connect-container-warn',
3300
+ liveboardId: 'myLiveboardId',
3301
+ preRenderContainer: ownerContainer,
3302
+ });
3303
+ await firstEmbed.preRender();
3304
+ await waitFor(() => !!getIFrameEl());
3305
+
3306
+ const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3307
+ preRenderId: 'connect-container-warn',
3308
+ liveboardId: 'myLiveboardId',
3309
+ preRenderContainer: otherContainer,
3310
+ });
3311
+ (connectingEmbed as any).connectPreRendered();
3312
+
3313
+ expect((connectingEmbed as any).preRenderContainerEl).toBe(ownerContainer);
3314
+ expect(warnSpy).toHaveBeenCalledWith(
3315
+ expect.stringContaining('preRenderContainer is applied only by'),
3316
+ );
3317
+
3318
+ warnSpy.mockRestore();
3319
+ firstEmbed.destroy();
3320
+ ownerContainer.remove();
3321
+ otherContainer.remove();
3322
+ });
3323
+
3324
+ it('should not warn when the connecting embed passes the same container element', async () => {
3325
+ createRootEleForEmbed();
3326
+ const customContainer = document.createElement('div');
3327
+ customContainer.id = 'connect-same-element-container';
3328
+ document.body.appendChild(customContainer);
3329
+ const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
3330
+
3331
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3332
+ preRenderId: 'connect-same-element',
3333
+ liveboardId: 'myLiveboardId',
3334
+ preRenderContainer: customContainer,
3335
+ });
3336
+ await firstEmbed.preRender();
3337
+ await waitFor(() => !!getIFrameEl());
3338
+
3339
+ // Same config on both components — the value is redundant but
3340
+ // not wrong, so nagging about it would be noise.
3341
+ const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3342
+ preRenderId: 'connect-same-element',
3343
+ liveboardId: 'myLiveboardId',
3344
+ preRenderContainer: customContainer,
3345
+ });
3346
+ (connectingEmbed as any).connectPreRendered();
3347
+
3348
+ expect((connectingEmbed as any).preRenderContainerEl).toBe(customContainer);
3349
+ expect(warnSpy).not.toHaveBeenCalledWith(
3350
+ expect.stringContaining('preRenderContainer is applied only by'),
3351
+ );
3352
+
3353
+ warnSpy.mockRestore();
3354
+ firstEmbed.destroy();
3355
+ customContainer.remove();
3356
+ });
3357
+
3358
+ it('should not warn when the connecting embed passes a selector for the same container', async () => {
3359
+ createRootEleForEmbed();
3360
+ const customContainer = document.createElement('div');
3361
+ customContainer.id = 'connect-same-selector-container';
3362
+ document.body.appendChild(customContainer);
3363
+ const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
3364
+
3365
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3366
+ preRenderId: 'connect-same-selector',
3367
+ liveboardId: 'myLiveboardId',
3368
+ preRenderContainer: customContainer,
3369
+ });
3370
+ await firstEmbed.preRender();
3371
+ await waitFor(() => !!getIFrameEl());
3372
+
3373
+ // A selector that resolves to the owner's element is the same
3374
+ // container, so the comparison must be by resolved node.
3375
+ const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3376
+ preRenderId: 'connect-same-selector',
3377
+ liveboardId: 'myLiveboardId',
3378
+ preRenderContainer: '#connect-same-selector-container',
3379
+ });
3380
+ (connectingEmbed as any).connectPreRendered();
3381
+
3382
+ expect((connectingEmbed as any).preRenderContainerEl).toBe(customContainer);
3383
+ expect(warnSpy).not.toHaveBeenCalledWith(
3384
+ expect.stringContaining('preRenderContainer is applied only by'),
3385
+ );
3386
+
3387
+ warnSpy.mockRestore();
3388
+ firstEmbed.destroy();
3389
+ customContainer.remove();
3390
+ });
3391
+
3392
+ it('should keep its own container when the same instance owns the pre-render', async () => {
3393
+ createRootEleForEmbed();
3394
+ const customContainer = document.createElement('div');
3395
+ customContainer.id = 'connect-self-container';
3396
+ document.body.appendChild(customContainer);
3397
+ const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
3398
+
3399
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3400
+ preRenderId: 'connect-self',
3401
+ liveboardId: 'myLiveboardId',
3402
+ preRenderContainer: customContainer,
3403
+ });
3404
+ await libEmbed.preRender();
3405
+ await waitFor(() => !!getIFrameEl());
3406
+
3407
+ // preRender() then render() on one instance: it owns the
3408
+ // wrapper, so there is nothing to inherit and its own
3409
+ // preRenderContainer is entirely legitimate.
3410
+ (libEmbed as any).connectPreRendered();
3411
+
3412
+ expect((libEmbed as any).preRenderContainerEl).toBe(customContainer);
3413
+ expect(warnSpy).not.toHaveBeenCalledWith(
3414
+ expect.stringContaining('preRenderContainer is applied only by'),
3415
+ );
3416
+
3417
+ warnSpy.mockRestore();
3418
+ libEmbed.destroy();
3419
+ customContainer.remove();
3420
+ });
3421
+
3422
+ it('should stay on document.body when the wrapper has no owning embed', async () => {
3423
+ createRootEleForEmbed();
3424
+ const customContainer = document.createElement('div');
3425
+ customContainer.id = 'connect-unstamped-container';
3426
+ document.body.appendChild(customContainer);
3427
+ const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
3428
+
3429
+ const firstEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3430
+ preRenderId: 'connect-unstamped',
3431
+ liveboardId: 'myLiveboardId',
3432
+ preRenderContainer: customContainer,
3433
+ });
3434
+ await firstEmbed.preRender();
3435
+ await waitFor(() => !!getIFrameEl());
3436
+
3437
+ // Simulate a wrapper with no live embed behind it: the
3438
+ // __tsEmbed stamp used to locate the owner is gone, so there
3439
+ // is no container to inherit.
3440
+ const wrapper = document.getElementById(
3441
+ firstEmbed.getPreRenderIds().wrapper,
3442
+ );
3443
+ delete (wrapper as any).__tsEmbed;
3444
+
3445
+ const connectingEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3446
+ preRenderId: 'connect-unstamped',
3447
+ liveboardId: 'myLiveboardId',
3448
+ });
3449
+ (connectingEmbed as any).connectPreRendered();
3450
+
3451
+ // Falls back to the sentinel rather than crashing on the
3452
+ // missing owner, and warns about nothing.
3453
+ expect((connectingEmbed as any).preRenderContainerEl).toBe(document.body);
3454
+ expect(warnSpy).not.toHaveBeenCalledWith(
3455
+ expect.stringContaining('preRenderContainer is applied only by'),
3456
+ );
3457
+
3458
+ warnSpy.mockRestore();
3459
+ firstEmbed.destroy();
3460
+ customContainer.remove();
3461
+ });
3462
+ });
3463
+ });
3464
+
3465
+ describe('preRenderConfig', () => {
3466
+ beforeEach(() => {
3467
+ (window as any).ResizeObserver = jest.fn().mockImplementation(() => ({
3468
+ disconnect: jest.fn(),
3469
+ observe: jest.fn(),
3470
+ unobserve: jest.fn(),
3471
+ }));
3472
+ });
3473
+
3474
+ it('should use default z-index of -1000 when preRenderConfig is not set', async () => {
3475
+ createRootEleForEmbed();
3476
+
3477
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3478
+ preRenderId: 'pre-render-config-defaults',
3479
+ liveboardId: 'myLiveboardId',
3480
+ });
3481
+
3482
+ await libEmbed.preRender();
3483
+ await waitFor(() => !!getIFrameEl());
3484
+
3485
+ const preRenderIds = libEmbed.getPreRenderIds();
3486
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
3487
+
3488
+ expect(preRenderWrapper.style.zIndex).toBe('-1000');
3489
+
3490
+ libEmbed.destroy();
3491
+ });
3492
+
3493
+ it('should apply custom zIndex from preRenderConfig when hidden', async () => {
3494
+ createRootEleForEmbed();
3495
+
3496
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3497
+ preRenderId: 'pre-render-config-custom-z-index',
3498
+ liveboardId: 'myLiveboardId',
3499
+ preRenderConfig: {
3500
+ zIndex: -10,
3501
+ },
3502
+ });
3503
+
3504
+ await libEmbed.preRender();
3505
+ await waitFor(() => !!getIFrameEl());
3506
+
3507
+ const preRenderIds = libEmbed.getPreRenderIds();
3508
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
3509
+
3510
+ expect(preRenderWrapper.style.zIndex).toBe('-10');
3511
+
3512
+ libEmbed.destroy();
3513
+ });
3514
+
3515
+ it('should clear z-index on showPreRender and restore custom zIndex on hidePreRender', async () => {
3516
+ createRootEleForEmbed();
3517
+
3518
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3519
+ preRenderId: 'pre-render-config-cycle',
3520
+ liveboardId: 'myLiveboardId',
3521
+ preRenderConfig: {
3522
+ zIndex: -5,
3523
+ },
3524
+ });
3525
+
3526
+ await libEmbed.preRender();
3527
+ await waitFor(() => !!getIFrameEl());
3528
+
3529
+ const preRenderIds = libEmbed.getPreRenderIds();
3530
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
3531
+
3532
+ expect(preRenderWrapper.style.zIndex).toBe('-5');
3533
+
3534
+ libEmbed.showPreRender();
3535
+ expect(preRenderWrapper.style.zIndex).toBe('');
3536
+
3537
+ libEmbed.hidePreRender();
3538
+ expect(preRenderWrapper.style.zIndex).toBe('-5');
3539
+
3540
+ libEmbed.destroy();
3541
+ });
3542
+
3543
+ it('should use preRenderConfig.preRenderId to identify the pre-render instance', async () => {
3544
+ createRootEleForEmbed();
3545
+
3546
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3547
+ liveboardId: 'myLiveboardId',
3548
+ preRenderConfig: {
3549
+ preRenderId: 'config-level-id',
3550
+ },
3551
+ });
3552
+
3553
+ await libEmbed.preRender();
3554
+ await waitFor(() => !!getIFrameEl());
3555
+
3556
+ const preRenderIds = libEmbed.getPreRenderIds();
3557
+ expect(preRenderIds.wrapper).toContain('config-level-id');
3558
+ expect(document.getElementById(preRenderIds.wrapper)).not.toBeNull();
3559
+
3560
+ libEmbed.destroy();
3561
+ });
3562
+
3563
+ it('should prefer preRenderConfig.preRenderId over top-level preRenderId', async () => {
3564
+ createRootEleForEmbed();
3565
+
3566
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3567
+ liveboardId: 'myLiveboardId',
3568
+ preRenderId: 'top-level-id',
3569
+ preRenderConfig: {
3570
+ preRenderId: 'config-level-id',
3571
+ },
3572
+ });
3573
+
3574
+ await libEmbed.preRender();
3575
+ await waitFor(() => !!getIFrameEl());
3576
+
3577
+ const preRenderIds = libEmbed.getPreRenderIds();
3578
+ expect(preRenderIds.wrapper).toContain('config-level-id');
3579
+ expect(preRenderIds.wrapper).not.toContain('top-level-id');
3580
+
3581
+ libEmbed.destroy();
3582
+ });
3583
+
3584
+ it('should mount the wrapper into preRenderConfig.preRenderContainer', async () => {
3585
+ createRootEleForEmbed();
3586
+ const customContainer = document.createElement('div');
3587
+ customContainer.id = 'config-level-container';
3588
+ document.body.appendChild(customContainer);
3589
+
3590
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3591
+ liveboardId: 'myLiveboardId',
3592
+ preRenderConfig: {
3593
+ preRenderId: 'container-via-config',
3594
+ preRenderContainer: '#config-level-container',
3595
+ },
3596
+ });
3597
+
3598
+ await libEmbed.preRender();
3599
+ await waitFor(() => !!getIFrameEl());
3600
+
3601
+ const preRenderIds = libEmbed.getPreRenderIds();
3602
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
3603
+ expect(customContainer.contains(preRenderWrapper)).toBe(true);
3604
+
3605
+ libEmbed.destroy();
3606
+ customContainer.remove();
3607
+ });
3608
+
3609
+ it('should prefer preRenderConfig.preRenderContainer over top-level preRenderContainer', async () => {
3610
+ createRootEleForEmbed();
3611
+ const topContainer = document.createElement('div');
3612
+ topContainer.id = 'top-level-container';
3613
+ document.body.appendChild(topContainer);
3614
+ const configContainer = document.createElement('div');
3615
+ configContainer.id = 'config-level-container-2';
3616
+ document.body.appendChild(configContainer);
3617
+
3618
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3619
+ liveboardId: 'myLiveboardId',
3620
+ preRenderContainer: '#top-level-container',
3621
+ preRenderConfig: {
3622
+ preRenderId: 'container-precedence',
3623
+ preRenderContainer: '#config-level-container-2',
3624
+ },
3625
+ });
3626
+
3627
+ await libEmbed.preRender();
3628
+ await waitFor(() => !!getIFrameEl());
3629
+
3630
+ const preRenderIds = libEmbed.getPreRenderIds();
3631
+ const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
3632
+ expect(configContainer.contains(preRenderWrapper)).toBe(true);
3633
+ expect(topContainer.contains(preRenderWrapper)).toBe(false);
3634
+
3635
+ libEmbed.destroy();
3636
+ topContainer.remove();
3637
+ configContainer.remove();
3638
+ });
3639
+
3640
+ it('should not attach ResizeObserver when preRenderConfig.doNotTrackPreRenderSize is true', async () => {
3641
+ createRootEleForEmbed();
3642
+ const observeMock = jest.fn();
3643
+ (window as any).ResizeObserver = jest.fn().mockImplementation(() => ({
3644
+ disconnect: jest.fn(),
3645
+ observe: observeMock,
3646
+ unobserve: jest.fn(),
3647
+ }));
3648
+
3649
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3650
+ liveboardId: 'myLiveboardId',
3651
+ preRenderConfig: {
3652
+ preRenderId: 'no-track-via-config',
3653
+ doNotTrackPreRenderSize: true,
3654
+ },
3655
+ });
3656
+
3657
+ await libEmbed.preRender();
3658
+ await waitFor(() => !!getIFrameEl());
3659
+ libEmbed.showPreRender();
3660
+
3661
+ expect(observeMock).not.toHaveBeenCalled();
3662
+
3663
+ libEmbed.destroy();
3664
+ });
3665
+
3666
+ it('should prefer preRenderConfig.doNotTrackPreRenderSize over top-level doNotTrackPreRenderSize', async () => {
3667
+ createRootEleForEmbed();
3668
+ const observeMock = jest.fn();
3669
+ (window as any).ResizeObserver = jest.fn().mockImplementation(() => ({
3670
+ disconnect: jest.fn(),
3671
+ observe: observeMock,
3672
+ unobserve: jest.fn(),
3673
+ }));
3674
+
3675
+ const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
3676
+ liveboardId: 'myLiveboardId',
3677
+ doNotTrackPreRenderSize: false,
3678
+ preRenderConfig: {
3679
+ preRenderId: 'no-track-precedence',
3680
+ doNotTrackPreRenderSize: true,
3681
+ },
3682
+ });
3683
+
3684
+ await libEmbed.preRender();
3685
+ await waitFor(() => !!getIFrameEl());
3686
+ libEmbed.showPreRender();
3687
+
3688
+ expect(observeMock).not.toHaveBeenCalled();
3689
+
3690
+ libEmbed.destroy();
3691
+ });
2956
3692
  });
2957
3693
  });
2958
3694
 
@@ -4570,6 +5306,28 @@ describe('Trigger method edge cases', () => {
4570
5306
  expect(result).toBeNull();
4571
5307
  });
4572
5308
  });
5309
+
5310
+ test('isValidationError catch: handleError called with embedErrorDetails and error is rethrown', async () => {
5311
+ const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
5312
+ const handleErrorSpy = jest.spyOn(searchEmbed as any, 'handleError').mockImplementation(() => {});
5313
+ await searchEmbed.render();
5314
+
5315
+ // First processTrigger call is getAvailableUIPassthroughKeys for DrillDown
5316
+ mockProcessTrigger.mockResolvedValueOnce([
5317
+ { value: { keys: Object.values(UIPassthroughEvent) } },
5318
+ ]);
5319
+
5320
+ // DrillDown with no points → throwDrillDownValidationError → isValidationError=true
5321
+ await expect(
5322
+ searchEmbed.trigger(HostEvent.DrillDown, {} as any),
5323
+ ).rejects.toThrow('DrillDown requires a valid points object');
5324
+
5325
+ expect(handleErrorSpy).toHaveBeenCalledWith(
5326
+ expect.objectContaining({
5327
+ data: expect.objectContaining({ code: EmbedErrorCodes.HOST_EVENT_VALIDATION }),
5328
+ }),
5329
+ );
5330
+ });
4573
5331
  });
4574
5332
 
4575
5333
  describe('PreRender replaceExistingPreRender scenarios', () => {
@@ -4580,6 +5338,10 @@ describe('PreRender replaceExistingPreRender scenarios', () => {
4580
5338
  });
4581
5339
  });
4582
5340
 
5341
+ beforeEach(() => {
5342
+ document.body.innerHTML = getDocumentBody();
5343
+ });
5344
+
4583
5345
  afterEach(() => {
4584
5346
  const rootEle = document.getElementById('myRoot');
4585
5347
  rootEle?.remove();
@@ -4725,6 +5487,52 @@ describe('Fullscreen change handler behavior', () => {
4725
5487
  );
4726
5488
  });
4727
5489
  });
5490
+
5491
+ test('disableFullscreenPresentation undefined defaults to true — no fullscreenchange listener registered', () => {
5492
+ init({
5493
+ thoughtSpotHost: 'tshost',
5494
+ authType: AuthType.None,
5495
+ // disableFullscreenPresentation not set → defaults to true via ?? true
5496
+ });
5497
+
5498
+ const embed = new SearchEmbed(getRootEl(), defaultViewConfig);
5499
+ const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
5500
+
5501
+ embed['setupFullscreenChangeHandler']();
5502
+
5503
+ expect(addEventListenerSpy).not.toHaveBeenCalledWith('fullscreenchange', expect.any(Function));
5504
+
5505
+ addEventListenerSpy.mockRestore();
5506
+ // Restore for other tests in this describe
5507
+ init({ thoughtSpotHost: 'tshost', authType: AuthType.None, disableFullscreenPresentation: false });
5508
+ });
5509
+
5510
+ test('fullscreenchange fires but iFrame.contentWindow is null — trigger NOT called, debug log emitted', () => {
5511
+ const embed = new SearchEmbed(getRootEl(), defaultViewConfig);
5512
+ // Give the embed a fake iFrame with null contentWindow
5513
+ (embed as any).iFrame = { contentWindow: null };
5514
+
5515
+ const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
5516
+ const loggerDebugSpy = jest.spyOn(logger, 'debug').mockImplementation(() => {});
5517
+ const triggerSpy = jest.spyOn(embed, 'trigger' as any).mockImplementation(() => Promise.resolve(null));
5518
+
5519
+ embed['setupFullscreenChangeHandler']();
5520
+
5521
+ // Capture the registered handler directly instead of dispatching to document
5522
+ const fullscreenCall = addEventListenerSpy.mock.calls.find((c) => c[0] === 'fullscreenchange');
5523
+ expect(fullscreenCall).toBeTruthy();
5524
+ const handler = fullscreenCall[1] as EventListener;
5525
+
5526
+ Object.defineProperty(document, 'fullscreenElement', { value: null, writable: true, configurable: true });
5527
+ handler(new Event('fullscreenchange'));
5528
+
5529
+ expect(triggerSpy).not.toHaveBeenCalled();
5530
+ expect(loggerDebugSpy).toHaveBeenCalledWith(expect.stringContaining('Skipping ExitPresentMode'));
5531
+
5532
+ addEventListenerSpy.mockRestore();
5533
+ loggerDebugSpy.mockRestore();
5534
+ triggerSpy.mockRestore();
5535
+ });
4728
5536
  });
4729
5537
 
4730
5538
  describe('ShowPreRender with UpdateEmbedParams', () => {
@@ -5315,5 +6123,79 @@ describe('ShowPreRender with UpdateEmbedParams', () => {
5315
6123
 
5316
6124
  embed.destroy();
5317
6125
  });
6126
+
6127
+ // showPreRender located the stale placeholder with a subtree-wide
6128
+ // querySelector but removed it with hostElement.removeChild(), which
6129
+ // only accepts direct children. With fullHeight the placeholder can be
6130
+ // nested deeper than a direct child, so removeChild threw
6131
+ // "NotFoundError: Failed to execute 'removeChild' on 'Node'".
6132
+ it('showPreRender removes a placeholder nested below a direct child without throwing', async () => {
6133
+ createRootEleForEmbed();
6134
+ const embed = new LiveboardEmbed('#tsEmbedDiv', {
6135
+ preRenderId: 'ph-nested-test',
6136
+ liveboardId: 'lb-id',
6137
+ fullHeight: true,
6138
+ } as any);
6139
+ await embed.preRender();
6140
+ await waitFor(
6141
+ () => !!document.querySelector('#tsEmbed-pre-render-child-ph-nested-test'),
6142
+ );
6143
+
6144
+ const hostElement = document.querySelector('#tsEmbedDiv') as HTMLElement;
6145
+ const placeHolderId = 'tsEmbed-pre-render-placeholder-ph-nested-test';
6146
+ // Simulate the fullHeight layout where the placeholder ends up as
6147
+ // a grandchild of the host element rather than a direct child.
6148
+ const intermediateWrapper = document.createElement('div');
6149
+ const staleNestedPlaceholder = document.createElement('div');
6150
+ staleNestedPlaceholder.id = placeHolderId;
6151
+ intermediateWrapper.appendChild(staleNestedPlaceholder);
6152
+ hostElement.appendChild(intermediateWrapper);
6153
+
6154
+ expect(hostElement.querySelector(`#${placeHolderId}`)).toBe(staleNestedPlaceholder);
6155
+ // Before the fix this threw NotFoundError.
6156
+ await expect(embed.showPreRender()).resolves.toBeDefined();
6157
+ // The stale nested placeholder is gone; the fresh one is a direct child.
6158
+ expect(staleNestedPlaceholder.isConnected).toBe(false);
6159
+ const currentPlaceholder = (
6160
+ embed as any
6161
+ ).getPreRenderPlaceHolderElement() as HTMLDivElement;
6162
+ expect(currentPlaceholder.parentElement).toBe(hostElement);
6163
+
6164
+ embed.destroy();
6165
+ });
6166
+ });
6167
+
6168
+ describe('trigger typing contracts (compile-time)', () => {
6169
+ // Guards the BASE TsEmbed.trigger — the implementation every
6170
+ // non-Liveboard embed (SearchEmbed, AppEmbed, ConversationEmbed, …)
6171
+ // calls. LiveboardEmbed's override is covered in liveboard.spec.ts;
6172
+ // this locks the base defaults so a "lint fix" that drops them here
6173
+ // can't silently de-type every embed except Liveboard.
6174
+ // Enforced by ts-jest at compile time.
6175
+ type IsAny<T> = 0 extends 1 & T ? true : false;
6176
+ type Extends<A, B> = A extends B ? true : false;
6177
+
6178
+ it('infers typed response for no-payload calls on the base trigger', () => {
6179
+ const searchEmbed = new SearchEmbed(getRootEl(), defaultViewConfig);
6180
+
6181
+ const getTabs = () => searchEmbed.trigger(HostEvent.GetTabs);
6182
+ type TabsResponse = Awaited<ReturnType<typeof getTabs>>;
6183
+ const tabsIsTyped: IsAny<TabsResponse> = false;
6184
+ const tabsHasContractFields: Extends<
6185
+ TabsResponse,
6186
+ { numberOfTabs: number; orderedTabIds: string[] }
6187
+ > = true;
6188
+ const probeUnknownField = (r: TabsResponse) =>
6189
+ // @ts-expect-error — field not on the GetTabs contract (was silent when `any`)
6190
+ r.tabCount;
6191
+ void probeUnknownField;
6192
+
6193
+ const reload = () => searchEmbed.trigger(HostEvent.Reload);
6194
+ type ReloadResponse = Awaited<ReturnType<typeof reload>>;
6195
+ const reloadStaysAny: IsAny<ReloadResponse> = true;
6196
+
6197
+ expect([tabsIsTyped, tabsHasContractFields, reloadStaysAny])
6198
+ .toEqual([false, true, true]);
6199
+ });
5318
6200
  });
5319
6201
  });