@arrai-innovations/reactive-helpers 2.5.2 → 2.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrai-innovations/reactive-helpers",
3
- "version": "2.5.2",
3
+ "version": "2.6.0",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -1,8 +1,8 @@
1
- import { expectErrorToBeNull } from "../expectHelpers";
2
- import { isReactive, nextTick, reactive } from "vue";
3
- import { keyBy } from "lodash";
4
1
  import flushPromises from "flush-promises";
2
+ import { keyBy } from "lodash";
5
3
  import { inspect } from "util";
4
+ import { isReactive, nextTick, reactive } from "vue";
5
+ import { expectErrorToBeNull } from "../expectHelpers";
6
6
 
7
7
  afterAll(() => {
8
8
  jest.restoreAllMocks();
@@ -98,7 +98,7 @@ describe("use/listInstance.spec.js", function () {
98
98
  expectErrorToBeNull(listInstance.state.error);
99
99
  expect(listInstance.state.errored).toBe(false);
100
100
  expect(listInstance.state.loading).toBe(true);
101
- expect({ ...listInstance.state.object }).toEqual({});
101
+ expect({ ...listInstance.state.objects }).toEqual({});
102
102
 
103
103
  await nextTick();
104
104
 
@@ -196,7 +196,7 @@ describe("use/listInstance.spec.js", function () {
196
196
  expectErrorToBeNull(listInstance.state.error);
197
197
  expect(listInstance.state.errored).toBe(false);
198
198
  expect(listInstance.state.loading).toBe(true);
199
- expect({ ...listInstance.state.object }).toEqual({});
199
+ expect({ ...listInstance.state.objects }).toEqual({});
200
200
 
201
201
  await nextTick();
202
202
 
@@ -250,7 +250,7 @@ describe("use/listInstance.spec.js", function () {
250
250
  expectErrorToBeNull(listInstance.state.error);
251
251
  expect(listInstance.state.errored).toBe(false);
252
252
  expect(listInstance.state.loading).toBe(true);
253
- expect({ ...listInstance.state.object }).toEqual({});
253
+ expect({ ...listInstance.state.objects }).toEqual({});
254
254
 
255
255
  await nextTick();
256
256
 
@@ -285,6 +285,58 @@ describe("use/listInstance.spec.js", function () {
285
285
  });
286
286
  expect(globalList).toHaveBeenCalledTimes(1);
287
287
  });
288
+ it("clearList should empty the list", async function () {
289
+ const listArgs = reactive({
290
+ user: 1,
291
+ });
292
+ const retrieveArgs = reactive({
293
+ fields,
294
+ });
295
+ const listInstance = useListInstance({
296
+ listArgs,
297
+ retrieveArgs,
298
+ });
299
+ let crudListResolve;
300
+ const crudListPromise = new Promise((resolve) => {
301
+ crudListResolve = resolve;
302
+ });
303
+ let passedPageCallback;
304
+ globalList.mockImplementation(({ pageCallback }) => {
305
+ passedPageCallback = pageCallback;
306
+ return crudListPromise;
307
+ });
308
+ const liListResolve = listInstance.list();
309
+ passedPageCallback(crudListResolvedPage1);
310
+ passedPageCallback(crudListResolvedPage2);
311
+ crudListResolve();
312
+ await flushPromises();
313
+
314
+ await expect(liListResolve).resolves.toBe(true);
315
+
316
+ expectErrorToBeNull(listInstance.state.error);
317
+ expect(listInstance.state.errored).toBe(false);
318
+ expect(listInstance.state.loading).toBe(false);
319
+ expect({ ...listInstance.state.objects }).toEqual(crudListResolvedObjects);
320
+ await flushPromises();
321
+ expect(listInstance.state.order).toEqual(Object.keys(crudListResolvedObjects));
322
+ expect(listInstance.state.objectsInOrder).toEqual(Object.values(crudListResolvedObjects));
323
+ expect(globalList).toHaveBeenCalledWith({
324
+ crudArgs: { stream: "test_stream" },
325
+ listArgs: { user: 1 },
326
+ retrieveArgs: { fields: fields },
327
+ pageCallback: passedPageCallback,
328
+ });
329
+ expect(globalList).toHaveBeenCalledTimes(1);
330
+
331
+ listInstance.clearList();
332
+ expectErrorToBeNull(listInstance.state.error);
333
+ expect(listInstance.state.errored).toBe(false);
334
+ expect(listInstance.state.loading).toBe(false);
335
+ expect({ ...listInstance.state.objects }).toEqual({});
336
+ await flushPromises();
337
+ expect(listInstance.state.order).toEqual([]);
338
+ expect(listInstance.state.objectsInOrder).toEqual([]);
339
+ });
288
340
  });
289
341
  it("useListInstances", async function () {
290
342
  const listInstanceA = useListInstance({
@@ -1,8 +1,8 @@
1
+ import inspect from "browser-util-inspect";
1
2
  import { cloneDeep } from "lodash";
2
3
  import { computed, effectScope, reactive } from "vue";
3
4
  import { assignReactiveObject } from "../utils/assignReactiveObject";
4
5
  import { getFakeId } from "../utils/getFakeId";
5
- import inspect from "browser-util-inspect";
6
6
 
7
7
  export class ListError extends Error {
8
8
  constructor(message, code) {
@@ -31,6 +31,40 @@ export function useListInstances(listInstanceArgs) {
31
31
  }
32
32
 
33
33
  export function useListInstance({ crudArgs, listArgs = {}, retrieveArgs = {} }) {
34
+ // ### touching the _objectsMap or _objectsProxy directly will not trigger reactivity ###
35
+ const _objectsMap = new Map();
36
+ // ### touching the _objectsMap or _objectsProxy directly will not trigger reactivity ###
37
+ const _objectsProxy = new Proxy(_objectsMap, {
38
+ get(target, prop) {
39
+ return target.get(prop); // map's get not Reflect.get
40
+ },
41
+ has(target, prop) {
42
+ return target.has(prop); // map's has not Reflect.has
43
+ },
44
+ set(target, prop, value) {
45
+ target.set(prop, value); // map.set() returns the map, we don't need that
46
+ return true;
47
+ },
48
+ ownKeys(target) {
49
+ return [...target.keys()]; // map.keys() returns an iterator, we need an array
50
+ },
51
+ deleteProperty(target, p) {
52
+ target.delete(p); // map.delete() returns false if the key didn't exist
53
+ return true; // objects return true regardless of whether the property existed
54
+ },
55
+ getOwnPropertyDescriptor(target, p) {
56
+ const value = target.get(p);
57
+ return value
58
+ ? {
59
+ configurable: true,
60
+ enumerable: true,
61
+ value,
62
+ writable: true,
63
+ }
64
+ : Reflect.getOwnPropertyDescriptor(target, p); // we can't report target properties as non-existent re: proxy invariants
65
+ },
66
+ });
67
+ // ### touching the _objectsMap or _objectsProxy directly will not trigger reactivity ###
34
68
  const state = reactive({
35
69
  crud: {
36
70
  args: {},
@@ -38,11 +72,10 @@ export function useListInstance({ crudArgs, listArgs = {}, retrieveArgs = {} })
38
72
  },
39
73
  retrieveArgs,
40
74
  listArgs,
41
- objects: {},
75
+ objects: _objectsProxy,
42
76
  loading: undefined,
43
77
  errored: false,
44
78
  error: null,
45
- order: [],
46
79
  });
47
80
  // prevent linking of all instances to the same default .args object
48
81
  Object.assign(state.crud, cloneDeep(defaultCrud));
@@ -110,10 +143,7 @@ export function useListInstance({ crudArgs, listArgs = {}, retrieveArgs = {} })
110
143
  if (object.id in state.objects) {
111
144
  throw new ListError(`addListObject: list already has object for id: ${inspect(object.id)}`, "duplicate-id");
112
145
  }
113
- state.objects[object.id] = {};
114
- // objects keys are always strings.
115
- state.order.push(`${object.id}`);
116
- assignReactiveObject(state.objects[object.id], object);
146
+ state.objects[object.id] = object;
117
147
  }
118
148
 
119
149
  function updateListObject(object) {
@@ -136,16 +166,12 @@ export function useListInstance({ crudArgs, listArgs = {}, retrieveArgs = {} })
136
166
  "missing-object"
137
167
  );
138
168
  }
139
- // objects keys are always strings.
140
- state.order.splice(state.order.indexOf(`${objectId}`), 1);
141
169
  delete state.objects[objectId];
142
170
  }
143
171
 
144
172
  function clearList() {
145
- state.order.splice(0);
146
- for (const item in state.objects) {
147
- delete state.objects[item];
148
- }
173
+ assignReactiveObject(state.objects, {});
174
+ clearError();
149
175
  }
150
176
 
151
177
  function ourGetFakeId() {
@@ -160,7 +186,8 @@ export function useListInstance({ crudArgs, listArgs = {}, retrieveArgs = {} })
160
186
  const es = effectScope();
161
187
 
162
188
  es.run(() => {
163
- state.objectsInOrder = computed(() => state.order.map((id) => state.objects[id]));
189
+ state.objectsInOrder = computed(() => Object.values(state.objects));
190
+ state.order = computed(() => Object.keys(state.objects));
164
191
  });
165
192
 
166
193
  const returnedObject = {
package/use/listSort.js CHANGED
@@ -105,17 +105,12 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
105
105
 
106
106
  function sortWatch() {
107
107
  if (!state.orderByRules || !state.orderByRules.length) {
108
- const serverOrderObjectsInOrder = parentState.order.map((e) => parentState.objects[e]).filter(identity);
109
- assignReactiveObject(
110
- state.order,
111
- serverOrderObjectsInOrder.map((e) => String(e.id))
112
- );
113
- assignReactiveObject(state.objectsInOrder, serverOrderObjectsInOrder);
108
+ assignReactiveObject(state.order, Object.keys(parentState.objects));
109
+ assignReactiveObject(state.objectsInOrder, Object.values(parentState.objects));
114
110
  return;
115
111
  }
116
112
 
117
113
  let idList = Object.keys(parentState.objects);
118
-
119
114
  idList.sort((xKey, yKey) => {
120
115
  const xCriteria = state.sortCriteria[xKey];
121
116
  const yCriteria = state.sortCriteria[yKey];
@@ -166,7 +161,8 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
166
161
  immediate: true,
167
162
  });
168
163
 
169
- watch([toRef(state, "orderByDesc"), () => state.sortCriteria, () => parentState.order], throttledSortWatch, {
164
+ // watching parentState.order triggers some out of order `computed`s, now that listInstance.order is a computed.
165
+ watch([toRef(state, "orderByDesc"), () => state.sortCriteria], throttledSortWatch, {
170
166
  deep: true,
171
167
  });
172
168
  onScopeDispose(() => {
@@ -1,17 +1,17 @@
1
- import { isArray, isSet } from "lodash";
1
+ import { isArray, isSet, isMap } from "lodash";
2
2
 
3
- export function getFakeId(arraySetOrObject, key = "id") {
3
+ export function getFakeId(arraySetMapOrObject, key = "id") {
4
4
  // sets are assumed to be of ids
5
5
  // arrays are assumed to be objects with an property matching the passed key
6
6
  // objects are assumed to have keys that are ids
7
7
  let fakeId;
8
8
  let test;
9
- if (isSet(arraySetOrObject)) {
10
- test = () => arraySetOrObject.has(fakeId);
11
- } else if (isArray(arraySetOrObject)) {
12
- test = () => arraySetOrObject.some((item) => item[key] === fakeId);
9
+ if (isSet(arraySetMapOrObject) || isMap(arraySetMapOrObject)) {
10
+ test = () => arraySetMapOrObject.has(fakeId);
11
+ } else if (isArray(arraySetMapOrObject)) {
12
+ test = () => arraySetMapOrObject.some((item) => item[key] === fakeId);
13
13
  } else {
14
- test = () => fakeId in arraySetOrObject;
14
+ test = () => fakeId in arraySetMapOrObject;
15
15
  }
16
16
  do {
17
17
  fakeId = Math.floor(Math.random() * Number.MIN_SAFE_INTEGER);