@arrai-innovations/reactive-helpers 9.0.3 → 10.0.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.
@@ -0,0 +1,37 @@
1
+ import { addOrUpdateReactiveObject } from "../utils/assignReactiveObject.js";
2
+ import cloneDeep from "lodash-es/cloneDeep.js";
3
+ import isFunction from "lodash-es/isFunction.js";
4
+ import { isReactive, toRef } from "vue";
5
+
6
+ const defaultCrud = {
7
+ args: {},
8
+ list: undefined,
9
+ subscribe: undefined,
10
+ };
11
+
12
+ export const setListCrud = ({ list, subscribe, args = {}, ...rest }) => {
13
+ defaultCrud.list = list;
14
+ defaultCrud.subscribe = subscribe;
15
+ // defensive cloning
16
+ Object.assign(defaultCrud.args, cloneDeep(args));
17
+ if (Object.keys(rest).length) {
18
+ throw new Error(`Unknown key(s) passed to setListCrud: ${Object.keys(rest).join(", ")}`);
19
+ }
20
+ };
21
+
22
+ export const getListCrud = (reactiveCrud, { props, functions } = {}) => {
23
+ // don't mutate the default crud
24
+ Object.assign(reactiveCrud, cloneDeep(defaultCrud));
25
+ if (props?.crudArgs) {
26
+ addOrUpdateReactiveObject(reactiveCrud.args, props.crudArgs);
27
+ }
28
+ if (functions) {
29
+ for (const [key, value] of Object.entries(functions)) {
30
+ if (isFunction(value) && key in reactiveCrud) {
31
+ reactiveCrud[key] = isReactive(functions) ? toRef(functions, key) : value;
32
+ } else {
33
+ throw Error(`Invalid function "${key}" for getListCrud: invalid key or not a function.`);
34
+ }
35
+ }
36
+ }
37
+ };
@@ -0,0 +1,45 @@
1
+ import { addOrUpdateReactiveObject } from "../utils/assignReactiveObject.js";
2
+ import cloneDeep from "lodash-es/cloneDeep.js";
3
+ import isFunction from "lodash-es/isFunction.js";
4
+ import { isReactive, toRef } from "vue";
5
+
6
+ const defaultCrud = {
7
+ args: {},
8
+ retrieve: undefined,
9
+ create: undefined,
10
+ update: undefined,
11
+ patch: undefined,
12
+ delete: undefined,
13
+ subscribe: undefined,
14
+ };
15
+
16
+ export const setObjectCrud = ({ retrieve, create, update, patch, delete: deleteFn, subscribe, args = {}, ...rest }) => {
17
+ defaultCrud.retrieve = retrieve;
18
+ defaultCrud.create = create;
19
+ defaultCrud.update = update;
20
+ defaultCrud.patch = patch;
21
+ defaultCrud.delete = deleteFn;
22
+ defaultCrud.subscribe = subscribe;
23
+ // defensive cloning
24
+ Object.assign(defaultCrud.args, cloneDeep(args));
25
+ if (Object.keys(rest).length) {
26
+ throw new Error(`Unknown key(s) passed to setObjectCrud: ${Object.keys(rest).join(", ")}`);
27
+ }
28
+ };
29
+
30
+ export const getObjectCrud = (reactiveCrud, { props, functions } = {}) => {
31
+ // don't mutate the default crud
32
+ Object.assign(reactiveCrud, cloneDeep(defaultCrud));
33
+ if (props?.crudArgs) {
34
+ addOrUpdateReactiveObject(reactiveCrud.args, props.crudArgs);
35
+ }
36
+ if (functions) {
37
+ for (const [key, value] of Object.entries(functions)) {
38
+ if (isFunction(value) && key in reactiveCrud) {
39
+ reactiveCrud[key] = isReactive(functions) ? toRef(functions, key) : value;
40
+ } else {
41
+ throw Error(`Invalid function "${key}" for getObjectCrud: invalid key or not a function.`);
42
+ }
43
+ }
44
+ }
45
+ };
package/docs.md CHANGED
@@ -23,23 +23,31 @@
23
23
 
24
24
  ## Functions
25
25
 
26
- | Name | Description |
27
- | ---------------------------- | ----------- |
28
- | [useCombineClasses(classes)] |
26
+ | Name | Description |
27
+ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28
+ | [useCombineClasses(classes)] |
29
+ | [useListInstance(options)] | `useListInstance` is a Vue composition function that manages a list of objects. It has the ability to retrieve the list from an implementation, or subscribe to updates from an implementation. It tracks the objects in the list, and their added order. |
30
+ | [useListSubscription(options)] | `useListSubscription` creates a reactive object that manages a list of objects, as returned by `useListInstance`, causing the list to be re-fetched as needed and listening for updates to the list. |
29
31
 
30
32
  ## Typedefs
31
33
 
32
- | Name | Description |
33
- | -------------------- | ------------------------------------------------------------------------------------------------------ |
34
- | [CSSValue] | A string representing a CSS class or a space-separated list of CSS classes. |
35
- | [CSSObject] | A CSS object where keys are CSS classes and values are booleans indicating whether to apply the class. |
36
- | [CSSClasses] | A mixed array containing multiple ways of specifying CSS classes. |
37
- | [CSSStringOrObject] | The amalgamated classes as returned by `objectifyClasses` & `combineClasses`. |
38
- | [CSSValue] | A string representing a CSS class or a space-separated list of CSS classes. |
39
- | [CSSObject] | A CSS object where keys are CSS classes and values are booleans indicating whether to apply the class. |
40
- | [CSSClasses] | A mixed array containing multiple ways of specifying CSS classes. |
41
- | [CSSClassesWithRefs] | A mixed array containing multiple ways of specifying CSS classes. |
42
- | [CSSStringOrObject] | A CSS object or a space-separated list of CSS classes. |
34
+ | Name | Description |
35
+ | ------------------------- | ------------------------------------------------------------------------------------------------------ |
36
+ | [CSSValue] | A string representing a CSS class or a space-separated list of CSS classes. |
37
+ | [CSSObject] | A CSS object where keys are CSS classes and values are booleans indicating whether to apply the class. |
38
+ | [CSSClasses] | A mixed array containing multiple ways of specifying CSS classes. |
39
+ | [CSSStringOrObject] | The amalgamated classes as returned by `objectifyClasses` & `combineClasses`. |
40
+ | [ListInstanceOptions] | The configuration options used to create a list instance. |
41
+ | [ListInstanceState] | A reactive object that manages a list of objects, as returned by `useListInstance`. |
42
+ | [ListInstance] |
43
+ | [ListSubscriptionOptions] | The configuration options used to create a list subscription. |
44
+ | [ListSubscriptionState] | A reactive object that manages a list of objects, as returned by `useListInstance`. |
45
+ | [ListSubscription] |
46
+ | [CSSValue] | A string representing a CSS class or a space-separated list of CSS classes. |
47
+ | [CSSObject] | A CSS object where keys are CSS classes and values are booleans indicating whether to apply the class. |
48
+ | [CSSClasses] | A mixed array containing multiple ways of specifying CSS classes. |
49
+ | [CSSClassesWithRefs] | A mixed array containing multiple ways of specifying CSS classes. |
50
+ | [CSSStringOrObject] | A CSS object or a space-separated list of CSS classes. |
43
51
 
44
52
  ## utils/assignReactiveObject
45
53
 
@@ -213,7 +221,7 @@ Logs debug messages based on the specified categories and logging rules.
213
221
 
214
222
  Get all paths from an array or object.
215
223
 
216
- ### utils/flattenPaths~flattenPaths(arrayOrObject, currentPath)
224
+ ### utils/flattenPaths~flattenPaths(arrayOrObject, options)
217
225
 
218
226
  Turn an array or object into an array of path strings. Recurses for any found arrays or objects.
219
227
 
@@ -222,10 +230,13 @@ Array indexes are wrapped in square brackets and object keys are prefixed with a
222
230
  **Kind**: inner method of [`utils/flattenPaths`]
223
231
  **Returns**: `Array.<string>` - paths
224
232
 
225
- | Param | Type | Description |
226
- | ------------- | ------------------- | -------------------------------------------------- |
227
- | arrayOrObject | `Array` \| `object` | array or object to flatten |
228
- | currentPath | `string` | current path, for recursion or as a starting point |
233
+ | Param | Type | Description |
234
+ | ------------------- | ------------------- | -------------------------------------------------- |
235
+ | arrayOrObject | `Array` \| `object` | array or object to flatten |
236
+ | options | `object` | options |
237
+ | options.currentPath | `string` | current path, for recursion or as a starting point |
238
+ | options.depth | `number` | current depth, for recursion |
239
+ | options.limit | `number` | limit the depth of recursion |
229
240
 
230
241
  ## utils/keyDiff
231
242
 
@@ -260,11 +271,15 @@ what keys are removed, and what keys are added. Keys are sourced deeply in the o
260
271
  **Kind**: inner method of [`utils/keyDiff`]
261
272
  **Returns**: `KeyDiffResult` - - the differences
262
273
 
263
- | Param | Type | Description |
264
- | ----------- | ---------------- | ------------------------------ |
265
- | newObj | `object` | the new version of the object |
266
- | oldObj | `object` | the old version of the object |
267
- | \[options\] | `KeyDiffOptions` | which differences are returned |
274
+ | Param | Type | Description |
275
+ | ----------------------- | ---------------- | -------------------------------------- |
276
+ | newObj | `object` | the new version of the object |
277
+ | oldObj | `object` | the old version of the object |
278
+ | \[options\] | `KeyDiffOptions` | which differences are returned |
279
+ | \[options.sameKeys\] | `boolean` | if true, return keys that are the same |
280
+ | \[options.removedKeys\] | `boolean` | if true, return keys that are removed |
281
+ | \[options.addedKeys\] | `boolean` | if true, return keys that are added |
282
+ | \[options.limit\] | `number` | limit the depth of recursion |
268
283
 
269
284
  ### utils/keyDiff~KeyDiffOptions
270
285
 
@@ -427,6 +442,31 @@ Remove empty objects and undefined values from arrays in a mixed object array tr
427
442
  | ------- | -------------- | -------------------------------------------------------------------------------------------- |
428
443
  | classes | [`CSSClasses`] | A mixed array containing multiple ways of specifying CSS classes. Non-ref values are cloned. |
429
444
 
445
+ ## useListInstance(options)
446
+
447
+ `useListInstance` is a Vue composition function that manages a list of objects.
448
+ It has the ability to retrieve the list from an implementation, or subscribe to updates from an implementation.
449
+ It tracks the objects in the list, and their added order.
450
+
451
+ **Kind**: global function
452
+ **Returns**: [`ListInstance`] - - the list instance
453
+
454
+ | Param | Type | Description |
455
+ | ------- | ----------------------- | -------------------------------------------- |
456
+ | options | [`ListInstanceOptions`] | the options used to create the list instance |
457
+
458
+ ## useListSubscription(options)
459
+
460
+ `useListSubscription` creates a reactive object that manages a list of objects, as returned by `useListInstance`,
461
+ causing the list to be re-fetched as needed and listening for updates to the list.
462
+
463
+ **Kind**: global function
464
+ **Returns**: ListSubscription
465
+
466
+ | Param | Type |
467
+ | ------- | --------------------------- |
468
+ | options | [`ListSubscriptionOptions`] |
469
+
430
470
  ## CSSValue
431
471
 
432
472
  A string representing a CSS class or a space-separated list of CSS classes.
@@ -451,6 +491,115 @@ The amalgamated classes as returned by `objectifyClasses` & `combineClasses`.
451
491
 
452
492
  **Kind**: global typedef
453
493
 
494
+ ## ListInstanceOptions
495
+
496
+ The configuration options used to create a list instance.
497
+
498
+ **Kind**: global typedef
499
+ **Properties**
500
+
501
+ | Name | Type | Description |
502
+ | ------------------- | ---------- | ---------------------------------------------------------------------------------------- |
503
+ | props | `object` | props passed to the component |
504
+ | props.retrieveArgs | `object` | the arguments passed to the server |
505
+ | props.listArgs | `object` | the arguments passed to the server |
506
+ | props.crudArgs | `object` | implementation specific arguments |
507
+ | functions | `object` | optional. default implementation are used as set by `setListCrud`. |
508
+ | functions.list | `function` | provide the implementation for the list function |
509
+ | functions.subscribe | `function` | provide the implementation for the subscribe function |
510
+ | keepOldPages | `boolean` | if true, pages will not be cleared when defaultPageCallback is called. default is false. |
511
+
512
+ ## ListInstanceState
513
+
514
+ A reactive object that manages a list of objects, as returned by `useListInstance`.
515
+
516
+ **Kind**: global typedef
517
+ **Properties**
518
+
519
+ | Name | Type | Description |
520
+ | -------------- | ---------- | ------------------------------------------ |
521
+ | crud | `object` | the crud functions |
522
+ | crud.args | `object` | the arguments passed to the crud functions |
523
+ | crud.list | `function` | the list function |
524
+ | crud.subscribe | `function` | the subscribe function |
525
+ | retrieveArgs | `object` | the arguments passed to the server |
526
+ | listArgs | `object` | the arguments passed to the server |
527
+ | objects | `Map` | the objects in the list |
528
+ | loading | `boolean` | true if the list is loading |
529
+ | errored | `boolean` | true if the list has errored |
530
+ | error | `object` | the error object |
531
+ | objectsInOrder | `Array` | the objects in the list in order |
532
+ | order | `Array` | the order of the objects in the list |
533
+ | totalRecords | `number` | the total number of records |
534
+ | totalPages | `number` | the total number of pages |
535
+ | perPage | `number` | the number of records per page |
536
+
537
+ ## ListInstance
538
+
539
+ **Kind**: global typedef
540
+ **Properties**
541
+
542
+ | Name | Type | Description |
543
+ | ------------------- | --------------------- | -------------------------------------------- |
544
+ | list | `function` | subscribe to updates from the implementation |
545
+ | addListObject | `function` | add an object to the list |
546
+ | updateListObject | `function` | update an object in the list |
547
+ | deleteListObject | `function` | delete an object from the list |
548
+ | clearList | `function` | clear the list |
549
+ | clearError | `function` | clear the error |
550
+ | getFakeId | `function` | get a fake id |
551
+ | defaultPageCallback | `function` | the default page callback |
552
+ | pageCallback | `function` | the page callback |
553
+ | state | [`ListInstanceState`] | the list instance state |
554
+ | effectScope | `object` | a Vue effect scope |
555
+
556
+ ## ListSubscriptionOptions
557
+
558
+ The configuration options used to create a list subscription.
559
+
560
+ **Kind**: global typedef
561
+ **Properties**
562
+
563
+ | Name | Type | Description |
564
+ | ------------ | ---------------- | ---------------------------------------------------------------------------------------- |
565
+ | props | `object` | passed on to a created list instance if one is not provided |
566
+ | functions | `object` | passed on to a created list instance if one is not provided |
567
+ | listInstance | [`ListInstance`] | a list instance to use instead of creating one |
568
+ | keepOldPages | `boolean` | if true, pages will not be cleared when defaultPageCallback is called. default is false. |
569
+
570
+ ## ListSubscriptionState
571
+
572
+ A reactive object that manages a list of objects, as returned by `useListInstance`.
573
+
574
+ **Kind**: global typedef
575
+ **Extends**: [`ListInstanceState`]
576
+ **Properties**
577
+
578
+ | Name | Type | Description |
579
+ | ------------------- | --------- | ----------------------------------------------------------------- |
580
+ | subscriptionLoading | `boolean` | true if the subscription is loading |
581
+ | subscriptionErrored | `boolean` | true if the subscription errored |
582
+ | subscriptionError | `Error` | the error that caused the subscription to error |
583
+ | intendToList | `boolean` | true if the list should be fetched, or refetched when args change |
584
+ | intendToSubscribe | `boolean` | true if the list should subscribe for updates |
585
+ | subscribed | `boolean` | true if the subscription is active |
586
+
587
+ ## ListSubscription
588
+
589
+ **Kind**: global typedef
590
+ **Properties**
591
+
592
+ | Name | Type | Description |
593
+ | ------------------ | ------------------------- | -------------------------------------------------------------------------------------- |
594
+ | state | [`ListSubscriptionState`] | the reactive state of the list subscription |
595
+ | listInstance | [`ListInstance`] | the list instance used by the subscription |
596
+ | listIntent | `object` | the useCancelleableIntent object managing if the list should be (re)fetched |
597
+ | subscriptionIntent | `object` | the useCancelleableIntent object managing if the subscription should be (un)subscribed |
598
+ | subscribe | `function` | subscribe to the list |
599
+ | unsubscribe | `function` | unsubscribe from the list |
600
+ | clearError | `function` | clear the subscription error |
601
+ | effectScope | `object` | a Vue effect scope |
602
+
454
603
  ## CSSValue
455
604
 
456
605
  A string representing a CSS class or a space-separated list of CSS classes.
@@ -500,6 +649,12 @@ A CSS object or a space-separated list of CSS classes.
500
649
  [cssobject]: #cssobject
501
650
  [cssclasses]: #cssclasses
502
651
  [cssstringorobject]: #cssstringorobject
652
+ [listinstanceoptions]: #listinstanceoptions
653
+ [listinstancestate]: #listinstancestate
654
+ [listinstance]: #listinstance
655
+ [listsubscriptionoptions]: #listsubscriptionoptions
656
+ [listsubscriptionstate]: #listsubscriptionstate
657
+ [listsubscription]: #listsubscription
503
658
  [cssclasseswithrefs]: #cssclasseswithrefs
504
659
  [~validtargetorsource]: #utilsassignreactiveobjectvalidtargetorsource
505
660
  [`utils/assignreactiveobject`]: #utilsassignreactiveobject
@@ -515,7 +670,14 @@ A CSS object or a space-separated list of CSS classes.
515
670
  [`cssclasses`]: #cssclasses
516
671
  [`cssstringorobject`]: #cssstringorobject
517
672
  [`cssclasseswithrefs`]: #cssclasseswithrefs
673
+ [`listinstance`]: #listinstance
674
+ [`listinstanceoptions`]: #listinstanceoptions
675
+ [`listsubscriptionoptions`]: #listsubscriptionoptions
676
+ [`listinstancestate`]: #listinstancestate
677
+ [`listsubscriptionstate`]: #listsubscriptionstate
518
678
  [usecombineclasses(classes)]: #usecombineclassesclasses
679
+ [uselistinstance(options)]: #uselistinstanceoptions
680
+ [uselistsubscription(options)]: #uselistsubscriptionoptions
519
681
  [~addreactiveobject(target, source, \[exclude\], \[addedkeys\])]: #utilsassignreactiveobjectaddreactiveobjecttarget-source-exclude-addedkeys
520
682
  [~updatereactiveobject(target, source, \[exclude\], \[samekeys\])]: #utilsassignreactiveobjectupdatereactiveobjecttarget-source-exclude-samekeys
521
683
  [~addorupdatereactiveobject(target, source, \[exclude\], \[addedkeys\], \[samekeys\])]: #utilsassignreactiveobjectaddorupdatereactiveobjecttarget-source-exclude-addedkeys-samekeys
package/index.js CHANGED
@@ -1,2 +1,3 @@
1
+ export * from "./config/index.js";
1
2
  export * from "./use/index.js";
2
3
  export * from "./utils/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrai-innovations/reactive-helpers",
3
- "version": "9.0.3",
3
+ "version": "10.0.1",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -45,7 +45,7 @@
45
45
  "jsdoc-to-markdown": "^8.0.0",
46
46
  "lint-staged": "^13.2.2",
47
47
  "prettier": "2.6.2",
48
- "vitest": "^0.32.2"
48
+ "vitest": "^0.34.1"
49
49
  },
50
50
  "dependencies": {
51
51
  "browser-util-inspect": "^0.2.0",
@@ -0,0 +1,18 @@
1
+ import * as config from "../../../config/index.js";
2
+ import fs from "fs/promises";
3
+
4
+ describe("config/index.js", function () {
5
+ it("should export everything exported by individual files", async function () {
6
+ const files = await fs.readdir("./config");
7
+ const modules = files.filter((file) => file.endsWith(".js") && file !== "index.js");
8
+ const exportedKeys = [];
9
+ for (const module of modules) {
10
+ const moduleExports = await import(`../../../config/${module}`);
11
+ exportedKeys.push(...Object.keys(moduleExports).filter((key) => key !== "default"));
12
+ }
13
+ const useKeys = Object.keys(config);
14
+ useKeys.sort();
15
+ exportedKeys.sort();
16
+ expect(useKeys).toEqual(exportedKeys);
17
+ });
18
+ });
@@ -0,0 +1,107 @@
1
+ import cloneDeep from "lodash-es/cloneDeep.js";
2
+ import { reactive } from "vue";
3
+
4
+ describe("config/listCrud.js", () => {
5
+ describe("get & set", () => {
6
+ let setListCrud, getListCrud;
7
+ beforeAll(async () => {
8
+ const listCrudModule = await import("../../../config/listCrud.js");
9
+ setListCrud = listCrudModule.setListCrud;
10
+ getListCrud = listCrudModule.getListCrud;
11
+ });
12
+ it("should not die getting crud before setting", () => {
13
+ const reactiveCrud = reactive({});
14
+ expect(() => getListCrud(reactiveCrud)).not.toThrow();
15
+ });
16
+ it("should set and get the default crud, avoiding mutation", () => {
17
+ const crud = {
18
+ list: () => 1,
19
+ subscribe: () => 2,
20
+ args: {
21
+ test: "test",
22
+ },
23
+ };
24
+ expect(() => setListCrud(crud)).not.toThrow();
25
+
26
+ const retrievedCrud = reactive({});
27
+ expect(() => getListCrud(retrievedCrud)).not.toThrow();
28
+ expect(new Set(Object.keys(retrievedCrud))).toEqual(new Set(["list", "subscribe", "args"]));
29
+ expect(retrievedCrud.args).not.toBe(crud.args);
30
+ expect(retrievedCrud.args).toEqual(crud.args);
31
+ expect(retrievedCrud.list).toBe(crud.list);
32
+ expect(retrievedCrud.subscribe).toBe(crud.subscribe);
33
+
34
+ const originalCrud = cloneDeep(crud);
35
+ crud.args.test = "test2";
36
+ crud.list = () => 3;
37
+ crud.subscribe = () => 4;
38
+ expect(retrievedCrud.args.test).toBe(originalCrud.args.test);
39
+ expect(retrievedCrud.list).toBe(originalCrud.list);
40
+ expect(retrievedCrud.subscribe).toBe(originalCrud.subscribe);
41
+ });
42
+ it("should die if an unknown function is passed", () => {
43
+ expect(() =>
44
+ setListCrud({ list: () => 1, subscribe: () => 2, args: { test: "test" }, unknown: () => 3 })
45
+ ).toThrow("Unknown key(s) passed to setListCrud: unknown");
46
+ });
47
+ it("should customize via getListCrud", () => {
48
+ const defaultCrud = {
49
+ list: () => 1,
50
+ subscribe: () => 2,
51
+ args: {
52
+ test: "test",
53
+ },
54
+ };
55
+ const customCrudArgs = {
56
+ props: reactive({
57
+ crudArgs: {
58
+ test: "test2",
59
+ },
60
+ }),
61
+ functions: {
62
+ list: () => 3,
63
+ subscribe: () => 4,
64
+ },
65
+ };
66
+ const expectedCustomCrud = {
67
+ list: customCrudArgs.functions.list,
68
+ subscribe: customCrudArgs.functions.subscribe,
69
+ args: customCrudArgs.props.crudArgs,
70
+ };
71
+ expect(() => setListCrud(defaultCrud)).not.toThrow();
72
+ const defaultRetrievedCrud = reactive({});
73
+ getListCrud(defaultRetrievedCrud);
74
+ expect(defaultRetrievedCrud).toEqual(defaultCrud);
75
+ expect(defaultRetrievedCrud).not.toEqual(expectedCustomCrud);
76
+ const customRetrievedCrud = reactive({});
77
+ getListCrud(customRetrievedCrud, customCrudArgs);
78
+ expect(customRetrievedCrud).toEqual(expectedCustomCrud);
79
+ expect(customRetrievedCrud).not.toEqual(defaultCrud);
80
+ expect(defaultRetrievedCrud).toEqual(defaultCrud);
81
+ expect(defaultRetrievedCrud).not.toEqual(expectedCustomCrud);
82
+ });
83
+ it("should throw if passed functions object that has values that are not functions", () => {
84
+ const retrievedCrud = reactive({});
85
+ expect(() =>
86
+ getListCrud(retrievedCrud, {
87
+ functions: {
88
+ list: () => 1,
89
+ subscribe: true,
90
+ },
91
+ })
92
+ ).toThrow('Invalid function "subscribe" for getListCrud: invalid key or not a function.');
93
+ });
94
+ it("should throw if passed unexpected functions", () => {
95
+ const retrievedCrud = reactive({});
96
+ expect(() =>
97
+ getListCrud(retrievedCrud, {
98
+ functions: {
99
+ list: () => 1,
100
+ subscribe: () => 2,
101
+ unknown: () => 3,
102
+ },
103
+ })
104
+ ).toThrow('Invalid function "unknown" for getListCrud: invalid key or not a function.');
105
+ });
106
+ });
107
+ });
@@ -0,0 +1,155 @@
1
+ import cloneDeep from "lodash-es/cloneDeep.js";
2
+ import { reactive } from "vue";
3
+
4
+ describe("config/objectCrud.js", () => {
5
+ describe("get & set", () => {
6
+ let setObjectCrud, getObjectCrud;
7
+ beforeAll(async () => {
8
+ const objectCrudModule = await import("../../../config/objectCrud.js");
9
+ setObjectCrud = objectCrudModule.setObjectCrud;
10
+ getObjectCrud = objectCrudModule.getObjectCrud;
11
+ });
12
+ it("should not die getting crud before setting", () => {
13
+ const reactiveCrud = reactive({});
14
+ expect(() => getObjectCrud(reactiveCrud)).not.toThrow();
15
+ });
16
+ it("should set and get the default crud, avoiding mutation", () => {
17
+ const crud = {
18
+ retrieve: () => 1,
19
+ create: () => 2,
20
+ update: () => 3,
21
+ patch: () => 4,
22
+ delete: () => 5,
23
+ subscribe: () => 6,
24
+ args: {
25
+ test: "test",
26
+ },
27
+ };
28
+ expect(() => setObjectCrud(crud)).not.toThrow();
29
+
30
+ const retrievedCrud = reactive({});
31
+ expect(() => getObjectCrud(retrievedCrud)).not.toThrow();
32
+ expect(new Set(Object.keys(retrievedCrud))).toEqual(
33
+ new Set(["retrieve", "create", "update", "patch", "delete", "subscribe", "args"])
34
+ );
35
+ expect(retrievedCrud.args).not.toBe(crud.args);
36
+ expect(retrievedCrud.args).toEqual(crud.args);
37
+ expect(retrievedCrud.retrieve).toBe(crud.retrieve);
38
+ expect(retrievedCrud.create).toBe(crud.create);
39
+ expect(retrievedCrud.update).toBe(crud.update);
40
+ expect(retrievedCrud.patch).toBe(crud.patch);
41
+ expect(retrievedCrud.delete).toBe(crud.delete);
42
+ expect(retrievedCrud.subscribe).toBe(crud.subscribe);
43
+
44
+ const originalCrud = cloneDeep(crud);
45
+ crud.args.test = "test2";
46
+ crud.retrieve = () => 7;
47
+ crud.create = () => 8;
48
+ crud.update = () => 9;
49
+ crud.patch = () => 10;
50
+ crud.delete = () => 11;
51
+ crud.subscribe = () => 12;
52
+ expect(retrievedCrud.args.test).toBe(originalCrud.args.test);
53
+ expect(retrievedCrud.retrieve).toBe(originalCrud.retrieve);
54
+ expect(retrievedCrud.create).toBe(originalCrud.create);
55
+ expect(retrievedCrud.update).toBe(originalCrud.update);
56
+ expect(retrievedCrud.patch).toBe(originalCrud.patch);
57
+ expect(retrievedCrud.delete).toBe(originalCrud.delete);
58
+ expect(retrievedCrud.subscribe).toBe(originalCrud.subscribe);
59
+ });
60
+ it("should customize via getObjectCrud", () => {
61
+ const defaultCrud = {
62
+ retrieve: () => 1,
63
+ create: () => 2,
64
+ update: () => 3,
65
+ patch: () => 4,
66
+ delete: () => 5,
67
+ subscribe: () => 6,
68
+ args: {
69
+ test: "test",
70
+ },
71
+ };
72
+ const customCrudArgs = {
73
+ props: reactive({
74
+ crudArgs: {
75
+ test: "test2",
76
+ },
77
+ }),
78
+ functions: {
79
+ retrieve: () => 7,
80
+ create: () => 8,
81
+ update: () => 9,
82
+ patch: () => 10,
83
+ delete: () => 11,
84
+ subscribe: () => 12,
85
+ },
86
+ };
87
+ const expectedCustomCrud = {
88
+ retrieve: customCrudArgs.functions.retrieve,
89
+ create: customCrudArgs.functions.create,
90
+ update: customCrudArgs.functions.update,
91
+ patch: customCrudArgs.functions.patch,
92
+ delete: customCrudArgs.functions.delete,
93
+ subscribe: customCrudArgs.functions.subscribe,
94
+ args: customCrudArgs.props.crudArgs,
95
+ };
96
+ expect(() => setObjectCrud(defaultCrud)).not.toThrow();
97
+ const defaultRetrievedCrud = reactive({});
98
+ getObjectCrud(defaultRetrievedCrud);
99
+ expect(defaultRetrievedCrud).toEqual(defaultCrud);
100
+ expect(defaultRetrievedCrud).not.toEqual(expectedCustomCrud);
101
+ const customRetrievedCrud = reactive({});
102
+ getObjectCrud(customRetrievedCrud, customCrudArgs);
103
+ expect(customRetrievedCrud).toEqual(expectedCustomCrud);
104
+ expect(customRetrievedCrud).not.toEqual(defaultCrud);
105
+ expect(defaultRetrievedCrud).toEqual(defaultCrud);
106
+ expect(defaultRetrievedCrud).not.toEqual(expectedCustomCrud);
107
+ });
108
+ it("should die if an unknown function is passed", () => {
109
+ expect(() =>
110
+ setObjectCrud({
111
+ retrieve: () => 1,
112
+ create: () => 2,
113
+ update: () => 3,
114
+ patch: () => 4,
115
+ delete: () => 5,
116
+ subscribe: () => 6,
117
+ args: { test: "test" },
118
+ unknown: () => 7,
119
+ })
120
+ ).toThrow("Unknown key(s) passed to setObjectCrud: unknown");
121
+ });
122
+
123
+ it("should throw if passed functions object that has values that are not functions", () => {
124
+ const retrievedCrud = reactive({});
125
+ expect(() =>
126
+ getObjectCrud(retrievedCrud, {
127
+ functions: {
128
+ retrieve: () => 1,
129
+ create: () => 2,
130
+ update: 3,
131
+ patch: () => 4,
132
+ delete: () => 5,
133
+ subscribe: () => 6,
134
+ },
135
+ })
136
+ ).toThrow('Invalid function "update" for getObjectCrud: invalid key or not a function.');
137
+ });
138
+ it("should throw if passed unexpected functions", () => {
139
+ const retrievedCrud = reactive({});
140
+ expect(() =>
141
+ getObjectCrud(retrievedCrud, {
142
+ functions: {
143
+ retrieve: () => 1,
144
+ create: () => 2,
145
+ update: () => 3,
146
+ patch: () => 4,
147
+ delete: () => 5,
148
+ subscribe: () => 6,
149
+ unknown: () => 7,
150
+ },
151
+ })
152
+ ).toThrow('Invalid function "unknown" for getObjectCrud: invalid key or not a function.');
153
+ });
154
+ });
155
+ });