@arrai-innovations/reactive-helpers 11.1.0 → 11.3.0

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/docs.md CHANGED
@@ -686,12 +686,13 @@ The configuration options used to create a list subscription.
686
686
  **Kind**: global typedef
687
687
  **Properties**
688
688
 
689
- | Name | Type | Description |
690
- | ------------ | ---------------- | ---------------------------------------------------------------------------------------- |
691
- | props | `object` | passed on to a created list instance if one is not provided |
692
- | functions | `object` | passed on to a created list instance if one is not provided |
693
- | listInstance | [`ListInstance`] | a list instance to use instead of creating one |
694
- | keepOldPages | `boolean` | if true, pages will not be cleared when defaultPageCallback is called. default is false. |
689
+ | Name | Type | Description |
690
+ | ------------------------------ | ---------------- | ---------------------------------------------------------------------------------------- |
691
+ | props | `object` | passed on to a created list instance if one is not provided |
692
+ | functions | `object` | passed on to a created list instance if one is not provided |
693
+ | listInstance | [`ListInstance`] | a list instance to use instead of creating one |
694
+ | keepOldPages | `boolean` | if true, pages will not be cleared when defaultPageCallback is called. default is false. |
695
+ | clearListOnListIntentTriggered | `boolean` | if true, the list will be cleared when the list intent is triggered. default is false. |
695
696
 
696
697
  ## ListSubscriptionState
697
698
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrai-innovations/reactive-helpers",
3
- "version": "11.1.0",
3
+ "version": "11.3.0",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -3,7 +3,7 @@ import { expectErrorToBeNull } from "../expectHelpers.js";
3
3
  import flushPromises from "flush-promises";
4
4
  import keyBy from "lodash-es/keyBy.js";
5
5
  import { inspect } from "util";
6
- import { isReactive, nextTick, reactive } from "vue";
6
+ import { isReactive, nextTick, reactive, isRef, isReadonly } from "vue";
7
7
 
8
8
  afterAll(() => {
9
9
  vi.restoreAllMocks();
@@ -16,6 +16,7 @@ describe("use/listInstance.spec.js", function () {
16
16
  const listCrud = await import("../../../config/listCrud.js");
17
17
  const imported = await import("../../../use/listInstance");
18
18
  globalList = vi.fn();
19
+ globalList.cancel = vi.fn();
19
20
  listCrud.setListCrud({
20
21
  list: globalList,
21
22
  args: { stream: "test_stream" },
@@ -128,6 +129,7 @@ describe("use/listInstance.spec.js", function () {
128
129
  listArgs: { user: 1 },
129
130
  retrieveArgs: { fields: fields },
130
131
  pageCallback: passedPageCallback,
132
+ isCancelled: expect.any(Object), // ref
131
133
  });
132
134
  expect(globalList).toHaveBeenCalledTimes(1);
133
135
  });
@@ -153,6 +155,7 @@ describe("use/listInstance.spec.js", function () {
153
155
  listArgs: { user: 1 },
154
156
  retrieveArgs: { fields: fields },
155
157
  pageCallback: expect.any(Function),
158
+ isCancelled: expect.any(Object), // ref
156
159
  });
157
160
  expect(globalList).toHaveBeenCalledTimes(1);
158
161
  expectErrorToBeNull(listInstance.state.error);
@@ -207,6 +210,7 @@ describe("use/listInstance.spec.js", function () {
207
210
  listArgs: { user: 1 },
208
211
  retrieveArgs: { fields: fields },
209
212
  pageCallback: passedPageCallback,
213
+ isCancelled: expect.any(Object), // ref
210
214
  });
211
215
  expect(globalList).toHaveBeenCalledTimes(1);
212
216
  });
@@ -272,6 +276,7 @@ describe("use/listInstance.spec.js", function () {
272
276
  listArgs: { user: 1 },
273
277
  retrieveArgs: { fields: fields },
274
278
  pageCallback: passedPageCallback,
279
+ isCancelled: expect.any(Object), // ref
275
280
  });
276
281
  expect(globalList).toHaveBeenCalledTimes(1);
277
282
  });
@@ -318,6 +323,7 @@ describe("use/listInstance.spec.js", function () {
318
323
  listArgs: { user: 1 },
319
324
  retrieveArgs: { fields: fields },
320
325
  pageCallback: passedPageCallback,
326
+ isCancelled: expect.any(Object), // ref
321
327
  });
322
328
  expect(globalList).toHaveBeenCalledTimes(1);
323
329
 
@@ -560,4 +566,39 @@ describe("use/listInstance.spec.js", function () {
560
566
  expect(listInstance.state.order).toEqual(crudListResolvedPage3.map((e) => e.id.toString()));
561
567
  expect(listInstance.state.objectsInOrder).toEqual(crudListResolvedPage3);
562
568
  });
569
+ describe("list promise cancellation", function () {
570
+ it("cancels and sets isCancelled", async function () {
571
+ let myListFnCancelResolve, passedIsCancelled;
572
+ const myListFn = vi.fn().mockImplementation(({ isCancelled }) => {
573
+ passedIsCancelled = isCancelled;
574
+ const promise = new Promise(() => {});
575
+ promise.cancel = async () => {
576
+ await new Promise((resolve) => {
577
+ myListFnCancelResolve = resolve;
578
+ });
579
+ };
580
+ return promise;
581
+ });
582
+ const listInstance = useListInstance({
583
+ props: {},
584
+ functions: {
585
+ list: myListFn,
586
+ },
587
+ });
588
+
589
+ const cancelablePromise = listInstance.list();
590
+
591
+ expect(passedIsCancelled).toBeTruthy();
592
+ expect(isRef(passedIsCancelled)).toBe(true);
593
+ expect(isReadonly(passedIsCancelled)).toBe(true);
594
+ expect(passedIsCancelled.value).toBe(false);
595
+ expect(cancelablePromise.cancel).toBeTruthy();
596
+
597
+ const cancelPromise = cancelablePromise.cancel();
598
+ myListFnCancelResolve();
599
+ await cancelPromise;
600
+
601
+ expect(passedIsCancelled.value).toBe(true);
602
+ });
603
+ });
563
604
  });
@@ -142,7 +142,6 @@ describe("use/listSubscription.spec.js", function () {
142
142
  obj: listSubscription.subscribeIntent.state,
143
143
  prop: "active",
144
144
  });
145
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
146
145
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
147
146
  expect(returnValue).toBe(true);
148
147
 
@@ -288,7 +287,6 @@ describe("use/listSubscription.spec.js", function () {
288
287
  crudSubscribeResolvable[0].cancel.resolve(true);
289
288
  const returnValue = await unsubscribe;
290
289
  await poll(() => !listSubscription.state.subscribed);
291
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
292
290
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
293
291
  expect(returnValue).toBe(true);
294
292
  expect(listSubscription.state.subscribed).toBe(false);
@@ -322,7 +320,6 @@ describe("use/listSubscription.spec.js", function () {
322
320
  crudSubscribeResolvable[0].cancel.resolve(true);
323
321
  const returnValue = await unsubscribePromise;
324
322
  await poll(() => !listSubscription.state.subscribed);
325
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
326
323
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
327
324
  expect(returnValue).toBe(true);
328
325
  expect(listSubscription.state.subscribed).toBe(false);
@@ -382,7 +379,6 @@ describe("use/listSubscription.spec.js", function () {
382
379
  crudSubscribeResolvable[0].cancel.resolve(true);
383
380
  const returnValue = await unsubscribePromise;
384
381
  await poll(() => !listSubscription.state.subscribed);
385
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
386
382
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
387
383
  expect(returnValue).toBe(true);
388
384
  expect(listSubscription.state.subscribed).toBe(false);
@@ -455,7 +451,6 @@ describe("use/listSubscription.spec.js", function () {
455
451
  crudSubscribeResolvable[0].cancel.resolve(true);
456
452
  const returnValue = await unsubscribePromise;
457
453
  await poll(() => !listSubscription.state.subscribed);
458
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
459
454
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
460
455
  expect(returnValue).toBe(true);
461
456
  expect(listSubscription.state.subscribed).toBe(false);
@@ -508,7 +503,6 @@ describe("use/listSubscription.spec.js", function () {
508
503
  await crudSubscribeResolvable[1].resolve();
509
504
  await crudListResolvable[1].resolve();
510
505
  await poll(() => listSubscription.state.subscribed);
511
- expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledWith();
512
506
  expect(crudSubscribeResolvable[0].promise.cancel).toHaveBeenCalledTimes(1);
513
507
 
514
508
  expect(crudSubscribe).toHaveBeenCalledTimes(2);
@@ -518,7 +512,6 @@ describe("use/listSubscription.spec.js", function () {
518
512
  const returnValue = await unsubscribePromise;
519
513
  expect(returnValue).toBe(true);
520
514
  await poll(() => !listSubscription.state.subscribed);
521
- expect(crudSubscribeResolvable[1].promise.cancel).toHaveBeenCalledWith();
522
515
  expect(crudSubscribeResolvable[1].promise.cancel).toHaveBeenCalledTimes(1);
523
516
  expect(listSubscription.state.subscribed).toBe(false);
524
517
  });
@@ -622,4 +615,52 @@ describe("use/listSubscription.spec.js", function () {
622
615
  expect(inspect(listSubscription.A)).toEqual(inspect(listSubscriptionA));
623
616
  expect(inspect(listSubscription.B)).toEqual(inspect(listSubscriptionB));
624
617
  });
618
+ describe("clearListOnListIntentTriggered true", function () {
619
+ it("on true", async function () {
620
+ crudSubscribeResolvable[0].promise.cancel.mockImplementation(() => Promise.resolve(true));
621
+ const listArgs = reactive({
622
+ user: 1,
623
+ });
624
+ const retrieveArgs = reactive({
625
+ fields: fields,
626
+ });
627
+ const listInstance = useListInstance({
628
+ props: { listArgs, retrieveArgs },
629
+ });
630
+ listInstance.clearList = vi.fn().mockImplementationOnce(() => undefined);
631
+ const listSubscription = useListSubscription({
632
+ listInstance,
633
+ clearListOnListIntentTriggered: true,
634
+ });
635
+ listSubscription.subscribe();
636
+ await nextTick();
637
+ await flushPromises();
638
+ listArgs.user = 2;
639
+ expect(listInstance.clearList).toHaveBeenCalledTimes(1);
640
+ });
641
+ });
642
+ describe("clearListOnListIntentTriggered false", function () {
643
+ it("on true", async function () {
644
+ crudSubscribeResolvable[0].promise.cancel.mockImplementation(() => Promise.resolve(true));
645
+ const listArgs = reactive({
646
+ user: 1,
647
+ });
648
+ const retrieveArgs = reactive({
649
+ fields: fields,
650
+ });
651
+ const listInstance = useListInstance({
652
+ props: { listArgs, retrieveArgs },
653
+ });
654
+ listInstance.clearList = vi.fn().mockImplementationOnce(() => undefined);
655
+ const listSubscription = useListSubscription({
656
+ listInstance,
657
+ clearListOnListIntentTriggered: false,
658
+ });
659
+ listSubscription.subscribe();
660
+ await nextTick();
661
+ await flushPromises();
662
+ listArgs.user = 2;
663
+ expect(listInstance.clearList).toHaveBeenCalledTimes(0);
664
+ });
665
+ });
625
666
  });
@@ -54,7 +54,7 @@ export function useCancellableIntent({
54
54
  return false;
55
55
  }
56
56
 
57
- const doIntentWatch = async () => {
57
+ const doIntentWatch = () => {
58
58
  state.errored = false;
59
59
  state.error = null;
60
60
  if (state.activeCount === undefined) {
@@ -127,13 +127,13 @@ export function useCancellableIntent({
127
127
  }
128
128
  };
129
129
 
130
- const guardWatch = async () => {
130
+ const guardWatch = () => {
131
131
  if (delayedWatch) {
132
132
  // if all guards are false, run the watch
133
133
  if (Object.values(guardArguments).every((x) => !x)) {
134
134
  const myDelayedWatch = delayedWatch;
135
135
  delayedWatch = null;
136
- await myDelayedWatch();
136
+ myDelayedWatch();
137
137
  }
138
138
  }
139
139
  };
package/use/list.js CHANGED
@@ -31,6 +31,7 @@ export const useList = ({
31
31
  functions = {},
32
32
  paged = false,
33
33
  keepOldPages = false,
34
+ clearListOnListIntentTriggered = false,
34
35
  searchThrottle = 500,
35
36
  sortThrottleWait,
36
37
  searchShowAllWhenEmpty,
@@ -63,6 +64,7 @@ export const useList = ({
63
64
 
64
65
  managed.listSubscription = useListSubscription({
65
66
  listInstance: managed.listInstance,
67
+ clearListOnListIntentTriggered,
66
68
  });
67
69
  managed.listSubscription.state.intendToList = toRef(props, "intendToList");
68
70
  managed.listSubscription.state.intendToSubscribe = toRef(props, "intendToSubscribe");
@@ -2,7 +2,7 @@ import { getListCrud } from "../config/listCrud.js";
2
2
  import { assignReactiveObject } from "../utils/assignReactiveObject.js";
3
3
  import { getFakeId } from "../utils/getFakeId.js";
4
4
  import inspect from "browser-util-inspect";
5
- import { effectScope, reactive, toRef, watch, computed, unref } from "vue";
5
+ import { effectScope, reactive, toRef, watch, computed, unref, ref, readonly } from "vue";
6
6
 
7
7
  export class ListError extends Error {
8
8
  constructor(message, code) {
@@ -182,16 +182,19 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
182
182
  state.loading = true;
183
183
  state.errored = false;
184
184
  state.error = null;
185
+ const isCancelled = ref(false);
185
186
  const listPromise = state.crud.list({
186
187
  crudArgs: state.crud.args,
187
188
  retrieveArgs: state.retrieveArgs,
188
189
  listArgs: state.listArgs,
189
190
  pageCallback: returnedObject.pageCallback,
191
+ isCancelled: readonly(isCancelled),
190
192
  });
191
193
  let resolveState = false;
192
194
  if (listPromise.cancel) {
193
195
  promises.list.cancel = async () => {
194
196
  let promise = listPromise.cancel();
197
+ isCancelled.value = true;
195
198
  if (promise) {
196
199
  await promise;
197
200
  }
@@ -22,6 +22,7 @@ export class ListSubscriptionError extends Error {
22
22
  * @property {object} functions - passed on to a created list instance if one is not provided
23
23
  * @property {ListInstance} listInstance - a list instance to use instead of creating one
24
24
  * @property {boolean} keepOldPages - if true, pages will not be cleared when defaultPageCallback is called. default is false.
25
+ * @property {boolean} clearListOnListIntentTriggered - if true, the list will be cleared when the list intent is triggered. default is false.
25
26
  */
26
27
 
27
28
  /* eslint-disable jsdoc/check-types */
@@ -70,7 +71,13 @@ export function useListSubscriptions(args, listInstances = {}) {
70
71
  * @param {ListSubscriptionOptions} options - the configuration options for the list subscription
71
72
  * @returns {ListSubscription} - the list subscription
72
73
  */
73
- export function useListSubscription({ listInstance, props, functions, keepOldPages = false }) {
74
+ export function useListSubscription({
75
+ listInstance,
76
+ props,
77
+ functions,
78
+ keepOldPages = false,
79
+ clearListOnListIntentTriggered = false,
80
+ }) {
74
81
  if (!listInstance && !props) {
75
82
  throw new ListSubscriptionError("useListSubscription should be passed listInstance or props and functions.");
76
83
  }
@@ -233,7 +240,12 @@ export function useListSubscription({ listInstance, props, functions, keepOldPag
233
240
  state.subscribed = toRef(subscribeIntent.state, "active");
234
241
 
235
242
  listIntent = useCancellableIntent({
236
- awaitableWithCancel: listInstance.list,
243
+ awaitableWithCancel: () => {
244
+ if (clearListOnListIntentTriggered) {
245
+ listInstance.clearList();
246
+ }
247
+ return listInstance.list();
248
+ },
237
249
  watchArguments: reactive({
238
250
  intendToList: toRef(state, "intendToList"),
239
251
  listArgs: toRef(parentState, "listArgs"),