@thoughtspot/visual-embed-sdk 1.52.0 → 1.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/package.json +9 -1
- package/cjs/src/contracts/contracts.spec.d.ts +2 -0
- package/cjs/src/contracts/contracts.spec.d.ts.map +1 -0
- package/cjs/src/contracts/contracts.spec.js +345 -0
- package/cjs/src/contracts/contracts.spec.js.map +1 -0
- package/cjs/src/contracts/embed-event-payloads.d.ts +73 -0
- package/cjs/src/contracts/embed-event-payloads.d.ts.map +1 -0
- package/cjs/src/contracts/embed-event-payloads.js +3 -0
- package/cjs/src/contracts/embed-event-payloads.js.map +1 -0
- package/cjs/src/contracts/host-event-contracts.d.ts +378 -0
- package/cjs/src/contracts/host-event-contracts.d.ts.map +1 -0
- package/cjs/src/contracts/host-event-contracts.js +3 -0
- package/cjs/src/contracts/host-event-contracts.js.map +1 -0
- package/cjs/src/contracts/index.d.ts +22 -0
- package/cjs/src/contracts/index.d.ts.map +1 -0
- package/cjs/src/contracts/index.js +33 -0
- package/cjs/src/contracts/index.js.map +1 -0
- package/cjs/src/contracts/ui-passthrough-contracts.d.ts +277 -0
- package/cjs/src/contracts/ui-passthrough-contracts.d.ts.map +1 -0
- package/cjs/src/{embed/hostEventClient/contracts.js → contracts/ui-passthrough-contracts.js} +6 -2
- package/cjs/src/contracts/ui-passthrough-contracts.js.map +1 -0
- package/cjs/src/embed/app.d.ts +17 -140
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +20 -120
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +70 -26
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/events.spec.js +12 -2
- package/cjs/src/embed/events.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts +3 -2
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.js +23 -18
- package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js +36 -36
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.d.ts +1 -1
- package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.js +3 -3
- package/cjs/src/embed/hostEventClient/utils.js.map +1 -1
- package/cjs/src/embed/liveboard.d.ts +9 -161
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +66 -134
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/liveboard.spec.js +211 -28
- package/cjs/src/embed/liveboard.spec.js.map +1 -1
- package/cjs/src/embed/search.d.ts +14 -0
- package/cjs/src/embed/search.d.ts.map +1 -1
- package/cjs/src/embed/search.js +4 -1
- package/cjs/src/embed/search.js.map +1 -1
- package/cjs/src/embed/search.spec.js +65 -0
- package/cjs/src/embed/search.spec.js.map +1 -1
- package/cjs/src/embed/ts-embed.d.ts +17 -22
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +82 -56
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +325 -17
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/cjs/src/full-height.d.ts +137 -0
- package/cjs/src/full-height.d.ts.map +1 -0
- package/cjs/src/full-height.js +272 -0
- package/cjs/src/full-height.js.map +1 -0
- package/cjs/src/full-height.spec.d.ts +2 -0
- package/cjs/src/full-height.spec.d.ts.map +1 -0
- package/cjs/src/full-height.spec.js +835 -0
- package/cjs/src/full-height.spec.js.map +1 -0
- package/cjs/src/index.d.ts +4 -3
- package/cjs/src/index.d.ts.map +1 -1
- package/cjs/src/index.js +3 -3
- package/cjs/src/index.js.map +1 -1
- package/cjs/src/react/all-types-export.d.ts +1 -1
- package/cjs/src/react/all-types-export.d.ts.map +1 -1
- package/cjs/src/react/all-types-export.js.map +1 -1
- package/cjs/src/react/all-types-export.parity.spec.js +55 -3
- package/cjs/src/react/all-types-export.parity.spec.js.map +1 -1
- package/cjs/src/types.d.ts +540 -3
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +326 -1
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/processTrigger.d.ts +3 -1
- package/cjs/src/utils/processTrigger.d.ts.map +1 -1
- package/cjs/src/utils/processTrigger.js +19 -51
- package/cjs/src/utils/processTrigger.js.map +1 -1
- package/cjs/src/utils/transport/iframe-transport.d.ts +55 -0
- package/cjs/src/utils/transport/iframe-transport.d.ts.map +1 -0
- package/cjs/src/utils/transport/iframe-transport.js +108 -0
- package/cjs/src/utils/transport/iframe-transport.js.map +1 -0
- package/cjs/src/utils/transport/iframe-transport.spec.d.ts +2 -0
- package/cjs/src/utils/transport/iframe-transport.spec.d.ts.map +1 -0
- package/cjs/src/utils/transport/iframe-transport.spec.js +140 -0
- package/cjs/src/utils/transport/iframe-transport.spec.js.map +1 -0
- package/cjs/src/utils.d.ts +18 -0
- package/cjs/src/utils.d.ts.map +1 -1
- package/cjs/src/utils.js +39 -1
- package/cjs/src/utils.js.map +1 -1
- package/cjs/src/utils.spec.js +62 -0
- package/cjs/src/utils.spec.js.map +1 -1
- package/dist/{index-BMppCU63.js → index-C1pmYb5H.js} +1 -1
- package/dist/src/contracts/contracts.spec.d.ts +2 -0
- package/dist/src/contracts/contracts.spec.d.ts.map +1 -0
- package/dist/src/contracts/embed-event-payloads.d.ts +73 -0
- package/dist/src/contracts/embed-event-payloads.d.ts.map +1 -0
- package/dist/src/contracts/host-event-contracts.d.ts +378 -0
- package/dist/src/contracts/host-event-contracts.d.ts.map +1 -0
- package/dist/src/contracts/index.d.ts +22 -0
- package/dist/src/contracts/index.d.ts.map +1 -0
- package/dist/src/contracts/ui-passthrough-contracts.d.ts +277 -0
- package/dist/src/contracts/ui-passthrough-contracts.d.ts.map +1 -0
- package/dist/src/embed/app.d.ts +17 -140
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/host-event-client.d.ts +3 -2
- package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/utils.d.ts +1 -1
- package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -1
- package/dist/src/embed/liveboard.d.ts +9 -161
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/search.d.ts +14 -0
- package/dist/src/embed/search.d.ts.map +1 -1
- package/dist/src/embed/ts-embed.d.ts +17 -22
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/full-height.d.ts +137 -0
- package/dist/src/full-height.d.ts.map +1 -0
- package/dist/src/full-height.spec.d.ts +2 -0
- package/dist/src/full-height.spec.d.ts.map +1 -0
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/react/all-types-export.d.ts +1 -1
- package/dist/src/react/all-types-export.d.ts.map +1 -1
- package/dist/src/types.d.ts +540 -3
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/processTrigger.d.ts +3 -1
- package/dist/src/utils/processTrigger.d.ts.map +1 -1
- package/dist/src/utils/transport/iframe-transport.d.ts +55 -0
- package/dist/src/utils/transport/iframe-transport.d.ts.map +1 -0
- package/dist/src/utils/transport/iframe-transport.spec.d.ts +2 -0
- package/dist/src/utils/transport/iframe-transport.spec.d.ts.map +1 -0
- package/dist/src/utils.d.ts +18 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +925 -363
- package/dist/tsembed-react.js +924 -362
- package/dist/tsembed.es.js +925 -363
- package/dist/tsembed.js +924 -362
- package/dist/visual-embed-sdk-react-full.d.ts +1357 -656
- package/dist/visual-embed-sdk-react.d.ts +1257 -606
- package/dist/visual-embed-sdk.d.ts +1320 -642
- package/lib/package.json +9 -1
- package/lib/src/contracts/contracts.spec.d.ts +2 -0
- package/lib/src/contracts/contracts.spec.d.ts.map +1 -0
- package/lib/src/contracts/contracts.spec.js +342 -0
- package/lib/src/contracts/contracts.spec.js.map +1 -0
- package/lib/src/contracts/embed-event-payloads.d.ts +73 -0
- package/lib/src/contracts/embed-event-payloads.d.ts.map +1 -0
- package/lib/src/contracts/embed-event-payloads.js +2 -0
- package/lib/src/contracts/embed-event-payloads.js.map +1 -0
- package/lib/src/contracts/host-event-contracts.d.ts +378 -0
- package/lib/src/contracts/host-event-contracts.d.ts.map +1 -0
- package/lib/src/contracts/host-event-contracts.js +2 -0
- package/lib/src/contracts/host-event-contracts.js.map +1 -0
- package/lib/src/contracts/index.d.ts +22 -0
- package/lib/src/contracts/index.d.ts.map +1 -0
- package/lib/src/contracts/index.js +22 -0
- package/lib/src/contracts/index.js.map +1 -0
- package/lib/src/contracts/ui-passthrough-contracts.d.ts +277 -0
- package/lib/src/contracts/ui-passthrough-contracts.d.ts.map +1 -0
- package/lib/src/{embed/hostEventClient/contracts.js → contracts/ui-passthrough-contracts.js} +6 -2
- package/lib/src/contracts/ui-passthrough-contracts.js.map +1 -0
- package/lib/src/embed/app.d.ts +17 -140
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +22 -122
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +70 -26
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/events.spec.js +12 -2
- package/lib/src/embed/events.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.d.ts +3 -2
- package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.js +6 -1
- package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.spec.js +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/utils.d.ts +1 -1
- package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/utils.js +1 -1
- package/lib/src/embed/hostEventClient/utils.js.map +1 -1
- package/lib/src/embed/liveboard.d.ts +9 -161
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +67 -135
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/liveboard.spec.js +211 -28
- package/lib/src/embed/liveboard.spec.js.map +1 -1
- package/lib/src/embed/search.d.ts +14 -0
- package/lib/src/embed/search.d.ts.map +1 -1
- package/lib/src/embed/search.js +4 -1
- package/lib/src/embed/search.js.map +1 -1
- package/lib/src/embed/search.spec.js +65 -0
- package/lib/src/embed/search.spec.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts +17 -22
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +82 -56
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +322 -14
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/full-height.d.ts +137 -0
- package/lib/src/full-height.d.ts.map +1 -0
- package/lib/src/full-height.js +268 -0
- package/lib/src/full-height.js.map +1 -0
- package/lib/src/full-height.spec.d.ts +2 -0
- package/lib/src/full-height.spec.d.ts.map +1 -0
- package/lib/src/full-height.spec.js +833 -0
- package/lib/src/full-height.spec.js.map +1 -0
- package/lib/src/index.d.ts +4 -3
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js +1 -1
- package/lib/src/index.js.map +1 -1
- package/lib/src/react/all-types-export.d.ts +1 -1
- package/lib/src/react/all-types-export.d.ts.map +1 -1
- package/lib/src/react/all-types-export.js.map +1 -1
- package/lib/src/react/all-types-export.parity.spec.js +55 -3
- package/lib/src/react/all-types-export.parity.spec.js.map +1 -1
- package/lib/src/types.d.ts +540 -3
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +326 -1
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/processTrigger.d.ts +3 -1
- package/lib/src/utils/processTrigger.d.ts.map +1 -1
- package/lib/src/utils/processTrigger.js +19 -51
- package/lib/src/utils/processTrigger.js.map +1 -1
- package/lib/src/utils/transport/iframe-transport.d.ts +55 -0
- package/lib/src/utils/transport/iframe-transport.d.ts.map +1 -0
- package/lib/src/utils/transport/iframe-transport.js +101 -0
- package/lib/src/utils/transport/iframe-transport.js.map +1 -0
- package/lib/src/utils/transport/iframe-transport.spec.d.ts +2 -0
- package/lib/src/utils/transport/iframe-transport.spec.d.ts.map +1 -0
- package/lib/src/utils/transport/iframe-transport.spec.js +138 -0
- package/lib/src/utils/transport/iframe-transport.spec.js.map +1 -0
- package/lib/src/utils.d.ts +18 -0
- package/lib/src/utils.d.ts.map +1 -1
- package/lib/src/utils.js +37 -0
- package/lib/src/utils.js.map +1 -1
- package/lib/src/utils.spec.js +63 -1
- package/lib/src/utils.spec.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +1320 -642
- package/package.json +9 -1
- package/src/contracts/contracts.spec.ts +392 -0
- package/src/contracts/embed-event-payloads.ts +83 -0
- package/src/contracts/host-event-contracts.ts +456 -0
- package/src/contracts/index.ts +53 -0
- package/src/contracts/ui-passthrough-contracts.ts +301 -0
- package/src/embed/app.spec.ts +86 -34
- package/src/embed/app.ts +37 -264
- package/src/embed/events.spec.ts +14 -2
- package/src/embed/hostEventClient/host-event-client.spec.ts +2 -2
- package/src/embed/hostEventClient/host-event-client.ts +18 -5
- package/src/embed/hostEventClient/utils.ts +2 -1
- package/src/embed/liveboard.spec.ts +257 -38
- package/src/embed/liveboard.ts +75 -296
- package/src/embed/search.spec.ts +80 -0
- package/src/embed/search.ts +20 -0
- package/src/embed/ts-embed.spec.ts +448 -15
- package/src/embed/ts-embed.ts +109 -66
- package/src/full-height.spec.ts +1008 -0
- package/src/full-height.ts +361 -0
- package/src/index.ts +93 -5
- package/src/react/all-types-export.parity.spec.ts +63 -3
- package/src/react/all-types-export.ts +45 -0
- package/src/types.ts +540 -1
- package/src/utils/processTrigger.ts +28 -58
- package/src/utils/transport/iframe-transport.spec.ts +177 -0
- package/src/utils/transport/iframe-transport.ts +122 -0
- package/src/utils.spec.ts +71 -0
- package/src/utils.ts +38 -0
- package/cjs/src/embed/hostEventClient/contracts.d.ts +0 -231
- package/cjs/src/embed/hostEventClient/contracts.d.ts.map +0 -1
- package/cjs/src/embed/hostEventClient/contracts.js.map +0 -1
- package/dist/src/embed/hostEventClient/contracts.d.ts +0 -231
- package/dist/src/embed/hostEventClient/contracts.d.ts.map +0 -1
- package/lib/src/embed/hostEventClient/contracts.d.ts +0 -231
- package/lib/src/embed/hostEventClient/contracts.d.ts.map +0 -1
- package/lib/src/embed/hostEventClient/contracts.js.map +0 -1
- package/src/embed/hostEventClient/contracts.ts +0 -262
|
@@ -0,0 +1,1008 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FullHeightController,
|
|
3
|
+
FullHeightControllerViewConfig as ControllerConfig,
|
|
4
|
+
FullHeightEmbedHost,
|
|
5
|
+
} from './full-height';
|
|
6
|
+
import {
|
|
7
|
+
EmbedEvent, HostEvent, MessageCallback, Param,
|
|
8
|
+
} from './types';
|
|
9
|
+
import { logger } from './utils/logger';
|
|
10
|
+
import { DEFAULT_LAZY_LOADING_MARGIN } from './config';
|
|
11
|
+
|
|
12
|
+
describe('FullHeightController', () => {
|
|
13
|
+
let iFrame: HTMLIFrameElement;
|
|
14
|
+
let host: FullHeightEmbedHost;
|
|
15
|
+
let handlers: Map<EmbedEvent, MessageCallback>;
|
|
16
|
+
|
|
17
|
+
const originalResizeObserver = (window as any).ResizeObserver;
|
|
18
|
+
const originalScrollY = window.scrollY;
|
|
19
|
+
const originalInnerHeight = window.innerHeight;
|
|
20
|
+
const originalInnerWidth = window.innerWidth;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A rect that only carries the edges the visibility math reads.
|
|
24
|
+
*/
|
|
25
|
+
const rectOf = (top: number, bottom: number, left = 0, right = 500) => ({
|
|
26
|
+
top,
|
|
27
|
+
bottom,
|
|
28
|
+
left,
|
|
29
|
+
right,
|
|
30
|
+
width: right - left,
|
|
31
|
+
height: bottom - top,
|
|
32
|
+
} as DOMRect);
|
|
33
|
+
|
|
34
|
+
const setViewport = (scrollY: number, innerHeight: number, innerWidth = 1024) => {
|
|
35
|
+
Object.defineProperty(window, 'scrollY', { value: scrollY, configurable: true });
|
|
36
|
+
Object.defineProperty(window, 'innerHeight', { value: innerHeight, configurable: true });
|
|
37
|
+
Object.defineProperty(window, 'innerWidth', { value: innerWidth, configurable: true });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const createControllerFor = (viewConfig: ControllerConfig) => createController(viewConfig);
|
|
41
|
+
|
|
42
|
+
const createController = (
|
|
43
|
+
viewConfig: ControllerConfig,
|
|
44
|
+
options: { iframe?: HTMLIFrameElement } = {},
|
|
45
|
+
) => {
|
|
46
|
+
handlers = new Map();
|
|
47
|
+
iFrame = document.createElement('iframe');
|
|
48
|
+
document.body.appendChild(iFrame);
|
|
49
|
+
const hostIframe = 'iframe' in options ? options.iframe : iFrame;
|
|
50
|
+
host = {
|
|
51
|
+
getIframe: () => hostIframe,
|
|
52
|
+
setFrameHeight: jest.fn(),
|
|
53
|
+
on: (eventType, callback) => {
|
|
54
|
+
handlers.set(eventType, callback);
|
|
55
|
+
},
|
|
56
|
+
trigger: jest.fn(),
|
|
57
|
+
};
|
|
58
|
+
// The controller resolves the defaults and registers its handlers in
|
|
59
|
+
// its own constructor, so nothing else is needed here.
|
|
60
|
+
return new FullHeightController(viewConfig, host);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The query params a freshly built controller contributes. The defaults are
|
|
65
|
+
* applied to the controller's private copy of the view config, so the
|
|
66
|
+
* params are how the host app observes them.
|
|
67
|
+
*/
|
|
68
|
+
const queryParamsFor = (viewConfig: ControllerConfig) => {
|
|
69
|
+
const params: any = {};
|
|
70
|
+
createController(viewConfig).addQueryParams(params);
|
|
71
|
+
return params;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Puts the embed inside a scrollable container and hands back the spies the
|
|
76
|
+
* container assertions need. The caller drives `onRender`, so tests can
|
|
77
|
+
* assert what happens before it too.
|
|
78
|
+
*/
|
|
79
|
+
const mountInScrollContainer = (
|
|
80
|
+
viewConfig: ControllerConfig,
|
|
81
|
+
options: { withResizeObserver?: boolean } = {},
|
|
82
|
+
) => {
|
|
83
|
+
const observe = jest.fn();
|
|
84
|
+
const disconnect = jest.fn();
|
|
85
|
+
let resizeCallback: () => void;
|
|
86
|
+
const resizeObserverCtor = jest.fn((callback: () => void) => {
|
|
87
|
+
resizeCallback = callback;
|
|
88
|
+
return { observe, disconnect };
|
|
89
|
+
});
|
|
90
|
+
if (options.withResizeObserver === false) {
|
|
91
|
+
delete (window as any).ResizeObserver;
|
|
92
|
+
} else {
|
|
93
|
+
(window as any).ResizeObserver = resizeObserverCtor;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const scrollContainer = document.createElement('div');
|
|
97
|
+
scrollContainer.style.overflow = 'auto';
|
|
98
|
+
const addContainerListener = jest.spyOn(scrollContainer, 'addEventListener');
|
|
99
|
+
const removeContainerListener = jest.spyOn(scrollContainer, 'removeEventListener');
|
|
100
|
+
|
|
101
|
+
const controller = createController(viewConfig);
|
|
102
|
+
scrollContainer.appendChild(iFrame);
|
|
103
|
+
document.body.appendChild(scrollContainer);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
controller,
|
|
107
|
+
scrollContainer,
|
|
108
|
+
addContainerListener,
|
|
109
|
+
removeContainerListener,
|
|
110
|
+
observe,
|
|
111
|
+
disconnect,
|
|
112
|
+
resizeObserverCtor,
|
|
113
|
+
fireResizeObserver: () => resizeCallback(),
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const visibleCoordinates = () => {
|
|
118
|
+
const responder = jest.fn();
|
|
119
|
+
handlers.get(EmbedEvent.RequestVisibleEmbedCoordinates)({} as any, responder);
|
|
120
|
+
return responder.mock.calls[0][0].data;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
afterEach(() => {
|
|
124
|
+
document.body.innerHTML = '';
|
|
125
|
+
(window as any).ResizeObserver = originalResizeObserver;
|
|
126
|
+
setViewport(originalScrollY, originalInnerHeight, originalInnerWidth);
|
|
127
|
+
jest.restoreAllMocks();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('registerEventHandlers', () => {
|
|
131
|
+
it('registers every full-height handler when fullHeight is enabled', () => {
|
|
132
|
+
createController({ fullHeight: true });
|
|
133
|
+
expect([...handlers.keys()]).toEqual([
|
|
134
|
+
EmbedEvent.RouteChange,
|
|
135
|
+
EmbedEvent.EmbedHeight,
|
|
136
|
+
EmbedEvent.EmbedIframeCenter,
|
|
137
|
+
EmbedEvent.RequestVisibleEmbedCoordinates,
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('registers nothing when fullHeight is not enabled', () => {
|
|
142
|
+
createController({});
|
|
143
|
+
expect(handlers.size).toBe(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('registers nothing when fullHeight is explicitly false', () => {
|
|
147
|
+
// Lazy loading on its own must not switch the feature on.
|
|
148
|
+
createController({ fullHeight: false, lazyLoadingForFullHeight: true });
|
|
149
|
+
expect(handlers.size).toBe(0);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('viewConfig', () => {
|
|
154
|
+
it('reads back the lazy-loading settings defaulted for the host app', () => {
|
|
155
|
+
const controller = createController({ fullHeight: true });
|
|
156
|
+
expect(controller.viewConfig).toEqual({
|
|
157
|
+
fullHeight: true,
|
|
158
|
+
lazyLoadingForFullHeight: true,
|
|
159
|
+
enableScrollableContainerLazyLoading: true,
|
|
160
|
+
lazyLoadingMargin: DEFAULT_LAZY_LOADING_MARGIN,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('preserves an explicit opt-out', () => {
|
|
165
|
+
const controller = createController({
|
|
166
|
+
fullHeight: true,
|
|
167
|
+
lazyLoadingForFullHeight: false,
|
|
168
|
+
enableScrollableContainerLazyLoading: false,
|
|
169
|
+
lazyLoadingMargin: '0px',
|
|
170
|
+
});
|
|
171
|
+
expect(controller.viewConfig).toEqual({
|
|
172
|
+
fullHeight: true,
|
|
173
|
+
lazyLoadingForFullHeight: false,
|
|
174
|
+
enableScrollableContainerLazyLoading: false,
|
|
175
|
+
lazyLoadingMargin: '0px',
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('defaults only what the host app left unset', () => {
|
|
180
|
+
const controller = createController({ fullHeight: true, lazyLoadingMargin: '50px' });
|
|
181
|
+
expect(controller.viewConfig).toEqual({
|
|
182
|
+
fullHeight: true,
|
|
183
|
+
lazyLoadingForFullHeight: true,
|
|
184
|
+
enableScrollableContainerLazyLoading: true,
|
|
185
|
+
lazyLoadingMargin: '50px',
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('re-resolves the defaults when the config is replaced', () => {
|
|
190
|
+
const controller = createController({ fullHeight: true, minimumHeight: 400 });
|
|
191
|
+
expect(controller.minimumHeight).toBe(400);
|
|
192
|
+
|
|
193
|
+
const next: ControllerConfig = { fullHeight: true, minimumHeight: 900 };
|
|
194
|
+
controller.viewConfig = next;
|
|
195
|
+
|
|
196
|
+
expect(controller.minimumHeight).toBe(900);
|
|
197
|
+
expect(controller.viewConfig.lazyLoadingMargin).toBe(DEFAULT_LAZY_LOADING_MARGIN);
|
|
198
|
+
// The replacement is copied, not adopted, so the caller's object
|
|
199
|
+
// does not grow the resolved flags either.
|
|
200
|
+
expect(next).toEqual({ fullHeight: true, minimumHeight: 900 });
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('feeds the query params from the replaced config', () => {
|
|
204
|
+
const controller = createController({ fullHeight: true });
|
|
205
|
+
controller.viewConfig = { fullHeight: true, lazyLoadingForFullHeight: false };
|
|
206
|
+
const params: any = {};
|
|
207
|
+
controller.addQueryParams(params);
|
|
208
|
+
expect(params).toEqual({ [Param.fullHeight]: true });
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('lazy loading defaults', () => {
|
|
213
|
+
it('turns lazy loading on for a full-height embed', () => {
|
|
214
|
+
expect(queryParamsFor({ fullHeight: true })).toEqual({
|
|
215
|
+
[Param.fullHeight]: true,
|
|
216
|
+
[Param.IsLazyLoadingForEmbedEnabled]: true,
|
|
217
|
+
[Param.RootMarginForLazyLoad]: DEFAULT_LAZY_LOADING_MARGIN,
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('tracks the scrollable containers without the host app opting in', () => {
|
|
222
|
+
const { controller, addContainerListener, observe } = mountInScrollContainer({
|
|
223
|
+
fullHeight: true,
|
|
224
|
+
});
|
|
225
|
+
controller.onRender();
|
|
226
|
+
expect(addContainerListener).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
227
|
+
expect(observe).toHaveBeenCalled();
|
|
228
|
+
controller.destroy();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('leaves the view config the host app passed in untouched', () => {
|
|
232
|
+
// The controller defaults into a copy of its own, so the caller's
|
|
233
|
+
// object never grows the resolved flags.
|
|
234
|
+
const viewConfig: ControllerConfig = { fullHeight: true };
|
|
235
|
+
const controller = new FullHeightController(viewConfig, {
|
|
236
|
+
getIframe: () => iFrame,
|
|
237
|
+
setFrameHeight: jest.fn(),
|
|
238
|
+
on: jest.fn(),
|
|
239
|
+
trigger: jest.fn(),
|
|
240
|
+
});
|
|
241
|
+
controller.addQueryParams({});
|
|
242
|
+
controller.onRender();
|
|
243
|
+
controller.destroy();
|
|
244
|
+
expect(viewConfig).toEqual({ fullHeight: true });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('leaves an explicit opt-out alone', () => {
|
|
248
|
+
expect(queryParamsFor({
|
|
249
|
+
fullHeight: true,
|
|
250
|
+
lazyLoadingForFullHeight: false,
|
|
251
|
+
lazyLoadingMargin: '0px',
|
|
252
|
+
})).toEqual({ [Param.fullHeight]: true });
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('honours an explicit container opt-out', () => {
|
|
256
|
+
const { controller, addContainerListener, resizeObserverCtor } = mountInScrollContainer(
|
|
257
|
+
{ fullHeight: true, enableScrollableContainerLazyLoading: false },
|
|
258
|
+
);
|
|
259
|
+
controller.onRender();
|
|
260
|
+
expect(addContainerListener).not.toHaveBeenCalled();
|
|
261
|
+
expect(resizeObserverCtor).not.toHaveBeenCalled();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('defaults only the values the host app left unset', () => {
|
|
265
|
+
// Lazy loading defaults on, while the supplied margin survives.
|
|
266
|
+
const { controller, addContainerListener } = mountInScrollContainer({
|
|
267
|
+
fullHeight: true,
|
|
268
|
+
enableScrollableContainerLazyLoading: false,
|
|
269
|
+
lazyLoadingMargin: '50px',
|
|
270
|
+
});
|
|
271
|
+
const params: any = {};
|
|
272
|
+
controller.addQueryParams(params);
|
|
273
|
+
expect(params).toEqual({
|
|
274
|
+
[Param.fullHeight]: true,
|
|
275
|
+
[Param.IsLazyLoadingForEmbedEnabled]: true,
|
|
276
|
+
[Param.RootMarginForLazyLoad]: '50px',
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
controller.onRender();
|
|
280
|
+
expect(addContainerListener).not.toHaveBeenCalled();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('defaults nothing when fullHeight is not enabled', () => {
|
|
284
|
+
const viewConfig: ControllerConfig = {};
|
|
285
|
+
const controller = createControllerFor(viewConfig);
|
|
286
|
+
expect(viewConfig).toEqual({});
|
|
287
|
+
|
|
288
|
+
const add = jest.spyOn(window, 'addEventListener');
|
|
289
|
+
controller.onRender();
|
|
290
|
+
expect(add).not.toHaveBeenCalled();
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('minimumHeight', () => {
|
|
295
|
+
it('falls back to 500 when neither height is configured', () => {
|
|
296
|
+
expect(createController({ fullHeight: true }).minimumHeight).toBe(500);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('prefers minimumHeight over the deprecated defaultHeight', () => {
|
|
300
|
+
const controller = createController({
|
|
301
|
+
fullHeight: true,
|
|
302
|
+
defaultHeight: 700,
|
|
303
|
+
minimumHeight: 800,
|
|
304
|
+
});
|
|
305
|
+
expect(controller.minimumHeight).toBe(800);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('honours the deprecated defaultHeight when minimumHeight is absent', () => {
|
|
309
|
+
const controller = createController({ fullHeight: true, defaultHeight: 700 });
|
|
310
|
+
expect(controller.minimumHeight).toBe(700);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('treats a zero minimumHeight as unset and honours defaultHeight', () => {
|
|
314
|
+
const controller = createController({
|
|
315
|
+
fullHeight: true,
|
|
316
|
+
minimumHeight: 0,
|
|
317
|
+
defaultHeight: 700,
|
|
318
|
+
});
|
|
319
|
+
expect(controller.minimumHeight).toBe(700);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('falls back to 500 when both heights are zero', () => {
|
|
323
|
+
const controller = createController({
|
|
324
|
+
fullHeight: true,
|
|
325
|
+
minimumHeight: 0,
|
|
326
|
+
defaultHeight: 0,
|
|
327
|
+
});
|
|
328
|
+
expect(controller.minimumHeight).toBe(500);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('is available even when fullHeight is off', () => {
|
|
332
|
+
expect(createController({ minimumHeight: 800 }).minimumHeight).toBe(800);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe('addQueryParams', () => {
|
|
337
|
+
it('adds no params when fullHeight is not enabled', () => {
|
|
338
|
+
expect(queryParamsFor({ lazyLoadingForFullHeight: true })).toEqual({});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('adds only the full height param when lazy loading is off', () => {
|
|
342
|
+
expect(queryParamsFor({
|
|
343
|
+
fullHeight: true,
|
|
344
|
+
lazyLoadingForFullHeight: false,
|
|
345
|
+
})).toEqual({ [Param.fullHeight]: true });
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('adds the lazy loading params, including a valid margin', () => {
|
|
349
|
+
expect(queryParamsFor({
|
|
350
|
+
fullHeight: true,
|
|
351
|
+
lazyLoadingForFullHeight: true,
|
|
352
|
+
lazyLoadingMargin: '100px 0px',
|
|
353
|
+
})).toEqual({
|
|
354
|
+
[Param.fullHeight]: true,
|
|
355
|
+
[Param.IsLazyLoadingForEmbedEnabled]: true,
|
|
356
|
+
[Param.RootMarginForLazyLoad]: '100px 0px',
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('accepts a four sided margin', () => {
|
|
361
|
+
expect(queryParamsFor({
|
|
362
|
+
fullHeight: true,
|
|
363
|
+
lazyLoadingMargin: '10px 20px 30px 40px',
|
|
364
|
+
})[Param.RootMarginForLazyLoad]).toBe('10px 20px 30px 40px');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('accepts a unitless zero margin', () => {
|
|
368
|
+
expect(queryParamsFor({
|
|
369
|
+
fullHeight: true,
|
|
370
|
+
lazyLoadingMargin: '0',
|
|
371
|
+
})[Param.RootMarginForLazyLoad]).toBe('0');
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('keeps the params the embed has already collected', () => {
|
|
375
|
+
const params: any = { existing: 'value' };
|
|
376
|
+
createController({ fullHeight: true }).addQueryParams(params);
|
|
377
|
+
expect(params.existing).toBe('value');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('produces the same params when called for a second render', () => {
|
|
381
|
+
const controller = createController({ fullHeight: true });
|
|
382
|
+
const first: any = {};
|
|
383
|
+
const second: any = {};
|
|
384
|
+
controller.addQueryParams(first);
|
|
385
|
+
controller.addQueryParams(second);
|
|
386
|
+
expect(second).toEqual(first);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('drops an invalid lazy loading margin', () => {
|
|
390
|
+
// An invalid margin is reported to the developer, not sent on.
|
|
391
|
+
const loggerError = jest.spyOn(logger, 'error').mockImplementation(jest.fn());
|
|
392
|
+
const params = queryParamsFor({
|
|
393
|
+
fullHeight: true,
|
|
394
|
+
lazyLoadingForFullHeight: true,
|
|
395
|
+
lazyLoadingMargin: 'not-a-margin',
|
|
396
|
+
});
|
|
397
|
+
expect(params[Param.RootMarginForLazyLoad]).toBeUndefined();
|
|
398
|
+
expect(loggerError).toHaveBeenCalled();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('drops a margin with more than four sides', () => {
|
|
402
|
+
const loggerError = jest.spyOn(logger, 'error').mockImplementation(jest.fn());
|
|
403
|
+
const params = queryParamsFor({
|
|
404
|
+
fullHeight: true,
|
|
405
|
+
lazyLoadingMargin: '1px 2px 3px 4px 5px',
|
|
406
|
+
});
|
|
407
|
+
expect(params[Param.RootMarginForLazyLoad]).toBeUndefined();
|
|
408
|
+
expect(loggerError).toHaveBeenCalled();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('drops a margin that is not a string', () => {
|
|
412
|
+
const loggerError = jest.spyOn(logger, 'error').mockImplementation(jest.fn());
|
|
413
|
+
const params = queryParamsFor({
|
|
414
|
+
fullHeight: true,
|
|
415
|
+
lazyLoadingMargin: 100 as any,
|
|
416
|
+
});
|
|
417
|
+
expect(params[Param.RootMarginForLazyLoad]).toBeUndefined();
|
|
418
|
+
expect(loggerError).toHaveBeenCalled();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('drops an empty lazy loading margin but keeps the other params', () => {
|
|
422
|
+
const loggerError = jest.spyOn(logger, 'error').mockImplementation(jest.fn());
|
|
423
|
+
const params = queryParamsFor({
|
|
424
|
+
fullHeight: true,
|
|
425
|
+
lazyLoadingForFullHeight: true,
|
|
426
|
+
lazyLoadingMargin: '',
|
|
427
|
+
});
|
|
428
|
+
expect(params).toEqual({
|
|
429
|
+
[Param.fullHeight]: true,
|
|
430
|
+
[Param.IsLazyLoadingForEmbedEnabled]: true,
|
|
431
|
+
});
|
|
432
|
+
expect(loggerError).toHaveBeenCalled();
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
describe('EmbedHeight', () => {
|
|
437
|
+
const embedHeight = (data: unknown) => ({ data } as any);
|
|
438
|
+
|
|
439
|
+
it('never sizes the frame below the configured minimum', () => {
|
|
440
|
+
createController({ fullHeight: true, minimumHeight: 800 });
|
|
441
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight(300));
|
|
442
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(800);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('uses the height reported by the app when it clears the minimum', () => {
|
|
446
|
+
createController({ fullHeight: true, minimumHeight: 800 });
|
|
447
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight(1200));
|
|
448
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(1200);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('never sizes the frame below the 500 default floor', () => {
|
|
452
|
+
createController({ fullHeight: true });
|
|
453
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight(100));
|
|
454
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(500);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('clamps a negative height to the minimum', () => {
|
|
458
|
+
createController({ fullHeight: true, minimumHeight: 800 });
|
|
459
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight(-50));
|
|
460
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(800);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('clamps a zero height to the minimum', () => {
|
|
464
|
+
// Zero is a height the app really reported, not a missing one.
|
|
465
|
+
createController({ fullHeight: true, minimumHeight: 800 });
|
|
466
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight(0));
|
|
467
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(800);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('keeps a fractional height that clears the minimum', () => {
|
|
471
|
+
createController({ fullHeight: true, minimumHeight: 800 });
|
|
472
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight(1200.5));
|
|
473
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(1200.5);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('accepts a numeric height sent as a string', () => {
|
|
477
|
+
createController({ fullHeight: true });
|
|
478
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight('1200'));
|
|
479
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(1200);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('leaves the height alone when the app reports a non-numeric height', () => {
|
|
483
|
+
createController({ fullHeight: true });
|
|
484
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight('tall'));
|
|
485
|
+
expect(host.setFrameHeight).not.toHaveBeenCalled();
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('leaves the height alone when the payload carries no height', () => {
|
|
489
|
+
createController({ fullHeight: true });
|
|
490
|
+
handlers.get(EmbedEvent.EmbedHeight)({} as any);
|
|
491
|
+
expect(host.setFrameHeight).not.toHaveBeenCalled();
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('survives a missing payload', () => {
|
|
495
|
+
createController({ fullHeight: true });
|
|
496
|
+
expect(() => handlers.get(EmbedEvent.EmbedHeight)(undefined as any)).not.toThrow();
|
|
497
|
+
expect(host.setFrameHeight).not.toHaveBeenCalled();
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('still pushes the visible coordinates for an unusable height', () => {
|
|
501
|
+
createController({ fullHeight: true, lazyLoadingForFullHeight: true });
|
|
502
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight('tall'));
|
|
503
|
+
expect(host.trigger).toHaveBeenCalledWith(
|
|
504
|
+
HostEvent.VisibleEmbedCoordinates,
|
|
505
|
+
expect.objectContaining({ top: expect.any(Number) }),
|
|
506
|
+
);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('pushes the visible coordinates only when lazy loading is on', () => {
|
|
510
|
+
createController({ fullHeight: true, lazyLoadingForFullHeight: false });
|
|
511
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight(1200));
|
|
512
|
+
expect(host.trigger).not.toHaveBeenCalled();
|
|
513
|
+
|
|
514
|
+
createController({ fullHeight: true, lazyLoadingForFullHeight: true });
|
|
515
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight(1200));
|
|
516
|
+
expect(host.trigger).toHaveBeenCalledWith(
|
|
517
|
+
HostEvent.VisibleEmbedCoordinates,
|
|
518
|
+
expect.objectContaining({ top: expect.any(Number) }),
|
|
519
|
+
);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('pushes no coordinates when the iframe is not there yet', () => {
|
|
523
|
+
// There is nothing to measure, so the app is left alone.
|
|
524
|
+
createController({ fullHeight: true }, { iframe: null });
|
|
525
|
+
handlers.get(EmbedEvent.EmbedHeight)(embedHeight(1200));
|
|
526
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(1200);
|
|
527
|
+
expect(host.trigger).not.toHaveBeenCalled();
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
describe('RouteChange', () => {
|
|
532
|
+
const routeChange = (currentPath: string) => ({ data: { currentPath } } as any);
|
|
533
|
+
|
|
534
|
+
it('leaves the height alone while navigating within a Liveboard', () => {
|
|
535
|
+
createController({ fullHeight: true });
|
|
536
|
+
handlers.get(EmbedEvent.RouteChange)(routeChange('/embed/viz/abc'));
|
|
537
|
+
expect(host.setFrameHeight).not.toHaveBeenCalled();
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it.each([
|
|
541
|
+
'/pinboard/abc',
|
|
542
|
+
'/insights/pinboard/abc',
|
|
543
|
+
'/schedules/abc',
|
|
544
|
+
'/embed/viz/abc',
|
|
545
|
+
'/embed/insights/viz/abc',
|
|
546
|
+
'/liveboard/abc',
|
|
547
|
+
'/insights/liveboard/abc',
|
|
548
|
+
'/tsl-editor/PINBOARD_ANSWER_BOOK/abc',
|
|
549
|
+
'/import-tsl/PINBOARD_ANSWER_BOOK/abc',
|
|
550
|
+
])('leaves the height alone on the Liveboard route %s', (currentPath) => {
|
|
551
|
+
createController({ fullHeight: true });
|
|
552
|
+
handlers.get(EmbedEvent.RouteChange)(routeChange(currentPath));
|
|
553
|
+
expect(host.setFrameHeight).not.toHaveBeenCalled();
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('leaves the height alone on a bare Liveboard route', () => {
|
|
557
|
+
createController({ fullHeight: true });
|
|
558
|
+
handlers.get(EmbedEvent.RouteChange)(routeChange('/liveboard/'));
|
|
559
|
+
expect(host.setFrameHeight).not.toHaveBeenCalled();
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('resets to frameParams.height when leaving the Liveboard routes', () => {
|
|
563
|
+
createController({ fullHeight: true, frameParams: { height: 640 } });
|
|
564
|
+
handlers.get(EmbedEvent.RouteChange)(routeChange('/some/other/path/'));
|
|
565
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(640);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('passes a non-numeric frameParams.height through unchanged', () => {
|
|
569
|
+
createController({ fullHeight: true, frameParams: { height: '100%' } });
|
|
570
|
+
handlers.get(EmbedEvent.RouteChange)(routeChange('/some/other/path/'));
|
|
571
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith('100%');
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('resets to the minimum height when frameParams has no height', () => {
|
|
575
|
+
createController({ fullHeight: true, minimumHeight: 800 });
|
|
576
|
+
handlers.get(EmbedEvent.RouteChange)(routeChange('/some/other/path/'));
|
|
577
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(800);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it('resets when a Liveboard route only appears part way into the path', () => {
|
|
581
|
+
// The routes are matched as prefixes, not as substrings.
|
|
582
|
+
createController({ fullHeight: true, minimumHeight: 800 });
|
|
583
|
+
handlers.get(EmbedEvent.RouteChange)(routeChange('/app/embed/viz/abc'));
|
|
584
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(800);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it('resets on a Liveboard route missing its trailing slash', () => {
|
|
588
|
+
createController({ fullHeight: true, minimumHeight: 800 });
|
|
589
|
+
handlers.get(EmbedEvent.RouteChange)(routeChange('/liveboard'));
|
|
590
|
+
expect(host.setFrameHeight).toHaveBeenCalledWith(800);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it('leaves the height alone when the payload carries no path', () => {
|
|
594
|
+
createController({ fullHeight: true });
|
|
595
|
+
handlers.get(EmbedEvent.RouteChange)({ data: {} } as any);
|
|
596
|
+
expect(host.setFrameHeight).not.toHaveBeenCalled();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('survives a missing payload', () => {
|
|
600
|
+
createController({ fullHeight: true });
|
|
601
|
+
expect(() => handlers.get(EmbedEvent.RouteChange)(undefined as any)).not.toThrow();
|
|
602
|
+
expect(host.setFrameHeight).not.toHaveBeenCalled();
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('does not push coordinates on a route change', () => {
|
|
606
|
+
// Only a height report and the viewport listeners do that.
|
|
607
|
+
createController({ fullHeight: true });
|
|
608
|
+
handlers.get(EmbedEvent.RouteChange)(routeChange('/some/other/path/'));
|
|
609
|
+
expect(host.trigger).not.toHaveBeenCalled();
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
describe('coordinate requests', () => {
|
|
614
|
+
it('responds to RequestVisibleEmbedCoordinates with the visible region', () => {
|
|
615
|
+
createController({ fullHeight: true });
|
|
616
|
+
const responder = jest.fn();
|
|
617
|
+
handlers.get(EmbedEvent.RequestVisibleEmbedCoordinates)({} as any, responder);
|
|
618
|
+
expect(responder).toHaveBeenCalledWith({
|
|
619
|
+
type: EmbedEvent.RequestVisibleEmbedCoordinates,
|
|
620
|
+
data: expect.objectContaining({ top: expect.any(Number) }),
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it('responds to EmbedIframeCenter with the center of the visible region', () => {
|
|
625
|
+
createController({ fullHeight: true });
|
|
626
|
+
const responder = jest.fn();
|
|
627
|
+
handlers.get(EmbedEvent.EmbedIframeCenter)({} as any, responder);
|
|
628
|
+
expect(responder).toHaveBeenCalledWith({
|
|
629
|
+
type: EmbedEvent.EmbedIframeCenter,
|
|
630
|
+
data: expect.objectContaining({ iframeCenter: expect.any(Number) }),
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
it('measures the center against the viewport for an unscrolled page', () => {
|
|
635
|
+
setViewport(0, 768);
|
|
636
|
+
createController({ fullHeight: true });
|
|
637
|
+
Object.defineProperty(iFrame, 'offsetHeight', { value: 1000, configurable: true });
|
|
638
|
+
jest.spyOn(iFrame, 'getBoundingClientRect').mockReturnValue(rectOf(100, 1100));
|
|
639
|
+
|
|
640
|
+
const responder = jest.fn();
|
|
641
|
+
handlers.get(EmbedEvent.EmbedIframeCenter)({} as any, responder);
|
|
642
|
+
expect(responder.mock.calls[0][0].data).toEqual({
|
|
643
|
+
iframeCenter: 334,
|
|
644
|
+
iframeScrolled: -100,
|
|
645
|
+
iframeHeight: 1000,
|
|
646
|
+
viewPortHeight: 768,
|
|
647
|
+
iframeVisibleViewPort: 668,
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it('measures the center against the viewport for a scrolled page', () => {
|
|
652
|
+
setViewport(500, 600);
|
|
653
|
+
createController({ fullHeight: true });
|
|
654
|
+
Object.defineProperty(iFrame, 'offsetHeight', { value: 2000, configurable: true });
|
|
655
|
+
// The element starts at the page top, so it is 500px scrolled.
|
|
656
|
+
jest.spyOn(iFrame, 'getBoundingClientRect').mockReturnValue(rectOf(-500, 1500));
|
|
657
|
+
|
|
658
|
+
const responder = jest.fn();
|
|
659
|
+
handlers.get(EmbedEvent.EmbedIframeCenter)({} as any, responder);
|
|
660
|
+
expect(responder.mock.calls[0][0].data).toEqual({
|
|
661
|
+
iframeCenter: 800,
|
|
662
|
+
iframeScrolled: 500,
|
|
663
|
+
iframeHeight: 2000,
|
|
664
|
+
viewPortHeight: 600,
|
|
665
|
+
iframeVisibleViewPort: 600,
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('logs the RequestVisibleEmbedCoordinates request', () => {
|
|
670
|
+
const loggerInfo = jest.spyOn(logger, 'info').mockImplementation(jest.fn());
|
|
671
|
+
createController({ fullHeight: true });
|
|
672
|
+
const payload = { type: EmbedEvent.RequestVisibleEmbedCoordinates } as any;
|
|
673
|
+
handlers.get(EmbedEvent.RequestVisibleEmbedCoordinates)(payload, jest.fn());
|
|
674
|
+
expect(loggerInfo).toHaveBeenCalledWith(
|
|
675
|
+
'Sending RequestVisibleEmbedCoordinates',
|
|
676
|
+
payload,
|
|
677
|
+
);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it('responds with null coordinates when the iframe is not there yet', () => {
|
|
681
|
+
createController({ fullHeight: true }, { iframe: null });
|
|
682
|
+
const responder = jest.fn();
|
|
683
|
+
handlers.get(EmbedEvent.RequestVisibleEmbedCoordinates)({} as any, responder);
|
|
684
|
+
expect(responder).toHaveBeenCalledWith({
|
|
685
|
+
type: EmbedEvent.RequestVisibleEmbedCoordinates,
|
|
686
|
+
data: null,
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
it('stays silent on EmbedIframeCenter when the iframe is not there yet', () => {
|
|
691
|
+
createController({ fullHeight: true }, { iframe: null });
|
|
692
|
+
const responder = jest.fn();
|
|
693
|
+
handlers.get(EmbedEvent.EmbedIframeCenter)({} as any, responder);
|
|
694
|
+
expect(responder).not.toHaveBeenCalled();
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('survives an app that asks for coordinates without a responder', () => {
|
|
698
|
+
createController({ fullHeight: true });
|
|
699
|
+
expect(() => {
|
|
700
|
+
handlers.get(EmbedEvent.RequestVisibleEmbedCoordinates)({} as any);
|
|
701
|
+
handlers.get(EmbedEvent.EmbedIframeCenter)({} as any);
|
|
702
|
+
}).not.toThrow();
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('reports the region left uncovered when the embed is off screen', () => {
|
|
706
|
+
setViewport(0, 768);
|
|
707
|
+
createController({ fullHeight: true });
|
|
708
|
+
jest.spyOn(iFrame, 'getBoundingClientRect').mockReturnValue(rectOf(900, 1400));
|
|
709
|
+
expect(visibleCoordinates()).toEqual({
|
|
710
|
+
top: 0, height: 0, left: 0, width: 500,
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('clips the visible region to the containers when container lazy loading is on', () => {
|
|
715
|
+
setViewport(0, 768);
|
|
716
|
+
const clippingContainer = document.createElement('div');
|
|
717
|
+
clippingContainer.style.overflow = 'hidden';
|
|
718
|
+
createController({
|
|
719
|
+
fullHeight: true,
|
|
720
|
+
enableScrollableContainerLazyLoading: true,
|
|
721
|
+
});
|
|
722
|
+
clippingContainer.appendChild(iFrame);
|
|
723
|
+
document.body.appendChild(clippingContainer);
|
|
724
|
+
jest.spyOn(iFrame, 'getBoundingClientRect').mockReturnValue(rectOf(-100, 400));
|
|
725
|
+
jest.spyOn(clippingContainer, 'getBoundingClientRect')
|
|
726
|
+
.mockReturnValue(rectOf(50, 300));
|
|
727
|
+
|
|
728
|
+
expect(visibleCoordinates()).toEqual({
|
|
729
|
+
top: 150, height: 250, left: 0, width: 500,
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('ignores the containers when container lazy loading is off', () => {
|
|
734
|
+
setViewport(0, 768);
|
|
735
|
+
const clippingContainer = document.createElement('div');
|
|
736
|
+
clippingContainer.style.overflow = 'hidden';
|
|
737
|
+
createController({
|
|
738
|
+
fullHeight: true,
|
|
739
|
+
enableScrollableContainerLazyLoading: false,
|
|
740
|
+
});
|
|
741
|
+
clippingContainer.appendChild(iFrame);
|
|
742
|
+
document.body.appendChild(clippingContainer);
|
|
743
|
+
jest.spyOn(iFrame, 'getBoundingClientRect').mockReturnValue(rectOf(-100, 400));
|
|
744
|
+
jest.spyOn(clippingContainer, 'getBoundingClientRect')
|
|
745
|
+
.mockReturnValue(rectOf(50, 300));
|
|
746
|
+
|
|
747
|
+
expect(visibleCoordinates()).toEqual({
|
|
748
|
+
top: 100, height: 400, left: 0, width: 500,
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it('clips the visible region horizontally too', () => {
|
|
753
|
+
setViewport(0, 768, 1024);
|
|
754
|
+
const clippingContainer = document.createElement('div');
|
|
755
|
+
clippingContainer.style.overflow = 'hidden';
|
|
756
|
+
createController({ fullHeight: true });
|
|
757
|
+
clippingContainer.appendChild(iFrame);
|
|
758
|
+
document.body.appendChild(clippingContainer);
|
|
759
|
+
jest.spyOn(iFrame, 'getBoundingClientRect').mockReturnValue(rectOf(0, 400, -60, 600));
|
|
760
|
+
jest.spyOn(clippingContainer, 'getBoundingClientRect')
|
|
761
|
+
.mockReturnValue(rectOf(0, 400, 40, 500));
|
|
762
|
+
|
|
763
|
+
expect(visibleCoordinates()).toEqual({
|
|
764
|
+
top: 0, height: 400, left: 100, width: 460,
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
describe('lazy load listeners', () => {
|
|
770
|
+
it('attaches window listeners on render and removes them on destroy', () => {
|
|
771
|
+
const add = jest.spyOn(window, 'addEventListener');
|
|
772
|
+
const remove = jest.spyOn(window, 'removeEventListener');
|
|
773
|
+
const controller = createController({
|
|
774
|
+
fullHeight: true,
|
|
775
|
+
lazyLoadingForFullHeight: true,
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
controller.onRender();
|
|
779
|
+
expect(add).toHaveBeenCalledWith('resize', expect.any(Function));
|
|
780
|
+
expect(add).toHaveBeenCalledWith('scroll', expect.any(Function), true);
|
|
781
|
+
|
|
782
|
+
controller.destroy();
|
|
783
|
+
expect(remove).toHaveBeenCalledWith('resize', expect.any(Function));
|
|
784
|
+
expect(remove).toHaveBeenCalledWith('scroll', expect.any(Function), true);
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
it('attaches nothing when lazy loading is off', () => {
|
|
788
|
+
const add = jest.spyOn(window, 'addEventListener');
|
|
789
|
+
createController({
|
|
790
|
+
fullHeight: true,
|
|
791
|
+
lazyLoadingForFullHeight: false,
|
|
792
|
+
}).onRender();
|
|
793
|
+
expect(add).not.toHaveBeenCalled();
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
it('attaches nothing when fullHeight is off', () => {
|
|
797
|
+
const add = jest.spyOn(window, 'addEventListener');
|
|
798
|
+
createController({ lazyLoadingForFullHeight: true }).onRender();
|
|
799
|
+
expect(add).not.toHaveBeenCalled();
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it('attaches nothing when the iframe has not rendered yet', () => {
|
|
803
|
+
const add = jest.spyOn(window, 'addEventListener');
|
|
804
|
+
createController({ fullHeight: true }, { iframe: null }).onRender();
|
|
805
|
+
expect(add).not.toHaveBeenCalled();
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
it('removes nothing on destroy when lazy loading is off', () => {
|
|
809
|
+
const remove = jest.spyOn(window, 'removeEventListener');
|
|
810
|
+
createController({
|
|
811
|
+
fullHeight: true,
|
|
812
|
+
lazyLoadingForFullHeight: false,
|
|
813
|
+
}).destroy();
|
|
814
|
+
expect(remove).not.toHaveBeenCalled();
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('pushes the visible coordinates on a window scroll and resize', () => {
|
|
818
|
+
const controller = createController({ fullHeight: true });
|
|
819
|
+
controller.onRender();
|
|
820
|
+
|
|
821
|
+
window.dispatchEvent(new Event('scroll'));
|
|
822
|
+
expect(host.trigger).toHaveBeenCalledWith(
|
|
823
|
+
HostEvent.VisibleEmbedCoordinates,
|
|
824
|
+
expect.objectContaining({ top: expect.any(Number) }),
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
(host.trigger as jest.Mock).mockClear();
|
|
828
|
+
window.dispatchEvent(new Event('resize'));
|
|
829
|
+
expect(host.trigger).toHaveBeenCalledTimes(1);
|
|
830
|
+
|
|
831
|
+
controller.destroy();
|
|
832
|
+
(host.trigger as jest.Mock).mockClear();
|
|
833
|
+
window.dispatchEvent(new Event('scroll'));
|
|
834
|
+
window.dispatchEvent(new Event('resize'));
|
|
835
|
+
expect(host.trigger).not.toHaveBeenCalled();
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it('pushes the visible coordinates for a scroll inside a nested element', () => {
|
|
839
|
+
// The window scroll listener is registered in the capture phase, so
|
|
840
|
+
// a scroll on an inner element reaches it too.
|
|
841
|
+
const controller = createController({ fullHeight: true });
|
|
842
|
+
controller.onRender();
|
|
843
|
+
iFrame.dispatchEvent(new Event('scroll', { bubbles: false }));
|
|
844
|
+
expect(host.trigger).toHaveBeenCalledWith(
|
|
845
|
+
HostEvent.VisibleEmbedCoordinates,
|
|
846
|
+
expect.objectContaining({ top: expect.any(Number) }),
|
|
847
|
+
);
|
|
848
|
+
controller.destroy();
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it('picks the listeners back up when the embed re-renders after destroy', () => {
|
|
852
|
+
const controller = createController({ fullHeight: true });
|
|
853
|
+
controller.onRender();
|
|
854
|
+
controller.destroy();
|
|
855
|
+
|
|
856
|
+
controller.onRender();
|
|
857
|
+
window.dispatchEvent(new Event('scroll'));
|
|
858
|
+
expect(host.trigger).toHaveBeenCalledWith(
|
|
859
|
+
HostEvent.VisibleEmbedCoordinates,
|
|
860
|
+
expect.objectContaining({ top: expect.any(Number) }),
|
|
861
|
+
);
|
|
862
|
+
controller.destroy();
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it('does not stack duplicate window listeners across renders', () => {
|
|
866
|
+
const add = jest.spyOn(window, 'addEventListener');
|
|
867
|
+
const controller = createController({ fullHeight: true });
|
|
868
|
+
|
|
869
|
+
controller.onRender();
|
|
870
|
+
controller.onRender();
|
|
871
|
+
const scrollHandlers = add.mock.calls
|
|
872
|
+
.filter(([eventType]) => eventType === 'scroll')
|
|
873
|
+
.map(([, handler]) => handler);
|
|
874
|
+
// The same reference is re-added, so the browser keeps a
|
|
875
|
+
// single listener.
|
|
876
|
+
expect(scrollHandlers).toHaveLength(2);
|
|
877
|
+
expect(scrollHandlers[0]).toBe(scrollHandlers[1]);
|
|
878
|
+
|
|
879
|
+
controller.destroy();
|
|
880
|
+
(host.trigger as jest.Mock).mockClear();
|
|
881
|
+
window.dispatchEvent(new Event('scroll'));
|
|
882
|
+
expect(host.trigger).not.toHaveBeenCalled();
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
it('observes the scrollable ancestors when container lazy loading is on', () => {
|
|
886
|
+
const {
|
|
887
|
+
controller, addContainerListener, removeContainerListener, observe, disconnect,
|
|
888
|
+
} = mountInScrollContainer({
|
|
889
|
+
fullHeight: true,
|
|
890
|
+
lazyLoadingForFullHeight: true,
|
|
891
|
+
enableScrollableContainerLazyLoading: true,
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
controller.onRender();
|
|
895
|
+
expect(addContainerListener).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
896
|
+
expect(observe).toHaveBeenCalled();
|
|
897
|
+
|
|
898
|
+
controller.destroy();
|
|
899
|
+
expect(removeContainerListener).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
900
|
+
expect(disconnect).toHaveBeenCalled();
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
it('pushes the visible coordinates on a container scroll', () => {
|
|
904
|
+
const { controller, scrollContainer } = mountInScrollContainer({ fullHeight: true });
|
|
905
|
+
controller.onRender();
|
|
906
|
+
|
|
907
|
+
scrollContainer.dispatchEvent(new Event('scroll'));
|
|
908
|
+
expect(host.trigger).toHaveBeenCalledWith(
|
|
909
|
+
HostEvent.VisibleEmbedCoordinates,
|
|
910
|
+
expect.objectContaining({ top: expect.any(Number) }),
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
controller.destroy();
|
|
914
|
+
(host.trigger as jest.Mock).mockClear();
|
|
915
|
+
scrollContainer.dispatchEvent(new Event('scroll'));
|
|
916
|
+
expect(host.trigger).not.toHaveBeenCalled();
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
it('pushes the visible coordinates when an observed container resizes', () => {
|
|
920
|
+
const { controller, fireResizeObserver } = mountInScrollContainer({ fullHeight: true });
|
|
921
|
+
controller.onRender();
|
|
922
|
+
|
|
923
|
+
fireResizeObserver();
|
|
924
|
+
expect(host.trigger).toHaveBeenCalledWith(
|
|
925
|
+
HostEvent.VisibleEmbedCoordinates,
|
|
926
|
+
expect.objectContaining({ top: expect.any(Number) }),
|
|
927
|
+
);
|
|
928
|
+
controller.destroy();
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
it('observes the iframe parent even without a scrollable ancestor', () => {
|
|
932
|
+
const observe = jest.fn();
|
|
933
|
+
(window as any).ResizeObserver = jest.fn(() => ({
|
|
934
|
+
observe,
|
|
935
|
+
disconnect: jest.fn(),
|
|
936
|
+
}));
|
|
937
|
+
|
|
938
|
+
const controller = createController({ fullHeight: true });
|
|
939
|
+
controller.onRender();
|
|
940
|
+
expect(observe).toHaveBeenCalledWith(iFrame.parentElement);
|
|
941
|
+
controller.destroy();
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
it('observes each resize target only once', () => {
|
|
945
|
+
const { controller, scrollContainer, observe } = mountInScrollContainer({
|
|
946
|
+
fullHeight: true,
|
|
947
|
+
});
|
|
948
|
+
// The parent is also the clipping ancestor, so it must not
|
|
949
|
+
// be observed twice.
|
|
950
|
+
jest.spyOn(iFrame, 'getBoundingClientRect').mockReturnValue(rectOf(-100, 400));
|
|
951
|
+
jest.spyOn(scrollContainer, 'getBoundingClientRect').mockReturnValue(rectOf(50, 300));
|
|
952
|
+
|
|
953
|
+
controller.onRender();
|
|
954
|
+
expect(observe).toHaveBeenCalledTimes(1);
|
|
955
|
+
expect(observe).toHaveBeenCalledWith(scrollContainer);
|
|
956
|
+
controller.destroy();
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
it('does not touch the containers when container lazy loading is off', () => {
|
|
960
|
+
const { controller, addContainerListener, resizeObserverCtor } = mountInScrollContainer(
|
|
961
|
+
{
|
|
962
|
+
fullHeight: true,
|
|
963
|
+
lazyLoadingForFullHeight: true,
|
|
964
|
+
enableScrollableContainerLazyLoading: false,
|
|
965
|
+
},
|
|
966
|
+
);
|
|
967
|
+
|
|
968
|
+
controller.onRender();
|
|
969
|
+
expect(addContainerListener).not.toHaveBeenCalled();
|
|
970
|
+
expect(resizeObserverCtor).not.toHaveBeenCalled();
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
it('still tracks the containers in an environment without ResizeObserver', () => {
|
|
974
|
+
const { controller, addContainerListener } = mountInScrollContainer(
|
|
975
|
+
{ fullHeight: true },
|
|
976
|
+
{ withResizeObserver: false },
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
expect(() => controller.onRender()).not.toThrow();
|
|
980
|
+
expect(addContainerListener).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
981
|
+
expect(() => controller.destroy()).not.toThrow();
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
it('drops the previous containers when the embed re-renders', () => {
|
|
985
|
+
const {
|
|
986
|
+
controller, removeContainerListener, disconnect,
|
|
987
|
+
} = mountInScrollContainer({ fullHeight: true });
|
|
988
|
+
|
|
989
|
+
controller.onRender();
|
|
990
|
+
controller.onRender();
|
|
991
|
+
expect(removeContainerListener).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
992
|
+
expect(disconnect).toHaveBeenCalledTimes(1);
|
|
993
|
+
controller.destroy();
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it('is safe to destroy more than once', () => {
|
|
997
|
+
const { controller } = mountInScrollContainer({ fullHeight: true });
|
|
998
|
+
controller.onRender();
|
|
999
|
+
controller.destroy();
|
|
1000
|
+
expect(() => controller.destroy()).not.toThrow();
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
it('is safe to destroy before the embed has rendered', () => {
|
|
1004
|
+
const controller = createController({ fullHeight: true });
|
|
1005
|
+
expect(() => controller.destroy()).not.toThrow();
|
|
1006
|
+
});
|
|
1007
|
+
});
|
|
1008
|
+
});
|