@arrai-innovations/reactive-helpers 10.4.2 → 11.0.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.
@@ -0,0 +1,505 @@
1
+ import { doAwaitNot, doAwaitTimeout } from "../../../utils/watches.js";
2
+ import { nextTick, reactive, ref, unref } from "vue";
3
+ import { deepUnref } from "vue-deepunref";
4
+
5
+ describe("use/listSearch", () => {
6
+ let useListInstance, useListSearch, useListSearches, useListSort, useListRelated, useListCalculated;
7
+ beforeEach(async () => {
8
+ const listInstanceModule = await import("../../../use/listInstance");
9
+ useListInstance = listInstanceModule.useListInstance;
10
+ const listSearchModule = await import("../../../use/listSearch");
11
+ useListSearch = listSearchModule.useListSearch;
12
+ useListSearches = listSearchModule.useListSearches;
13
+ const listSortModule = await import("../../../use/listSort");
14
+ useListSort = listSortModule.useListSort;
15
+ const listRelatedModule = await import("../../../use/listRelated");
16
+ useListRelated = listRelatedModule.useListRelated;
17
+ const listCalculatedModule = await import("../../../use/listCalculated");
18
+ useListCalculated = listCalculatedModule.useListCalculated;
19
+ });
20
+ it("should match by search term", async () => {
21
+ const textSearchValue = ref("one");
22
+ // const textSearchValue = ref("");
23
+ const list = useListInstance({ props: {} });
24
+ const search = useListSearch({
25
+ parentState: list.state,
26
+ props: reactive({
27
+ textSearchRules: ["name"],
28
+ textSearchValue,
29
+ }),
30
+ throttle: 20,
31
+ });
32
+ await doAwaitNot({
33
+ obj: search.state,
34
+ prop: "running",
35
+ });
36
+ expect(search.state.objects).toEqual({});
37
+ list.addListObject({ id: 1, name: "one", has_things: true });
38
+ await doAwaitNot({
39
+ obj: search.state,
40
+ prop: "running",
41
+ });
42
+ await doAwaitTimeout(100);
43
+ expect(search.state.objects).toEqual({
44
+ 1: { id: 1, name: "one", has_things: true },
45
+ });
46
+ list.addListObject({ id: 2, name: "two", has_things: true });
47
+ await doAwaitNot({
48
+ obj: search.state,
49
+ prop: "running",
50
+ });
51
+ expect(search.state.objects).toEqual({
52
+ 1: { id: 1, name: "one", has_things: true },
53
+ });
54
+ list.addListObject({ id: 3, name: "three", has_things: true });
55
+ await doAwaitNot({
56
+ obj: search.state,
57
+ prop: "running",
58
+ });
59
+ expect(search.state.objects).toEqual({
60
+ 1: { id: 1, name: "one", has_things: true },
61
+ });
62
+ textSearchValue.value = "three";
63
+ await doAwaitNot({
64
+ obj: search.state,
65
+ prop: "running",
66
+ });
67
+ expect(search.state.objects).toEqual({
68
+ 3: { id: 3, name: "three", has_things: true },
69
+ });
70
+ list.addListObject({ id: 4, name: "four", has_things: true });
71
+ await doAwaitNot({
72
+ obj: search.state,
73
+ prop: "running",
74
+ });
75
+ expect(search.state.objects).toEqual({
76
+ 3: { id: 3, name: "three", has_things: true },
77
+ });
78
+ });
79
+ it("no args: returns objects unsearched", async () => {
80
+ const listInstance = useListInstance({ props: {} });
81
+ const listItems = [
82
+ { id: 4, name: "four", has_things: true },
83
+ { id: 2, name: "two", has_things: true },
84
+ { id: 3, name: "three", has_things: true },
85
+ { id: 1, name: "one", has_things: true },
86
+ ];
87
+ for (const item of listItems) {
88
+ listInstance.addListObject(item);
89
+ }
90
+ const search = useListSearch({
91
+ parentState: listInstance.state,
92
+ throttle: 20,
93
+ });
94
+ await nextTick();
95
+ await nextTick();
96
+ await nextTick();
97
+ expect(search.state.objects).toEqual(listInstance.state.objects);
98
+ });
99
+ describe("useListSearch operates on parentState modified by useListSort", () => {
100
+ it("computes state.order and state.objects in order", async () => {
101
+ vi.resetAllMocks();
102
+ const orderByRules = [{ key: "name", desc: true, localeCompare: false }];
103
+ const sortThrottleWait = 0;
104
+ const listInstance = useListInstance({ props: {} });
105
+ const listItems = [
106
+ { id: 4, name: "four", has_things: true },
107
+ { id: 2, name: "two", has_things: true },
108
+ { id: 3, name: "three", has_things: true },
109
+ { id: 1, name: "one", has_things: true },
110
+ ];
111
+ const expectedOrder = ["2", "3", "1", "4"];
112
+ const orderedObjects = [
113
+ { id: 2, name: "two", has_things: true },
114
+ { id: 3, name: "three", has_things: true },
115
+ { id: 1, name: "one", has_things: true },
116
+ { id: 4, name: "four", has_things: true },
117
+ ];
118
+ const listSort = useListSort({ parentState: listInstance.state, orderByRules, sortThrottleWait });
119
+ for (const item of listItems) {
120
+ listInstance.addListObject(item);
121
+ }
122
+ const search = useListSearch({
123
+ parentState: listSort.state,
124
+ throttle: 20,
125
+ });
126
+ await doAwaitNot({
127
+ obj: search.state,
128
+ prop: "running",
129
+ });
130
+ expect(search.state.order).toEqual(expectedOrder);
131
+ expect(deepUnref(search.state.objectsInOrder)).toEqual(orderedObjects);
132
+ });
133
+ });
134
+ describe("useListSearches accepts args and parentStates", () => {
135
+ it("looks the same as individual useListSearch instances", async () => {
136
+ vi.resetAllMocks();
137
+ const fields = ["id", "__str__", "name"];
138
+ const listInstanceA = useListInstance({
139
+ props: {
140
+ crudArgs: { stream: "test_streamA" },
141
+ listArgs: { user: 1 },
142
+ retrieveArgs: {
143
+ fields,
144
+ },
145
+ },
146
+ });
147
+ const listInstanceB = useListInstance({
148
+ props: {
149
+ crudArgs: { stream: "test_streamB" },
150
+ listArgs: { user: 2 },
151
+ retrieveArgs: {
152
+ fields,
153
+ },
154
+ },
155
+ });
156
+
157
+ const textSearchValueMarkerA = { marker: "A" };
158
+ const textSearchValueA = ref(textSearchValueMarkerA);
159
+ const textSearchValueMarkerB = { marker: "B" };
160
+ const textSearchValueB = ref(textSearchValueMarkerB);
161
+
162
+ const listSearchA = useListSearch({
163
+ parentState: listInstanceA.state,
164
+ props: reactive({
165
+ textSearchRules: ["name"],
166
+ textSearchValue: textSearchValueA,
167
+ customSearchOptions: {
168
+ limit: 10,
169
+ },
170
+ customIndexOptions: {
171
+ tokenize: "full",
172
+ },
173
+ }),
174
+ throttle: 20,
175
+ });
176
+ const listSearchB = useListSearch({
177
+ parentState: listInstanceB.state,
178
+ props: reactive({
179
+ textSearchRules: ["code"],
180
+ textSearchValue: textSearchValueB,
181
+ customSearchOptions: {
182
+ limit: 30,
183
+ },
184
+ customIndexOptions: {
185
+ tokenize: "strict",
186
+ },
187
+ }),
188
+ throttle: 40,
189
+ });
190
+ const listSearchArgs = {
191
+ A: {
192
+ props: reactive({
193
+ textSearchRules: ["name"],
194
+ textSearchValue: textSearchValueA,
195
+ customSearchOptions: {
196
+ limit: 10,
197
+ },
198
+ customIndexOptions: {
199
+ tokenize: "full",
200
+ },
201
+ }),
202
+ throttle: 20,
203
+ },
204
+ B: {
205
+ props: reactive({
206
+ textSearchRules: ["code"],
207
+ textSearchValue: textSearchValueB,
208
+ customSearchOptions: {
209
+ limit: 30,
210
+ },
211
+ customIndexOptions: {
212
+ tokenize: "strict",
213
+ },
214
+ }),
215
+ throttle: 40,
216
+ },
217
+ };
218
+ const listInstances = {
219
+ A: listInstanceA,
220
+ B: listInstanceB,
221
+ };
222
+ const listSearches = useListSearches(listSearchArgs, listInstances);
223
+
224
+ expect(unref(listSearches.A.state.textSearchRules)).toEqual(unref(listSearchA.state.textSearchRules));
225
+ expect(unref(listSearches.B.state.textSearchRules)).toEqual(unref(listSearchB.state.textSearchRules));
226
+ expect(unref(listSearches.A.parentState)).toEqual(unref(listSearchA.parentState));
227
+ expect(unref(listSearches.B.parentState)).toEqual(unref(listSearchB.parentState));
228
+ expect(deepUnref(listSearches.A.state)).toEqual(deepUnref(listSearchA.state));
229
+ expect(deepUnref(listSearches.B.state)).toEqual(deepUnref(listSearchB.state));
230
+ expect(deepUnref(listSearches.A.textSearchIndex.state)).toEqual(
231
+ deepUnref(listSearchA.textSearchIndex.state)
232
+ );
233
+ expect(deepUnref(listSearches.B.textSearchIndex.state)).toEqual(
234
+ deepUnref(listSearchB.textSearchIndex.state)
235
+ );
236
+ });
237
+ });
238
+ describe("useListSearch accepts relatedItem. and calculatedItem. rules", () => {
239
+ it("in textSearchRules", async () => {
240
+ const textSearchValue = ref("four");
241
+ const list = useListInstance({ props: {} });
242
+ const relatedList = useListInstance({ props: {} });
243
+ const related = useListRelated({
244
+ parentState: list.state,
245
+ relatedObjectsRules: {
246
+ relatedRuleName: {
247
+ objects: relatedList.state.objects,
248
+ pkKey: "related_id",
249
+ },
250
+ },
251
+ });
252
+ const calculated = useListCalculated({
253
+ parentState: related.state,
254
+ calculatedObjectsRules: {
255
+ calculatedRuleName: (object) => object.id + "calculated",
256
+ },
257
+ });
258
+ const search = useListSearch({
259
+ parentState: calculated.state,
260
+ props: reactive({
261
+ textSearchRules: ["relatedItem.relatedRuleName.name", "calculatedItem.calculatedRuleName"],
262
+ textSearchValue,
263
+ }),
264
+ throttle: 20,
265
+ });
266
+ await doAwaitNot({
267
+ obj: search.state,
268
+ prop: "running",
269
+ });
270
+ expect(search.state.objects).toEqual({});
271
+ relatedList.addListObject({ id: 2, name: "two", has_things: false });
272
+ list.addListObject({ id: 1, name: "one", has_things: true, related_id: 2 });
273
+ relatedList.addListObject({ id: 4, name: "four", has_things: false });
274
+ list.addListObject({ id: 3, name: "three", has_things: true, related_id: 4 });
275
+ await doAwaitNot({
276
+ obj: search.state,
277
+ prop: "running",
278
+ });
279
+
280
+ expect(search.state.objects).toEqual({
281
+ 3: { id: 3, name: "three", has_things: true, related_id: 4 },
282
+ });
283
+ textSearchValue.value = "1calculated";
284
+ await doAwaitNot({
285
+ obj: search.state,
286
+ prop: "running",
287
+ });
288
+ expect(search.state.objects).toEqual({
289
+ 1: { id: 1, name: "one", has_things: true, related_id: 2 },
290
+ });
291
+ });
292
+ });
293
+ describe("useListSearch updates index when", () => {
294
+ it("parentState.objects is updated", async () => {
295
+ const list = useListInstance({ props: {} });
296
+ const textSearchValue = ref("");
297
+ const search = useListSearch({
298
+ parentState: list.state,
299
+ props: reactive({
300
+ textSearchRules: ["name"],
301
+ textSearchValue,
302
+ }),
303
+ throttle: 20,
304
+ });
305
+ expect(search.state.objects).toEqual({});
306
+ list.addListObject({ id: 1, name: "one" });
307
+ list.addListObject({ id: 2, name: "two" });
308
+
309
+ await doAwaitNot({
310
+ obj: search.state,
311
+ prop: "running",
312
+ });
313
+ expect(search.state.objects).toEqual({
314
+ 1: { id: 1, name: "one" },
315
+ 2: { id: 2, name: "two" },
316
+ });
317
+ textSearchValue.value = "one";
318
+ await doAwaitNot({
319
+ obj: search.state,
320
+ prop: "running",
321
+ });
322
+ await doAwaitTimeout(500);
323
+ expect(search.state.objects).toEqual({
324
+ 1: { id: 1, name: "one" },
325
+ });
326
+ list.state.objects[2].name = "one";
327
+ await doAwaitNot({
328
+ obj: search.state,
329
+ prop: "running",
330
+ });
331
+ expect(search.state.objects).toEqual({
332
+ 1: { id: 1, name: "one" },
333
+ 2: { id: 2, name: "one" },
334
+ });
335
+ });
336
+ it("parentState.relatedObjects is updated", async () => {
337
+ const list = useListInstance({ props: {} });
338
+ const relatedList = useListInstance({ props: {} });
339
+ const related = useListRelated({
340
+ parentState: list.state,
341
+ relatedObjectsRules: reactive({
342
+ relatedRuleName: {
343
+ objects: relatedList.state.objects,
344
+ pkKey: "related_id",
345
+ },
346
+ }),
347
+ });
348
+ const textSearchValue = ref("");
349
+ const search = useListSearch({
350
+ parentState: related.state,
351
+ props: reactive({
352
+ textSearchRules: ["relatedItem.relatedRuleName.name"],
353
+ textSearchValue,
354
+ }),
355
+ throttle: 20,
356
+ });
357
+ await nextTick();
358
+ expect(search.state.objects).toEqual({});
359
+ relatedList.addListObject({ id: 2, name: "two", has_things: false });
360
+ list.addListObject({ id: 1, name: "one", has_things: true, related_id: 2 });
361
+ relatedList.addListObject({ id: 4, name: "four", has_things: false });
362
+ list.addListObject({ id: 3, name: "three", has_things: true, related_id: 4 });
363
+ await doAwaitNot({
364
+ obj: search.state,
365
+ prop: "running",
366
+ });
367
+ expect(search.state.objects).toEqual({
368
+ 1: { id: 1, name: "one", has_things: true, related_id: 2 },
369
+ 3: { id: 3, name: "three", has_things: true, related_id: 4 },
370
+ });
371
+ textSearchValue.value = "four";
372
+ await doAwaitNot({
373
+ obj: search.state,
374
+ prop: "running",
375
+ });
376
+ expect(search.state.objects).toEqual({
377
+ 3: { id: 3, name: "three", has_things: true, related_id: 4 },
378
+ });
379
+ });
380
+ it("parentState.calculatedObjects is updated", async () => {
381
+ const list = useListInstance({ props: {} });
382
+ const calculated = useListCalculated({
383
+ parentState: list.state,
384
+ calculatedObjectsRules: reactive({
385
+ calculatedRuleName: (object) => object.basis.split("").join("z"),
386
+ }),
387
+ });
388
+ const textSearchValue = ref("");
389
+ const search = useListSearch({
390
+ parentState: calculated.state,
391
+ props: reactive({
392
+ textSearchRules: ["calculatedItem.calculatedRuleName"],
393
+ textSearchValue,
394
+ }),
395
+ throttle: 20,
396
+ });
397
+ await nextTick();
398
+ expect(search.state.objects).toEqual({});
399
+ list.addListObject({ id: 1, name: "one", basis: "one" });
400
+ list.addListObject({ id: 2, name: "two", basis: "two" });
401
+ await doAwaitNot({
402
+ obj: search.state,
403
+ prop: "running",
404
+ });
405
+ expect(search.state.objects).toEqual({
406
+ 1: { id: 1, name: "one", basis: "one" },
407
+ 2: { id: 2, name: "two", basis: "two" },
408
+ });
409
+ textSearchValue.value = "oznze";
410
+ await doAwaitNot({
411
+ obj: search.state,
412
+ prop: "running",
413
+ });
414
+ expect(search.state.objects).toEqual({
415
+ 1: { id: 1, name: "one", basis: "one" },
416
+ });
417
+ list.state.objects[2].basis = "one";
418
+ await doAwaitNot({
419
+ obj: search.state,
420
+ prop: "running",
421
+ });
422
+ expect(search.state.objects).toEqual({
423
+ 1: { id: 1, name: "one", basis: "one" },
424
+ 2: { id: 2, name: "two", basis: "one" },
425
+ });
426
+ });
427
+ it("textSearchRules is updated", async () => {
428
+ const list = useListInstance({ props: {} });
429
+ const textSearchValue = ref("");
430
+ const searchProps = reactive({
431
+ textSearchValue,
432
+ textSearchRules: ["name"],
433
+ });
434
+ const search = useListSearch({
435
+ parentState: list.state,
436
+ props: searchProps,
437
+ throttle: 20,
438
+ });
439
+ await nextTick();
440
+ expect(search.state.objects).toEqual({});
441
+ list.addListObject({ id: 1, name: "one", code: "eno" });
442
+ list.addListObject({ id: 2, name: "two", code: "owt" });
443
+ await doAwaitNot({
444
+ obj: search.state,
445
+ prop: "running",
446
+ });
447
+ expect(search.state.objects).toEqual({
448
+ 1: { id: 1, name: "one", code: "eno" },
449
+ 2: { id: 2, name: "two", code: "owt" },
450
+ });
451
+ textSearchValue.value = "one";
452
+ await doAwaitNot({
453
+ obj: search.state,
454
+ prop: "running",
455
+ });
456
+ expect(search.state.objects).toEqual({
457
+ 1: { id: 1, name: "one", code: "eno" },
458
+ });
459
+ searchProps.textSearchRules = ["code"];
460
+ await doAwaitNot({
461
+ obj: search.state,
462
+ prop: "running",
463
+ });
464
+ expect(search.state.objects).toEqual({});
465
+ textSearchValue.value = "eno";
466
+ await doAwaitNot({
467
+ obj: search.state,
468
+ prop: "running",
469
+ });
470
+ expect(search.state.objects).toEqual({
471
+ 1: { id: 1, name: "one", code: "eno" },
472
+ });
473
+ });
474
+ });
475
+ it("does not pass through when showAllWhenEmpty is false", async () => {
476
+ const list = useListInstance({ props: {} });
477
+ const textSearchValue = ref("");
478
+ const search = useListSearch({
479
+ parentState: list.state,
480
+ props: reactive({
481
+ textSearchRules: ["name"],
482
+ textSearchValue,
483
+ }),
484
+ throttle: 20,
485
+ showAllWhenEmpty: false,
486
+ });
487
+ await doAwaitNot({
488
+ obj: search.state,
489
+ prop: "running",
490
+ });
491
+ expect(search.state.objects).toEqual({});
492
+ list.addListObject({ id: 1, name: "one" });
493
+ list.addListObject({ id: 2, name: "two" });
494
+ await doAwaitNot({
495
+ obj: search.state,
496
+ prop: "running",
497
+ });
498
+ expect(search.state.objects).toEqual({});
499
+ textSearchValue.value = "one";
500
+ await doAwaitNot({
501
+ obj: search.state,
502
+ prop: "running",
503
+ });
504
+ });
505
+ });
@@ -85,7 +85,6 @@ describe("use/useListSort", () => {
85
85
  expect(listSort.state.order).toEqual([]);
86
86
  expect(listSort.state.objectsInOrder).toEqual([]);
87
87
  expect(listSort.state.sortCriteria).toEqual({});
88
- expect(listSort.state.sortCriteriaEffectScopes).toEqual({});
89
88
  expect(listSort.state.orderByDesc).toEqual([true, false]);
90
89
  });
91
90
  describe("addSortCriteria and removeSortCriteria", () => {