@dotcms/client 0.0.1-alpha.37 → 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.
- package/.eslintrc.json +18 -0
- package/README.md +4 -4
- package/jest.config.ts +15 -0
- package/package.json +3 -15
- package/project.json +72 -0
- package/src/index.ts +30 -0
- package/src/lib/client/content/builders/collection/collection.spec.ts +515 -0
- package/src/lib/client/content/builders/collection/collection.ts +416 -0
- package/src/lib/client/content/content-api.ts +139 -0
- package/src/lib/client/content/shared/const.ts +15 -0
- package/src/lib/client/content/shared/types.ts +155 -0
- package/src/lib/client/content/shared/utils.ts +28 -0
- package/src/lib/client/models/index.ts +19 -0
- package/src/lib/client/models/types.ts +14 -0
- package/src/lib/client/sdk-js-client.spec.ts +483 -0
- package/src/lib/client/sdk-js-client.ts +442 -0
- package/src/lib/editor/listeners/listeners.spec.ts +119 -0
- package/src/lib/editor/listeners/listeners.ts +223 -0
- package/src/lib/editor/models/{client.model.d.ts → client.model.ts} +19 -16
- package/src/lib/editor/models/{editor.model.d.ts → editor.model.ts} +9 -5
- package/src/lib/editor/models/{listeners.model.d.ts → listeners.model.ts} +9 -6
- package/src/lib/editor/sdk-editor-vtl.ts +31 -0
- package/src/lib/editor/sdk-editor.spec.ts +116 -0
- package/src/lib/editor/sdk-editor.ts +105 -0
- package/src/lib/editor/utils/editor.utils.spec.ts +206 -0
- package/src/lib/editor/utils/editor.utils.ts +258 -0
- package/src/lib/query-builder/lucene-syntax/{Equals.d.ts → Equals.ts} +83 -18
- package/src/lib/query-builder/lucene-syntax/Field.ts +40 -0
- package/src/lib/query-builder/lucene-syntax/{NotOperand.d.ts → NotOperand.ts} +18 -6
- package/src/lib/query-builder/lucene-syntax/{Operand.d.ts → Operand.ts} +21 -7
- package/src/lib/query-builder/sdk-query-builder.spec.ts +159 -0
- package/src/lib/query-builder/sdk-query-builder.ts +87 -0
- package/src/lib/query-builder/utils/index.ts +179 -0
- package/src/lib/utils/graphql/transforms.spec.ts +150 -0
- package/src/lib/utils/graphql/transforms.ts +99 -0
- package/src/lib/utils/page/common-utils.spec.ts +37 -0
- package/src/lib/utils/page/common-utils.ts +64 -0
- package/tsconfig.json +22 -0
- package/tsconfig.lib.json +13 -0
- package/tsconfig.spec.json +9 -0
- package/index.cjs.d.ts +0 -1
- package/index.cjs.default.js +0 -1
- package/index.cjs.js +0 -1490
- package/index.cjs.mjs +0 -2
- package/index.esm.d.ts +0 -1
- package/index.esm.js +0 -1481
- package/src/index.d.ts +0 -6
- package/src/lib/client/content/builders/collection/collection.d.ts +0 -148
- package/src/lib/client/content/content-api.d.ts +0 -78
- package/src/lib/client/content/shared/const.d.ts +0 -3
- package/src/lib/client/content/shared/types.d.ts +0 -62
- package/src/lib/client/content/shared/utils.d.ts +0 -12
- package/src/lib/client/models/index.d.ts +0 -1
- package/src/lib/client/models/types.d.ts +0 -5
- package/src/lib/client/sdk-js-client.d.ts +0 -193
- package/src/lib/editor/listeners/listeners.d.ts +0 -46
- package/src/lib/editor/sdk-editor-vtl.d.ts +0 -6
- package/src/lib/editor/sdk-editor.d.ts +0 -29
- package/src/lib/editor/utils/editor.utils.d.ts +0 -83
- package/src/lib/query-builder/lucene-syntax/Field.d.ts +0 -23
- package/src/lib/query-builder/sdk-query-builder.d.ts +0 -42
- package/src/lib/query-builder/utils/index.d.ts +0 -91
- package/src/lib/utils/graphql/transforms.d.ts +0 -11
- package/src/lib/utils/page/common-utils.d.ts +0 -11
- /package/src/lib/query-builder/lucene-syntax/{index.d.ts → index.ts} +0 -0
- /package/src/lib/utils/{index.d.ts → index.ts} +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Equals } from '../../../query-builder/lucene-syntax';
|
|
2
|
+
import { QueryBuilder } from '../../../query-builder/sdk-query-builder';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Model to sort by fields.
|
|
6
|
+
*/
|
|
7
|
+
export type SortBy = {
|
|
8
|
+
/**
|
|
9
|
+
* The field to sort by.
|
|
10
|
+
*/
|
|
11
|
+
field: string;
|
|
12
|
+
/**
|
|
13
|
+
* The order of sorting: 'asc' for ascending, 'desc' for descending.
|
|
14
|
+
*/
|
|
15
|
+
order: 'asc' | 'desc';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Callback to build a query.
|
|
20
|
+
*
|
|
21
|
+
* @callback BuildQuery
|
|
22
|
+
* @param {QueryBuilder} qb - The query builder instance.
|
|
23
|
+
* @returns {Equals} The built query.
|
|
24
|
+
*/
|
|
25
|
+
export type BuildQuery = (qb: QueryBuilder) => Equals;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Main fields of a Contentlet (Inherited from the Content Type).
|
|
29
|
+
*/
|
|
30
|
+
export interface ContentTypeMainFields {
|
|
31
|
+
hostName: string;
|
|
32
|
+
modDate: string;
|
|
33
|
+
publishDate: string;
|
|
34
|
+
title: string;
|
|
35
|
+
baseType: string;
|
|
36
|
+
inode: string;
|
|
37
|
+
archived: boolean;
|
|
38
|
+
ownerName: string;
|
|
39
|
+
host: string;
|
|
40
|
+
working: boolean;
|
|
41
|
+
locked: boolean;
|
|
42
|
+
stInode: string;
|
|
43
|
+
contentType: string;
|
|
44
|
+
live: boolean;
|
|
45
|
+
owner: string;
|
|
46
|
+
identifier: string;
|
|
47
|
+
publishUserName: string;
|
|
48
|
+
publishUser: string;
|
|
49
|
+
languageId: number;
|
|
50
|
+
creationDate: string;
|
|
51
|
+
url: string;
|
|
52
|
+
titleImage: string;
|
|
53
|
+
modUserName: string;
|
|
54
|
+
hasLiveVersion: boolean;
|
|
55
|
+
folder: string;
|
|
56
|
+
hasTitleImage: boolean;
|
|
57
|
+
sortOrder: number;
|
|
58
|
+
modUser: string;
|
|
59
|
+
__icon__: string;
|
|
60
|
+
contentTypeIcon: string;
|
|
61
|
+
variant: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The contentlet has the main fields and the custom fields of the content type.
|
|
66
|
+
*
|
|
67
|
+
* @template T - The custom fields of the content type.
|
|
68
|
+
*/
|
|
69
|
+
export type Contentlet<T> = T & ContentTypeMainFields;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Callback for a fulfilled promise.
|
|
73
|
+
*
|
|
74
|
+
* @template T - The type of the response.
|
|
75
|
+
* @callback OnFullfilled
|
|
76
|
+
* @param {GetCollectionResponse<T>} value - The response value.
|
|
77
|
+
* @returns {GetCollectionResponse<T> | PromiseLike<GetCollectionResponse<T>> | void} The processed response or a promise.
|
|
78
|
+
*/
|
|
79
|
+
export type OnFullfilled<T> =
|
|
80
|
+
| ((
|
|
81
|
+
value: GetCollectionResponse<T>
|
|
82
|
+
) => GetCollectionResponse<T> | PromiseLike<GetCollectionResponse<T>> | void)
|
|
83
|
+
| undefined
|
|
84
|
+
| null;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Callback for a rejected promise.
|
|
88
|
+
*
|
|
89
|
+
* @callback OnRejected
|
|
90
|
+
* @param {GetCollectionError} error - The error object.
|
|
91
|
+
* @returns {GetCollectionError | PromiseLike<GetCollectionError> | void} The processed error or a promise.
|
|
92
|
+
*/
|
|
93
|
+
export type OnRejected =
|
|
94
|
+
| ((error: GetCollectionError) => GetCollectionError | PromiseLike<GetCollectionError> | void)
|
|
95
|
+
| undefined
|
|
96
|
+
| null;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Response of the get collection method.
|
|
100
|
+
*
|
|
101
|
+
* @template T - The type of the contentlet.
|
|
102
|
+
*/
|
|
103
|
+
export interface GetCollectionResponse<T> {
|
|
104
|
+
/**
|
|
105
|
+
* The list of contentlets.
|
|
106
|
+
*/
|
|
107
|
+
contentlets: Contentlet<T>[];
|
|
108
|
+
/**
|
|
109
|
+
* The current page number.
|
|
110
|
+
*/
|
|
111
|
+
page: number;
|
|
112
|
+
/**
|
|
113
|
+
* The size of the page.
|
|
114
|
+
*/
|
|
115
|
+
size: number;
|
|
116
|
+
/**
|
|
117
|
+
* The total number of contentlets.
|
|
118
|
+
*/
|
|
119
|
+
total: number;
|
|
120
|
+
/**
|
|
121
|
+
* The fields by which the contentlets are sorted.
|
|
122
|
+
*/
|
|
123
|
+
sortedBy?: SortBy[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Raw response of the get collection method.
|
|
128
|
+
*
|
|
129
|
+
* @template T - The type of the contentlet.
|
|
130
|
+
*/
|
|
131
|
+
export interface GetCollectionRawResponse<T> {
|
|
132
|
+
entity: {
|
|
133
|
+
jsonObjectView: {
|
|
134
|
+
/**
|
|
135
|
+
* The list of contentlets.
|
|
136
|
+
*/
|
|
137
|
+
contentlets: Contentlet<T>[];
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* The size of the results.
|
|
141
|
+
*/
|
|
142
|
+
resultsSize: number;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Error object for the get collection method.
|
|
148
|
+
*/
|
|
149
|
+
export interface GetCollectionError {
|
|
150
|
+
/**
|
|
151
|
+
* The status code of the error.
|
|
152
|
+
*/
|
|
153
|
+
status: number;
|
|
154
|
+
[key: string]: unknown;
|
|
155
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CONTENT_TYPE_MAIN_FIELDS } from './const';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @description
|
|
5
|
+
* Sanitizes the query for the given content type.
|
|
6
|
+
* It replaces the fields that are not content type fields with the correct format.
|
|
7
|
+
* Example: +field: -> +contentTypeVar.field:
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const query = '+field: value';
|
|
13
|
+
* const contentType = 'contentTypeVar';
|
|
14
|
+
* const sanitizedQuery = sanitizeQueryForContentType(query, contentType); // Output: '+contentTypeVar.field: value'
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @export
|
|
18
|
+
* @param {string} query - The query string to be sanitized.
|
|
19
|
+
* @param {string} contentType - The content type to be used for formatting the fields.
|
|
20
|
+
* @returns {string} The sanitized query string.
|
|
21
|
+
*/
|
|
22
|
+
export function sanitizeQueryForContentType(query: string, contentType: string): string {
|
|
23
|
+
return query.replace(/\+([^+:]*?):/g, (original, field) => {
|
|
24
|
+
return !CONTENT_TYPE_MAIN_FIELDS.includes(field) // Fields that are not content type fields
|
|
25
|
+
? `+${contentType}.${field}:` // Should have this format: +contentTypeVar.field:
|
|
26
|
+
: original; // Return the field if it is a content type field
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A record of HTTP status codes and their corresponding error messages.
|
|
3
|
+
*
|
|
4
|
+
* @type {Record<number, string>}
|
|
5
|
+
* @property {string} 401 - Unauthorized. Check the token and try again.
|
|
6
|
+
* @property {string} 403 - Forbidden. Check the permissions and try again.
|
|
7
|
+
* @property {string} 404 - Not Found. Check the URL and try again.
|
|
8
|
+
* @property {string} 500 - Internal Server Error. Try again later.
|
|
9
|
+
* @property {string} 502 - Bad Gateway. Try again later.
|
|
10
|
+
* @property {string} 503 - Service Unavailable. Try again later.
|
|
11
|
+
*/
|
|
12
|
+
export const ErrorMessages: Record<number, string> = {
|
|
13
|
+
401: 'Unauthorized. Check the token and try again.',
|
|
14
|
+
403: 'Forbidden. Check the permissions and try again.',
|
|
15
|
+
404: 'Not Found. Check the URL and try again.',
|
|
16
|
+
500: 'Internal Server Error. Try again later.',
|
|
17
|
+
502: 'Bad Gateway. Try again later.',
|
|
18
|
+
503: 'Service Unavailable. Try again later.'
|
|
19
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a listener for DotcmsClientListener.
|
|
3
|
+
*
|
|
4
|
+
* @typedef {Object} DotcmsClientListener
|
|
5
|
+
* @property {string} action - The action that triggers the event.
|
|
6
|
+
* @property {string} event - The name of the event.
|
|
7
|
+
* @property {function(...args: any[]): void} callback - The callback function to handle the event.
|
|
8
|
+
*/
|
|
9
|
+
export type DotcmsClientListener = {
|
|
10
|
+
action: string;
|
|
11
|
+
event: string;
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
callback: (...args: any[]) => void;
|
|
14
|
+
};
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
import { Content } from './content/content-api';
|
|
4
|
+
import { ClientConfig, DotCmsClient } from './sdk-js-client';
|
|
5
|
+
|
|
6
|
+
import * as dotcmsEditor from '../editor/sdk-editor';
|
|
7
|
+
|
|
8
|
+
global.fetch = jest.fn();
|
|
9
|
+
|
|
10
|
+
class TestDotCmsClient extends DotCmsClient {
|
|
11
|
+
/**
|
|
12
|
+
* Override the init method to test the static method
|
|
13
|
+
* Allows to test Singleton pattern with different configurations
|
|
14
|
+
*
|
|
15
|
+
* @param {*} config
|
|
16
|
+
* @return {*}
|
|
17
|
+
* @memberof TestDotCmsClient
|
|
18
|
+
*/
|
|
19
|
+
static override init(config: ClientConfig) {
|
|
20
|
+
return (this.instance = new TestDotCmsClient(config));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Utility function to mock fetch responses
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
const mockFetchResponse = (body: any, ok = true, status = 200) => {
|
|
27
|
+
(fetch as jest.Mock).mockImplementationOnce(() =>
|
|
28
|
+
Promise.resolve({
|
|
29
|
+
ok,
|
|
30
|
+
status,
|
|
31
|
+
json: () => Promise.resolve(body)
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('DotCmsClient', () => {
|
|
37
|
+
describe('with full configuration', () => {
|
|
38
|
+
let client: TestDotCmsClient;
|
|
39
|
+
let isInsideEditorSpy: jest.SpyInstance<boolean>;
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
(fetch as jest.Mock).mockClear();
|
|
43
|
+
|
|
44
|
+
client = TestDotCmsClient.init({
|
|
45
|
+
dotcmsUrl: 'http://localhost',
|
|
46
|
+
siteId: '123456',
|
|
47
|
+
authToken: 'ABC'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
isInsideEditorSpy = jest.spyOn(dotcmsEditor, 'isInsideEditor');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('init', () => {
|
|
54
|
+
it('should initialize with valid configuration', () => {
|
|
55
|
+
const config = {
|
|
56
|
+
dotcmsUrl: 'https://example.com',
|
|
57
|
+
siteId: '123456',
|
|
58
|
+
authToken: 'ABC'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const client = TestDotCmsClient.init(config);
|
|
62
|
+
expect(client).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should throw error on missing dotcmsUrl', () => {
|
|
66
|
+
const config = {
|
|
67
|
+
dotcmsUrl: '',
|
|
68
|
+
siteId: '123456',
|
|
69
|
+
authToken: 'ABC'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
expect(() => {
|
|
73
|
+
TestDotCmsClient.init(config);
|
|
74
|
+
}).toThrow("Invalid configuration - 'dotcmsUrl' is required");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should throw error if dotcmsUrl is not a valid URL', () => {
|
|
78
|
+
const config = {
|
|
79
|
+
dotcmsUrl: '//example.com',
|
|
80
|
+
siteId: '123456',
|
|
81
|
+
authToken: 'ABC'
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
expect(() => {
|
|
85
|
+
TestDotCmsClient.init(config);
|
|
86
|
+
}).toThrow("Invalid configuration - 'dotcmsUrl' must be a valid URL");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should throw error on missing authToken', () => {
|
|
90
|
+
const config = {
|
|
91
|
+
dotcmsUrl: 'https://example.com',
|
|
92
|
+
siteId: '123456',
|
|
93
|
+
authToken: ''
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
expect(() => {
|
|
97
|
+
TestDotCmsClient.init(config);
|
|
98
|
+
}).toThrow("Invalid configuration - 'authToken' is required");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should use a clean dotcmsUrl when passing an slash at the end', async () => {
|
|
102
|
+
const config = {
|
|
103
|
+
dotcmsUrl: 'https://example.com/',
|
|
104
|
+
siteId: '123456',
|
|
105
|
+
authToken: 'ABC'
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const mockResponse = { content: 'Page data' };
|
|
109
|
+
mockFetchResponse(mockResponse);
|
|
110
|
+
|
|
111
|
+
const client = TestDotCmsClient.init(config);
|
|
112
|
+
|
|
113
|
+
await client.page.get({ path: '/home' });
|
|
114
|
+
|
|
115
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
116
|
+
'https://example.com/api/v1/page/json/home?host_id=123456',
|
|
117
|
+
{
|
|
118
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should use a clean dotcmsUrl when passing a route at the end', async () => {
|
|
124
|
+
const config = {
|
|
125
|
+
dotcmsUrl: 'https://example.com/some/cool/route',
|
|
126
|
+
siteId: '123456',
|
|
127
|
+
authToken: 'ABC'
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const mockResponse = { content: 'Page data' };
|
|
131
|
+
mockFetchResponse(mockResponse);
|
|
132
|
+
|
|
133
|
+
const client = TestDotCmsClient.init(config);
|
|
134
|
+
|
|
135
|
+
await client.page.get({ path: '/home' });
|
|
136
|
+
|
|
137
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
138
|
+
'https://example.com/api/v1/page/json/home?host_id=123456',
|
|
139
|
+
{
|
|
140
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should use a clean dotcmsUrl when passing a port and an slash at the end', async () => {
|
|
146
|
+
const config = {
|
|
147
|
+
dotcmsUrl: 'https://example.com:3434/cool/route',
|
|
148
|
+
siteId: '123456',
|
|
149
|
+
authToken: 'ABC'
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const mockResponse = { content: 'Page data' };
|
|
153
|
+
mockFetchResponse(mockResponse);
|
|
154
|
+
|
|
155
|
+
const client = TestDotCmsClient.init(config);
|
|
156
|
+
|
|
157
|
+
await client.page.get({ path: '/home' });
|
|
158
|
+
|
|
159
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
160
|
+
'https://example.com:3434/api/v1/page/json/home?host_id=123456',
|
|
161
|
+
{
|
|
162
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should use a clean dotcmsUrl when passing a port and an slash at the end', async () => {
|
|
168
|
+
const config = {
|
|
169
|
+
dotcmsUrl: 'https://example.com:3434/',
|
|
170
|
+
siteId: '123456',
|
|
171
|
+
authToken: 'ABC'
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const mockResponse = { content: 'Page data' };
|
|
175
|
+
mockFetchResponse(mockResponse);
|
|
176
|
+
|
|
177
|
+
const client = TestDotCmsClient.init(config);
|
|
178
|
+
|
|
179
|
+
await client.page.get({ path: '/home' });
|
|
180
|
+
|
|
181
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
182
|
+
'https://example.com:3434/api/v1/page/json/home?host_id=123456',
|
|
183
|
+
{
|
|
184
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('page.get', () => {
|
|
191
|
+
it('should fetch page data successfully', async () => {
|
|
192
|
+
const mockResponse = { content: 'Page data' };
|
|
193
|
+
mockFetchResponse({ entity: mockResponse });
|
|
194
|
+
|
|
195
|
+
const data = await client.page.get({ path: '/home' });
|
|
196
|
+
|
|
197
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
198
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
199
|
+
'http://localhost/api/v1/page/json/home?host_id=123456',
|
|
200
|
+
{
|
|
201
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
expect(data).toEqual(mockResponse);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should throw an error if the path is not provided', async () => {
|
|
208
|
+
await expect(client.page.get({} as any)).rejects.toThrowError(
|
|
209
|
+
`The 'path' parameter is required for the Page API`
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should get the page for specified persona', () => {
|
|
214
|
+
const mockResponse = { content: 'Page data' };
|
|
215
|
+
mockFetchResponse(mockResponse);
|
|
216
|
+
|
|
217
|
+
client.page.get({ path: '/home', personaId: 'doe123' });
|
|
218
|
+
|
|
219
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
220
|
+
'http://localhost/api/v1/page/json/home?com.dotmarketing.persona.id=doe123&host_id=123456',
|
|
221
|
+
{
|
|
222
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should get the page for specified siteId', () => {
|
|
228
|
+
const mockResponse = { content: 'Page data' };
|
|
229
|
+
mockFetchResponse(mockResponse);
|
|
230
|
+
|
|
231
|
+
client.page.get({ path: '/home', siteId: 'host-123' });
|
|
232
|
+
|
|
233
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
234
|
+
'http://localhost/api/v1/page/json/home?host_id=host-123',
|
|
235
|
+
{
|
|
236
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should get the page for specified language', () => {
|
|
242
|
+
const mockResponse = { content: 'Page data' };
|
|
243
|
+
mockFetchResponse(mockResponse);
|
|
244
|
+
|
|
245
|
+
client.page.get({ path: '/home', language_id: 99 });
|
|
246
|
+
|
|
247
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
248
|
+
'http://localhost/api/v1/page/json/home?language_id=99&host_id=123456',
|
|
249
|
+
{
|
|
250
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should manage error response', () => {
|
|
256
|
+
const mockResponse = {};
|
|
257
|
+
mockFetchResponse(mockResponse, false, 401);
|
|
258
|
+
|
|
259
|
+
expect(client.page.get({ path: '/home' })).rejects.toEqual({
|
|
260
|
+
status: 401,
|
|
261
|
+
message: 'Unauthorized. Check the token and try again.'
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('editor.on', () => {
|
|
267
|
+
it('should listen to FETCH_PAGE_ASSET_FROM_UVE event', () => {
|
|
268
|
+
isInsideEditorSpy.mockReturnValue(true);
|
|
269
|
+
|
|
270
|
+
const callback = jest.fn();
|
|
271
|
+
client.editor.on('changes', callback);
|
|
272
|
+
|
|
273
|
+
const mockMessageEvent = {
|
|
274
|
+
data: {
|
|
275
|
+
name: 'SET_PAGE_DATA',
|
|
276
|
+
payload: { some: 'test' }
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
window.dispatchEvent(new MessageEvent('message', mockMessageEvent));
|
|
281
|
+
|
|
282
|
+
expect(callback).toHaveBeenCalledWith(mockMessageEvent.data.payload);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should do nothing if is outside editor', () => {
|
|
286
|
+
isInsideEditorSpy.mockReturnValue(false);
|
|
287
|
+
|
|
288
|
+
const callback = jest.fn();
|
|
289
|
+
client.editor.on('FETCH_PAGE_ASSET_FROM_UVE', callback);
|
|
290
|
+
|
|
291
|
+
const mockMessageEvent = {
|
|
292
|
+
data: {
|
|
293
|
+
name: 'SET_PAGE_DATA',
|
|
294
|
+
payload: { some: 'test' }
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
window.dispatchEvent(new MessageEvent('message', mockMessageEvent));
|
|
299
|
+
|
|
300
|
+
expect(callback).not.toHaveBeenCalled();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('editor.off', () => {
|
|
305
|
+
it('should remove a page event listener', () => {
|
|
306
|
+
isInsideEditorSpy.mockReturnValue(true);
|
|
307
|
+
|
|
308
|
+
const windowSpy = jest.spyOn(window, 'removeEventListener');
|
|
309
|
+
const callback = jest.fn();
|
|
310
|
+
client.editor.on('changes', callback);
|
|
311
|
+
|
|
312
|
+
client.editor.off('changes');
|
|
313
|
+
|
|
314
|
+
expect(windowSpy).toHaveBeenCalledWith('message', expect.anything());
|
|
315
|
+
|
|
316
|
+
const mockMessageEvent = {
|
|
317
|
+
data: {
|
|
318
|
+
name: 'SET_PAGE_DATA',
|
|
319
|
+
payload: { some: 'test' }
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
window.dispatchEvent(new MessageEvent('message', mockMessageEvent));
|
|
324
|
+
|
|
325
|
+
expect(callback).not.toHaveBeenCalled();
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe('content', () => {
|
|
330
|
+
it('should have an instance of the content API', () => {
|
|
331
|
+
expect(client.content instanceof Content).toBeTruthy();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('nav.get', () => {
|
|
336
|
+
it('should fetch navigation data successfully', async () => {
|
|
337
|
+
const mockResponse = { nav: 'Navigation data' };
|
|
338
|
+
mockFetchResponse(mockResponse);
|
|
339
|
+
|
|
340
|
+
const data = await client.nav.get({ path: '/', depth: 1 });
|
|
341
|
+
|
|
342
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
343
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost/api/v1/nav/?depth=1', {
|
|
344
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
345
|
+
});
|
|
346
|
+
expect(data).toEqual(mockResponse);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should correctly handle the root path', async () => {
|
|
350
|
+
const mockResponse = { nav: 'Root nav data' };
|
|
351
|
+
mockFetchResponse(mockResponse);
|
|
352
|
+
|
|
353
|
+
const data = await client.nav.get({ path: '/' });
|
|
354
|
+
|
|
355
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost/api/v1/nav/', {
|
|
356
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
357
|
+
});
|
|
358
|
+
expect(data).toEqual(mockResponse);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should throw an error if the path is not provided', async () => {
|
|
362
|
+
await expect(client.nav.get({} as any)).rejects.toThrowError(
|
|
363
|
+
`The 'path' parameter is required for the Nav API`
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('with minimal configuration', () => {
|
|
370
|
+
let client: DotCmsClient;
|
|
371
|
+
|
|
372
|
+
beforeEach(() => {
|
|
373
|
+
(fetch as jest.Mock).mockClear();
|
|
374
|
+
|
|
375
|
+
client = TestDotCmsClient.init({
|
|
376
|
+
dotcmsUrl: 'http://localhost',
|
|
377
|
+
authToken: 'ABC'
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should get the page without siteId', () => {
|
|
382
|
+
const mockResponse = { content: 'Page data' };
|
|
383
|
+
mockFetchResponse(mockResponse);
|
|
384
|
+
|
|
385
|
+
client.page.get({ path: '/home' });
|
|
386
|
+
|
|
387
|
+
expect(fetch).toHaveBeenCalledWith('http://localhost/api/v1/page/json/home', {
|
|
388
|
+
headers: { Authorization: 'Bearer ABC' }
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe('with requestOptions', () => {
|
|
394
|
+
let client: DotCmsClient;
|
|
395
|
+
|
|
396
|
+
beforeEach(() => {
|
|
397
|
+
(fetch as jest.Mock).mockClear();
|
|
398
|
+
|
|
399
|
+
client = TestDotCmsClient.init({
|
|
400
|
+
dotcmsUrl: 'http://localhost',
|
|
401
|
+
siteId: '123456',
|
|
402
|
+
authToken: 'ABC',
|
|
403
|
+
requestOptions: {
|
|
404
|
+
headers: {
|
|
405
|
+
'X-My-Header': 'my-value'
|
|
406
|
+
},
|
|
407
|
+
cache: 'no-cache'
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('should fetch page data with extra headers and cache', async () => {
|
|
413
|
+
const mockResponse = { content: 'Page data' };
|
|
414
|
+
mockFetchResponse({ entity: mockResponse });
|
|
415
|
+
|
|
416
|
+
const data = await client.page.get({ path: '/home' });
|
|
417
|
+
|
|
418
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
419
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
420
|
+
'http://localhost/api/v1/page/json/home?host_id=123456',
|
|
421
|
+
{
|
|
422
|
+
headers: { Authorization: 'Bearer ABC', 'X-My-Header': 'my-value' },
|
|
423
|
+
cache: 'no-cache'
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
expect(data).toEqual(mockResponse);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should fetch bav data with extra headers and cache', async () => {
|
|
430
|
+
const mockResponse = { content: 'Page data' };
|
|
431
|
+
mockFetchResponse(mockResponse);
|
|
432
|
+
|
|
433
|
+
const data = await client.nav.get();
|
|
434
|
+
|
|
435
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
436
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
437
|
+
'http://localhost/api/v1/nav/?depth=0&languageId=1',
|
|
438
|
+
{
|
|
439
|
+
headers: { Authorization: 'Bearer ABC', 'X-My-Header': 'my-value' },
|
|
440
|
+
cache: 'no-cache'
|
|
441
|
+
}
|
|
442
|
+
);
|
|
443
|
+
expect(data).toEqual(mockResponse);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
describe('Singleton pattern', () => {
|
|
448
|
+
it('should return the same instance and log a warning when calling init multiple times', () => {
|
|
449
|
+
const consoleWarnSpy = jest.spyOn(console, 'warn');
|
|
450
|
+
const expectedWarnMessage =
|
|
451
|
+
'DotCmsClient has already been initialized. Please use the instance to interact with the DotCMS API.';
|
|
452
|
+
const config = {
|
|
453
|
+
dotcmsUrl: 'https://example.com',
|
|
454
|
+
siteId: '123456',
|
|
455
|
+
authToken: 'ABC'
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const config2 = {
|
|
459
|
+
dotcmsUrl: 'https://example.com',
|
|
460
|
+
siteId: '123456',
|
|
461
|
+
authToken: 'DEF'
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const client1 = DotCmsClient.init(config);
|
|
465
|
+
const client2 = DotCmsClient.init(config2);
|
|
466
|
+
|
|
467
|
+
expect(client1).toBe(client2);
|
|
468
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(expectedWarnMessage);
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
describe('Properties', () => {
|
|
473
|
+
it('should return he current dotcmsUrl', () => {
|
|
474
|
+
TestDotCmsClient.init({
|
|
475
|
+
dotcmsUrl: 'http://localhost:8080',
|
|
476
|
+
siteId: '123456',
|
|
477
|
+
authToken: 'ABC'
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
expect(TestDotCmsClient.dotcmsUrl).toBe('http://localhost:8080');
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
});
|