@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
package/.eslintrc.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": ["../../../.eslintrc.base.json"],
3
+ "ignorePatterns": ["!**/*"],
4
+ "overrides": [
5
+ {
6
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7
+ "rules": {}
8
+ },
9
+ {
10
+ "files": ["*.ts", "*.tsx"],
11
+ "rules": {}
12
+ },
13
+ {
14
+ "files": ["*.js", "*.jsx"],
15
+ "rules": {}
16
+ }
17
+ ]
18
+ }
package/jest.config.ts ADDED
@@ -0,0 +1,15 @@
1
+ /* eslint-disable */
2
+ export default {
3
+ displayName: 'sdk-client',
4
+ preset: '../../../jest.preset.js',
5
+ globals: {
6
+ 'ts-jest': {
7
+ tsconfig: '<rootDir>/tsconfig.spec.json'
8
+ }
9
+ },
10
+ transform: {
11
+ '^.+\\.[tj]s$': 'ts-jest'
12
+ },
13
+ moduleFileExtensions: ['ts', 'js', 'html'],
14
+ coverageDirectory: '../../../coverage/libs/sdk/client'
15
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotcms/client",
3
- "version": "0.0.1-alpha.38",
3
+ "version": "0.0.1-alpha.39",
4
4
  "description": "Official JavaScript library for interacting with DotCMS REST APIs.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,17 +21,5 @@
21
21
  "bugs": {
22
22
  "url": "https://github.com/dotCMS/core/issues"
23
23
  },
24
- "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/client/README.md",
25
- "exports": {
26
- "./package.json": "./package.json",
27
- ".": {
28
- "module": "./index.esm.js",
29
- "types": "./index.esm.d.ts",
30
- "import": "./index.cjs.mjs",
31
- "default": "./index.cjs.js"
32
- }
33
- },
34
- "module": "./index.esm.js",
35
- "main": "./index.cjs.js",
36
- "types": "./index.esm.d.ts"
37
- }
24
+ "homepage": "https://github.com/dotCMS/core/tree/master/core-web/libs/sdk/client/README.md"
25
+ }
package/project.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "sdk-client",
3
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "libs/sdk/client/src",
5
+ "projectType": "library",
6
+ "targets": {
7
+ "build": {
8
+ "executor": "@nx/rollup:rollup",
9
+ "outputs": ["{options.outputPath}"],
10
+ "options": {
11
+ "format": ["esm", "cjs"],
12
+ "compiler": "tsc",
13
+ "generateExportsField": true,
14
+ "assets": [{ "input": "libs/sdk/client", "output": ".", "glob": "*.md" }],
15
+ "outputPath": "dist/libs/sdk/client",
16
+ "main": "libs/sdk/client/src/index.ts",
17
+ "tsConfig": "libs/sdk/client/tsconfig.lib.json"
18
+ }
19
+ },
20
+ "build:js": {
21
+ "executor": "@nx/esbuild:esbuild",
22
+ "outputs": ["{options.outputPath}"],
23
+ "options": {
24
+ "outputPath": "../dotCMS/src/main/webapp/html/js/editor-js",
25
+ "outputFileName": "sdk-editor",
26
+ "format": ["esm"],
27
+ "tsConfig": "libs/sdk/client/tsconfig.lib.json",
28
+ "project": "libs/sdk/client/package.json",
29
+ "entryFile": "libs/sdk/client/src/lib/editor/sdk-editor-vtl.ts",
30
+ "external": ["react/jsx-runtime"],
31
+ "rollupConfig": "@nrwl/react/plugins/bundle-rollup",
32
+ "compiler": "tsc",
33
+ "extractCss": false,
34
+ "minify": true
35
+ }
36
+ },
37
+ "publish": {
38
+ "executor": "nx:run-commands",
39
+ "options": {
40
+ "command": "node tools/scripts/publish.mjs sdk-client {args.ver} {args.tag}"
41
+ },
42
+ "dependsOn": ["build"]
43
+ },
44
+ "nx-release-publish": {
45
+ "options": {
46
+ "packageRoot": "dist/libs/sdk/client"
47
+ }
48
+ },
49
+ "lint": {
50
+ "executor": "@nx/eslint:lint",
51
+ "outputs": ["{options.outputFile}"],
52
+ "options": {
53
+ "lintFilePatterns": ["libs/sdk/client/**/*.ts"]
54
+ }
55
+ },
56
+ "test": {
57
+ "executor": "@nrwl/jest:jest",
58
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
59
+ "options": {
60
+ "jestConfig": "libs/sdk/client/jest.config.ts",
61
+ "passWithNoTests": true
62
+ },
63
+ "configurations": {
64
+ "ci": {
65
+ "ci": true,
66
+ "codeCoverage": true
67
+ }
68
+ }
69
+ }
70
+ },
71
+ "tags": []
72
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { ClientConfig, DotCmsClient } from './lib/client/sdk-js-client';
2
+ import { CUSTOMER_ACTIONS, postMessageToEditor } from './lib/editor/models/client.model';
3
+ import {
4
+ CustomClientParams,
5
+ DotCMSPageEditorConfig,
6
+ EditorConfig
7
+ } from './lib/editor/models/editor.model';
8
+ import {
9
+ destroyEditor,
10
+ initEditor,
11
+ isInsideEditor,
12
+ updateNavigation
13
+ } from './lib/editor/sdk-editor';
14
+ import { getPageRequestParams, graphqlToPageEntity } from './lib/utils';
15
+
16
+ export {
17
+ graphqlToPageEntity,
18
+ getPageRequestParams,
19
+ isInsideEditor,
20
+ DotCmsClient,
21
+ DotCMSPageEditorConfig,
22
+ CUSTOMER_ACTIONS,
23
+ CustomClientParams,
24
+ postMessageToEditor,
25
+ EditorConfig,
26
+ initEditor,
27
+ updateNavigation,
28
+ destroyEditor,
29
+ ClientConfig
30
+ };
@@ -0,0 +1,515 @@
1
+ /// <reference types="jest" />
2
+
3
+ import { CollectionBuilder } from './collection';
4
+
5
+ import { Equals } from '../../../../query-builder/lucene-syntax';
6
+ import { ClientOptions } from '../../../sdk-js-client';
7
+ import { CONTENT_API_URL } from '../../shared/const';
8
+ import { SortBy } from '../../shared/types';
9
+
10
+ global.fetch = jest.fn().mockReturnValue(
11
+ Promise.resolve({
12
+ ok: true,
13
+ json: () =>
14
+ Promise.resolve({
15
+ entity: {
16
+ jsonObjectView: {
17
+ contentlets: []
18
+ },
19
+ resultsSize: 0
20
+ }
21
+ })
22
+ })
23
+ );
24
+
25
+ describe('CollectionBuilder', () => {
26
+ const requestOptions: ClientOptions = {
27
+ cache: 'no-cache' // To simulate a valid request
28
+ };
29
+
30
+ const serverUrl = 'http://localhost:8080';
31
+
32
+ const baseRequest = {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json'
36
+ },
37
+ ...requestOptions
38
+ };
39
+
40
+ const requestURL = `${serverUrl}${CONTENT_API_URL}`;
41
+
42
+ beforeEach(() => {
43
+ (fetch as jest.Mock).mockClear();
44
+ });
45
+
46
+ it('should initialize with valid configuration', async () => {
47
+ const contentType = 'my-content-type';
48
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
49
+ expect(collectionBuilder).toBeDefined();
50
+ });
51
+
52
+ describe('successful requests', () => {
53
+ it('should build a query for a basic collection', async () => {
54
+ const contentType = 'song';
55
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
56
+
57
+ await collectionBuilder;
58
+
59
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
60
+ ...baseRequest,
61
+ body: JSON.stringify({
62
+ query: '+contentType:song +languageId:1 +live:true',
63
+ render: false,
64
+ limit: 10,
65
+ offset: 0,
66
+ depth: 0
67
+ })
68
+ });
69
+ });
70
+
71
+ it('should return the contentlets in the mapped response', async () => {
72
+ const contentType = 'song';
73
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
74
+
75
+ const response = await collectionBuilder;
76
+
77
+ expect(response).toEqual({
78
+ contentlets: [],
79
+ page: 1,
80
+ size: 0,
81
+ total: 0
82
+ });
83
+ });
84
+
85
+ it('should return the contentlets in the mapped response with sort', async () => {
86
+ const contentType = 'song';
87
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
88
+
89
+ const sortBy: SortBy[] = [
90
+ {
91
+ field: 'title',
92
+ order: 'asc'
93
+ },
94
+ {
95
+ field: 'duration',
96
+ order: 'desc'
97
+ }
98
+ ];
99
+
100
+ const response = await collectionBuilder.sortBy(sortBy);
101
+
102
+ expect(response).toEqual({
103
+ contentlets: [],
104
+ page: 1,
105
+ size: 0,
106
+ total: 0,
107
+ sortedBy: sortBy
108
+ });
109
+ });
110
+
111
+ it('should build a query for a collection with a specific language', async () => {
112
+ const contentType = 'ringsOfPower';
113
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
114
+
115
+ await collectionBuilder.language(13);
116
+
117
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
118
+ ...baseRequest,
119
+ body: JSON.stringify({
120
+ query: '+contentType:ringsOfPower +languageId:13 +live:true',
121
+ render: false,
122
+ limit: 10,
123
+ offset: 0,
124
+ depth: 0
125
+ })
126
+ });
127
+ });
128
+
129
+ it('should build a query for a collection with render on true', async () => {
130
+ const contentType = 'boringContentType';
131
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
132
+
133
+ await collectionBuilder.render();
134
+
135
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
136
+ ...baseRequest,
137
+ body: JSON.stringify({
138
+ query: '+contentType:boringContentType +languageId:1 +live:true',
139
+ render: true,
140
+ limit: 10,
141
+ offset: 0,
142
+ depth: 0
143
+ })
144
+ });
145
+ });
146
+
147
+ it("should build a query with multiply sortBy's", async () => {
148
+ const contentType = 'jedi';
149
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
150
+
151
+ await collectionBuilder.sortBy([
152
+ {
153
+ field: 'name',
154
+ order: 'asc'
155
+ },
156
+ {
157
+ field: 'force',
158
+ order: 'desc'
159
+ },
160
+ {
161
+ field: 'midichlorians',
162
+ order: 'desc'
163
+ }
164
+ ]);
165
+
166
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
167
+ ...baseRequest,
168
+ body: JSON.stringify({
169
+ query: '+contentType:jedi +languageId:1 +live:true',
170
+ render: false,
171
+ sort: 'name asc,force desc,midichlorians desc',
172
+ limit: 10,
173
+ offset: 0,
174
+ depth: 0
175
+ })
176
+ });
177
+ });
178
+
179
+ it('should build a query with a specific depth', async () => {
180
+ const contentType = 'droid';
181
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
182
+
183
+ await collectionBuilder.depth(2);
184
+
185
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
186
+ ...baseRequest,
187
+ body: JSON.stringify({
188
+ query: '+contentType:droid +languageId:1 +live:true',
189
+ render: false,
190
+ limit: 10,
191
+ offset: 0,
192
+ depth: 2
193
+ })
194
+ });
195
+ });
196
+
197
+ it('should build a query with a specific limit and page', async () => {
198
+ const contentType = 'ship';
199
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
200
+
201
+ await collectionBuilder.limit(20).page(3);
202
+
203
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
204
+ ...baseRequest,
205
+ body: JSON.stringify({
206
+ query: '+contentType:ship +languageId:1 +live:true',
207
+ render: false,
208
+ limit: 20,
209
+ offset: 40,
210
+ depth: 0
211
+ })
212
+ });
213
+ });
214
+
215
+ it('should build a query with an specific query with main fields and custom fields of the content type', async () => {
216
+ const contentType = 'lightsaber';
217
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
218
+
219
+ await collectionBuilder
220
+ .query(
221
+ (
222
+ qb // kyberCrystal is a custom field
223
+ ) => qb.field('kyberCrystal').equals('red')
224
+ )
225
+ .query('+modDate:2024-05-28'); // modDate is a main field so it doesn't need to specify the content type
226
+
227
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
228
+ ...baseRequest,
229
+ body: JSON.stringify({
230
+ query: '+lightsaber.kyberCrystal:red +contentType:lightsaber +languageId:1 +live:true +modDate:2024-05-28',
231
+ render: false,
232
+ limit: 10,
233
+ offset: 0,
234
+ depth: 0
235
+ })
236
+ });
237
+ });
238
+
239
+ it("should throw an error if the query doesn't end in an instance of Equals", async () => {
240
+ const contentType = 'jedi';
241
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
242
+
243
+ try {
244
+ // Force the error
245
+ await collectionBuilder.query((qb) => qb.field('name') as unknown as Equals);
246
+ } catch (error) {
247
+ expect(error).toEqual(
248
+ new Error(
249
+ 'Provided query is not valid. A query should end in an equals method call.\nExample:\n(queryBuilder) => queryBuilder.field("title").equals("Hello World")\nSee documentation for more information.'
250
+ )
251
+ );
252
+ }
253
+
254
+ expect(fetch).not.toHaveBeenCalled();
255
+ });
256
+
257
+ it('should throw an error if the parameter for query is not a function or string', async () => {
258
+ const contentType = 'jedi';
259
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
260
+
261
+ try {
262
+ // Force the error
263
+ await collectionBuilder.query({} as string);
264
+ } catch (error) {
265
+ expect(error).toEqual(
266
+ new Error(
267
+ `Parameter for query method should be a buildQuery function or a string.\nExample:\nclient.content.getCollection('Activity').query((queryBuilder) => queryBuilder.field('title').equals('Hello World'))\nor\nclient.content.getCollection('Activity').query('+Activity.title:"Hello World"') \nSee documentation for more information.`
268
+ )
269
+ );
270
+ }
271
+
272
+ expect(fetch).not.toHaveBeenCalled();
273
+ });
274
+
275
+ it('should throw an error if the depth is out of range (positive value)', async () => {
276
+ const contentType = 'jedi';
277
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
278
+
279
+ try {
280
+ // Force the error
281
+ await collectionBuilder.depth(5);
282
+ } catch (error) {
283
+ expect(error).toEqual(new Error('Depth value must be between 0 and 3'));
284
+ }
285
+
286
+ expect(fetch).not.toHaveBeenCalled();
287
+ });
288
+
289
+ it('should throw an error if the depth is out of range (negative value)', async () => {
290
+ const contentType = 'jedi';
291
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
292
+
293
+ try {
294
+ // Force the error
295
+ await collectionBuilder.depth(-5);
296
+ } catch (error) {
297
+ expect(error).toEqual(new Error('Depth value must be between 0 and 3'));
298
+ }
299
+
300
+ expect(fetch).not.toHaveBeenCalled();
301
+ });
302
+
303
+ it('should build a query for draft content', async () => {
304
+ const contentType = 'draftContent';
305
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
306
+
307
+ await collectionBuilder.draft();
308
+
309
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
310
+ ...baseRequest,
311
+ body: JSON.stringify({
312
+ query: '+contentType:draftContent +languageId:1 +live:false',
313
+ render: false,
314
+ limit: 10,
315
+ offset: 0,
316
+ depth: 0
317
+ })
318
+ });
319
+ });
320
+
321
+ it('should build a query for a collection with a specific variant', async () => {
322
+ const contentType = 'adventure';
323
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
324
+
325
+ await collectionBuilder.variant('dimension-1334-adventure');
326
+
327
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
328
+ ...baseRequest,
329
+ body: JSON.stringify({
330
+ query: '+contentType:adventure +variant:dimension-1334-adventure +languageId:1 +live:true',
331
+ render: false,
332
+ limit: 10,
333
+ offset: 0,
334
+ depth: 0
335
+ })
336
+ });
337
+ });
338
+
339
+ it('should handle all the query methods on GetCollection', async () => {
340
+ const contentType = 'forceSensitive';
341
+ const collectionBuilder = new CollectionBuilder(requestOptions, serverUrl, contentType);
342
+
343
+ // be sure that this test is updated when new methods are added
344
+ let methods = Object.getOwnPropertyNames(
345
+ Object.getPrototypeOf(collectionBuilder)
346
+ ) as Array<keyof CollectionBuilder>;
347
+
348
+ // Remove the constructor and the methods that are not part of the query builder.
349
+ // Fetch method is removed because it is the one that makes the request and we already test that
350
+ // For example: ["constructor", "thisMethodIsPrivate", "thisMethodIsNotAQueryMethod", "formatQuery"]
351
+ const methodsToIgnore = ['constructor', 'formatResponse', 'fetchContentApi'];
352
+
353
+ // Filter to take only the methods that are part of the query builder
354
+ methods = methods.filter((method) => {
355
+ return (
356
+ !methodsToIgnore.includes(method) &&
357
+ typeof collectionBuilder[method] === 'function'
358
+ );
359
+ });
360
+
361
+ // Spy on all the methods
362
+ methods.forEach((method) => {
363
+ jest.spyOn(collectionBuilder, method as keyof CollectionBuilder);
364
+ });
365
+
366
+ // Start of the test
367
+
368
+ // Call all the methods and fetch the content
369
+ await collectionBuilder
370
+ .language(13) // Language Id
371
+ .render() // To retrieve the content with the render
372
+ .sortBy([
373
+ // Sort by multiple fields
374
+ {
375
+ field: 'name',
376
+ order: 'asc'
377
+ },
378
+ {
379
+ field: 'midichlorians',
380
+ order: 'desc'
381
+ }
382
+ ])
383
+ .depth(2) // Depth of the content for relationships
384
+ .limit(20) // Limit of content per page
385
+ .page(3) // Page to fetch
386
+ .query(
387
+ (
388
+ qb // Lucene query to append to the main query for more complex queries
389
+ ) =>
390
+ qb
391
+ .field('kyberCrystal')
392
+ .equals('red')
393
+ .and()
394
+ .equals('blue')
395
+ .field('master')
396
+ .equals('Yoda')
397
+ .or()
398
+ .equals('Obi-Wan')
399
+ )
400
+ .draft() // To retrieve the draft content
401
+ .variant('legends-forceSensitive') // Variant of the content
402
+ .query('+modDate:2024-05-28 +conhost:MyCoolSite'); // Raw query to append to the main query // Fetch the content
403
+
404
+ // Check that the request was made with the correct query
405
+ expect(fetch).toHaveBeenCalledWith(requestURL, {
406
+ ...baseRequest,
407
+ body: JSON.stringify({
408
+ query: '+forceSensitive.kyberCrystal:red AND blue +forceSensitive.master:Yoda OR Obi-Wan +contentType:forceSensitive +variant:legends-forceSensitive +languageId:13 +live:false +modDate:2024-05-28 +conhost:MyCoolSite',
409
+ render: true,
410
+ sort: 'name asc,midichlorians desc',
411
+ limit: 20,
412
+ offset: 40,
413
+ depth: 2
414
+ })
415
+ });
416
+
417
+ // Chech that all functions for the queryBuilder were called
418
+ methods.forEach((method) => {
419
+ expect(collectionBuilder[method]).toHaveBeenCalled();
420
+ });
421
+ });
422
+ });
423
+
424
+ describe('fetch is rejected', () => {
425
+ it('should trigger onrejected callback', (done) => {
426
+ const contentType = 'song';
427
+ const collectionBuilder = new CollectionBuilder(
428
+ requestOptions,
429
+ serverUrl,
430
+ contentType
431
+ ).language(13);
432
+
433
+ // Mock the fetch to return a rejected promise
434
+ (fetch as jest.Mock).mockRejectedValue(new Error('URL is invalid'));
435
+
436
+ collectionBuilder.then(
437
+ () => {
438
+ /* */
439
+ },
440
+ (error) => {
441
+ expect(error).toEqual(new Error('URL is invalid'));
442
+ done();
443
+ }
444
+ );
445
+ });
446
+
447
+ it('should trigger catch method', (done) => {
448
+ const contentType = 'song';
449
+ const collectionBuilder = new CollectionBuilder(
450
+ requestOptions,
451
+ serverUrl,
452
+ contentType
453
+ ).query((dotQuery) => dotQuery.field('author').equals('Linkin Park'));
454
+
455
+ // Mock the fetch to return a rejected promise
456
+ (fetch as jest.Mock).mockRejectedValue(new Error('DNS are not resolving'));
457
+
458
+ collectionBuilder.then().catch((error) => {
459
+ expect(error).toEqual(new Error('DNS are not resolving'));
460
+ done();
461
+ });
462
+ });
463
+
464
+ it('should trigger catch of try catch block', async () => {
465
+ const contentType = 'song';
466
+ const collectionBuilder = new CollectionBuilder(
467
+ requestOptions,
468
+ serverUrl,
469
+ contentType
470
+ ).query((dotQuery) => dotQuery.field('author').equals('Linkin Park'));
471
+
472
+ // Mock a network error
473
+ (fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
474
+
475
+ try {
476
+ await collectionBuilder;
477
+ } catch (e) {
478
+ expect(e).toEqual(new Error('Network error'));
479
+ }
480
+ });
481
+ });
482
+
483
+ describe('fetch resolves on error', () => {
484
+ it('should have the error content on then', async () => {
485
+ const contentType = 'song';
486
+ const collectionBuilder = new CollectionBuilder(
487
+ requestOptions,
488
+ serverUrl,
489
+ contentType
490
+ ).limit(10);
491
+
492
+ const error = {
493
+ message: 'Internal server error',
494
+ buffer: {
495
+ stacktrace: 'Some really long server stacktrace'
496
+ }
497
+ };
498
+
499
+ // Mock the fetch to return a rejected promise
500
+ (fetch as jest.Mock).mockReturnValue(
501
+ Promise.resolve({
502
+ status: 500,
503
+ json: () => Promise.resolve(error)
504
+ })
505
+ );
506
+
507
+ collectionBuilder.then((response) => {
508
+ expect(response).toEqual({
509
+ status: 500,
510
+ ...error
511
+ });
512
+ });
513
+ });
514
+ });
515
+ });