@dotcms/client 0.0.1-alpha.38 → 0.0.1-alpha.39

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 (53) hide show
  1. package/.eslintrc.json +18 -0
  2. package/jest.config.ts +15 -0
  3. package/package.json +3 -15
  4. package/project.json +72 -0
  5. package/src/index.ts +30 -0
  6. package/src/lib/client/content/builders/collection/collection.spec.ts +515 -0
  7. package/src/lib/client/content/builders/collection/{collection.d.ts → collection.ts} +209 -19
  8. package/src/lib/client/content/{content-api.d.ts → content-api.ts} +14 -4
  9. package/src/lib/client/content/shared/{const.d.ts → const.ts} +5 -3
  10. package/src/lib/client/content/shared/{types.d.ts → types.ts} +19 -2
  11. package/src/lib/client/content/shared/{utils.d.ts → utils.ts} +9 -1
  12. package/src/lib/client/models/{index.d.ts → index.ts} +8 -1
  13. package/src/lib/client/models/{types.d.ts → types.ts} +1 -0
  14. package/src/lib/client/sdk-js-client.spec.ts +483 -0
  15. package/src/lib/client/{sdk-js-client.d.ts → sdk-js-client.ts} +181 -15
  16. package/src/lib/editor/listeners/listeners.spec.ts +119 -0
  17. package/src/lib/editor/listeners/listeners.ts +223 -0
  18. package/src/lib/editor/models/{client.model.d.ts → client.model.ts} +19 -16
  19. package/src/lib/editor/models/{editor.model.d.ts → editor.model.ts} +9 -5
  20. package/src/lib/editor/models/{listeners.model.d.ts → listeners.model.ts} +9 -6
  21. package/src/lib/editor/sdk-editor-vtl.ts +31 -0
  22. package/src/lib/editor/sdk-editor.spec.ts +116 -0
  23. package/src/lib/editor/sdk-editor.ts +105 -0
  24. package/src/lib/editor/utils/editor.utils.spec.ts +206 -0
  25. package/src/lib/editor/utils/{editor.utils.d.ts → editor.utils.ts} +121 -22
  26. package/src/lib/query-builder/lucene-syntax/{Equals.d.ts → Equals.ts} +45 -11
  27. package/src/lib/query-builder/lucene-syntax/{Field.d.ts → Field.ts} +13 -5
  28. package/src/lib/query-builder/lucene-syntax/{NotOperand.d.ts → NotOperand.ts} +13 -5
  29. package/src/lib/query-builder/lucene-syntax/{Operand.d.ts → Operand.ts} +21 -7
  30. package/src/lib/query-builder/sdk-query-builder.spec.ts +159 -0
  31. package/src/lib/query-builder/{sdk-query-builder.d.ts → sdk-query-builder.ts} +16 -5
  32. package/src/lib/query-builder/utils/{index.d.ts → index.ts} +49 -12
  33. package/src/lib/utils/graphql/transforms.spec.ts +150 -0
  34. package/src/lib/utils/graphql/transforms.ts +99 -0
  35. package/src/lib/utils/page/common-utils.spec.ts +37 -0
  36. package/src/lib/utils/page/common-utils.ts +64 -0
  37. package/tsconfig.json +22 -0
  38. package/tsconfig.lib.json +13 -0
  39. package/tsconfig.spec.json +9 -0
  40. package/index.cjs.d.ts +0 -1
  41. package/index.cjs.default.js +0 -1
  42. package/index.cjs.js +0 -1953
  43. package/index.cjs.mjs +0 -2
  44. package/index.esm.d.ts +0 -1
  45. package/index.esm.js +0 -1944
  46. package/src/index.d.ts +0 -6
  47. package/src/lib/editor/listeners/listeners.d.ts +0 -50
  48. package/src/lib/editor/sdk-editor-vtl.d.ts +0 -6
  49. package/src/lib/editor/sdk-editor.d.ts +0 -54
  50. package/src/lib/utils/graphql/transforms.d.ts +0 -24
  51. package/src/lib/utils/page/common-utils.d.ts +0 -33
  52. /package/src/lib/query-builder/lucene-syntax/{index.d.ts → index.ts} +0 -0
  53. /package/src/lib/utils/{index.d.ts → index.ts} +0 -0
@@ -4,61 +4,62 @@
4
4
  * @export
5
5
  * @enum {number}
6
6
  */
7
- export declare enum CUSTOMER_ACTIONS {
7
+ export enum CUSTOMER_ACTIONS {
8
8
  /**
9
9
  * Tell the dotcms editor that page change
10
10
  */
11
- NAVIGATION_UPDATE = "set-url",
11
+ NAVIGATION_UPDATE = 'set-url',
12
12
  /**
13
13
  * Send the element position of the rows, columnsm containers and contentlets
14
14
  */
15
- SET_BOUNDS = "set-bounds",
15
+ SET_BOUNDS = 'set-bounds',
16
16
  /**
17
17
  * Send the information of the hovered contentlet
18
18
  */
19
- SET_CONTENTLET = "set-contentlet",
19
+ SET_CONTENTLET = 'set-contentlet',
20
20
  /**
21
21
  * Tell the editor that the page is being scrolled
22
22
  */
23
- IFRAME_SCROLL = "scroll",
23
+ IFRAME_SCROLL = 'scroll',
24
24
  /**
25
25
  * Tell the editor that the page has stopped scrolling
26
26
  */
27
- IFRAME_SCROLL_END = "scroll-end",
27
+ IFRAME_SCROLL_END = 'scroll-end',
28
28
  /**
29
29
  * Ping the editor to see if the page is inside the editor
30
30
  */
31
- PING_EDITOR = "ping-editor",
31
+ PING_EDITOR = 'ping-editor',
32
32
  /**
33
33
  * Tell the editor to init the inline editing editor.
34
34
  */
35
- INIT_INLINE_EDITING = "init-inline-editing",
35
+ INIT_INLINE_EDITING = 'init-inline-editing',
36
36
  /**
37
37
  * Tell the editor to open the Copy-contentlet dialog
38
38
  * To copy a content and then edit it inline.
39
39
  */
40
- COPY_CONTENTLET_INLINE_EDITING = "copy-contentlet-inline-editing",
40
+ COPY_CONTENTLET_INLINE_EDITING = 'copy-contentlet-inline-editing',
41
41
  /**
42
42
  * Tell the editor to save inline edited contentlet
43
43
  */
44
- UPDATE_CONTENTLET_INLINE_EDITING = "update-contentlet-inline-editing",
44
+ UPDATE_CONTENTLET_INLINE_EDITING = 'update-contentlet-inline-editing',
45
45
  /**
46
46
  * Tell the editor to trigger a menu reorder
47
47
  */
48
- REORDER_MENU = "reorder-menu",
48
+ REORDER_MENU = 'reorder-menu',
49
49
  /**
50
50
  * Tell the editor to send the page info to iframe
51
51
  */
52
- GET_PAGE_DATA = "get-page-data",
52
+ GET_PAGE_DATA = 'get-page-data',
53
53
  /**
54
54
  * Tell the editor an user send a graphql query
55
55
  */
56
- CLIENT_READY = "client-ready",
56
+ CLIENT_READY = 'client-ready',
57
57
  /**
58
58
  * Tell the editor to do nothing
59
59
  */
60
- NOOP = "noop"
60
+ NOOP = 'noop'
61
61
  }
62
+
62
63
  /**
63
64
  * Post message props
64
65
  *
@@ -70,6 +71,7 @@ type PostMessageProps<T> = {
70
71
  action: CUSTOMER_ACTIONS;
71
72
  payload?: T;
72
73
  };
74
+
73
75
  /**
74
76
  * Post message to dotcms page editor
75
77
  *
@@ -77,5 +79,6 @@ type PostMessageProps<T> = {
77
79
  * @template T
78
80
  * @param {PostMessageProps<T>} message
79
81
  */
80
- export declare function postMessageToEditor<T = unknown>(message: PostMessageProps<T>): void;
81
- export {};
82
+ export function postMessageToEditor<T = unknown>(message: PostMessageProps<T>) {
83
+ window.parent.postMessage(message, '*');
84
+ }
@@ -4,15 +4,19 @@
4
4
  export type CustomClientParams = {
5
5
  depth: string;
6
6
  };
7
+
7
8
  /**
8
9
  * @description Union type for fetch configurations.
9
10
  * @typedef {GraphQLFetchConfig | PageAPIFetchConfig} DotCMSFetchConfig
10
11
  */
11
- export type EditorConfig = {
12
- params: CustomClientParams;
13
- } | {
14
- query: string;
15
- };
12
+ export type EditorConfig =
13
+ | {
14
+ params: CustomClientParams;
15
+ }
16
+ | {
17
+ query: string;
18
+ };
19
+
16
20
  /**
17
21
  * Represents the configuration options for the DotCMS page editor.
18
22
  * @export
@@ -4,26 +4,28 @@
4
4
  * @export
5
5
  * @enum {number}
6
6
  */
7
- export declare enum NOTIFY_CUSTOMER {
7
+ export enum NOTIFY_CUSTOMER {
8
8
  /**
9
9
  * Request to page to reload
10
10
  */
11
- EMA_RELOAD_PAGE = "ema-reload-page",
11
+ EMA_RELOAD_PAGE = 'ema-reload-page',
12
12
  /**
13
13
  * Request the bounds for the elements
14
14
  */
15
- EMA_REQUEST_BOUNDS = "ema-request-bounds",
15
+ EMA_REQUEST_BOUNDS = 'ema-request-bounds',
16
16
  /**
17
17
  * Received pong from the editor
18
18
  */
19
- EMA_EDITOR_PONG = "ema-editor-pong",
19
+ EMA_EDITOR_PONG = 'ema-editor-pong',
20
20
  /**
21
21
  * Received scroll event trigger from the editor
22
22
  */
23
- EMA_SCROLL_INSIDE_IFRAME = "scroll-inside-iframe"
23
+ EMA_SCROLL_INSIDE_IFRAME = 'scroll-inside-iframe'
24
24
  }
25
+
25
26
  type ListenerCallbackMessage = (event: MessageEvent) => void;
26
27
  type ListenerCallbackPointer = (event: PointerEvent) => void;
28
+
27
29
  /**
28
30
  * Listener for the dotcms editor
29
31
  *
@@ -34,6 +36,7 @@ interface DotCMSPageEditorListener {
34
36
  event: string;
35
37
  callback: ListenerCallbackMessage | ListenerCallbackPointer;
36
38
  }
39
+
37
40
  /**
38
41
  * Observer for the dotcms editor
39
42
  *
@@ -43,5 +46,5 @@ interface DotCMSPageEditorObserver {
43
46
  type: 'observer';
44
47
  observer: MutationObserver;
45
48
  }
49
+
46
50
  export type DotCMSPageEditorSubscription = DotCMSPageEditorListener | DotCMSPageEditorObserver;
47
- export {};
@@ -0,0 +1,31 @@
1
+ import {
2
+ listenEditorMessages,
3
+ listenHoveredContentlet,
4
+ preserveScrollOnIframe,
5
+ scrollHandler
6
+ } from './listeners/listeners';
7
+ import { isInsideEditor, addClassToEmptyContentlets } from './sdk-editor';
8
+
9
+ declare global {
10
+ interface Window {
11
+ lastScrollYPosition: number;
12
+ }
13
+ }
14
+
15
+ /**
16
+ * This is the main entry point for the SDK VTL.
17
+ * This is added to VTL Script in the EditPage
18
+ *
19
+ * @remarks
20
+ * This module sets up the necessary listeners and functionality for the SDK VTL.
21
+ * It checks if the script is running inside the editor and then initializes the client by pinging the editor,
22
+ * listening for editor messages, hovered contentlet changes, and content changes.
23
+ *
24
+ */
25
+ if (isInsideEditor()) {
26
+ listenEditorMessages();
27
+ scrollHandler();
28
+ preserveScrollOnIframe();
29
+ listenHoveredContentlet();
30
+ addClassToEmptyContentlets();
31
+ }
@@ -0,0 +1,116 @@
1
+ import {
2
+ listenEditorMessages,
3
+ listenHoveredContentlet,
4
+ fetchPageDataFromInsideUVE,
5
+ scrollHandler
6
+ } from './listeners/listeners';
7
+ import { postMessageToEditor, CUSTOMER_ACTIONS } from './models/client.model';
8
+ import {
9
+ addClassToEmptyContentlets,
10
+ initEditor,
11
+ isInsideEditor,
12
+ updateNavigation
13
+ } from './sdk-editor';
14
+
15
+ jest.mock('./models/client.model', () => ({
16
+ postMessageToEditor: jest.fn(),
17
+ CUSTOMER_ACTIONS: {
18
+ NAVIGATION_UPDATE: 'set-url',
19
+ SET_BOUNDS: 'set-bounds',
20
+ SET_CONTENTLET: 'set-contentlet',
21
+ IFRAME_SCROLL: 'scroll',
22
+ PING_EDITOR: 'ping-editor',
23
+ CONTENT_CHANGE: 'content-change',
24
+ NOOP: 'noop'
25
+ }
26
+ }));
27
+
28
+ jest.mock('./listeners/listeners', () => ({
29
+ pingEditor: jest.fn(),
30
+ listenEditorMessages: jest.fn(),
31
+ listenHoveredContentlet: jest.fn(),
32
+ scrollHandler: jest.fn(),
33
+ listenContentChange: jest.fn(),
34
+ fetchPageDataFromInsideUVE: jest.fn()
35
+ }));
36
+
37
+ describe('DotCMSPageEditor', () => {
38
+ describe('is NOT inside editor', () => {
39
+ beforeEach(() => {
40
+ const mockWindow = {
41
+ ...window,
42
+ parent: window
43
+ };
44
+
45
+ const spy = jest.spyOn(global, 'window', 'get');
46
+ spy.mockReturnValueOnce(mockWindow as unknown as Window & typeof globalThis);
47
+ });
48
+
49
+ afterEach(() => {
50
+ jest.clearAllMocks();
51
+ });
52
+
53
+ it('should initialize without any listener', () => {
54
+ const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
55
+
56
+ expect(isInsideEditor()).toBe(false);
57
+ expect(addEventListenerSpy).not.toHaveBeenCalled();
58
+ });
59
+ });
60
+
61
+ describe('is inside editor', () => {
62
+ beforeEach(() => {
63
+ const mockWindow = {
64
+ ...window,
65
+ parent: null
66
+ };
67
+
68
+ const spy = jest.spyOn(global, 'window', 'get');
69
+ spy.mockReturnValue(mockWindow as unknown as Window & typeof globalThis);
70
+ });
71
+
72
+ afterEach(() => {
73
+ jest.clearAllMocks();
74
+ });
75
+
76
+ it('should initialize properly', () => {
77
+ expect(isInsideEditor()).toBe(true);
78
+ });
79
+
80
+ it('should update navigation', () => {
81
+ updateNavigation('/');
82
+ expect(postMessageToEditor).toHaveBeenCalledWith({
83
+ action: CUSTOMER_ACTIONS.NAVIGATION_UPDATE,
84
+ payload: {
85
+ url: 'index'
86
+ }
87
+ });
88
+ });
89
+
90
+ it('should init editor calling listeners', () => {
91
+ initEditor({ pathname: 'some-url' });
92
+ expect(fetchPageDataFromInsideUVE).toHaveBeenCalledWith('some-url');
93
+ expect(listenEditorMessages).toHaveBeenCalled();
94
+ expect(listenHoveredContentlet).toHaveBeenCalled();
95
+ expect(scrollHandler).toHaveBeenCalled();
96
+ });
97
+ });
98
+
99
+ describe('Add Class to Empty Contentets', () => {
100
+ it('should add class to empty contentlets', () => {
101
+ const contentlet = document.createElement('div');
102
+ contentlet.setAttribute('data-dot-object', 'contentlet');
103
+ Object.defineProperty(contentlet, 'clientHeight', { value: 100 }); // Emulate a contentlet with height in the DOM
104
+ document.body.appendChild(contentlet);
105
+
106
+ const emptyContentlet = document.createElement('div');
107
+ emptyContentlet.setAttribute('data-dot-object', 'contentlet');
108
+ document.body.appendChild(emptyContentlet);
109
+
110
+ addClassToEmptyContentlets();
111
+
112
+ expect(emptyContentlet.classList.contains('empty-contentlet')).toBe(true);
113
+ expect(contentlet.classList.contains('empty-contentlet')).toBe(false);
114
+ });
115
+ });
116
+ });
@@ -0,0 +1,105 @@
1
+ import {
2
+ fetchPageDataFromInsideUVE,
3
+ listenEditorMessages,
4
+ listenHoveredContentlet,
5
+ scrollHandler,
6
+ subscriptions
7
+ } from './listeners/listeners';
8
+ import { CUSTOMER_ACTIONS, postMessageToEditor } from './models/client.model';
9
+ import { DotCMSPageEditorConfig } from './models/editor.model';
10
+
11
+ /**
12
+ * Updates the navigation in the editor.
13
+ *
14
+ * @param {string} pathname - The pathname to update the navigation with.
15
+ * @memberof DotCMSPageEditor
16
+ * @example
17
+ * updateNavigation('/home'); // Sends a message to the editor to update the navigation to '/home'
18
+ */
19
+ export function updateNavigation(pathname: string): void {
20
+ postMessageToEditor({
21
+ action: CUSTOMER_ACTIONS.NAVIGATION_UPDATE,
22
+ payload: {
23
+ url: pathname === '/' ? 'index' : pathname?.replace('/', '')
24
+ }
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Checks if the code is running inside an editor.
30
+ *
31
+ * @returns {boolean} Returns true if the code is running inside an editor, otherwise false.
32
+ * @example
33
+ * ```ts
34
+ * if (isInsideEditor()) {
35
+ * console.log('Running inside the editor');
36
+ * } else {
37
+ * console.log('Running outside the editor');
38
+ * }
39
+ * ```
40
+ */
41
+ export function isInsideEditor(): boolean {
42
+ if (typeof window === 'undefined') {
43
+ return false;
44
+ }
45
+
46
+ return window.parent !== window;
47
+ }
48
+
49
+ /**
50
+ * Initializes the DotCMS page editor.
51
+ *
52
+ * @param {DotCMSPageEditorConfig} config - Optional configuration for the editor.
53
+ * @example
54
+ * ```ts
55
+ * const config = { pathname: '/home' };
56
+ * initEditor(config); // Initializes the editor with the provided configuration
57
+ * ```
58
+ */
59
+ export function initEditor(config: DotCMSPageEditorConfig): void {
60
+ fetchPageDataFromInsideUVE(config.pathname);
61
+ listenEditorMessages();
62
+ listenHoveredContentlet();
63
+ scrollHandler();
64
+ }
65
+
66
+ /**
67
+ * Destroys the editor by removing event listeners and disconnecting observers.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * destroyEditor(); // Cleans up the editor by removing all event listeners and disconnecting observers
72
+ * ```
73
+ */
74
+ export function destroyEditor(): void {
75
+ subscriptions.forEach((subscription) => {
76
+ if (subscription.type === 'listener') {
77
+ window.removeEventListener(subscription.event, subscription.callback as EventListener);
78
+ }
79
+
80
+ if (subscription.type === 'observer') {
81
+ subscription.observer.disconnect();
82
+ }
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Adds a style class to empty contentlets.
88
+ *
89
+ * @export
90
+ * @example
91
+ * ```ts
92
+ * addClassToEmptyContentlets(); // Adds the 'empty-contentlet' class to all contentlets that have no height
93
+ * ```
94
+ */
95
+ export function addClassToEmptyContentlets(): void {
96
+ const contentlets = document.querySelectorAll('[data-dot-object="contentlet"]');
97
+
98
+ contentlets.forEach((contentlet) => {
99
+ if (contentlet.clientHeight) {
100
+ return;
101
+ }
102
+
103
+ contentlet.classList.add('empty-contentlet');
104
+ });
105
+ }
@@ -0,0 +1,206 @@
1
+ import { getContentletsBound, scrollIsInBottom } from './editor.utils';
2
+
3
+ describe('getContentletsBound', () => {
4
+ const createContentlet = ({
5
+ x,
6
+ y,
7
+ width,
8
+ height,
9
+ dataset
10
+ }: {
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ dataset: { [key: string]: string };
16
+ }): HTMLDivElement => {
17
+ const contentlet = document.createElement('div');
18
+ const mockGetBoundingClientRect = jest.fn(() => ({
19
+ x,
20
+ y,
21
+ width,
22
+ height
23
+ })) as unknown as () => DOMRect;
24
+ contentlet.getBoundingClientRect = mockGetBoundingClientRect;
25
+ Object.keys(dataset).forEach((key) => {
26
+ contentlet.setAttribute(`data-${key}`, dataset[key]);
27
+ });
28
+
29
+ return contentlet;
30
+ };
31
+
32
+ const containerRect = {
33
+ x: 0,
34
+ y: 0,
35
+ width: 100,
36
+ height: 100,
37
+ top: 0,
38
+ right: 100,
39
+ bottom: 100,
40
+ left: 0
41
+ } as DOMRect;
42
+
43
+ const contentlets: HTMLDivElement[] = [
44
+ createContentlet({
45
+ x: 10,
46
+ y: 20,
47
+ width: 30,
48
+ height: 40,
49
+ dataset: {
50
+ 'dot-container': JSON.stringify({ uuid: 'container1' }),
51
+ 'dot-identifier': 'contentlet1',
52
+ 'dot-title': 'Contentlet 1',
53
+ 'dot-inode': 'inode1',
54
+ 'dot-type': 'type1'
55
+ }
56
+ }),
57
+ createContentlet({
58
+ x: 50,
59
+ y: 60,
60
+ width: 70,
61
+ height: 80,
62
+ dataset: {
63
+ 'dot-container': JSON.stringify({ uuid: 'container1' }),
64
+ 'dot-identifier': 'contentlet2',
65
+ 'dot-title': 'Contentlet 2',
66
+ 'dot-inode': 'inode2',
67
+ 'dot-type': 'type2'
68
+ }
69
+ })
70
+ ];
71
+
72
+ it('should return an array of contentlets bound from contentlet with data atrribute dotContainer ', () => {
73
+ const result = getContentletsBound(containerRect, contentlets);
74
+
75
+ expect(result).toEqual([
76
+ {
77
+ x: 0,
78
+ y: 20,
79
+ width: 30,
80
+ height: 40,
81
+ payload: JSON.stringify({
82
+ container: { uuid: 'container1' },
83
+ contentlet: {
84
+ identifier: 'contentlet1',
85
+ title: 'Contentlet 1',
86
+ inode: 'inode1',
87
+ contentType: 'type1'
88
+ }
89
+ })
90
+ },
91
+ {
92
+ x: 0,
93
+ y: 60,
94
+ width: 70,
95
+ height: 80,
96
+ payload: JSON.stringify({
97
+ container: { uuid: 'container1' },
98
+ contentlet: {
99
+ identifier: 'contentlet2',
100
+ title: 'Contentlet 2',
101
+ inode: 'inode2',
102
+ contentType: 'type2'
103
+ }
104
+ })
105
+ }
106
+ ]);
107
+ });
108
+
109
+ it('should return an empty array if contentlets is empty', () => {
110
+ const result = getContentletsBound(containerRect, []);
111
+
112
+ expect(result).toEqual([]);
113
+ });
114
+
115
+ it('should return an array of contentlets with correct properties when dotContainer is not present in dataset', () => {
116
+ const contentletsWithMissingContainer: HTMLDivElement[] = [
117
+ createContentlet({
118
+ x: 10,
119
+ y: 20,
120
+ width: 30,
121
+ height: 40,
122
+ dataset: {
123
+ 'dot-identifier': 'contentlet1',
124
+ 'dot-title': 'Contentlet 1',
125
+ 'dot-inode': 'inode1',
126
+ 'dot-type': 'type1'
127
+ }
128
+ })
129
+ ];
130
+
131
+ const container = document.createElement('div');
132
+
133
+ container.appendChild(contentletsWithMissingContainer[0]);
134
+ container.setAttribute('data-dot-object', 'container');
135
+ container.setAttribute('data-dot-accept-types', '[Blogs]');
136
+ container.setAttribute('data-dot-identifier', '1');
137
+ container.setAttribute('data-max-contentlets', '1');
138
+ container.setAttribute('data-dot-uuid', '1');
139
+ const result = getContentletsBound(containerRect, contentletsWithMissingContainer);
140
+
141
+ expect(result).toEqual([
142
+ {
143
+ x: 0,
144
+ y: 20,
145
+ width: 30,
146
+ height: 40,
147
+ payload: JSON.stringify({
148
+ container: {
149
+ acceptTypes: '[Blogs]',
150
+ identifier: '1',
151
+ maxContentlets: '1',
152
+ uuid: '1'
153
+ },
154
+ contentlet: {
155
+ identifier: 'contentlet1',
156
+ title: 'Contentlet 1',
157
+ inode: 'inode1',
158
+ contentType: 'type1'
159
+ }
160
+ })
161
+ }
162
+ ]);
163
+ });
164
+ });
165
+
166
+ describe('scrollIsInBottom', () => {
167
+ it('should return true when scroll position + viewport height equals document height', () => {
168
+ Object.defineProperty(window, 'innerHeight', {
169
+ writable: true,
170
+ configurable: true,
171
+ value: 500
172
+ });
173
+ Object.defineProperty(window, 'scrollY', {
174
+ writable: true,
175
+ configurable: true,
176
+ value: 500
177
+ });
178
+ Object.defineProperty(document.documentElement, 'scrollHeight', {
179
+ writable: true,
180
+ configurable: true,
181
+ value: 1000
182
+ });
183
+
184
+ expect(scrollIsInBottom()).toBe(true);
185
+ });
186
+
187
+ it('should return false when scroll position + viewport height is less than document height', () => {
188
+ Object.defineProperty(window, 'innerHeight', {
189
+ writable: true,
190
+ configurable: true,
191
+ value: 500
192
+ });
193
+ Object.defineProperty(window, 'scrollY', {
194
+ writable: true,
195
+ configurable: true,
196
+ value: 400
197
+ });
198
+ Object.defineProperty(document.documentElement, 'scrollHeight', {
199
+ writable: true,
200
+ configurable: true,
201
+ value: 1000
202
+ });
203
+
204
+ expect(scrollIsInBottom()).toBe(false);
205
+ });
206
+ });