@thoughtspot/visual-embed-sdk 1.39.2 → 1.40.0

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 (173) 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 +75 -15
  5. package/cjs/src/embed/app.d.ts.map +1 -1
  6. package/cjs/src/embed/app.js +69 -9
  7. package/cjs/src/embed/app.js.map +1 -1
  8. package/cjs/src/embed/app.spec.js +378 -15
  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 +56 -17
  23. package/cjs/src/embed/liveboard.d.ts.map +1 -1
  24. package/cjs/src/embed/liveboard.js +48 -4
  25. package/cjs/src/embed/liveboard.js.map +1 -1
  26. package/cjs/src/embed/liveboard.spec.js +218 -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 +331 -13
  47. package/cjs/src/types.d.ts.map +1 -1
  48. package/cjs/src/types.js +296 -8
  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/processTrigger.js +2 -1
  55. package/cjs/src/utils/processTrigger.js.map +1 -1
  56. package/cjs/src/utils.d.ts +6 -0
  57. package/cjs/src/utils.d.ts.map +1 -1
  58. package/cjs/src/utils.js +23 -3
  59. package/cjs/src/utils.js.map +1 -1
  60. package/cjs/src/utils.spec.js +237 -1
  61. package/cjs/src/utils.spec.js.map +1 -1
  62. package/dist/{index-CmEQfuE3.js → index-CAEHQGLc.js} +1 -1
  63. package/dist/src/embed/app.d.ts +75 -15
  64. package/dist/src/embed/app.d.ts.map +1 -1
  65. package/dist/src/embed/bodyless-conversation.d.ts +23 -7
  66. package/dist/src/embed/bodyless-conversation.d.ts.map +1 -1
  67. package/dist/src/embed/embedConfig.d.ts +9 -7
  68. package/dist/src/embed/embedConfig.d.ts.map +1 -1
  69. package/dist/src/embed/liveboard.d.ts +56 -17
  70. package/dist/src/embed/liveboard.d.ts.map +1 -1
  71. package/dist/src/errors.d.ts +1 -0
  72. package/dist/src/errors.d.ts.map +1 -1
  73. package/dist/src/index.d.ts +2 -2
  74. package/dist/src/index.d.ts.map +1 -1
  75. package/dist/src/react/all-types-export.d.ts +1 -1
  76. package/dist/src/react/all-types-export.d.ts.map +1 -1
  77. package/dist/src/react/index.d.ts +73 -20
  78. package/dist/src/react/index.d.ts.map +1 -1
  79. package/dist/src/types.d.ts +331 -13
  80. package/dist/src/types.d.ts.map +1 -1
  81. package/dist/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
  82. package/dist/src/utils.d.ts +6 -0
  83. package/dist/src/utils.d.ts.map +1 -1
  84. package/dist/tsembed-react.es.js +568 -87
  85. package/dist/tsembed-react.js +568 -85
  86. package/dist/tsembed.es.js +486 -40
  87. package/dist/tsembed.js +484 -38
  88. package/dist/visual-embed-sdk-react-full.d.ts +545 -79
  89. package/dist/visual-embed-sdk-react.d.ts +545 -79
  90. package/dist/visual-embed-sdk.d.ts +473 -60
  91. package/lib/package.json +1 -1
  92. package/lib/src/config.spec.js +9 -0
  93. package/lib/src/config.spec.js.map +1 -1
  94. package/lib/src/embed/app.d.ts +75 -15
  95. package/lib/src/embed/app.d.ts.map +1 -1
  96. package/lib/src/embed/app.js +69 -9
  97. package/lib/src/embed/app.js.map +1 -1
  98. package/lib/src/embed/app.spec.js +380 -17
  99. package/lib/src/embed/app.spec.js.map +1 -1
  100. package/lib/src/embed/bodyless-conversation.d.ts +23 -7
  101. package/lib/src/embed/bodyless-conversation.d.ts.map +1 -1
  102. package/lib/src/embed/bodyless-conversation.js +30 -5
  103. package/lib/src/embed/bodyless-conversation.js.map +1 -1
  104. package/lib/src/embed/bodyless-conversation.spec.js +9 -191
  105. package/lib/src/embed/bodyless-conversation.spec.js.map +1 -1
  106. package/lib/src/embed/conversation.spec.js +30 -2
  107. package/lib/src/embed/conversation.spec.js.map +1 -1
  108. package/lib/src/embed/embedConfig.d.ts +9 -7
  109. package/lib/src/embed/embedConfig.d.ts.map +1 -1
  110. package/lib/src/embed/embedConfig.js +9 -7
  111. package/lib/src/embed/embedConfig.js.map +1 -1
  112. package/lib/src/embed/liveboard.d.ts +56 -17
  113. package/lib/src/embed/liveboard.d.ts.map +1 -1
  114. package/lib/src/embed/liveboard.js +49 -5
  115. package/lib/src/embed/liveboard.js.map +1 -1
  116. package/lib/src/embed/liveboard.spec.js +218 -11
  117. package/lib/src/embed/liveboard.spec.js.map +1 -1
  118. package/lib/src/errors.d.ts +1 -0
  119. package/lib/src/errors.d.ts.map +1 -1
  120. package/lib/src/errors.js +1 -0
  121. package/lib/src/errors.js.map +1 -1
  122. package/lib/src/index.d.ts +2 -2
  123. package/lib/src/index.d.ts.map +1 -1
  124. package/lib/src/index.js +2 -2
  125. package/lib/src/index.js.map +1 -1
  126. package/lib/src/react/all-types-export.d.ts +1 -1
  127. package/lib/src/react/all-types-export.d.ts.map +1 -1
  128. package/lib/src/react/all-types-export.js +1 -1
  129. package/lib/src/react/all-types-export.js.map +1 -1
  130. package/lib/src/react/index.d.ts +73 -20
  131. package/lib/src/react/index.d.ts.map +1 -1
  132. package/lib/src/react/index.js +79 -43
  133. package/lib/src/react/index.js.map +1 -1
  134. package/lib/src/react/index.spec.js +441 -103
  135. package/lib/src/react/index.spec.js.map +1 -1
  136. package/lib/src/types.d.ts +331 -13
  137. package/lib/src/types.d.ts.map +1 -1
  138. package/lib/src/types.js +296 -8
  139. package/lib/src/types.js.map +1 -1
  140. package/lib/src/utils/global-styles.js +1 -1
  141. package/lib/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
  142. package/lib/src/utils/graphql/nlsService/conversation-service.js +7 -1
  143. package/lib/src/utils/graphql/nlsService/conversation-service.js.map +1 -1
  144. package/lib/src/utils/processTrigger.js +2 -1
  145. package/lib/src/utils/processTrigger.js.map +1 -1
  146. package/lib/src/utils.d.ts +6 -0
  147. package/lib/src/utils.d.ts.map +1 -1
  148. package/lib/src/utils.js +21 -2
  149. package/lib/src/utils.js.map +1 -1
  150. package/lib/src/utils.spec.js +238 -2
  151. package/lib/src/utils.spec.js.map +1 -1
  152. package/lib/src/visual-embed-sdk.d.ts +474 -61
  153. package/package.json +1 -1
  154. package/src/config.spec.ts +11 -0
  155. package/src/embed/app.spec.ts +486 -30
  156. package/src/embed/app.ts +133 -27
  157. package/src/embed/bodyless-conversation.spec.ts +9 -203
  158. package/src/embed/bodyless-conversation.ts +34 -10
  159. package/src/embed/conversation.spec.ts +40 -2
  160. package/src/embed/embedConfig.ts +10 -8
  161. package/src/embed/liveboard.spec.ts +259 -5
  162. package/src/embed/liveboard.ts +98 -27
  163. package/src/errors.ts +1 -0
  164. package/src/index.ts +2 -0
  165. package/src/react/all-types-export.ts +2 -1
  166. package/src/react/index.spec.tsx +558 -157
  167. package/src/react/index.tsx +117 -51
  168. package/src/types.ts +368 -50
  169. package/src/utils/global-styles.ts +1 -1
  170. package/src/utils/graphql/nlsService/conversation-service.ts +7 -1
  171. package/src/utils/processTrigger.ts +1 -1
  172. package/src/utils.spec.ts +279 -2
  173. package/src/utils.ts +28 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thoughtspot/visual-embed-sdk",
3
- "version": "1.39.2",
3
+ "version": "1.40.0",
4
4
  "description": "ThoughtSpot Embed SDK",
5
5
  "module": "lib/src/index.js",
6
6
  "main": "dist/tsembed.js",
@@ -21,6 +21,17 @@ describe('getThoughtSpotHost', () => {
21
21
  expect(testFn).toThrow(Error);
22
22
  });
23
23
 
24
+ test('invalid URL format', () => {
25
+ const testFn = () => {
26
+ getThoughtSpotHost({
27
+ ...embedConfig,
28
+ thoughtSpotHost: '',
29
+ });
30
+ };
31
+
32
+ expect(testFn).toThrow('Error parsing ThoughtSpot host. Please provide a valid URL.');
33
+ });
34
+
24
35
  test('IP address/hostname only', () => {
25
36
  expect(
26
37
  getThoughtSpotHost({
@@ -6,11 +6,10 @@ import {
6
6
  HomePageSearchBarMode,
7
7
  PrimaryNavbarVersion,
8
8
  HomePage,
9
+ ListPage,
9
10
  } from './app';
10
11
  import { init } from '../index';
11
- import {
12
- Action, AuthType, EmbedEvent, HostEvent, RuntimeFilterOp,
13
- } from '../types';
12
+ import { Action, AuthType, EmbedEvent, HostEvent, RuntimeFilterOp } from '../types';
14
13
  import {
15
14
  executeAfterWait,
16
15
  getDocumentBody,
@@ -45,12 +44,13 @@ beforeAll(() => {
45
44
  authType: AuthType.None,
46
45
  });
47
46
  jest.spyOn(auth, 'postLoginService').mockImplementation(() => Promise.resolve({}));
48
- (window as any).ResizeObserver = window.ResizeObserver
49
- || jest.fn().mockImplementation(() => ({
50
- disconnect: jest.fn(),
51
- observe: jest.fn(),
52
- unobserve: jest.fn(),
53
- }));
47
+ (window as any).ResizeObserver =
48
+ window.ResizeObserver ||
49
+ jest.fn().mockImplementation(() => ({
50
+ disconnect: jest.fn(),
51
+ observe: jest.fn(),
52
+ unobserve: jest.fn(),
53
+ }));
54
54
  });
55
55
 
56
56
  const cleanUp = () => {
@@ -131,7 +131,7 @@ describe('App embed tests', () => {
131
131
  });
132
132
 
133
133
  describe('should render the correct routes for pages', () => {
134
- /* eslint-disable no-loop-func */
134
+
135
135
  const pageRouteMap = {
136
136
  [Page.Search]: 'answer',
137
137
  [Page.Answers]: 'answers',
@@ -148,7 +148,7 @@ describe('App embed tests', () => {
148
148
  const pageId = pageIds[i];
149
149
 
150
150
  test(`${pageId}`, async () => {
151
- const route = pageRouteMap[pageId];
151
+ const route = pageRouteMap[pageId as keyof typeof pageRouteMap];
152
152
  const appEmbed = new AppEmbed(getRootEl(), {
153
153
  ...defaultViewConfig,
154
154
  pageId: pageId as Page,
@@ -181,7 +181,7 @@ describe('App embed tests', () => {
181
181
  const pageIdsForModularHome = pageIdsForModularHomes[i];
182
182
 
183
183
  test(`${pageIdsForModularHome}`, async () => {
184
- const route = pageRouteMap[pageIdsForModularHome];
184
+ const route = pageRouteMapForModularHome[pageIdsForModularHome as keyof typeof pageRouteMapForModularHome];
185
185
  const appEmbed = new AppEmbed(getRootEl(), {
186
186
  ...defaultViewConfig,
187
187
  modularHomeExperience: true,
@@ -316,6 +316,20 @@ describe('App embed tests', () => {
316
316
  });
317
317
  });
318
318
 
319
+ test('should set coverAndFilterOptionInPDF to false in url', async () => {
320
+ const appEmbed = new AppEmbed(getRootEl(), {
321
+ ...defaultViewConfig,
322
+ coverAndFilterOptionInPDF: false,
323
+ } as AppViewConfig);
324
+ appEmbed.render();
325
+ await executeAfterWait(() => {
326
+ expectUrlMatchesWithParams(
327
+ getIFrameSrc(),
328
+ `http://${thoughtSpotHost}/?embedApp=true&profileAndHelpInNavBarHidden=false&arePdfCoverFilterPageCheckboxesEnabled=false${defaultParamsPost}#/home`,
329
+ );
330
+ });
331
+ });
332
+
319
333
  test('should set isLiveboardStylingAndGroupingEnabled to true in url', async () => {
320
334
  const appEmbed = new AppEmbed(getRootEl(), {
321
335
  ...defaultViewConfig,
@@ -611,6 +625,59 @@ describe('App embed tests', () => {
611
625
  });
612
626
  });
613
627
 
628
+ test('Should add listpageVersion=v3 when listPageVersion is ListWithUXChanges to the iframe src', async () => {
629
+ const appEmbed = new AppEmbed(getRootEl(), {
630
+ ...defaultViewConfig,
631
+ discoveryExperience: {
632
+ listPageVersion: ListPage.ListWithUXChanges,
633
+ },
634
+ } as AppViewConfig);
635
+
636
+ appEmbed.render();
637
+ await executeAfterWait(() => {
638
+ expectUrlMatchesWithParams(
639
+ getIFrameSrc(),
640
+ `http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=true&profileAndHelpInNavBarHidden=false&modularHomeExperience=false&listpageVersion=v3${defaultParams}${defaultParamsPost}#/home`,
641
+ );
642
+ });
643
+ });
644
+
645
+ test('Should not add listpageVersion when listPageVersion is List (v2) to the iframe src', async () => {
646
+ const appEmbed = new AppEmbed(getRootEl(), {
647
+ ...defaultViewConfig,
648
+ discoveryExperience: {
649
+ listPageVersion: ListPage.List,
650
+ },
651
+ } as AppViewConfig);
652
+
653
+ appEmbed.render();
654
+ await executeAfterWait(() => {
655
+ expectUrlMatchesWithParams(
656
+ getIFrameSrc(),
657
+ `http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=true&profileAndHelpInNavBarHidden=false&modularHomeExperience=false${defaultParams}${defaultParamsPost}#/home`,
658
+ );
659
+ });
660
+ });
661
+
662
+ test('Should add listpageVersion=v3 combined with other discoveryExperience options to the iframe src', async () => {
663
+ const appEmbed = new AppEmbed(getRootEl(), {
664
+ ...defaultViewConfig,
665
+ discoveryExperience: {
666
+ primaryNavbarVersion: PrimaryNavbarVersion.Sliding,
667
+ homePage: HomePage.Modular,
668
+ listPageVersion: ListPage.ListWithUXChanges,
669
+ },
670
+ } as AppViewConfig);
671
+
672
+ appEmbed.render();
673
+ await executeAfterWait(() => {
674
+ expectUrlMatchesWithParams(
675
+ getIFrameSrc(),
676
+ `http://${thoughtSpotHost}/?embedApp=true&primaryNavHidden=true&profileAndHelpInNavBarHidden=false&modularHomeExperience=true&navigationVersion=v3&listpageVersion=v3${defaultParams}${defaultParamsPost}#/home`,
677
+ );
678
+ });
679
+ });
680
+
614
681
  test('Should add enablePendoHelp flag to the iframe src conditional on navbar', async () => {
615
682
  const appEmbed = new AppEmbed(getRootEl(), {
616
683
  ...defaultViewConfig,
@@ -686,8 +753,9 @@ describe('App embed tests', () => {
686
753
  test('Should add dataPanelCustomGroupsAccordionInitialState flag to the iframe src', async () => {
687
754
  const appEmbed = new AppEmbed(getRootEl(), {
688
755
  ...defaultViewConfig,
689
- // eslint-disable-next-line max-len
690
- dataPanelCustomGroupsAccordionInitialState: DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST,
756
+
757
+ dataPanelCustomGroupsAccordionInitialState:
758
+ DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST,
691
759
  } as AppViewConfig);
692
760
 
693
761
  appEmbed.render();
@@ -700,34 +768,41 @@ describe('App embed tests', () => {
700
768
  });
701
769
 
702
770
  test('should register event handlers to adjust iframe height', async () => {
703
- const onSpy = jest.spyOn(AppEmbed.prototype, 'on')
704
- .mockImplementation((event, callback) => {
705
- if (event === EmbedEvent.RouteChange) {
706
- callback({ data: { currentPath: '/answers' } }, jest.fn());
707
- }
708
- if (event === EmbedEvent.EmbedHeight) {
709
- callback({ data: '100%' });
710
- }
711
- if (event === EmbedEvent.EmbedIframeCenter) {
712
- callback({}, jest.fn());
713
- }
714
- return null;
715
- });
771
+ let embedHeightCallback: any = () => { };
772
+ const onSpy = jest.spyOn(AppEmbed.prototype, 'on').mockImplementation((event, callback) => {
773
+ if (event === EmbedEvent.RouteChange) {
774
+ callback({ data: { currentPath: '/answers' } }, jest.fn());
775
+ }
776
+ if (event === EmbedEvent.EmbedHeight) {
777
+ embedHeightCallback = callback;
778
+ }
779
+ if (event === EmbedEvent.EmbedIframeCenter) {
780
+ callback({}, jest.fn());
781
+ }
782
+ return null;
783
+ });
716
784
  jest.spyOn(TsEmbed.prototype as any, 'getIframeCenter').mockReturnValue({});
717
785
  jest.spyOn(TsEmbed.prototype as any, 'setIFrameHeight').mockReturnValue({});
718
786
  const appEmbed = new AppEmbed(getRootEl(), {
719
787
  ...defaultViewConfig,
720
788
  fullHeight: true,
789
+ lazyLoadingForFullHeight: true,
721
790
  } as AppViewConfig);
722
791
 
723
- appEmbed.render();
792
+ // Set the iframe before render
793
+ (appEmbed as any).iFrame = document.createElement('iframe');
724
794
 
795
+ // Wait for render to complete
796
+ await appEmbed.render();
797
+ embedHeightCallback({ data: '100%' });
798
+
799
+ // Verify event handlers were registered
725
800
  await executeAfterWait(() => {
726
801
  expect(onSpy).toHaveBeenCalledWith(EmbedEvent.EmbedHeight, expect.anything());
727
802
  expect(onSpy).toHaveBeenCalledWith(EmbedEvent.RouteChange, expect.anything());
728
803
  expect(onSpy).toHaveBeenCalledWith(EmbedEvent.EmbedIframeCenter, expect.anything());
729
- });
730
- jest.clearAllMocks();
804
+ expect(onSpy).toHaveBeenCalledWith(EmbedEvent.RequestVisibleEmbedCoordinates, expect.anything());
805
+ }, 100);
731
806
  });
732
807
 
733
808
  describe('Navigate to Page API', () => {
@@ -815,4 +890,385 @@ describe('App embed tests', () => {
815
890
  );
816
891
  });
817
892
  });
893
+
894
+ describe('LazyLoadingForFullHeight functionality', () => {
895
+ let mockIFrame: HTMLIFrameElement;
896
+
897
+ beforeEach(() => {
898
+ mockIFrame = document.createElement('iframe');
899
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
900
+ top: 100,
901
+ left: 150,
902
+ bottom: 600,
903
+ right: 800,
904
+ width: 650,
905
+ height: 500,
906
+ });
907
+ jest.spyOn(document, 'createElement').mockImplementation((tagName) => {
908
+ if (tagName === 'iframe') {
909
+ return mockIFrame;
910
+ }
911
+ return document.createElement(tagName);
912
+ });
913
+ });
914
+
915
+ afterEach(() => {
916
+ jest.restoreAllMocks();
917
+ });
918
+
919
+ test('should set lazyLoadingMargin parameter when provided', async () => {
920
+ const appEmbed = new AppEmbed(getRootEl(), {
921
+ ...defaultViewConfig,
922
+ fullHeight: true,
923
+ lazyLoadingForFullHeight: true,
924
+ lazyLoadingMargin: '100px 0px',
925
+ } as AppViewConfig);
926
+
927
+ await appEmbed.render();
928
+
929
+ await executeAfterWait(() => {
930
+ const iframeSrc = getIFrameSrc();
931
+ expect(iframeSrc).toContain('isLazyLoadingForEmbedEnabled=true');
932
+ expect(iframeSrc).toContain('isFullHeightPinboard=true');
933
+ expect(iframeSrc).toContain('rootMarginForLazyLoad=100px%200px');
934
+ }, 100);
935
+ });
936
+
937
+ test('should set isLazyLoadingForEmbedEnabled=true when both fullHeight and lazyLoadingForFullHeight are enabled', async () => {
938
+ // Mock the iframe element first
939
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
940
+ top: 100,
941
+ left: 150,
942
+ bottom: 600,
943
+ right: 800,
944
+ width: 650,
945
+ height: 500,
946
+ });
947
+ Object.defineProperty(mockIFrame, 'scrollHeight', { value: 500 });
948
+
949
+ // Mock the event handlers
950
+ const onSpy = jest.spyOn(AppEmbed.prototype, 'on').mockImplementation((event, callback) => {
951
+ return null;
952
+ });
953
+ jest.spyOn(TsEmbed.prototype as any, 'getIframeCenter').mockReturnValue({});
954
+ jest.spyOn(TsEmbed.prototype as any, 'setIFrameHeight').mockReturnValue({});
955
+
956
+ // Create the AppEmbed instance
957
+ const appEmbed = new AppEmbed(getRootEl(), {
958
+ ...defaultViewConfig,
959
+ fullHeight: true,
960
+ lazyLoadingForFullHeight: true,
961
+ } as AppViewConfig);
962
+
963
+ // Set the iframe before render
964
+ (appEmbed as any).iFrame = mockIFrame;
965
+
966
+ // Add the iframe to the DOM
967
+ const rootEl = getRootEl();
968
+ rootEl.appendChild(mockIFrame);
969
+
970
+ // Wait for render to complete
971
+ await appEmbed.render();
972
+
973
+ // Wait for iframe initialization and URL parameters to be set
974
+ await executeAfterWait(() => {
975
+ const iframeSrc = appEmbed.getIFrameSrc();
976
+ expect(iframeSrc).toContain('isLazyLoadingForEmbedEnabled=true');
977
+ expect(iframeSrc).toContain('isFullHeightPinboard=true');
978
+ }, 100);
979
+ });
980
+
981
+ test('should not set lazyLoadingForEmbed when lazyLoadingForFullHeight is enabled but fullHeight is false', async () => {
982
+ const appEmbed = new AppEmbed(getRootEl(), {
983
+ ...defaultViewConfig,
984
+ fullHeight: false,
985
+ lazyLoadingForFullHeight: true,
986
+ } as AppViewConfig);
987
+
988
+ // Wait for render to complete
989
+ await appEmbed.render();
990
+
991
+ // Wait for iframe initialization and URL parameters to be set
992
+ await executeAfterWait(() => {
993
+ const iframeSrc = getIFrameSrc();
994
+ expect(iframeSrc).not.toContain('isLazyLoadingForEmbedEnabled=true');
995
+ expect(iframeSrc).not.toContain('isFullHeightPinboard=true');
996
+ }, 100); // 100ms wait time to ensure iframe src is set
997
+ });
998
+
999
+ test('should not set isLazyLoadingForEmbedEnabled when fullHeight is true but lazyLoadingForFullHeight is false', async () => {
1000
+ const appEmbed = new AppEmbed(getRootEl(), {
1001
+ ...defaultViewConfig,
1002
+ fullHeight: true,
1003
+ lazyLoadingForFullHeight: false,
1004
+ } as AppViewConfig);
1005
+
1006
+ // Wait for render to complete
1007
+ await appEmbed.render();
1008
+
1009
+ // Wait for iframe initialization and URL parameters to be set
1010
+ await executeAfterWait(() => {
1011
+ const iframeSrc = getIFrameSrc();
1012
+ expect(iframeSrc).not.toContain('isLazyLoadingForEmbedEnabled=true');
1013
+ expect(iframeSrc).toContain('isFullHeightPinboard=true');
1014
+ }, 100); // 100ms wait time to ensure iframe src is set
1015
+ });
1016
+
1017
+ test('should register RequestFullHeightLazyLoadData event handler when fullHeight is enabled', async () => {
1018
+ const onSpy = jest.spyOn(AppEmbed.prototype, 'on');
1019
+
1020
+ const appEmbed = new AppEmbed(getRootEl(), {
1021
+ ...defaultViewConfig,
1022
+ fullHeight: true,
1023
+ } as AppViewConfig);
1024
+
1025
+ await appEmbed.render();
1026
+
1027
+ expect(onSpy).toHaveBeenCalledWith(EmbedEvent.RequestVisibleEmbedCoordinates, expect.any(Function));
1028
+
1029
+ onSpy.mockRestore();
1030
+ });
1031
+
1032
+ test('should send correct visible data when RequestFullHeightLazyLoadData is triggered', async () => {
1033
+ const appEmbed = new AppEmbed(getRootEl(), {
1034
+ ...defaultViewConfig,
1035
+ fullHeight: true,
1036
+ lazyLoadingForFullHeight: true,
1037
+ } as AppViewConfig);
1038
+
1039
+ const mockTrigger = jest.spyOn(appEmbed, 'trigger');
1040
+
1041
+ await appEmbed.render();
1042
+
1043
+ // Trigger the lazy load data calculation
1044
+ (appEmbed as any).sendFullHeightLazyLoadData();
1045
+
1046
+ expect(mockTrigger).toHaveBeenCalledWith(HostEvent.VisibleEmbedCoordinates, {
1047
+ top: 0,
1048
+ height: 500,
1049
+ left: 0,
1050
+ width: 650,
1051
+ });
1052
+ });
1053
+
1054
+ test('should calculate correct visible data for partially visible full height element', async () => {
1055
+ // Mock iframe partially clipped from top and left
1056
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
1057
+ top: -50,
1058
+ left: -30,
1059
+ bottom: 700,
1060
+ right: 1024,
1061
+ width: 1054,
1062
+ height: 750,
1063
+ });
1064
+
1065
+ const appEmbed = new AppEmbed(getRootEl(), {
1066
+ ...defaultViewConfig,
1067
+ fullHeight: true,
1068
+ lazyLoadingForFullHeight: true,
1069
+ } as AppViewConfig);
1070
+
1071
+ const mockTrigger = jest.spyOn(appEmbed, 'trigger');
1072
+
1073
+ await appEmbed.render();
1074
+
1075
+ // Trigger the lazy load data calculation
1076
+ (appEmbed as any).sendFullHeightLazyLoadData();
1077
+
1078
+ expect(mockTrigger).toHaveBeenCalledWith(HostEvent.VisibleEmbedCoordinates, {
1079
+ top: 50, // 50px clipped from top
1080
+ height: 700, // visible height (from 0 to 700)
1081
+ left: 30, // 30px clipped from left
1082
+ width: 1024, // visible width (from 0 to 1024)
1083
+ });
1084
+ });
1085
+
1086
+ test('should add window event listeners for resize and scroll when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
1087
+ const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
1088
+
1089
+ const appEmbed = new AppEmbed(getRootEl(), {
1090
+ ...defaultViewConfig,
1091
+ fullHeight: true,
1092
+ lazyLoadingForFullHeight: true,
1093
+ } as AppViewConfig);
1094
+
1095
+ await appEmbed.render();
1096
+
1097
+ // Wait for the post-render events to be registered
1098
+ await executeAfterWait(() => {
1099
+ expect(addEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function));
1100
+ expect(addEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function), true);
1101
+ }, 100);
1102
+
1103
+ addEventListenerSpy.mockRestore();
1104
+ });
1105
+
1106
+ test('should remove window event listeners on destroy when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
1107
+ const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
1108
+
1109
+ const appEmbed = new AppEmbed(getRootEl(), {
1110
+ ...defaultViewConfig,
1111
+ fullHeight: true,
1112
+ lazyLoadingForFullHeight: true,
1113
+ } as AppViewConfig);
1114
+
1115
+ await appEmbed.render();
1116
+ appEmbed.destroy();
1117
+
1118
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function));
1119
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
1120
+
1121
+ removeEventListenerSpy.mockRestore();
1122
+ });
1123
+
1124
+ test('should handle RequestVisibleEmbedCoordinates event and respond with correct data', async () => {
1125
+ // Mock the iframe element
1126
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
1127
+ top: 100,
1128
+ left: 150,
1129
+ bottom: 600,
1130
+ right: 800,
1131
+ width: 650,
1132
+ height: 500,
1133
+ });
1134
+ Object.defineProperty(mockIFrame, 'scrollHeight', { value: 500 });
1135
+
1136
+ const appEmbed = new AppEmbed(getRootEl(), {
1137
+ ...defaultViewConfig,
1138
+ fullHeight: true,
1139
+ lazyLoadingForFullHeight: true,
1140
+ } as AppViewConfig);
1141
+
1142
+ // Set the iframe before render
1143
+ (appEmbed as any).iFrame = mockIFrame;
1144
+
1145
+ await appEmbed.render();
1146
+
1147
+ // Create a mock responder function
1148
+ const mockResponder = jest.fn();
1149
+
1150
+ // Trigger the handler directly
1151
+ (appEmbed as any).requestVisibleEmbedCoordinatesHandler({}, mockResponder);
1152
+
1153
+ // Verify the responder was called with the correct data
1154
+ expect(mockResponder).toHaveBeenCalledWith({
1155
+ type: EmbedEvent.RequestVisibleEmbedCoordinates,
1156
+ data: {
1157
+ top: 0,
1158
+ height: 500,
1159
+ left: 0,
1160
+ width: 650,
1161
+ },
1162
+ });
1163
+ });
1164
+ });
1165
+
1166
+ describe('IFrame height management', () => {
1167
+ let mockIFrame: HTMLIFrameElement;
1168
+
1169
+ beforeEach(() => {
1170
+ mockIFrame = document.createElement('iframe');
1171
+ mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
1172
+ top: 100,
1173
+ left: 150,
1174
+ bottom: 600,
1175
+ right: 800,
1176
+ width: 650,
1177
+ height: 500,
1178
+ });
1179
+ Object.defineProperty(mockIFrame, 'scrollHeight', { value: 500 });
1180
+ });
1181
+
1182
+ test('should not call setIFrameHeight if currentPath starts with "/embed/viz/"', () => {
1183
+ const appEmbed = new AppEmbed(getRootEl(), {
1184
+ ...defaultViewConfig,
1185
+ fullHeight: true,
1186
+ } as AppViewConfig) as any;
1187
+ const spySetIFrameHeight = jest.spyOn(appEmbed, 'setIFrameHeight');
1188
+
1189
+ appEmbed.render();
1190
+ appEmbed.setIframeHeightForNonEmbedLiveboard({
1191
+ data: { currentPath: '/embed/viz/' },
1192
+ type: 'Route',
1193
+ });
1194
+
1195
+ expect(spySetIFrameHeight).not.toHaveBeenCalled();
1196
+ });
1197
+
1198
+ test('should not call setIFrameHeight if currentPath starts with "/embed/insights/viz/"', () => {
1199
+ const appEmbed = new AppEmbed(getRootEl(), {
1200
+ ...defaultViewConfig,
1201
+ fullHeight: true,
1202
+ } as AppViewConfig) as any;
1203
+ const spySetIFrameHeight = jest.spyOn(appEmbed, 'setIFrameHeight');
1204
+
1205
+ appEmbed.render();
1206
+ appEmbed.setIframeHeightForNonEmbedLiveboard({
1207
+ data: { currentPath: '/embed/insights/viz/' },
1208
+ type: 'Route',
1209
+ });
1210
+
1211
+ expect(spySetIFrameHeight).not.toHaveBeenCalled();
1212
+ });
1213
+
1214
+ test('should call setIFrameHeight if currentPath starts with "/some/other/path/"', () => {
1215
+ const appEmbed = new AppEmbed(getRootEl(), {
1216
+ ...defaultViewConfig,
1217
+ fullHeight: true,
1218
+ } as AppViewConfig) as any;
1219
+ const spySetIFrameHeight = jest
1220
+ .spyOn(appEmbed, 'setIFrameHeight')
1221
+ .mockImplementation(jest.fn());
1222
+
1223
+ appEmbed.render();
1224
+ appEmbed.setIframeHeightForNonEmbedLiveboard({
1225
+ data: { currentPath: '/some/other/path/' },
1226
+ type: 'Route',
1227
+ });
1228
+
1229
+ expect(spySetIFrameHeight).toHaveBeenCalled();
1230
+ });
1231
+
1232
+ test('should update iframe height correctly', async () => {
1233
+ const appEmbed = new AppEmbed(getRootEl(), {
1234
+ ...defaultViewConfig,
1235
+ fullHeight: true,
1236
+ } as AppViewConfig) as any;
1237
+
1238
+ // Set up the mock iframe
1239
+ appEmbed.iFrame = mockIFrame;
1240
+ document.body.appendChild(mockIFrame);
1241
+
1242
+ await appEmbed.render();
1243
+ const mockEvent = {
1244
+ data: 600,
1245
+ type: EmbedEvent.EmbedHeight,
1246
+ };
1247
+ appEmbed.updateIFrameHeight(mockEvent);
1248
+
1249
+ // Check if the iframe style was updated
1250
+ expect(mockIFrame.style.height).toBe('600px');
1251
+ });
1252
+
1253
+ test('should handle updateIFrameHeight with default height', async () => {
1254
+ const appEmbed = new AppEmbed(getRootEl(), {
1255
+ ...defaultViewConfig,
1256
+ fullHeight: true,
1257
+ } as AppViewConfig) as any;
1258
+
1259
+ // Set up the mock iframe
1260
+ appEmbed.iFrame = mockIFrame;
1261
+ document.body.appendChild(mockIFrame);
1262
+
1263
+ await appEmbed.render();
1264
+ const mockEvent = {
1265
+ data: 0, // This will make it use the scrollHeight
1266
+ type: EmbedEvent.EmbedHeight,
1267
+ };
1268
+ appEmbed.updateIFrameHeight(mockEvent);
1269
+
1270
+ // Should use the scrollHeight
1271
+ expect(mockIFrame.style.height).toBe('500px');
1272
+ });
1273
+ });
818
1274
  });