@arrai-innovations/reactive-helpers 1.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.
- package/.circleci/config.yml +91 -0
- package/.commitlintrc.json +3 -0
- package/.editorconfig +5 -0
- package/.eslintignore +2 -0
- package/.eslintrc.js +26 -0
- package/.husky/commit-msg +2 -0
- package/.husky/pre-commit +2 -0
- package/.idea/encodings.xml +6 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +8 -0
- package/.idea/reactive-helpers.iml +15 -0
- package/.lintstagedrc +11 -0
- package/.prettierignore +4 -0
- package/.prettierrc.js +5 -0
- package/README.md +598 -0
- package/babel.config.js +15 -0
- package/index.js +2 -0
- package/jest.config.mjs +27 -0
- package/package.json +59 -0
- package/tests/unit/.eslintrc.js +10 -0
- package/tests/unit/expectHelpers.js +6 -0
- package/tests/unit/mockOnUnmounted.js +9 -0
- package/tests/unit/use/index.spec.js +18 -0
- package/tests/unit/use/listFilter.spec.js +332 -0
- package/tests/unit/use/listInstance.spec.js +424 -0
- package/tests/unit/use/listRelated.spec.js +73 -0
- package/tests/unit/use/listSort.spec.js +220 -0
- package/tests/unit/use/listSubscription.spec.js +540 -0
- package/tests/unit/use/objectInstance.spec.js +897 -0
- package/tests/unit/use/objectSubscription.spec.js +671 -0
- package/tests/unit/use/search.spec.js +67 -0
- package/tests/unit/use/watches.spec.js +252 -0
- package/tests/unit/utils/assignReactiveObject.spec.js +127 -0
- package/tests/unit/utils/index.spec.js +18 -0
- package/tests/unit/utils/keyDiff.spec.js +67 -0
- package/tests/unit/utils/set.spec.js +68 -0
- package/tests/unit/utils/unrefAndToRawDeep.spec.js +170 -0
- package/use/index.js +8 -0
- package/use/listFilter.js +157 -0
- package/use/listInstance.js +144 -0
- package/use/listRelated.js +135 -0
- package/use/listSort.js +190 -0
- package/use/listSubscription.js +143 -0
- package/use/objectInstance.js +222 -0
- package/use/objectSubscription.js +188 -0
- package/use/search.js +104 -0
- package/utils/assignReactiveObject.js +62 -0
- package/utils/index.js +5 -0
- package/utils/keyDiff.js +10 -0
- package/utils/set.js +48 -0
- package/utils/unrefAndToRawDeep.js +49 -0
- package/utils/watches.js +170 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import * as use from "../../../use";
|
|
3
|
+
|
|
4
|
+
describe("use/index.js", function () {
|
|
5
|
+
it("should export everything exported by individual files", async function () {
|
|
6
|
+
const files = await fs.readdir("./use");
|
|
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(`../../../use/${module}`);
|
|
11
|
+
exportedKeys.push(...Object.keys(moduleExports).filter((key) => key !== "default"));
|
|
12
|
+
}
|
|
13
|
+
const useKeys = Object.keys(use);
|
|
14
|
+
useKeys.sort();
|
|
15
|
+
exportedKeys.sort();
|
|
16
|
+
expect(useKeys).toEqual(exportedKeys);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { nextTick, reactive, ref } from "vue";
|
|
2
|
+
import { doAwaitNot } from "../../../utils/watches";
|
|
3
|
+
import { useListFilters, useListSort } from "../../../use";
|
|
4
|
+
import { unrefAndToRawDeep } from "../../../utils";
|
|
5
|
+
|
|
6
|
+
describe("use/listFilter", () => {
|
|
7
|
+
let useListInstance, useListFilter, setDefaultSearchOptions;
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
const listInstanceModule = await import("../../../use/listInstance");
|
|
10
|
+
useListInstance = listInstanceModule.useListInstance;
|
|
11
|
+
const listFilterModule = await import("../../../use/listFilter");
|
|
12
|
+
useListFilter = listFilterModule.useListFilter;
|
|
13
|
+
const searchModule = await import("../../../use/search");
|
|
14
|
+
setDefaultSearchOptions = searchModule.setDefaultSearchOptions;
|
|
15
|
+
setDefaultSearchOptions({
|
|
16
|
+
throttle: 0,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should match an allowed values list", async () => {
|
|
21
|
+
const list = useListInstance({});
|
|
22
|
+
const filter = useListFilter({
|
|
23
|
+
parentState: list.state,
|
|
24
|
+
allowedValues: reactive({
|
|
25
|
+
1: true,
|
|
26
|
+
3: true,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
await nextTick();
|
|
30
|
+
expect(filter.state.objects).toEqual({});
|
|
31
|
+
list.addListObject({ id: 1, name: "one", has_things: true });
|
|
32
|
+
await nextTick();
|
|
33
|
+
expect(filter.state.objects).toEqual({
|
|
34
|
+
1: { id: 1, name: "one", has_things: true },
|
|
35
|
+
});
|
|
36
|
+
list.addListObject({ id: 2, name: "two", has_things: true });
|
|
37
|
+
await nextTick();
|
|
38
|
+
expect(filter.state.objects).toEqual({
|
|
39
|
+
1: { id: 1, name: "one", has_things: true },
|
|
40
|
+
});
|
|
41
|
+
list.addListObject({ id: 3, name: "three", has_things: true });
|
|
42
|
+
await nextTick();
|
|
43
|
+
expect(filter.state.objects).toEqual({
|
|
44
|
+
1: { id: 1, name: "one", has_things: true },
|
|
45
|
+
3: { id: 3, name: "three", has_things: true },
|
|
46
|
+
});
|
|
47
|
+
list.addListObject({ id: 4, name: "four", has_things: true });
|
|
48
|
+
await nextTick();
|
|
49
|
+
expect(filter.state.objects).toEqual({
|
|
50
|
+
1: { id: 1, name: "one", has_things: true },
|
|
51
|
+
3: { id: 3, name: "three", has_things: true },
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it("should match an allowed filter function", async () => {
|
|
55
|
+
const list = useListInstance({});
|
|
56
|
+
const filter = useListFilter({
|
|
57
|
+
parentState: list.state,
|
|
58
|
+
allowedFilter: (object) => object.id == 1 || object.id == 3,
|
|
59
|
+
});
|
|
60
|
+
await nextTick();
|
|
61
|
+
expect(filter.state.objects).toEqual({});
|
|
62
|
+
list.addListObject({ id: 1, name: "one", has_things: true });
|
|
63
|
+
await nextTick();
|
|
64
|
+
expect(filter.state.objects).toEqual({
|
|
65
|
+
1: { id: 1, name: "one", has_things: true },
|
|
66
|
+
});
|
|
67
|
+
list.addListObject({ id: 2, name: "two", has_things: true });
|
|
68
|
+
await nextTick();
|
|
69
|
+
expect(filter.state.objects).toEqual({
|
|
70
|
+
1: { id: 1, name: "one", has_things: true },
|
|
71
|
+
});
|
|
72
|
+
list.addListObject({ id: 3, name: "three", has_things: true });
|
|
73
|
+
await nextTick();
|
|
74
|
+
expect(filter.state.objects).toEqual({
|
|
75
|
+
1: { id: 1, name: "one", has_things: true },
|
|
76
|
+
3: { id: 3, name: "three", has_things: true },
|
|
77
|
+
});
|
|
78
|
+
list.addListObject({ id: 4, name: "four", has_things: true });
|
|
79
|
+
await nextTick();
|
|
80
|
+
expect(filter.state.objects).toEqual({
|
|
81
|
+
1: { id: 1, name: "one", has_things: true },
|
|
82
|
+
3: { id: 3, name: "three", has_things: true },
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
it("should match by search term", async () => {
|
|
86
|
+
const textSearchValue = ref("one");
|
|
87
|
+
const list = useListInstance({});
|
|
88
|
+
const filter = useListFilter({
|
|
89
|
+
parentState: list.state,
|
|
90
|
+
useTextSearch: true,
|
|
91
|
+
textSearchRules: ["name"],
|
|
92
|
+
textSearchValue,
|
|
93
|
+
});
|
|
94
|
+
await nextTick();
|
|
95
|
+
// todo: it would be nice to wait for all searches to finish, instead of awaiting the first one back.
|
|
96
|
+
await doAwaitNot({
|
|
97
|
+
obj: filter.state,
|
|
98
|
+
prop: "searching",
|
|
99
|
+
});
|
|
100
|
+
await nextTick();
|
|
101
|
+
expect(filter.state.objects).toEqual({});
|
|
102
|
+
list.addListObject({ id: 1, name: "one", has_things: true });
|
|
103
|
+
await nextTick();
|
|
104
|
+
await doAwaitNot({
|
|
105
|
+
obj: filter.state,
|
|
106
|
+
prop: "searching",
|
|
107
|
+
});
|
|
108
|
+
await nextTick();
|
|
109
|
+
expect(filter.state.objects).toEqual({
|
|
110
|
+
1: { id: 1, name: "one", has_things: true },
|
|
111
|
+
});
|
|
112
|
+
list.addListObject({ id: 2, name: "two", has_things: true });
|
|
113
|
+
await nextTick();
|
|
114
|
+
await doAwaitNot({
|
|
115
|
+
obj: filter.state,
|
|
116
|
+
prop: "searching",
|
|
117
|
+
});
|
|
118
|
+
await nextTick();
|
|
119
|
+
expect(filter.state.objects).toEqual({
|
|
120
|
+
1: { id: 1, name: "one", has_things: true },
|
|
121
|
+
});
|
|
122
|
+
list.addListObject({ id: 3, name: "three", has_things: true });
|
|
123
|
+
await nextTick();
|
|
124
|
+
await doAwaitNot({
|
|
125
|
+
obj: filter.state,
|
|
126
|
+
prop: "searching",
|
|
127
|
+
});
|
|
128
|
+
await nextTick();
|
|
129
|
+
expect(filter.state.objects).toEqual({
|
|
130
|
+
1: { id: 1, name: "one", has_things: true },
|
|
131
|
+
});
|
|
132
|
+
textSearchValue.value = "three";
|
|
133
|
+
await nextTick();
|
|
134
|
+
await doAwaitNot({
|
|
135
|
+
obj: filter.state,
|
|
136
|
+
prop: "searching",
|
|
137
|
+
});
|
|
138
|
+
await nextTick();
|
|
139
|
+
expect(filter.state.objects).toEqual({
|
|
140
|
+
3: { id: 3, name: "three", has_things: true },
|
|
141
|
+
});
|
|
142
|
+
list.addListObject({ id: 4, name: "four", has_things: true });
|
|
143
|
+
await nextTick();
|
|
144
|
+
await doAwaitNot({
|
|
145
|
+
obj: filter.state,
|
|
146
|
+
prop: "searching",
|
|
147
|
+
});
|
|
148
|
+
await nextTick();
|
|
149
|
+
expect(filter.state.objects).toEqual({
|
|
150
|
+
3: { id: 3, name: "three", has_things: true },
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
it("should match an excluded filter function", async () => {
|
|
154
|
+
const list = useListInstance({});
|
|
155
|
+
const filter = useListFilter({
|
|
156
|
+
parentState: list.state,
|
|
157
|
+
excludedFilter: (object) => object.id == 2 || object.id == 4,
|
|
158
|
+
});
|
|
159
|
+
await nextTick();
|
|
160
|
+
expect(filter.state.objects).toEqual({});
|
|
161
|
+
list.addListObject({ id: 1, name: "one", has_things: true });
|
|
162
|
+
await nextTick();
|
|
163
|
+
expect(filter.state.objects).toEqual({
|
|
164
|
+
1: { id: 1, name: "one", has_things: true },
|
|
165
|
+
});
|
|
166
|
+
list.addListObject({ id: 2, name: "two", has_things: true });
|
|
167
|
+
await nextTick();
|
|
168
|
+
expect(filter.state.objects).toEqual({
|
|
169
|
+
1: { id: 1, name: "one", has_things: true },
|
|
170
|
+
});
|
|
171
|
+
list.addListObject({ id: 3, name: "three", has_things: true });
|
|
172
|
+
await nextTick();
|
|
173
|
+
expect(filter.state.objects).toEqual({
|
|
174
|
+
1: { id: 1, name: "one", has_things: true },
|
|
175
|
+
3: { id: 3, name: "three", has_things: true },
|
|
176
|
+
});
|
|
177
|
+
list.addListObject({ id: 4, name: "four", has_things: true });
|
|
178
|
+
await nextTick();
|
|
179
|
+
expect(filter.state.objects).toEqual({
|
|
180
|
+
1: { id: 1, name: "one", has_things: true },
|
|
181
|
+
3: { id: 3, name: "three", has_things: true },
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
it("should exclude an excludedValues parameter", async () => {
|
|
185
|
+
const list = useListInstance({});
|
|
186
|
+
const filter = useListFilter({
|
|
187
|
+
parentState: list.state,
|
|
188
|
+
excludedValues: reactive({
|
|
189
|
+
2: true,
|
|
190
|
+
4: true,
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
await nextTick();
|
|
194
|
+
expect(filter.state.objects).toEqual({});
|
|
195
|
+
list.addListObject({ id: 1, name: "one", has_things: true });
|
|
196
|
+
await nextTick();
|
|
197
|
+
expect(filter.state.objects).toEqual({
|
|
198
|
+
1: { id: 1, name: "one", has_things: true },
|
|
199
|
+
});
|
|
200
|
+
list.addListObject({ id: 2, name: "two", has_things: true });
|
|
201
|
+
await nextTick();
|
|
202
|
+
expect(filter.state.objects).toEqual({
|
|
203
|
+
1: { id: 1, name: "one", has_things: true },
|
|
204
|
+
});
|
|
205
|
+
list.addListObject({ id: 3, name: "three", has_things: true });
|
|
206
|
+
await nextTick();
|
|
207
|
+
expect(filter.state.objects).toEqual({
|
|
208
|
+
1: { id: 1, name: "one", has_things: true },
|
|
209
|
+
3: { id: 3, name: "three", has_things: true },
|
|
210
|
+
});
|
|
211
|
+
list.addListObject({ id: 4, name: "four", has_things: true });
|
|
212
|
+
await nextTick();
|
|
213
|
+
expect(filter.state.objects).toEqual({
|
|
214
|
+
1: { id: 1, name: "one", has_things: true },
|
|
215
|
+
3: { id: 3, name: "three", has_things: true },
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
it("no args: returns objects unfiltered", async () => {
|
|
219
|
+
const listInstance = useListInstance({});
|
|
220
|
+
const listItems = [
|
|
221
|
+
{ id: 4, name: "four", has_things: true },
|
|
222
|
+
{ id: 2, name: "two", has_things: true },
|
|
223
|
+
{ id: 3, name: "three", has_things: true },
|
|
224
|
+
{ id: 1, name: "one", has_things: true },
|
|
225
|
+
];
|
|
226
|
+
for (const item of listItems) {
|
|
227
|
+
listInstance.addListObject(item);
|
|
228
|
+
}
|
|
229
|
+
const filter = useListFilter({
|
|
230
|
+
parentState: listInstance.state,
|
|
231
|
+
useTextSearch: true,
|
|
232
|
+
});
|
|
233
|
+
await nextTick();
|
|
234
|
+
expect(filter.state.objects).toEqual(listInstance.state.objects);
|
|
235
|
+
});
|
|
236
|
+
describe("useListFilter operates on parentState modified by useListSort", () => {
|
|
237
|
+
it("computes state.order and state.objects in order", async () => {
|
|
238
|
+
jest.resetAllMocks();
|
|
239
|
+
const orderByRules = [{ key: "name", desc: true, localeCompare: false }];
|
|
240
|
+
const sortThrottleWait = 0;
|
|
241
|
+
const listInstance = useListInstance({});
|
|
242
|
+
const listItems = [
|
|
243
|
+
{ id: 4, name: "four", has_things: true },
|
|
244
|
+
{ id: 2, name: "two", has_things: true },
|
|
245
|
+
{ id: 3, name: "three", has_things: true },
|
|
246
|
+
{ id: 1, name: "one", has_things: true },
|
|
247
|
+
];
|
|
248
|
+
const expectedOrder = ["2", "3", "1", "4"];
|
|
249
|
+
const orderedObjects = [
|
|
250
|
+
{ id: 2, name: "two", has_things: true },
|
|
251
|
+
{ id: 3, name: "three", has_things: true },
|
|
252
|
+
{ id: 1, name: "one", has_things: true },
|
|
253
|
+
{ id: 4, name: "four", has_things: true },
|
|
254
|
+
];
|
|
255
|
+
const listSort = useListSort({ parentState: listInstance.state, orderByRules, sortThrottleWait });
|
|
256
|
+
for (const item of listItems) {
|
|
257
|
+
listInstance.addListObject(item);
|
|
258
|
+
}
|
|
259
|
+
const filter = useListFilter({
|
|
260
|
+
parentState: listSort.state,
|
|
261
|
+
});
|
|
262
|
+
await nextTick();
|
|
263
|
+
expect(filter.state.order).toEqual(expectedOrder);
|
|
264
|
+
expect(filter.state.objectsInOrder).toEqual(orderedObjects);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
describe("useListFilters accepts args and parentInstances", () => {
|
|
268
|
+
it("returns filtered objects", async () => {
|
|
269
|
+
jest.resetAllMocks();
|
|
270
|
+
const fields = ["id", "__str__", "name"];
|
|
271
|
+
const listInstanceA = useListInstance({
|
|
272
|
+
crudArgs: { stream: "test_streamA" },
|
|
273
|
+
listArgs: { user: 1 },
|
|
274
|
+
retrieveArgs: {
|
|
275
|
+
fields,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
const listInstanceB = useListInstance({
|
|
279
|
+
crudArgs: { stream: "test_streamB" },
|
|
280
|
+
listArgs: { user: 2 },
|
|
281
|
+
retrieveArgs: {
|
|
282
|
+
fields,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
const listFilterA = useListFilter({
|
|
286
|
+
parentState: listInstanceA.state,
|
|
287
|
+
excludedValues: {
|
|
288
|
+
id: 1,
|
|
289
|
+
name: "three",
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
const listFilterB = useListFilter({
|
|
293
|
+
parentState: listInstanceB.state,
|
|
294
|
+
allowedValues: {
|
|
295
|
+
id: 2,
|
|
296
|
+
name: "four",
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
const args = {
|
|
300
|
+
A: {
|
|
301
|
+
excludedValues: {
|
|
302
|
+
id: 1,
|
|
303
|
+
name: "three",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
B: {
|
|
307
|
+
allowedValues: {
|
|
308
|
+
id: 2,
|
|
309
|
+
name: "four",
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
const listInstanceModule = await import("../../../use/listInstance");
|
|
314
|
+
const listInstances = listInstanceModule.useListInstances({
|
|
315
|
+
A: {
|
|
316
|
+
listInstanceA,
|
|
317
|
+
},
|
|
318
|
+
B: {
|
|
319
|
+
listInstanceB,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
const listFilters = useListFilters(args, listInstances);
|
|
323
|
+
|
|
324
|
+
expect(listFilters.A.state.excludedValues).toEqual({ id: 1, name: "three" });
|
|
325
|
+
expect(listFilters.B.state.allowedValues).toEqual({ id: 2, name: "four" });
|
|
326
|
+
expect(unrefAndToRawDeep(listFilters.A.parentState)).toEqual(unrefAndToRawDeep(listInstanceA.state));
|
|
327
|
+
expect(unrefAndToRawDeep(listFilters.B.parentState)).toEqual(unrefAndToRawDeep(listInstanceB.state));
|
|
328
|
+
expect(unrefAndToRawDeep(listFilters.A.state)).toEqual(unrefAndToRawDeep(listFilterA.state));
|
|
329
|
+
expect(unrefAndToRawDeep(listFilters.B.state)).toEqual(unrefAndToRawDeep(listFilterB.state));
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
});
|