@thoughtspot/visual-embed-sdk 1.39.2 → 1.39.3

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 (157) hide show
  1. package/cjs/package.json +1 -1
  2. package/cjs/src/config.spec.js +9 -0
  3. package/cjs/src/config.spec.js.map +1 -1
  4. package/cjs/src/embed/app.d.ts +19 -15
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +23 -2
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +56 -8
  9. package/cjs/src/embed/app.spec.js.map +1 -1
  10. package/cjs/src/embed/bodyless-conversation.d.ts +23 -7
  11. package/cjs/src/embed/bodyless-conversation.d.ts.map +1 -1
  12. package/cjs/src/embed/bodyless-conversation.js +31 -5
  13. package/cjs/src/embed/bodyless-conversation.js.map +1 -1
  14. package/cjs/src/embed/bodyless-conversation.spec.js +8 -190
  15. package/cjs/src/embed/bodyless-conversation.spec.js.map +1 -1
  16. package/cjs/src/embed/conversation.spec.js +28 -0
  17. package/cjs/src/embed/conversation.spec.js.map +1 -1
  18. package/cjs/src/embed/embedConfig.d.ts +9 -7
  19. package/cjs/src/embed/embedConfig.d.ts.map +1 -1
  20. package/cjs/src/embed/embedConfig.js +9 -7
  21. package/cjs/src/embed/embedConfig.js.map +1 -1
  22. package/cjs/src/embed/liveboard.d.ts +0 -17
  23. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  24. package/cjs/src/embed/liveboard.js +2 -4
  25. package/cjs/src/embed/liveboard.js.map +1 -1
  26. package/cjs/src/embed/liveboard.spec.js +12 -11
  27. package/cjs/src/embed/liveboard.spec.js.map +1 -1
  28. package/cjs/src/errors.d.ts +1 -0
  29. package/cjs/src/errors.d.ts.map +1 -1
  30. package/cjs/src/errors.js +1 -0
  31. package/cjs/src/errors.js.map +1 -1
  32. package/cjs/src/index.d.ts +2 -2
  33. package/cjs/src/index.d.ts.map +1 -1
  34. package/cjs/src/index.js +2 -1
  35. package/cjs/src/index.js.map +1 -1
  36. package/cjs/src/react/all-types-export.d.ts +1 -1
  37. package/cjs/src/react/all-types-export.d.ts.map +1 -1
  38. package/cjs/src/react/all-types-export.js +3 -2
  39. package/cjs/src/react/all-types-export.js.map +1 -1
  40. package/cjs/src/react/index.d.ts +73 -20
  41. package/cjs/src/react/index.d.ts.map +1 -1
  42. package/cjs/src/react/index.js +79 -42
  43. package/cjs/src/react/index.js.map +1 -1
  44. package/cjs/src/react/index.spec.js +438 -100
  45. package/cjs/src/react/index.spec.js.map +1 -1
  46. package/cjs/src/types.d.ts +262 -6
  47. package/cjs/src/types.d.ts.map +1 -1
  48. package/cjs/src/types.js +228 -2
  49. package/cjs/src/types.js.map +1 -1
  50. package/cjs/src/utils/global-styles.js +1 -1
  51. package/cjs/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
  52. package/cjs/src/utils/graphql/nlsService/conversation-service.js +7 -1
  53. package/cjs/src/utils/graphql/nlsService/conversation-service.js.map +1 -1
  54. package/cjs/src/utils.spec.js +25 -0
  55. package/cjs/src/utils.spec.js.map +1 -1
  56. package/dist/{index-CmEQfuE3.js → index-ZrE8YYq8.js} +1 -1
  57. package/dist/src/embed/app.d.ts +19 -15
  58. package/dist/src/embed/app.d.ts.map +1 -1
  59. package/dist/src/embed/bodyless-conversation.d.ts +23 -7
  60. package/dist/src/embed/bodyless-conversation.d.ts.map +1 -1
  61. package/dist/src/embed/embedConfig.d.ts +9 -7
  62. package/dist/src/embed/embedConfig.d.ts.map +1 -1
  63. package/dist/src/embed/liveboard.d.ts +0 -17
  64. package/dist/src/embed/liveboard.d.ts.map +1 -1
  65. package/dist/src/errors.d.ts +1 -0
  66. package/dist/src/errors.d.ts.map +1 -1
  67. package/dist/src/index.d.ts +2 -2
  68. package/dist/src/index.d.ts.map +1 -1
  69. package/dist/src/react/all-types-export.d.ts +1 -1
  70. package/dist/src/react/all-types-export.d.ts.map +1 -1
  71. package/dist/src/react/index.d.ts +73 -20
  72. package/dist/src/react/index.d.ts.map +1 -1
  73. package/dist/src/types.d.ts +262 -6
  74. package/dist/src/types.d.ts.map +1 -1
  75. package/dist/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
  76. package/dist/tsembed-react.es.js +385 -71
  77. package/dist/tsembed-react.js +385 -69
  78. package/dist/tsembed.es.js +303 -24
  79. package/dist/tsembed.js +301 -22
  80. package/dist/visual-embed-sdk-react-full.d.ts +386 -72
  81. package/dist/visual-embed-sdk-react.d.ts +386 -72
  82. package/dist/visual-embed-sdk.d.ts +314 -53
  83. package/lib/package.json +1 -1
  84. package/lib/src/config.spec.js +9 -0
  85. package/lib/src/config.spec.js.map +1 -1
  86. package/lib/src/embed/app.d.ts +19 -15
  87. package/lib/src/embed/app.d.ts.map +1 -1
  88. package/lib/src/embed/app.js +22 -1
  89. package/lib/src/embed/app.js.map +1 -1
  90. package/lib/src/embed/app.spec.js +58 -10
  91. package/lib/src/embed/app.spec.js.map +1 -1
  92. package/lib/src/embed/bodyless-conversation.d.ts +23 -7
  93. package/lib/src/embed/bodyless-conversation.d.ts.map +1 -1
  94. package/lib/src/embed/bodyless-conversation.js +30 -5
  95. package/lib/src/embed/bodyless-conversation.js.map +1 -1
  96. package/lib/src/embed/bodyless-conversation.spec.js +9 -191
  97. package/lib/src/embed/bodyless-conversation.spec.js.map +1 -1
  98. package/lib/src/embed/conversation.spec.js +30 -2
  99. package/lib/src/embed/conversation.spec.js.map +1 -1
  100. package/lib/src/embed/embedConfig.d.ts +9 -7
  101. package/lib/src/embed/embedConfig.d.ts.map +1 -1
  102. package/lib/src/embed/embedConfig.js +9 -7
  103. package/lib/src/embed/embedConfig.js.map +1 -1
  104. package/lib/src/embed/liveboard.d.ts +0 -17
  105. package/lib/src/embed/liveboard.d.ts.map +1 -1
  106. package/lib/src/embed/liveboard.js +2 -4
  107. package/lib/src/embed/liveboard.js.map +1 -1
  108. package/lib/src/embed/liveboard.spec.js +12 -11
  109. package/lib/src/embed/liveboard.spec.js.map +1 -1
  110. package/lib/src/errors.d.ts +1 -0
  111. package/lib/src/errors.d.ts.map +1 -1
  112. package/lib/src/errors.js +1 -0
  113. package/lib/src/errors.js.map +1 -1
  114. package/lib/src/index.d.ts +2 -2
  115. package/lib/src/index.d.ts.map +1 -1
  116. package/lib/src/index.js +2 -2
  117. package/lib/src/index.js.map +1 -1
  118. package/lib/src/react/all-types-export.d.ts +1 -1
  119. package/lib/src/react/all-types-export.d.ts.map +1 -1
  120. package/lib/src/react/all-types-export.js +1 -1
  121. package/lib/src/react/all-types-export.js.map +1 -1
  122. package/lib/src/react/index.d.ts +73 -20
  123. package/lib/src/react/index.d.ts.map +1 -1
  124. package/lib/src/react/index.js +79 -43
  125. package/lib/src/react/index.js.map +1 -1
  126. package/lib/src/react/index.spec.js +441 -103
  127. package/lib/src/react/index.spec.js.map +1 -1
  128. package/lib/src/types.d.ts +262 -6
  129. package/lib/src/types.d.ts.map +1 -1
  130. package/lib/src/types.js +228 -2
  131. package/lib/src/types.js.map +1 -1
  132. package/lib/src/utils/global-styles.js +1 -1
  133. package/lib/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
  134. package/lib/src/utils/graphql/nlsService/conversation-service.js +7 -1
  135. package/lib/src/utils/graphql/nlsService/conversation-service.js.map +1 -1
  136. package/lib/src/utils.spec.js +26 -1
  137. package/lib/src/utils.spec.js.map +1 -1
  138. package/lib/src/visual-embed-sdk.d.ts +315 -54
  139. package/package.json +1 -1
  140. package/src/config.spec.ts +11 -0
  141. package/src/embed/app.spec.ts +90 -23
  142. package/src/embed/app.ts +27 -15
  143. package/src/embed/bodyless-conversation.spec.ts +9 -203
  144. package/src/embed/bodyless-conversation.ts +34 -10
  145. package/src/embed/conversation.spec.ts +40 -2
  146. package/src/embed/embedConfig.ts +10 -8
  147. package/src/embed/liveboard.spec.ts +5 -4
  148. package/src/embed/liveboard.ts +2 -22
  149. package/src/errors.ts +1 -0
  150. package/src/index.ts +2 -0
  151. package/src/react/all-types-export.ts +2 -1
  152. package/src/react/index.spec.tsx +558 -157
  153. package/src/react/index.tsx +117 -51
  154. package/src/types.ts +301 -44
  155. package/src/utils/global-styles.ts +1 -1
  156. package/src/utils/graphql/nlsService/conversation-service.ts +7 -1
  157. package/src/utils.spec.ts +29 -0
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import '@testing-library/jest-dom';
3
3
  import '@testing-library/jest-dom/extend-expect';
4
4
  import {
@@ -15,8 +15,7 @@ import {
15
15
  mockMessageChannel,
16
16
  } from '../test/test-utils';
17
17
  import {
18
- SearchEmbed, AppEmbed, LiveboardEmbed, useEmbedRef, SearchBarEmbed, PreRenderedLiveboardEmbed,
19
- SpotterAgentEmbed
18
+ SearchEmbed, AppEmbed, LiveboardEmbed, useEmbedRef, SearchBarEmbed, PreRenderedLiveboardEmbed, PreRenderedSearchEmbed, PreRenderedAppEmbed, useSpotterAgent, SpotterMessage, useInit
20
19
  } from './index';
21
20
  import * as allExports from './index';
22
21
  import {
@@ -236,199 +235,601 @@ describe('React Components', () => {
236
235
  });
237
236
  });
238
237
 
239
- describe('SpotterAgentEmbed', () => {
240
- it('Should work as a React component integrating BodylessConversation', async () => {
241
- const mockDiv = document.createElement('div');
238
+ describe('SpotterMessage', () => {
239
+ const mockMessage = {
240
+ sessionId: "session123",
241
+ genNo: 1,
242
+ acSessionId: "acSession123",
243
+ acGenNo: 2,
244
+ worksheetId: "worksheet123",
245
+ convId: "conv123",
246
+ messageId: "message123"
247
+ };
248
+
249
+ it('Should render the SpotterMessage component with required props', async () => {
250
+ const { container } = render(
251
+ <SpotterMessage message={mockMessage} />,
252
+ );
253
+
254
+ await waitFor(() => getIFrameEl(container));
255
+
256
+ expect(getIFrameEl(container)).not.toBe(null);
257
+ expect(getIFrameSrc(container)).toContain('sessionId=session123');
258
+ expect(getIFrameSrc(container)).toContain('genNo=1');
259
+ expect(getIFrameSrc(container)).toContain('acSessionId=acSession123');
260
+ expect(getIFrameSrc(container)).toContain('acGenNo=2');
261
+ });
262
+
263
+ it('Should render the SpotterMessage component with optional query', async () => {
264
+ const { container } = render(
265
+ <SpotterMessage
266
+ message={mockMessage}
267
+ query="show me sales"
268
+ />,
269
+ );
270
+
271
+ await waitFor(() => getIFrameEl(container));
272
+
273
+ expect(getIFrameEl(container)).not.toBe(null);
274
+ expect(getIFrameSrc(container)).toContain('sessionId=session123');
275
+ });
276
+
277
+ it('Should have the correct container element with className', async () => {
278
+ const { container } = render(
279
+ <SpotterMessage
280
+ message={mockMessage}
281
+ className="custom-class"
282
+ />,
283
+ );
284
+
285
+ await waitFor(() => getIFrameEl(container));
286
+
287
+ expect(
288
+ getIFrameEl(container).parentElement.classList.contains('custom-class'),
289
+ ).toBe(true);
290
+ });
291
+
292
+ // Note: insertAsSibling is not supported for SpotterMessage as it's not part of the allowed props
293
+ });
294
+
295
+ describe('Component Factory Coverage', () => {
296
+ it('Should test basic component creation', () => {
297
+ expect(() => {
298
+ render(<LiveboardEmbed liveboardId="test" />);
299
+ }).not.toThrow();
242
300
 
243
- let conversationService: any = null;
301
+ expect(() => {
302
+ render(<SearchEmbed dataSource="test" />);
303
+ }).not.toThrow();
244
304
 
245
- render(
246
- <SpotterAgentEmbed
247
- ref={(instance: any) => {
248
- conversationService = instance;
249
-
250
- if (instance) {
251
- instance.sendMessage = jest.fn().mockResolvedValue({
252
- container: mockDiv
253
- });
254
- }
255
- }}
256
- worksheetId="test-worksheet-id"
257
- />
305
+ expect(() => {
306
+ render(<AppEmbed showPrimaryNavbar={false} />);
307
+ }).not.toThrow();
308
+ });
309
+
310
+ it('Should test component factory existence', () => {
311
+ expect(PreRenderedLiveboardEmbed).toBeDefined();
312
+ expect(PreRenderedSearchEmbed).toBeDefined();
313
+ expect(PreRenderedAppEmbed).toBeDefined();
314
+ expect(typeof PreRenderedLiveboardEmbed).toBe('object');
315
+ expect(typeof PreRenderedSearchEmbed).toBe('object');
316
+ expect(typeof PreRenderedAppEmbed).toBe('object');
317
+ });
318
+ });
319
+
320
+ describe('Components with insertAsSibling', () => {
321
+ it('Should render LiveboardEmbed with insertAsSibling', async () => {
322
+ const { container } = render(
323
+ <LiveboardEmbed
324
+ liveboardId="test-liveboard"
325
+ insertAsSibling={true}
326
+ />,
258
327
  );
259
328
 
260
- expect(conversationService).not.toBeNull();
261
-
262
- if (conversationService) {
263
- expect(typeof conversationService.sendMessage).toBe('function');
329
+ await waitFor(() => getIFrameEl(container));
330
+ expect(container.querySelector('span')).not.toBe(null);
331
+ expect(container.querySelector('span')?.style.position).toBe('absolute');
332
+ });
333
+
334
+ it('Should render SearchEmbed with insertAsSibling', async () => {
335
+ const { container } = render(
336
+ <SearchEmbed
337
+ dataSource="test-datasource"
338
+ insertAsSibling={true}
339
+ />,
340
+ );
341
+
342
+ await waitFor(() => getIFrameEl(container));
343
+ expect(container.querySelector('span')).not.toBe(null);
344
+ expect(container.querySelector('span')?.style.position).toBe('absolute');
345
+ });
346
+
347
+ it('Should render AppEmbed with insertAsSibling', async () => {
348
+ const { container } = render(
349
+ <AppEmbed
350
+ showPrimaryNavbar={false}
351
+ insertAsSibling={true}
352
+ />,
353
+ );
354
+
355
+ await waitFor(() => getIFrameEl(container));
356
+ expect(container.querySelector('span')).not.toBe(null);
357
+ expect(container.querySelector('span')?.style.position).toBe('absolute');
358
+ });
359
+
360
+ it('Should render SearchBarEmbed with insertAsSibling', async () => {
361
+ const { container } = render(
362
+ <SearchBarEmbed
363
+ dataSource="test-datasource"
364
+ insertAsSibling={true}
365
+ />,
366
+ );
367
+
368
+ await waitFor(() => getIFrameEl(container));
369
+ expect(container.querySelector('span')).not.toBe(null);
370
+ expect(container.querySelector('span')?.style.position).toBe('absolute');
371
+ });
372
+
373
+ it('Should render components with both insertAsSibling and className', async () => {
374
+ const { container } = render(
375
+ <LiveboardEmbed
376
+ liveboardId="test-liveboard"
377
+ insertAsSibling={true}
378
+ className="custom-class"
379
+ />,
380
+ );
381
+
382
+ await waitFor(() => getIFrameEl(container));
383
+ expect(container.querySelector('span')).not.toBe(null);
384
+ expect(getIFrameEl(container).classList.contains('custom-class')).toBe(true);
385
+ });
386
+ });
387
+
388
+ describe('useSpotterAgent', () => {
389
+ it('Should return an object with sendMessage function', () => {
390
+ const TestComponent = () => {
391
+ const spotterAgent = useSpotterAgent({ worksheetId: 'test-worksheet' });
392
+ expect(typeof spotterAgent).toBe('object');
393
+ expect(typeof spotterAgent.sendMessage).toBe('function');
394
+ return <div>Test</div>;
395
+ };
396
+
397
+ render(<TestComponent />);
398
+ });
399
+
400
+ it('Should have proper sendMessage callback structure', () => {
401
+ const TestComponent = () => {
402
+ const { sendMessage } = useSpotterAgent({ worksheetId: 'test-worksheet' });
403
+
404
+ // Test that sendMessage is a function that accepts a string
405
+ expect(typeof sendMessage).toBe('function');
406
+ expect(sendMessage.length).toBe(1); // Should accept one parameter
264
407
 
265
- const response = await conversationService.sendMessage("What are my sales this month?");
266
- expect(response.container).toBe(mockDiv);
267
- }
408
+ return <div>Test</div>;
409
+ };
410
+
411
+ render(<TestComponent />);
268
412
  });
269
413
 
270
- it('Should work as a React component with ref support', () => {
271
- const mockSendMessage = jest.fn().mockResolvedValue({
272
- container: document.createElement('div'),
273
- viz: {}
274
- });
414
+ it('Should return error when service is not initialized', async () => {
415
+ let sendMessageResult: any;
275
416
 
276
417
  const TestComponent = () => {
277
- const conversationRef = React.useRef(null);
418
+ const { sendMessage } = useSpotterAgent({ worksheetId: 'test-worksheet' });
278
419
 
279
- const handleClick = () => {
280
- if (conversationRef.current) {
281
- conversationRef.current.sendMessage = mockSendMessage;
282
-
283
- conversationRef.current.sendMessage("Test message");
284
- }
285
- };
420
+ // Call sendMessage immediately before service has time to initialize
421
+ sendMessageResult = sendMessage('test query');
286
422
 
287
- return (
288
- <>
289
- <SpotterAgentEmbed
290
- ref={conversationRef}
291
- worksheetId="test-worksheet-id"
292
- />
293
- <button data-testid="test-button" onClick={handleClick}>
294
- Send Message
295
- </button>
296
- </>
297
- );
423
+ return <div>Test</div>;
298
424
  };
425
+
426
+ render(<TestComponent />);
299
427
 
300
- const { getByTestId } = render(<TestComponent />);
428
+ const result = await sendMessageResult;
429
+ expect(result).toEqual({
430
+ error: expect.any(Error)
431
+ });
432
+ expect(result.error.message).toBe('SpotterAgent not initialized');
433
+ });
434
+
435
+ it('Should call sendMessage and handle async behavior', async () => {
436
+ let sendMessageFunction: any;
301
437
 
302
- fireEvent.click(getByTestId('test-button'));
438
+ const TestComponent = () => {
439
+ const { sendMessage } = useSpotterAgent({ worksheetId: 'test-worksheet' });
440
+ sendMessageFunction = sendMessage;
441
+ return <div>Test</div>;
442
+ };
443
+
444
+ render(<TestComponent />);
303
445
 
304
- expect(mockSendMessage).toHaveBeenCalledWith("Test message");
446
+ // Test that sendMessage is a function
447
+ expect(typeof sendMessageFunction).toBe('function');
448
+
449
+ // Call sendMessage - should not throw
450
+ expect(() => {
451
+ sendMessageFunction('test query');
452
+ }).not.toThrow();
305
453
  });
306
454
 
307
- it('Should work with the useEmbedRef hook', () => {
308
- const mockSendMessage = jest.fn().mockResolvedValue({
309
- container: document.createElement('div'),
310
- viz: {}
311
- });
455
+ it('Should handle multiple calls to sendMessage', async () => {
456
+ let sendMessageFunction: any;
312
457
 
313
458
  const TestComponent = () => {
314
- const embedRef = useEmbedRef<typeof SpotterAgentEmbed>();
315
-
316
- const handleClick = () => {
317
- if (embedRef.current) {
318
- const service = embedRef.current as unknown as {
319
- sendMessage: typeof mockSendMessage
320
- };
321
-
322
- service.sendMessage = mockSendMessage;
323
-
324
- service.sendMessage("Test with useEmbedRef");
325
- }
326
- };
327
-
328
- return (
329
- <>
330
- <SpotterAgentEmbed
331
- ref={embedRef}
332
- worksheetId="test-worksheet-id"
333
- />
334
- <button data-testid="use-embed-ref-button" onClick={handleClick}>
335
- Send with useEmbedRef
336
- </button>
337
- </>
338
- );
459
+ const { sendMessage } = useSpotterAgent({ worksheetId: 'test-worksheet' });
460
+ sendMessageFunction = sendMessage;
461
+ return <div>Test</div>;
339
462
  };
463
+
464
+ render(<TestComponent />);
340
465
 
341
- const { getByTestId } = render(<TestComponent />);
466
+ // Multiple calls should not throw
467
+ expect(() => {
468
+ sendMessageFunction('query 1');
469
+ sendMessageFunction('query 2');
470
+ sendMessageFunction('query 3');
471
+ }).not.toThrow();
472
+ });
473
+
474
+ it('Should handle config object changes', () => {
475
+ const TestComponent = ({ config }: { config: any }) => {
476
+ const { sendMessage } = useSpotterAgent(config);
477
+ expect(sendMessage).toBeDefined();
478
+ return <div>Test</div>;
479
+ };
480
+
481
+ const config1 = { worksheetId: 'test1' };
482
+ const config2 = { worksheetId: 'test2' };
342
483
 
343
- fireEvent.click(getByTestId('use-embed-ref-button'));
484
+ const { rerender } = render(<TestComponent config={config1} />);
344
485
 
345
- expect(mockSendMessage).toHaveBeenCalledWith("Test with useEmbedRef");
486
+ // Should not throw when config changes
487
+ expect(() => {
488
+ rerender(<TestComponent config={config2} />);
489
+ }).not.toThrow();
346
490
  });
347
491
 
348
- it('Should work with the className prop', async () => {
349
- let capturedInstance: any = null;
350
-
492
+ it('Should handle unmounting without errors', () => {
351
493
  const TestComponent = () => {
352
- const embedRef = useEmbedRef<typeof SpotterAgentEmbed>();
353
-
354
- React.useEffect(() => {
355
- capturedInstance = embedRef.current;
356
-
357
- if (capturedInstance) {
358
- const mockConversationService = {
359
- sendMessage: jest.fn().mockResolvedValue({
360
- data: {
361
- sessionId: 'test-session',
362
- genNo: 1,
363
- stateKey: {
364
- transactionId: 'test-transaction',
365
- generationNumber: 1
366
- }
367
- }
368
- })
369
- };
370
- (capturedInstance as any).conversationService = mockConversationService;
371
- }
372
- }, []);
373
-
374
- return (
375
- <SpotterAgentEmbed
376
- ref={embedRef}
377
- worksheetId="test-worksheet-id"
378
- className="embedClass"
379
- />
380
- );
494
+ const { sendMessage } = useSpotterAgent({ worksheetId: 'test-worksheet' });
495
+ expect(sendMessage).toBeDefined();
496
+ return <div>Test</div>;
381
497
  };
498
+
499
+ const { unmount } = render(<TestComponent />);
382
500
 
383
- render(<TestComponent />);
501
+ // Should not throw when unmounting
502
+ expect(() => {
503
+ unmount();
504
+ }).not.toThrow();
505
+ });
506
+
507
+ it('Should create stable hook structure', () => {
508
+ let hookResult1: any, hookResult2: any;
384
509
 
385
- expect(capturedInstance).not.toBeNull();
510
+ const TestComponent = ({ counter }: { counter: number }) => {
511
+ const result = useSpotterAgent({ worksheetId: 'test-worksheet' });
512
+
513
+ if (counter === 1) {
514
+ hookResult1 = result;
515
+ } else {
516
+ hookResult2 = result;
517
+ }
518
+
519
+ return <div>Test</div>;
520
+ };
521
+
522
+ const { rerender } = render(<TestComponent counter={1} />);
523
+ rerender(<TestComponent counter={2} />);
386
524
 
387
- if (capturedInstance) {
388
- const result = await capturedInstance.sendMessage("test");
389
- expect(result.container.className).toBe("embedClass");
390
- }
525
+ // Both should have same structure
526
+ expect(hookResult1).toEqual({ sendMessage: expect.any(Function) });
527
+ expect(hookResult2).toEqual({ sendMessage: expect.any(Function) });
391
528
  });
529
+
530
+ it('Should handle different worksheet IDs', () => {
531
+ const TestComponent = ({ worksheetId }: { worksheetId: string }) => {
532
+ const { sendMessage } = useSpotterAgent({ worksheetId });
533
+ expect(sendMessage).toBeDefined();
534
+ return <div>Test</div>;
535
+ };
536
+
537
+ const { rerender } = render(<TestComponent worksheetId="worksheet1" />);
538
+
539
+ // Should handle different worksheet IDs
540
+ expect(() => {
541
+ rerender(<TestComponent worksheetId="worksheet2" />);
542
+ rerender(<TestComponent worksheetId="worksheet3" />);
543
+ }).not.toThrow();
544
+ });
545
+
546
+ it('Should handle empty query strings', () => {
547
+ let sendMessageFunction: any;
548
+
549
+ const TestComponent = () => {
550
+ const { sendMessage } = useSpotterAgent({ worksheetId: 'test-worksheet' });
551
+ sendMessageFunction = sendMessage;
552
+ return <div>Test</div>;
553
+ };
554
+
555
+ render(<TestComponent />);
556
+
557
+ // Should handle empty strings
558
+ expect(() => {
559
+ sendMessageFunction('');
560
+ sendMessageFunction(' ');
561
+ }).not.toThrow();
562
+ });
563
+
564
+ it('Should handle complex config objects', () => {
565
+ const complexConfig = {
566
+ worksheetId: 'test-worksheet',
567
+ hiddenActions: [Action.ReportError],
568
+ className: 'test-class',
569
+ searchOptions: {
570
+ searchQuery: 'test query'
571
+ }
572
+ };
573
+
574
+ const TestComponent = () => {
575
+ const { sendMessage } = useSpotterAgent(complexConfig);
576
+ expect(sendMessage).toBeDefined();
577
+ return <div>Test</div>;
578
+ };
579
+
580
+ // Should not throw with complex config
581
+ expect(() => {
582
+ render(<TestComponent />);
583
+ }).not.toThrow();
584
+ });
585
+
586
+ it('Should maintain function identity across re-renders with same config', () => {
587
+ let sendMessage1: any, sendMessage2: any;
588
+
589
+ const TestComponent = ({ forceRender }: { forceRender: number }) => {
590
+ const { sendMessage } = useSpotterAgent({ worksheetId: 'test-worksheet' });
591
+
592
+ if (forceRender === 1) {
593
+ sendMessage1 = sendMessage;
594
+ } else {
595
+ sendMessage2 = sendMessage;
596
+ }
597
+
598
+ return <div>Test</div>;
599
+ };
600
+
601
+ const { rerender } = render(<TestComponent forceRender={1} />);
602
+ rerender(<TestComponent forceRender={2} />);
603
+
604
+ // Functions should exist
605
+ expect(sendMessage1).toBeDefined();
606
+ expect(sendMessage2).toBeDefined();
607
+ expect(typeof sendMessage1).toBe('function');
608
+ expect(typeof sendMessage2).toBe('function');
609
+ });
610
+
611
+ it('Should handle sendMessage calls with null service ref', async () => {
612
+ let capturedSendMessage: any;
613
+
614
+ const TestComponent = () => {
615
+ const { sendMessage } = useSpotterAgent({ worksheetId: 'test-worksheet' });
616
+ capturedSendMessage = sendMessage;
617
+ return <div>Test</div>;
618
+ };
619
+
620
+ const { unmount } = render(<TestComponent />);
621
+
622
+ // Unmount to trigger cleanup
623
+ unmount();
624
+
625
+ // Now call sendMessage after unmount - should return error
626
+ const result = await capturedSendMessage('test query');
627
+ expect(result).toEqual({
628
+ error: expect.any(Error)
629
+ });
630
+ });
631
+
632
+ it('Should test service ref cleanup on config change', () => {
633
+ const TestComponent = ({ worksheetId }: { worksheetId: string }) => {
634
+ const { sendMessage } = useSpotterAgent({ worksheetId });
635
+ expect(sendMessage).toBeDefined();
636
+ return <div>Test</div>;
637
+ };
638
+
639
+ const { rerender } = render(<TestComponent worksheetId="worksheet1" />);
640
+
641
+ // This should trigger the cleanup and create new service
642
+ rerender(<TestComponent worksheetId="worksheet2" />);
643
+
644
+ // Should still work after rerender
645
+ expect(() => {
646
+ rerender(<TestComponent worksheetId="worksheet3" />);
647
+ }).not.toThrow();
648
+ });
649
+
650
+ it('Should test different config variations', () => {
651
+ const configs = [
652
+ { worksheetId: 'test1' },
653
+ { worksheetId: 'test2', hiddenActions: [Action.ReportError] },
654
+ { worksheetId: 'test3', className: 'test-class' },
655
+ { worksheetId: 'test4', searchOptions: { searchQuery: 'test' } }
656
+ ];
657
+
658
+ configs.forEach((config, index) => {
659
+ const TestComponent = () => {
660
+ const { sendMessage } = useSpotterAgent(config);
661
+ expect(sendMessage).toBeDefined();
662
+ return <div>Test {index}</div>;
663
+ };
664
+
665
+ expect(() => {
666
+ const { unmount } = render(<TestComponent />);
667
+ unmount();
668
+ }).not.toThrow();
669
+ });
670
+ });
671
+
672
+ it('Should handle rapid config changes', () => {
673
+ const TestComponent = ({ worksheetId }: { worksheetId: string }) => {
674
+ const { sendMessage } = useSpotterAgent({ worksheetId });
675
+ expect(sendMessage).toBeDefined();
676
+ return <div>Test</div>;
677
+ };
678
+
679
+ const { rerender } = render(<TestComponent worksheetId="worksheet1" />);
680
+
681
+ // Rapid config changes to test cleanup logic
682
+ for (let i = 2; i <= 10; i++) {
683
+ rerender(<TestComponent worksheetId={`worksheet${i}`} />);
684
+ }
685
+
686
+ // Should still work after many changes
687
+ expect(() => {
688
+ rerender(<TestComponent worksheetId="final-worksheet" />);
689
+ }).not.toThrow();
690
+ });
691
+
692
+ it('Should handle sendMessage with different query types', () => {
693
+ let sendMessageFunction: any;
694
+
695
+ const TestComponent = () => {
696
+ const { sendMessage } = useSpotterAgent({ worksheetId: 'test-worksheet' });
697
+ sendMessageFunction = sendMessage;
698
+ return <div>Test</div>;
699
+ };
700
+
701
+ render(<TestComponent />);
702
+
703
+ // Test different query types
704
+ const queries = [
705
+ 'simple query',
706
+ 'query with numbers 123',
707
+ 'query with special chars !@#$%',
708
+ 'very long query that might test different code paths in the system when processing',
709
+ '',
710
+ ' whitespace ',
711
+ 'null',
712
+ 'undefined'
713
+ ];
714
+
715
+ queries.forEach(query => {
716
+ expect(() => {
717
+ sendMessageFunction(query);
718
+ }).not.toThrow();
719
+ });
720
+ });
721
+
722
+ it('Should handle service ref cleanup when it already exists', () => {
723
+ const TestComponent = ({ worksheetId }: { worksheetId: string }) => {
724
+ const { sendMessage } = useSpotterAgent({ worksheetId });
725
+ expect(sendMessage).toBeDefined();
726
+ return <div>Test</div>;
727
+ };
728
+
729
+ const { rerender } = render(<TestComponent worksheetId="worksheet1" />);
730
+
731
+ // This should trigger the "if (serviceRef.current)" branch in useEffect
732
+ rerender(<TestComponent worksheetId="worksheet1" />);
733
+ rerender(<TestComponent worksheetId="worksheet2" />);
734
+ rerender(<TestComponent worksheetId="worksheet3" />);
735
+
736
+ // Multiple rapid changes should exercise the cleanup logic
737
+ for (let i = 0; i < 5; i++) {
738
+ rerender(<TestComponent worksheetId={`worksheet${i}`} />);
739
+ }
740
+ });
741
+
742
+ it('Should test various config combinations to hit all branches', () => {
743
+ const testConfigs = [
744
+ { worksheetId: 'test1' },
745
+ { worksheetId: 'test2', className: 'custom-class' },
746
+ { worksheetId: 'test3', hiddenActions: [Action.ReportError] },
747
+ { worksheetId: 'test4', searchOptions: { searchQuery: 'test' } },
748
+ { worksheetId: 'test5', insertAsSibling: true },
749
+ { worksheetId: 'test6', insertAsSibling: false },
750
+ ];
751
+
752
+ testConfigs.forEach((config, index) => {
753
+ const TestComponent = () => {
754
+ const { sendMessage } = useSpotterAgent(config);
755
+ expect(sendMessage).toBeDefined();
756
+ return <div>Test {index}</div>;
757
+ };
758
+
759
+ const { unmount } = render(<TestComponent />);
760
+ unmount();
761
+ });
762
+ });
392
763
  });
393
764
 
394
- describe('PreRenderedLiveboardEmbed', () => {
395
- it('should preRender the liveboard ', async () => {
396
- const preRenderId = 'tsEmbed-pre-render-wrapper-test';
765
+ describe('Component Props and Functions', () => {
766
+ it('Should have PreRenderedLiveboardEmbed component', () => {
767
+ expect(PreRenderedLiveboardEmbed).toBeDefined();
768
+ expect(typeof PreRenderedLiveboardEmbed).toBe('object');
769
+ });
397
770
 
398
- const { container } = render(
399
- <PreRenderedLiveboardEmbed
400
- className="embedClass"
401
- preRenderId="test"
402
- liveboardId="libId"
403
- />,
404
- );
771
+ it('Should have useInit hook', () => {
772
+ expect(typeof useInit).toBe('function');
773
+ });
405
774
 
406
- await waitFor(() => getIFrameEl());
407
- const preRenderWrapper = document.body.querySelector(`#${preRenderId}`) as HTMLDivElement;
775
+ it('Should test basic component factory patterns', () => {
776
+ // Test that components can be created without errors
777
+ expect(() => {
778
+ const TestComponent = () => <div>Test</div>;
779
+ render(<TestComponent />);
780
+ }).not.toThrow();
781
+ });
782
+ });
408
783
 
409
- expect(preRenderWrapper).toBeInstanceOf(HTMLDivElement);
410
- expect((preRenderWrapper as HTMLDivElement).childElementCount).toBe(1);
784
+ describe('Hook Coverage', () => {
785
+ it('Should have useInit function available', () => {
786
+ expect(typeof useInit).toBe('function');
787
+ });
411
788
 
412
- const preRenderChildId = 'tsEmbed-pre-render-child-test';
413
- const preRenderChild = document.body.querySelector(`#${preRenderChildId}`);
414
- expect(preRenderWrapper.children[0]).toBe(preRenderChild);
789
+ it('Should test useInit hook basic functionality', () => {
790
+ const TestComponent = () => {
791
+ const authEE = useInit({
792
+ thoughtSpotHost: 'localhost',
793
+ authType: AuthType.None
794
+ });
795
+ expect(authEE).toBeDefined();
796
+ expect(authEE.current).toBeDefined();
797
+ return <div>Test</div>;
798
+ };
415
799
 
416
- (window as any).ResizeObserver = jest.fn().mockImplementation(() => ({
417
- disconnect: jest.fn(),
418
- observe: jest.fn(),
419
- unobserve: jest.fn(),
420
- }));
421
- const { container: libContainer } = render(
422
- <LiveboardEmbed
423
- className="embedClass"
424
- preRenderId="test"
425
- liveboardId="libId"
426
- />,
427
- );
800
+ render(<TestComponent />);
801
+ });
428
802
 
429
- expect(preRenderWrapper.style.opacity).toBe('');
430
- expect(preRenderWrapper.style.pointerEvents).toBe('');
431
- expect(preRenderWrapper.style.zIndex).toBe('');
803
+ it('Should handle useInit with different config changes', () => {
804
+ const TestComponent = ({ host }: { host: string }) => {
805
+ const authEE = useInit({
806
+ thoughtSpotHost: host,
807
+ authType: AuthType.None
808
+ });
809
+ expect(authEE).toBeDefined();
810
+ return <div>Test</div>;
811
+ };
812
+
813
+ const { rerender } = render(<TestComponent host="localhost" />);
814
+
815
+ // Change config to test useDeepCompareEffect
816
+ rerender(<TestComponent host="localhost2" />);
817
+ rerender(<TestComponent host="localhost3" />);
818
+ });
819
+
820
+ it('Should test useInit with complex config objects', () => {
821
+ const TestComponent = () => {
822
+ const authEE = useInit({
823
+ thoughtSpotHost: 'localhost',
824
+ authType: AuthType.None,
825
+ suppressNoCookieAccessAlert: true,
826
+ suppressErrorAlerts: true
827
+ });
828
+ expect(authEE).toBeDefined();
829
+ return <div>Test</div>;
830
+ };
831
+
832
+ render(<TestComponent />);
432
833
  });
433
834
  });
434
835
  });