@jskit-ai/users-web 0.1.151 → 0.1.153
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.descriptor.mjs +7 -7
- package/package.json +7 -7
- package/src/client/composables/records/useList.js +42 -30
- package/src/client/composables/records/useView.js +2 -0
- package/src/client/composables/support/listQueryParamSupport.js +19 -5
- package/src/client/composables/support/requestQueryRuntimeSupport.js +53 -12
- package/src/client/composables/useCrudListFilters.js +28 -1
- package/src/client/composables/useCrudListScreen.js +3 -0
- package/src/client/composables/useCrudViewScreen.js +2 -0
- package/test/listQueryParamSupport.test.js +33 -0
- package/test/useAddEditRequestQueryParams.test.js +28 -0
- package/test/useCrudListFilters.test.js +34 -0
- package/test/useListRouteInitialization.test.js +337 -0
package/package.descriptor.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { HOME_COG_OUTLET } from "./src/shared/toolsOutletContracts.js";
|
|
|
3
3
|
export default Object.freeze({
|
|
4
4
|
packageVersion: 1,
|
|
5
5
|
packageId: "@jskit-ai/users-web",
|
|
6
|
-
version: "0.1.
|
|
6
|
+
version: "0.1.153",
|
|
7
7
|
kind: "runtime",
|
|
8
8
|
description: "Users web module: account/profile UI plus shared users web widgets.",
|
|
9
9
|
dependsOn: [
|
|
@@ -286,12 +286,12 @@ export default Object.freeze({
|
|
|
286
286
|
dependencies: {
|
|
287
287
|
runtime: {
|
|
288
288
|
"@mdi/js": "^7.4.47",
|
|
289
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
290
|
-
"@jskit-ai/realtime": "0.1.
|
|
291
|
-
"@jskit-ai/kernel": "0.1.
|
|
292
|
-
"@jskit-ai/shell-web": "0.1.
|
|
293
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
294
|
-
"@jskit-ai/users-core": "0.1.
|
|
289
|
+
"@jskit-ai/http-runtime": "0.1.134",
|
|
290
|
+
"@jskit-ai/realtime": "0.1.133",
|
|
291
|
+
"@jskit-ai/kernel": "0.1.136",
|
|
292
|
+
"@jskit-ai/shell-web": "0.1.137",
|
|
293
|
+
"@jskit-ai/uploads-image-web": "0.1.112",
|
|
294
|
+
"@jskit-ai/users-core": "0.1.148"
|
|
295
295
|
},
|
|
296
296
|
dev: {}
|
|
297
297
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/users-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.153",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@mdi/js": "^7.4.47",
|
|
49
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
50
|
-
"@jskit-ai/kernel": "0.1.
|
|
51
|
-
"@jskit-ai/realtime": "0.1.
|
|
52
|
-
"@jskit-ai/shell-web": "0.1.
|
|
53
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
54
|
-
"@jskit-ai/users-core": "0.1.
|
|
49
|
+
"@jskit-ai/http-runtime": "0.1.134",
|
|
50
|
+
"@jskit-ai/kernel": "0.1.136",
|
|
51
|
+
"@jskit-ai/realtime": "0.1.133",
|
|
52
|
+
"@jskit-ai/shell-web": "0.1.137",
|
|
53
|
+
"@jskit-ai/uploads-image-web": "0.1.112",
|
|
54
|
+
"@jskit-ai/users-core": "0.1.148"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@tanstack/vue-query": "^5.90.5",
|
|
@@ -7,7 +7,10 @@ import { resolveOperationAdapter } from "../runtime/operationAdapters.js";
|
|
|
7
7
|
import { setupOperationErrorReporting } from "../runtime/operationUiHelpers.js";
|
|
8
8
|
import { createListUiRuntime } from "../runtime/listUiRuntime.js";
|
|
9
9
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
buildRequestQueryObject,
|
|
12
|
+
createRequestQueryRuntime
|
|
13
|
+
} from "../support/requestQueryRuntimeSupport.js";
|
|
11
14
|
import {
|
|
12
15
|
normalizeListSearchConfig,
|
|
13
16
|
matchesLocalSearch
|
|
@@ -57,13 +60,18 @@ function useList({
|
|
|
57
60
|
editUrlTemplate = "",
|
|
58
61
|
search = null,
|
|
59
62
|
queryParams = null,
|
|
63
|
+
routeQueryValueResolvers = null,
|
|
60
64
|
requestQueryParams = null,
|
|
65
|
+
requestFieldsets = null,
|
|
61
66
|
syncToRoute = false
|
|
62
67
|
} = {}) {
|
|
63
68
|
const searchConfig = normalizeListSearchConfig(search);
|
|
64
69
|
const routeSyncConfig = normalizeListSyncToRouteConfig(syncToRoute, {
|
|
65
70
|
defaultSearchParam: searchConfig.queryParam
|
|
66
71
|
});
|
|
72
|
+
const routeSyncHydrated = ref(
|
|
73
|
+
routeSyncConfig.enabled !== true || routeSyncConfig.hydrateFromRoute !== true
|
|
74
|
+
);
|
|
67
75
|
const router = routeSyncConfig.enabled === true ? useRouter() : null;
|
|
68
76
|
const searchQuery = ref(searchConfig.initialQuery);
|
|
69
77
|
const debouncedSearchQuery = ref(searchConfig.initialQuery);
|
|
@@ -140,24 +148,29 @@ function useList({
|
|
|
140
148
|
});
|
|
141
149
|
});
|
|
142
150
|
const queryParamDescriptors = computed(() => {
|
|
143
|
-
return resolveQueryParamDescriptors(queryParams, queryParamsContext.value
|
|
151
|
+
return resolveQueryParamDescriptors(queryParams, queryParamsContext.value, {
|
|
152
|
+
routeValueResolvers: routeQueryValueResolvers
|
|
153
|
+
});
|
|
144
154
|
});
|
|
145
|
-
const
|
|
146
|
-
|
|
155
|
+
const requestQueryRuntime = createRequestQueryRuntime({
|
|
156
|
+
requestQueryParams,
|
|
157
|
+
requestFieldsets,
|
|
158
|
+
context: queryParamsContext
|
|
147
159
|
});
|
|
160
|
+
const {
|
|
161
|
+
activeRequestQueryParamEntries,
|
|
162
|
+
activeRequestQueryParamsToken,
|
|
163
|
+
activeRequestFieldsets,
|
|
164
|
+
activeRequestFieldsetsToken,
|
|
165
|
+
queryKeyParts: requestQueryKeyParts
|
|
166
|
+
} = requestQueryRuntime;
|
|
148
167
|
const declaredQueryParamKeys = computed(() => {
|
|
149
168
|
return queryParamDescriptors.value.map((descriptor) => descriptor.key);
|
|
150
169
|
});
|
|
151
170
|
const activeQueryParamEntries = computed(() => {
|
|
152
171
|
return resolveActiveQueryParamEntries(queryParamDescriptors.value);
|
|
153
172
|
});
|
|
154
|
-
const activeRequestQueryParamEntries = computed(() => {
|
|
155
|
-
return resolveActiveQueryParamEntries(requestQueryParamDescriptors.value);
|
|
156
|
-
});
|
|
157
173
|
const activeQueryParamsToken = computed(() => buildQueryParamEntriesToken(activeQueryParamEntries.value));
|
|
158
|
-
const activeRequestQueryParamsToken = computed(() => {
|
|
159
|
-
return buildQueryParamEntriesToken(activeRequestQueryParamEntries.value);
|
|
160
|
-
});
|
|
161
174
|
const writableQueryParamBindings = computed(() => {
|
|
162
175
|
return resolveWritableQueryParamBindings(queryParamDescriptors.value);
|
|
163
176
|
});
|
|
@@ -191,7 +204,9 @@ function useList({
|
|
|
191
204
|
entries.push(...activeRequestQueryParamEntries.value);
|
|
192
205
|
entries.push(...activeQueryParamEntries.value);
|
|
193
206
|
|
|
194
|
-
const query = buildRequestQueryObject(entries
|
|
207
|
+
const query = buildRequestQueryObject(entries, {
|
|
208
|
+
fieldsets: activeRequestFieldsets.value
|
|
209
|
+
});
|
|
195
210
|
const baseOptions = asPlainObject(requestOptions);
|
|
196
211
|
if (Object.keys(query).length < 1) {
|
|
197
212
|
return baseOptions;
|
|
@@ -209,9 +224,7 @@ function useList({
|
|
|
209
224
|
: sourceQueryKey == null
|
|
210
225
|
? []
|
|
211
226
|
: [sourceQueryKey];
|
|
212
|
-
|
|
213
|
-
baseQueryKey.push("__request_query__", activeRequestQueryParamsToken.value);
|
|
214
|
-
}
|
|
227
|
+
baseQueryKey.push(...requestQueryKeyParts.value);
|
|
215
228
|
if (querySearchEnabled.value) {
|
|
216
229
|
baseQueryKey.push("__search__", searchConfig.queryParam, activeSearchQuery.value);
|
|
217
230
|
}
|
|
@@ -221,10 +234,11 @@ function useList({
|
|
|
221
234
|
return baseQueryKey;
|
|
222
235
|
});
|
|
223
236
|
|
|
237
|
+
const listReadEnabled = computed(() => canView.value && routeSyncHydrated.value);
|
|
224
238
|
const list = useListCore({
|
|
225
239
|
queryKey: listQueryKey,
|
|
226
240
|
path: listPath,
|
|
227
|
-
enabled: operationScope.queryCanRun(
|
|
241
|
+
enabled: operationScope.queryCanRun(listReadEnabled),
|
|
228
242
|
client,
|
|
229
243
|
transport,
|
|
230
244
|
initialPageParam,
|
|
@@ -236,7 +250,6 @@ function useList({
|
|
|
236
250
|
requestRecoveryLabel,
|
|
237
251
|
fallbackLoadError
|
|
238
252
|
});
|
|
239
|
-
const routeSyncHydrated = ref(routeSyncConfig.enabled !== true);
|
|
240
253
|
const routeSyncApplying = ref(false);
|
|
241
254
|
const routeSyncManagedKeyHistory = ref([]);
|
|
242
255
|
const routeSyncQueryParamBlacklist = computed(() => {
|
|
@@ -306,6 +319,7 @@ function useList({
|
|
|
306
319
|
watch(
|
|
307
320
|
() => operationScope.routeContext.route?.query || {},
|
|
308
321
|
(routeQuery) => {
|
|
322
|
+
const initialHydration = routeSyncHydrated.value !== true;
|
|
309
323
|
if (routeSyncConfig.hydrateFromRoute !== true || routeSyncApplying.value === true) {
|
|
310
324
|
routeSyncHydrated.value = true;
|
|
311
325
|
return;
|
|
@@ -324,7 +338,9 @@ function useList({
|
|
|
324
338
|
if (routeSyncQueryParamBlacklistSet.value.has(binding.key)) {
|
|
325
339
|
continue;
|
|
326
340
|
}
|
|
327
|
-
const nextValue = parseRouteBindingValue(binding, routeQuerySource[binding.key]
|
|
341
|
+
const nextValue = parseRouteBindingValue(binding, routeQuerySource[binding.key], {
|
|
342
|
+
initial: initialHydration
|
|
343
|
+
});
|
|
328
344
|
const currentValue = typeof binding.get === "function" ? binding.get() : undefined;
|
|
329
345
|
if (areQueryParamBindingValuesEqual(currentValue, nextValue)) {
|
|
330
346
|
continue;
|
|
@@ -397,20 +413,16 @@ function useList({
|
|
|
397
413
|
|
|
398
414
|
list.trimToFirstPage();
|
|
399
415
|
});
|
|
400
|
-
watch(
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (nextValue === previousValue) {
|
|
409
|
-
return;
|
|
416
|
+
watch(
|
|
417
|
+
[
|
|
418
|
+
activeQueryParamsToken,
|
|
419
|
+
activeRequestQueryParamsToken,
|
|
420
|
+
activeRequestFieldsetsToken
|
|
421
|
+
],
|
|
422
|
+
() => {
|
|
423
|
+
list.trimToFirstPage();
|
|
410
424
|
}
|
|
411
|
-
|
|
412
|
-
list.trimToFirstPage();
|
|
413
|
-
});
|
|
425
|
+
);
|
|
414
426
|
const filteredItems = computed(() => {
|
|
415
427
|
const sourceItems = Array.isArray(list.items.value) ? list.items.value : [];
|
|
416
428
|
if (searchConfig.enabled !== true || searchConfig.mode !== "local") {
|
|
@@ -31,6 +31,7 @@ function useView({
|
|
|
31
31
|
model,
|
|
32
32
|
mapLoadedToModel,
|
|
33
33
|
requestQueryParams = null,
|
|
34
|
+
requestFieldsets = null,
|
|
34
35
|
recordIdParam = "recordId",
|
|
35
36
|
routeParams = null,
|
|
36
37
|
routeRecordId = null,
|
|
@@ -95,6 +96,7 @@ function useView({
|
|
|
95
96
|
});
|
|
96
97
|
const requestQueryRuntime = createRequestQueryRuntime({
|
|
97
98
|
requestQueryParams,
|
|
99
|
+
requestFieldsets,
|
|
98
100
|
context: queryParamsContext,
|
|
99
101
|
sourceQueryKey: baseQueryKey
|
|
100
102
|
});
|
|
@@ -137,7 +137,8 @@ function createWritableQueryParamBinding({
|
|
|
137
137
|
source = {},
|
|
138
138
|
rawKey = "",
|
|
139
139
|
rawValue = null,
|
|
140
|
-
key = ""
|
|
140
|
+
key = "",
|
|
141
|
+
resolveRouteValue = null
|
|
141
142
|
} = {}) {
|
|
142
143
|
const valueSourceIsRef = isRef(rawValue);
|
|
143
144
|
const read = valueSourceIsRef
|
|
@@ -160,12 +161,18 @@ function createWritableQueryParamBinding({
|
|
|
160
161
|
? resolveArrayQueryParamItemType(currentValue)
|
|
161
162
|
: QUERY_PARAM_BINDING_TYPE_TEXT,
|
|
162
163
|
get: read,
|
|
163
|
-
set: write
|
|
164
|
+
set: write,
|
|
165
|
+
resolveRouteValue
|
|
164
166
|
};
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
function resolveQueryParamDescriptors(
|
|
169
|
+
function resolveQueryParamDescriptors(
|
|
170
|
+
queryParams,
|
|
171
|
+
context = {},
|
|
172
|
+
{ routeValueResolvers = null } = {}
|
|
173
|
+
) {
|
|
168
174
|
const source = resolveQueryParamsInput(queryParams, context);
|
|
175
|
+
const resolvers = asPlainObject(unref(routeValueResolvers));
|
|
169
176
|
const descriptorsByKey = new Map();
|
|
170
177
|
const canWriteToSource = typeof queryParams !== "function";
|
|
171
178
|
|
|
@@ -189,7 +196,10 @@ function resolveQueryParamDescriptors(queryParams, context = {}) {
|
|
|
189
196
|
source,
|
|
190
197
|
rawKey,
|
|
191
198
|
rawValue,
|
|
192
|
-
key
|
|
199
|
+
key,
|
|
200
|
+
resolveRouteValue: typeof resolvers[key] === "function"
|
|
201
|
+
? resolvers[key]
|
|
202
|
+
: null
|
|
193
203
|
});
|
|
194
204
|
}
|
|
195
205
|
|
|
@@ -323,7 +333,11 @@ function parseRouteQueryItemValue(value, itemType = QUERY_PARAM_BINDING_TYPE_TEX
|
|
|
323
333
|
return normalized || fallback;
|
|
324
334
|
}
|
|
325
335
|
|
|
326
|
-
function parseRouteBindingValue(binding, routeQueryValue) {
|
|
336
|
+
function parseRouteBindingValue(binding, routeQueryValue, context = {}) {
|
|
337
|
+
if (typeof binding?.resolveRouteValue === "function") {
|
|
338
|
+
return binding.resolveRouteValue(routeQueryValue, context);
|
|
339
|
+
}
|
|
340
|
+
|
|
327
341
|
const valueType = normalizeText(binding?.valueType).toLowerCase();
|
|
328
342
|
if (valueType === QUERY_PARAM_BINDING_TYPE_BOOLEAN) {
|
|
329
343
|
return parseRouteBooleanValue(routeQueryValue, false);
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { computed, unref } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
buildJsonApiFieldsetsToken,
|
|
4
|
+
normalizeJsonApiFieldsets
|
|
5
|
+
} from "@jskit-ai/kernel/shared/support/jsonApiFieldsets";
|
|
2
6
|
import {
|
|
3
7
|
resolveQueryParamDescriptors,
|
|
4
8
|
resolveActiveQueryParamEntries,
|
|
@@ -21,6 +25,13 @@ function resolveRequestQueryBaseKey(sourceQueryKey = null) {
|
|
|
21
25
|
return [source];
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
function resolveRequestFieldsets(requestFieldsets = null, context = {}) {
|
|
29
|
+
const source = typeof requestFieldsets === "function"
|
|
30
|
+
? requestFieldsets(context)
|
|
31
|
+
: unref(requestFieldsets);
|
|
32
|
+
return normalizeJsonApiFieldsets(source);
|
|
33
|
+
}
|
|
34
|
+
|
|
24
35
|
function appendRequestQueryValue(target = {}, key = "", values = []) {
|
|
25
36
|
const normalizedKey = String(key || "").trim();
|
|
26
37
|
const normalizedValues = (Array.isArray(values) ? values : [])
|
|
@@ -40,7 +51,7 @@ function appendRequestQueryValue(target = {}, key = "", values = []) {
|
|
|
40
51
|
target[normalizedKey] = [...currentValues, ...normalizedValues];
|
|
41
52
|
}
|
|
42
53
|
|
|
43
|
-
function buildRequestQueryObject(entries = []) {
|
|
54
|
+
function buildRequestQueryObject(entries = [], { fieldsets = null } = {}) {
|
|
44
55
|
const sourceEntries = Array.isArray(entries) ? entries : [];
|
|
45
56
|
const query = {};
|
|
46
57
|
|
|
@@ -48,11 +59,18 @@ function buildRequestQueryObject(entries = []) {
|
|
|
48
59
|
appendRequestQueryValue(query, entry?.key, entry?.values);
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
const normalizedFieldsets = normalizeJsonApiFieldsets(fieldsets);
|
|
63
|
+
return Object.freeze({
|
|
64
|
+
...query,
|
|
65
|
+
...(Object.keys(normalizedFieldsets).length > 0
|
|
66
|
+
? { fields: normalizedFieldsets }
|
|
67
|
+
: {})
|
|
68
|
+
});
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
function createRequestQueryRuntime({
|
|
55
72
|
requestQueryParams = null,
|
|
73
|
+
requestFieldsets = null,
|
|
56
74
|
context = null,
|
|
57
75
|
sourceQueryKey = null
|
|
58
76
|
} = {}) {
|
|
@@ -65,36 +83,59 @@ function createRequestQueryRuntime({
|
|
|
65
83
|
const activeRequestQueryParamsToken = computed(() => {
|
|
66
84
|
return buildQueryParamEntriesToken(activeRequestQueryParamEntries.value);
|
|
67
85
|
});
|
|
86
|
+
const activeRequestFieldsets = computed(() => {
|
|
87
|
+
return resolveRequestFieldsets(
|
|
88
|
+
requestFieldsets,
|
|
89
|
+
resolveRequestQueryContext(context)
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
const activeRequestFieldsetsToken = computed(() => {
|
|
93
|
+
return buildJsonApiFieldsetsToken(activeRequestFieldsets.value);
|
|
94
|
+
});
|
|
95
|
+
const queryKeyParts = computed(() => {
|
|
96
|
+
const parts = [];
|
|
97
|
+
if (activeRequestQueryParamsToken.value) {
|
|
98
|
+
parts.push("__request_query__", activeRequestQueryParamsToken.value);
|
|
99
|
+
}
|
|
100
|
+
if (activeRequestFieldsetsToken.value) {
|
|
101
|
+
parts.push("__request_fieldsets__", activeRequestFieldsetsToken.value);
|
|
102
|
+
}
|
|
103
|
+
return Object.freeze(parts);
|
|
104
|
+
});
|
|
68
105
|
const queryKey = computed(() => {
|
|
69
|
-
if (
|
|
106
|
+
if (queryKeyParts.value.length < 1) {
|
|
70
107
|
return unref(sourceQueryKey);
|
|
71
108
|
}
|
|
72
109
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
110
|
+
return [
|
|
111
|
+
...resolveRequestQueryBaseKey(sourceQueryKey),
|
|
112
|
+
...queryKeyParts.value
|
|
113
|
+
];
|
|
76
114
|
});
|
|
77
115
|
const requestQuery = computed(() => {
|
|
78
|
-
|
|
116
|
+
const query = buildRequestQueryObject(activeRequestQueryParamEntries.value, {
|
|
117
|
+
fieldsets: activeRequestFieldsets.value
|
|
118
|
+
});
|
|
119
|
+
if (Object.keys(query).length < 1) {
|
|
79
120
|
return null;
|
|
80
121
|
}
|
|
81
122
|
|
|
82
|
-
return
|
|
123
|
+
return query;
|
|
83
124
|
});
|
|
84
125
|
|
|
85
126
|
return Object.freeze({
|
|
86
127
|
requestQueryParamDescriptors,
|
|
87
128
|
activeRequestQueryParamEntries,
|
|
88
129
|
activeRequestQueryParamsToken,
|
|
130
|
+
activeRequestFieldsets,
|
|
131
|
+
activeRequestFieldsetsToken,
|
|
132
|
+
queryKeyParts,
|
|
89
133
|
queryKey,
|
|
90
134
|
requestQuery
|
|
91
135
|
});
|
|
92
136
|
}
|
|
93
137
|
|
|
94
138
|
export {
|
|
95
|
-
appendRequestQueryValue,
|
|
96
139
|
buildRequestQueryObject,
|
|
97
|
-
createRequestQueryRuntime
|
|
98
|
-
resolveRequestQueryBaseKey,
|
|
99
|
-
resolveRequestQueryContext
|
|
140
|
+
createRequestQueryRuntime
|
|
100
141
|
};
|
|
@@ -2,6 +2,7 @@ import { computed, reactive } from "vue";
|
|
|
2
2
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
3
|
import {
|
|
4
4
|
defineCrudListFilters,
|
|
5
|
+
createCrudListFilterEmptyValue,
|
|
5
6
|
createCrudListFilterInitialValue,
|
|
6
7
|
isCrudListFilterMultiValue,
|
|
7
8
|
isCrudListFilterStructuredValue,
|
|
@@ -10,7 +11,9 @@ import {
|
|
|
10
11
|
listCrudListFilterChipValues,
|
|
11
12
|
formatCrudListFilterDefaultChipLabel,
|
|
12
13
|
formatCrudListFilterQueryValue,
|
|
14
|
+
parseCrudListFilterQueryValue,
|
|
13
15
|
resolveCrudListFilterOptionLabel,
|
|
16
|
+
INVALID_CRUD_LIST_FILTER_QUERY_VALUE,
|
|
14
17
|
CRUD_LIST_FILTER_TYPE_FLAG
|
|
15
18
|
} from "@jskit-ai/kernel/shared/support/crudListFilters";
|
|
16
19
|
import { formatCrudListDateFilterChipLabel } from "../support/crudListDateFilterSupport.js";
|
|
@@ -107,7 +110,7 @@ function assignFilterValue(values, filter = {}, rawValue) {
|
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
function resetFilterValue(values, filter = {}) {
|
|
110
|
-
assignFilterValue(values, filter,
|
|
113
|
+
assignFilterValue(values, filter, createCrudListFilterEmptyValue(filter));
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
function applyPresetFilterValue(values, filter = {}, rawValue) {
|
|
@@ -134,6 +137,28 @@ function createQueryParams(values, filterEntries = []) {
|
|
|
134
137
|
return Object.freeze(queryParams);
|
|
135
138
|
}
|
|
136
139
|
|
|
140
|
+
function resolveFilterRouteValue(filter, routeValue, { initial = false } = {}) {
|
|
141
|
+
if (routeValue === undefined) {
|
|
142
|
+
return initial
|
|
143
|
+
? createCrudListFilterInitialValue(filter)
|
|
144
|
+
: createCrudListFilterEmptyValue(filter);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const parsedValue = parseCrudListFilterQueryValue(filter, routeValue);
|
|
148
|
+
return parsedValue === INVALID_CRUD_LIST_FILTER_QUERY_VALUE
|
|
149
|
+
? createCrudListFilterInitialValue(filter)
|
|
150
|
+
: parsedValue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function createRouteQueryValueResolvers(filterEntries = []) {
|
|
154
|
+
return Object.freeze(Object.fromEntries(
|
|
155
|
+
filterEntries.map((filter) => [
|
|
156
|
+
filter.queryKey,
|
|
157
|
+
(routeValue, context) => resolveFilterRouteValue(filter, routeValue, context)
|
|
158
|
+
])
|
|
159
|
+
));
|
|
160
|
+
}
|
|
161
|
+
|
|
137
162
|
function resolveAtomicValueLabel(filter = {}, value = "", labelResolvers = {}) {
|
|
138
163
|
const customResolver = labelResolvers[filter.key];
|
|
139
164
|
if (typeof customResolver === "function") {
|
|
@@ -174,6 +199,7 @@ function useCrudListFilters(definitions = {}, { labelResolvers = {}, chipLabels
|
|
|
174
199
|
}
|
|
175
200
|
|
|
176
201
|
const queryParams = createQueryParams(values, filterEntries);
|
|
202
|
+
const routeQueryValueResolvers = createRouteQueryValueResolvers(filterEntries);
|
|
177
203
|
|
|
178
204
|
const activeChips = computed(() => {
|
|
179
205
|
const chips = [];
|
|
@@ -296,6 +322,7 @@ function useCrudListFilters(definitions = {}, { labelResolvers = {}, chipLabels
|
|
|
296
322
|
filters,
|
|
297
323
|
values,
|
|
298
324
|
queryParams,
|
|
325
|
+
routeQueryValueResolvers,
|
|
299
326
|
options: Object.freeze(options),
|
|
300
327
|
presets: normalizedPresets,
|
|
301
328
|
activeChips,
|
|
@@ -118,6 +118,7 @@ function useCrudListScreen({
|
|
|
118
118
|
syntheticRows = null,
|
|
119
119
|
routeQueryBlacklist = Object.freeze(["include", "cursor", "limit"]),
|
|
120
120
|
requestQueryParams = null,
|
|
121
|
+
requestFieldsets = null,
|
|
121
122
|
readEnabled = true,
|
|
122
123
|
requestRecoveryLabel = "Records",
|
|
123
124
|
fallbackLoadError = "Unable to load records."
|
|
@@ -143,6 +144,7 @@ function useCrudListScreen({
|
|
|
143
144
|
mode: "query"
|
|
144
145
|
},
|
|
145
146
|
queryParams: filterRuntime.queryParams,
|
|
147
|
+
routeQueryValueResolvers: filterRuntime.routeQueryValueResolvers,
|
|
146
148
|
syncToRoute: {
|
|
147
149
|
enabled: true,
|
|
148
150
|
mode: "replace",
|
|
@@ -152,6 +154,7 @@ function useCrudListScreen({
|
|
|
152
154
|
},
|
|
153
155
|
placementSource: `ui-generator.${normalizedResourceNamespace}.list`,
|
|
154
156
|
requestQueryParams,
|
|
157
|
+
requestFieldsets,
|
|
155
158
|
readEnabled,
|
|
156
159
|
requestRecoveryLabel,
|
|
157
160
|
fallbackLoadError,
|
|
@@ -13,6 +13,7 @@ function useCrudViewScreen({
|
|
|
13
13
|
editUrlTemplate = "",
|
|
14
14
|
recordChangedEvent = "",
|
|
15
15
|
requestQueryParams = null,
|
|
16
|
+
requestFieldsets = null,
|
|
16
17
|
readEnabled = true,
|
|
17
18
|
queryKeyFactory = null,
|
|
18
19
|
requestRecoveryLabel = "Record",
|
|
@@ -36,6 +37,7 @@ function useCrudViewScreen({
|
|
|
36
37
|
includeRecordIdInQueryKey: true,
|
|
37
38
|
queryKeyFactory: typeof queryKeyFactory === "function" ? queryKeyFactory : defaultQueryKeyFactory,
|
|
38
39
|
requestQueryParams,
|
|
40
|
+
requestFieldsets,
|
|
39
41
|
readEnabled,
|
|
40
42
|
placementSource: `ui-generator.${normalizedResourceNamespace}.view`,
|
|
41
43
|
requestRecoveryLabel,
|
|
@@ -151,6 +151,39 @@ test("parseRouteBindingValue handles boolean, number and array bindings", () =>
|
|
|
151
151
|
);
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
test("route-aware query parameter bindings can distinguish initial hydration from later clearing", () => {
|
|
155
|
+
const currentness = ref("active");
|
|
156
|
+
const [binding] = resolveWritableQueryParamBindings(
|
|
157
|
+
resolveQueryParamDescriptors(
|
|
158
|
+
{ currentness },
|
|
159
|
+
{},
|
|
160
|
+
{
|
|
161
|
+
routeValueResolvers: {
|
|
162
|
+
currentness(routeValue, { initial }) {
|
|
163
|
+
if (routeValue === undefined) {
|
|
164
|
+
return initial ? "active" : "";
|
|
165
|
+
}
|
|
166
|
+
return String(routeValue || "").trim() || "active";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
assert.equal(
|
|
174
|
+
parseRouteBindingValue(binding, undefined, { initial: true }),
|
|
175
|
+
"active"
|
|
176
|
+
);
|
|
177
|
+
assert.equal(
|
|
178
|
+
parseRouteBindingValue(binding, "archived", { initial: true }),
|
|
179
|
+
"archived"
|
|
180
|
+
);
|
|
181
|
+
assert.equal(
|
|
182
|
+
parseRouteBindingValue(binding, undefined, { initial: false }),
|
|
183
|
+
""
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
154
187
|
test("route sync key helpers preserve declared key history for cleanup", () => {
|
|
155
188
|
const history = mergeManagedQueryParamKeyHistory(
|
|
156
189
|
["status"],
|
|
@@ -160,3 +160,31 @@ test("request query runtime preserves inactive scalar query keys", () => {
|
|
|
160
160
|
assert.equal(runtime.queryKey.value, "products");
|
|
161
161
|
assert.equal(runtime.requestQuery.value, null);
|
|
162
162
|
});
|
|
163
|
+
|
|
164
|
+
test("request query runtime carries typed fieldsets as a first-class query and cache-key dimension", () => {
|
|
165
|
+
const runtime = createRequestQueryRuntime({
|
|
166
|
+
requestQueryParams: {
|
|
167
|
+
include: "operator"
|
|
168
|
+
},
|
|
169
|
+
requestFieldsets: {
|
|
170
|
+
jobs: ["status", "id"],
|
|
171
|
+
contacts: ["displayName", "id"]
|
|
172
|
+
},
|
|
173
|
+
sourceQueryKey: ref(["jobs"])
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
assert.deepEqual(runtime.requestQuery.value, {
|
|
177
|
+
include: "operator",
|
|
178
|
+
fields: {
|
|
179
|
+
contacts: ["displayName", "id"],
|
|
180
|
+
jobs: ["id", "status"]
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
assert.deepEqual(runtime.queryKey.value, [
|
|
184
|
+
"jobs",
|
|
185
|
+
"__request_query__",
|
|
186
|
+
"include=operator",
|
|
187
|
+
"__request_fieldsets__",
|
|
188
|
+
'{"contacts":["displayName","id"],"jobs":["id","status"]}'
|
|
189
|
+
]);
|
|
190
|
+
});
|
|
@@ -257,3 +257,37 @@ test("useCrudListFilters preserves scalar, complete, partial, and cleared date c
|
|
|
257
257
|
assert.deepEqual(filters.values.arrivalDate, { from: "", to: "" });
|
|
258
258
|
assert.equal(filters.hasActiveFilters.value, false);
|
|
259
259
|
});
|
|
260
|
+
|
|
261
|
+
test("useCrudListFilters applies declarative defaults once while clear remains an explicit empty state", async () => {
|
|
262
|
+
const { useCrudListFilters } = await import("@jskit-ai/users-web/client/composables/useCrudListFilters");
|
|
263
|
+
|
|
264
|
+
const filters = useCrudListFilters({
|
|
265
|
+
currentness: {
|
|
266
|
+
type: "enum",
|
|
267
|
+
label: "Currentness",
|
|
268
|
+
options: [
|
|
269
|
+
{ value: "active", label: "Active" },
|
|
270
|
+
{ value: "archived", label: "Archived" }
|
|
271
|
+
],
|
|
272
|
+
defaultValue: "active"
|
|
273
|
+
},
|
|
274
|
+
onlyStaff: {
|
|
275
|
+
type: "flag",
|
|
276
|
+
label: "Staff",
|
|
277
|
+
defaultValue: true
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
assert.equal(filters.values.currentness, "active");
|
|
282
|
+
assert.equal(filters.values.onlyStaff, true);
|
|
283
|
+
assert.equal(filters.queryParams.currentness.value, "active");
|
|
284
|
+
assert.equal(filters.queryParams.onlyStaff.value, true);
|
|
285
|
+
|
|
286
|
+
filters.clearFilter("currentness");
|
|
287
|
+
filters.clearFilter("onlyStaff");
|
|
288
|
+
|
|
289
|
+
assert.equal(filters.values.currentness, "");
|
|
290
|
+
assert.equal(filters.values.onlyStaff, false);
|
|
291
|
+
assert.equal(filters.queryParams.currentness.value, "");
|
|
292
|
+
assert.equal(filters.queryParams.onlyStaff.value, false);
|
|
293
|
+
});
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
|
|
4
|
+
import {
|
|
5
|
+
computed,
|
|
6
|
+
createRenderer,
|
|
7
|
+
h,
|
|
8
|
+
unref
|
|
9
|
+
} from "vue";
|
|
10
|
+
import {
|
|
11
|
+
createMemoryHistory,
|
|
12
|
+
createRouter,
|
|
13
|
+
useRoute
|
|
14
|
+
} from "vue-router";
|
|
15
|
+
|
|
16
|
+
import { useList } from "../src/client/composables/records/useList.js";
|
|
17
|
+
import { useCrudListFilters } from "../src/client/composables/useCrudListFilters.js";
|
|
18
|
+
|
|
19
|
+
const CURRENTNESS_FILTERS = Object.freeze({
|
|
20
|
+
currentness: Object.freeze({
|
|
21
|
+
type: "enum",
|
|
22
|
+
defaultValue: "active",
|
|
23
|
+
options: Object.freeze([
|
|
24
|
+
Object.freeze({
|
|
25
|
+
label: "Active",
|
|
26
|
+
value: "active"
|
|
27
|
+
}),
|
|
28
|
+
Object.freeze({
|
|
29
|
+
label: "Archived",
|
|
30
|
+
value: "archived"
|
|
31
|
+
})
|
|
32
|
+
])
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
async function waitFor(check, message = "Expected condition was not met.") {
|
|
37
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
38
|
+
if (check()) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
assert.fail(message);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createTestRenderer() {
|
|
48
|
+
function insert(child, parent, anchor = null) {
|
|
49
|
+
const siblings = parent.children || (parent.children = []);
|
|
50
|
+
const index = anchor ? siblings.indexOf(anchor) : -1;
|
|
51
|
+
if (index < 0) {
|
|
52
|
+
siblings.push(child);
|
|
53
|
+
} else {
|
|
54
|
+
siblings.splice(index, 0, child);
|
|
55
|
+
}
|
|
56
|
+
child.parent = parent;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function remove(child) {
|
|
60
|
+
const parent = child?.parent;
|
|
61
|
+
const index = parent?.children?.indexOf(child) ?? -1;
|
|
62
|
+
if (index >= 0) {
|
|
63
|
+
parent.children.splice(index, 1);
|
|
64
|
+
}
|
|
65
|
+
child.parent = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return createRenderer({
|
|
69
|
+
patchProp(element, key, _previousValue, nextValue) {
|
|
70
|
+
element.properties[key] = nextValue;
|
|
71
|
+
},
|
|
72
|
+
insert,
|
|
73
|
+
remove,
|
|
74
|
+
createElement(type) {
|
|
75
|
+
return {
|
|
76
|
+
type,
|
|
77
|
+
properties: {},
|
|
78
|
+
children: [],
|
|
79
|
+
parent: null
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
createText(text) {
|
|
83
|
+
return {
|
|
84
|
+
type: "text",
|
|
85
|
+
text,
|
|
86
|
+
parent: null
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
createComment(text) {
|
|
90
|
+
return {
|
|
91
|
+
type: "comment",
|
|
92
|
+
text,
|
|
93
|
+
parent: null
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
setText(node, text) {
|
|
97
|
+
node.text = text;
|
|
98
|
+
},
|
|
99
|
+
setElementText(element, text) {
|
|
100
|
+
element.children = [{
|
|
101
|
+
type: "text",
|
|
102
|
+
text,
|
|
103
|
+
parent: element
|
|
104
|
+
}];
|
|
105
|
+
},
|
|
106
|
+
parentNode(node) {
|
|
107
|
+
return node?.parent || null;
|
|
108
|
+
},
|
|
109
|
+
nextSibling(node) {
|
|
110
|
+
const parent = node?.parent;
|
|
111
|
+
const index = parent?.children?.indexOf(node) ?? -1;
|
|
112
|
+
return index >= 0 ? parent.children[index + 1] || null : null;
|
|
113
|
+
},
|
|
114
|
+
querySelector() {
|
|
115
|
+
return null;
|
|
116
|
+
},
|
|
117
|
+
setScopeId() {},
|
|
118
|
+
cloneNode(node) {
|
|
119
|
+
return {
|
|
120
|
+
...node,
|
|
121
|
+
properties: {
|
|
122
|
+
...node.properties
|
|
123
|
+
},
|
|
124
|
+
children: [...(node.children || [])],
|
|
125
|
+
parent: null
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
insertStaticContent(content, parent, anchor) {
|
|
129
|
+
const node = {
|
|
130
|
+
type: "static",
|
|
131
|
+
text: content,
|
|
132
|
+
parent: null
|
|
133
|
+
};
|
|
134
|
+
insert(node, parent, anchor);
|
|
135
|
+
return [node, node];
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createListTestAdapter() {
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
useOperationScope() {
|
|
143
|
+
const route = useRoute();
|
|
144
|
+
|
|
145
|
+
return Object.freeze({
|
|
146
|
+
routeContext: Object.freeze({
|
|
147
|
+
route,
|
|
148
|
+
currentSurfaceId: computed(() => "admin")
|
|
149
|
+
}),
|
|
150
|
+
scopeParamValue: computed(() => ""),
|
|
151
|
+
normalizedOwnershipFilter: "none",
|
|
152
|
+
apiPath: computed(() => "/contacts"),
|
|
153
|
+
queryKey: computed(() => ["contacts"]),
|
|
154
|
+
queryCanRun(gate = true) {
|
|
155
|
+
return computed(() => Boolean(unref(gate)));
|
|
156
|
+
},
|
|
157
|
+
permissionGate() {
|
|
158
|
+
return computed(() => true);
|
|
159
|
+
},
|
|
160
|
+
loadError(error = "") {
|
|
161
|
+
return computed(() => String(unref(error) || ""));
|
|
162
|
+
},
|
|
163
|
+
isLoading(loading = false) {
|
|
164
|
+
return computed(() => Boolean(unref(loading)));
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function mountRouteSynchronizedList(initialLocation) {
|
|
172
|
+
const calls = [];
|
|
173
|
+
const client = {
|
|
174
|
+
async request(path, options) {
|
|
175
|
+
calls.push({
|
|
176
|
+
path,
|
|
177
|
+
options
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
items: [],
|
|
181
|
+
nextCursor: null
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const queryClient = new QueryClient({
|
|
186
|
+
defaultOptions: {
|
|
187
|
+
queries: {
|
|
188
|
+
retry: false
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
const router = createRouter({
|
|
193
|
+
history: createMemoryHistory(),
|
|
194
|
+
routes: [{
|
|
195
|
+
path: "/contacts",
|
|
196
|
+
component: {
|
|
197
|
+
render() {
|
|
198
|
+
return h("div");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}]
|
|
202
|
+
});
|
|
203
|
+
const renderer = createTestRenderer();
|
|
204
|
+
let filterRuntime = null;
|
|
205
|
+
const app = renderer.createApp({
|
|
206
|
+
setup() {
|
|
207
|
+
filterRuntime = useCrudListFilters(CURRENTNESS_FILTERS);
|
|
208
|
+
useList({
|
|
209
|
+
adapter: createListTestAdapter(),
|
|
210
|
+
client,
|
|
211
|
+
queryKeyFactory: () => ["contacts"],
|
|
212
|
+
queryParams: filterRuntime.queryParams,
|
|
213
|
+
routeQueryValueResolvers: filterRuntime.routeQueryValueResolvers,
|
|
214
|
+
syncToRoute: {
|
|
215
|
+
enabled: true,
|
|
216
|
+
search: false,
|
|
217
|
+
queryParams: true
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return () => h("div");
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
app.use(router);
|
|
225
|
+
app.use(VueQueryPlugin, {
|
|
226
|
+
queryClient
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
await router.push(initialLocation);
|
|
230
|
+
await router.isReady();
|
|
231
|
+
app.mount({
|
|
232
|
+
type: "root",
|
|
233
|
+
children: []
|
|
234
|
+
});
|
|
235
|
+
await waitFor(
|
|
236
|
+
() => calls.length > 0,
|
|
237
|
+
`Expected a list request for ${JSON.stringify(initialLocation)}.`
|
|
238
|
+
);
|
|
239
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
app,
|
|
243
|
+
calls,
|
|
244
|
+
filterRuntime,
|
|
245
|
+
queryClient,
|
|
246
|
+
router
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
test("route-synchronized lists make one initial request with the hydrated route filter", async () => {
|
|
251
|
+
const runtime = await mountRouteSynchronizedList({
|
|
252
|
+
path: "/contacts",
|
|
253
|
+
query: {
|
|
254
|
+
currentness: "archived"
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
assert.equal(runtime.calls.length, 1);
|
|
259
|
+
assert.deepEqual(runtime.calls[0], {
|
|
260
|
+
path: "/contacts",
|
|
261
|
+
options: {
|
|
262
|
+
method: "GET",
|
|
263
|
+
query: {
|
|
264
|
+
currentness: "archived",
|
|
265
|
+
limit: 20
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
runtime.app.unmount();
|
|
270
|
+
runtime.queryClient.clear();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("route-synchronized lists make one initial request with a declarative default", async () => {
|
|
274
|
+
const runtime = await mountRouteSynchronizedList("/contacts");
|
|
275
|
+
|
|
276
|
+
assert.equal(runtime.calls.length, 1);
|
|
277
|
+
assert.equal(runtime.calls[0].options.query.currentness, "active");
|
|
278
|
+
runtime.app.unmount();
|
|
279
|
+
runtime.queryClient.clear();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("invalid initial route filters fall back before the first list request", async () => {
|
|
283
|
+
const runtime = await mountRouteSynchronizedList({
|
|
284
|
+
path: "/contacts",
|
|
285
|
+
query: {
|
|
286
|
+
currentness: "not-a-valid-option"
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
assert.equal(runtime.calls.length, 1);
|
|
291
|
+
assert.equal(runtime.calls[0].options.query.currentness, "active");
|
|
292
|
+
runtime.app.unmount();
|
|
293
|
+
runtime.queryClient.clear();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("route navigation rehydrates filters and a later clear stays cleared", async () => {
|
|
297
|
+
const runtime = await mountRouteSynchronizedList({
|
|
298
|
+
path: "/contacts",
|
|
299
|
+
query: {
|
|
300
|
+
currentness: "archived"
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
await runtime.router.push({
|
|
305
|
+
path: "/contacts",
|
|
306
|
+
query: {
|
|
307
|
+
currentness: "active"
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
await waitFor(() => runtime.filterRuntime.values.currentness === "active");
|
|
311
|
+
await waitFor(() => runtime.calls.length === 2);
|
|
312
|
+
|
|
313
|
+
runtime.router.back();
|
|
314
|
+
await waitFor(() => runtime.router.currentRoute.value.query.currentness === "archived");
|
|
315
|
+
await waitFor(() => runtime.filterRuntime.values.currentness === "archived");
|
|
316
|
+
await waitFor(() => runtime.calls.length === 3);
|
|
317
|
+
|
|
318
|
+
runtime.router.forward();
|
|
319
|
+
await waitFor(() => runtime.router.currentRoute.value.query.currentness === "active");
|
|
320
|
+
await waitFor(() => runtime.filterRuntime.values.currentness === "active");
|
|
321
|
+
await waitFor(() => runtime.calls.length === 4);
|
|
322
|
+
|
|
323
|
+
runtime.filterRuntime.clearFilter("currentness");
|
|
324
|
+
await waitFor(() => !Object.hasOwn(runtime.router.currentRoute.value.query, "currentness"));
|
|
325
|
+
await waitFor(
|
|
326
|
+
() => runtime.calls.length >= 5,
|
|
327
|
+
`Expected one request after clearing the filter; received ${runtime.calls.length}.`
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
assert.equal(runtime.calls.length, 5, JSON.stringify(runtime.calls, null, 2));
|
|
331
|
+
assert.equal(runtime.filterRuntime.values.currentness, "");
|
|
332
|
+
assert.deepEqual(runtime.calls.at(-1).options.query, {
|
|
333
|
+
limit: 20
|
|
334
|
+
});
|
|
335
|
+
runtime.app.unmount();
|
|
336
|
+
runtime.queryClient.clear();
|
|
337
|
+
});
|