@jskit-ai/users-web 0.1.178-oldline.2 → 0.1.178
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 +197 -45
- package/patterns/account-settings/PATTERN.md +61 -0
- package/src/client/bootstrap/user-bootstrap-handler.js +2 -12
- package/src/client/composables/account-settings/accountSettingsAvatarUploadRuntime.js +2 -2
- package/src/client/composables/useAccountSettingsRuntime.js +3 -3
- package/src/client/index.js +0 -16
- package/src/client/providers/UsersWebClientProvider.js +13 -14
- package/test/bootstrap.test.js +6 -41
- package/test/exportsContract.test.js +3 -29
- package/test/packageMetadataOwnership.test.js +19 -0
- package/test/settingsPlacementContract.test.js +93 -405
- package/fixtures/crud-list-date-filters/App.vue +0 -107
- package/fixtures/crud-list-date-filters/index.html +0 -12
- package/fixtures/crud-list-date-filters/main.js +0 -24
- package/fixtures/crud-list-date-filters/vite.config.mjs +0 -18
- package/package.descriptor.mjs +0 -468
- package/src/client/bulkActions.js +0 -47
- package/src/client/components/CrudAddEditScreen.vue +0 -186
- package/src/client/components/CrudListBulkActionSurface.vue +0 -126
- package/src/client/components/CrudListDateFilterControl.vue +0 -188
- package/src/client/components/CrudListFilterSurface.vue +0 -388
- package/src/client/components/CrudListRecordActionMenu.vue +0 -120
- package/src/client/components/CrudListScreen.vue +0 -501
- package/src/client/components/CrudViewScreen.vue +0 -197
- package/src/client/composables/crud/crudBindingSupport.js +0 -75
- package/src/client/composables/crud/crudHttpClientSupport.js +0 -57
- package/src/client/composables/crud/crudJsonApiTransportSupport.js +0 -145
- package/src/client/composables/crud/crudLookupFieldLabelSupport.js +0 -151
- package/src/client/composables/crud/crudLookupFieldRuntime.js +0 -251
- package/src/client/composables/crud/crudSchemaFormHelpers.js +0 -464
- package/src/client/composables/internal/crudListFilterLookupSupport.js +0 -161
- package/src/client/composables/internal/crudListParentTitleSupport.js +0 -171
- package/src/client/composables/internal/useOperationScope.js +0 -144
- package/src/client/composables/records/useAddEdit.js +0 -224
- package/src/client/composables/records/useCrudAddEdit.js +0 -227
- package/src/client/composables/records/useCrudList.js +0 -101
- package/src/client/composables/records/useCrudView.js +0 -47
- package/src/client/composables/records/useList.js +0 -500
- package/src/client/composables/records/useView.js +0 -164
- package/src/client/composables/runtime/addEditUiRuntime.js +0 -130
- package/src/client/composables/runtime/listUiRuntime.js +0 -123
- package/src/client/composables/runtime/modelStateHelpers.js +0 -90
- package/src/client/composables/runtime/operationAdapters.js +0 -34
- package/src/client/composables/runtime/operationUiHelpers.js +0 -125
- package/src/client/composables/runtime/operationValidationHelpers.js +0 -51
- package/src/client/composables/runtime/useAddEditCore.js +0 -112
- package/src/client/composables/runtime/useCommandCore.js +0 -123
- package/src/client/composables/runtime/useEndpointResource.js +0 -199
- package/src/client/composables/runtime/useFieldErrorBag.js +0 -61
- package/src/client/composables/runtime/useListCore.js +0 -112
- package/src/client/composables/runtime/useUiFeedback.js +0 -98
- package/src/client/composables/runtime/useViewCore.js +0 -93
- package/src/client/composables/runtime/viewUiRuntime.js +0 -96
- package/src/client/composables/support/errorMessageHelpers.js +0 -66
- package/src/client/composables/support/listQueryParamSupport.js +0 -473
- package/src/client/composables/support/listSearchSupport.js +0 -70
- package/src/client/composables/support/refValueHelpers.js +0 -19
- package/src/client/composables/support/requestQueryRuntimeSupport.js +0 -141
- package/src/client/composables/support/resourceLoadStateHelpers.js +0 -229
- package/src/client/composables/support/routeTemplateHelpers.js +0 -285
- package/src/client/composables/support/scopeHelpers.js +0 -145
- package/src/client/composables/useAccess.js +0 -109
- package/src/client/composables/useCommand.js +0 -118
- package/src/client/composables/useCrudAddEditScreen.js +0 -88
- package/src/client/composables/useCrudDeleteAction.js +0 -160
- package/src/client/composables/useCrudListBulkActions.js +0 -147
- package/src/client/composables/useCrudListFilterLookups.js +0 -146
- package/src/client/composables/useCrudListFilters.js +0 -339
- package/src/client/composables/useCrudListParentTitle.js +0 -151
- package/src/client/composables/useCrudListRowActions.js +0 -144
- package/src/client/composables/useCrudListScreen.js +0 -237
- package/src/client/composables/useCrudViewScreen.js +0 -77
- package/src/client/composables/usePagedCollection.js +0 -181
- package/src/client/composables/usePaths.js +0 -120
- package/src/client/composables/useRealtimeQueryInvalidation.js +0 -156
- package/src/client/composables/useScopeRuntime.js +0 -135
- package/src/client/composables/useSurfaceRouteContext.js +0 -25
- package/src/client/filters.js +0 -15
- package/src/client/lib/httpClient.js +0 -47
- package/src/client/lib/permissions.js +0 -34
- package/src/client/rowActions.js +0 -47
- package/src/client/support/contractGuards.js +0 -34
- package/src/client/support/crudListDateFilterSupport.js +0 -91
- package/test/addEditUiRuntime.test.js +0 -110
- package/test/crudBindingSupport.test.js +0 -110
- package/test/crudHttpClientSupport.test.js +0 -77
- package/test/crudJsonApiTransportSupport.test.js +0 -179
- package/test/crudListBulkActionSurface.test.js +0 -27
- package/test/crudListDateFilterSupport.test.js +0 -92
- package/test/crudListDateFilterSurface.browser.test.js +0 -273
- package/test/crudListFilterSurface.test.js +0 -90
- package/test/crudLookupFieldRuntime.test.js +0 -264
- package/test/crudScreenComponents.test.js +0 -108
- package/test/errorIntentContract.test.js +0 -31
- package/test/errorMessageHelpers.test.js +0 -28
- package/test/listQueryParamSupport.test.js +0 -223
- package/test/listUiRuntime.test.js +0 -102
- package/test/operationValidationHelpers.test.js +0 -64
- package/test/packageDescriptorOwnership.test.js +0 -18
- package/test/permissions.test.js +0 -35
- package/test/refValueHelpers.test.js +0 -14
- package/test/requestTransportOptions.test.js +0 -457
- package/test/resourceLoadStateHelpers.test.js +0 -189
- package/test/routeTemplateHelpers.test.js +0 -85
- package/test/scopeHelpers.test.js +0 -57
- package/test/useAddEditCore.test.js +0 -124
- package/test/useAddEditRequestQueryParams.test.js +0 -190
- package/test/useCrudAddEdit.test.js +0 -435
- package/test/useCrudDeleteAction.test.js +0 -139
- package/test/useCrudListBulkActions.test.js +0 -65
- package/test/useCrudListFilterLookups.test.js +0 -114
- package/test/useCrudListFilters.test.js +0 -293
- package/test/useCrudListParentTitle.test.js +0 -262
- package/test/useCrudListRowActions.test.js +0 -77
- package/test/useListRouteInitialization.test.js +0 -337
- package/test/useListSearchSupport.test.js +0 -61
- package/test/usePagedCollection.test.js +0 -53
- package/test/useViewRequestQueryParams.test.js +0 -20
- package/test/viewCoreLoading.test.js +0 -44
- package/test/viewUiRuntime.test.js +0 -95
- /package/{templates → patterns/account-settings/example}/src/components/account/settings/AccountSettingsNotificationsSection.vue +0 -0
- /package/{templates → patterns/account-settings/example}/src/components/account/settings/AccountSettingsPreferencesSection.vue +0 -0
- /package/{templates → patterns/account-settings/example}/src/components/account/settings/AccountSettingsProfileSection.vue +0 -0
- /package/{templates → patterns/account-settings/example}/src/pages/account/index.vue +0 -0
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
resolveLookupItemLabel,
|
|
5
|
-
resolveLookupFieldDisplayValue,
|
|
6
|
-
resolveRecordTitle
|
|
7
|
-
} from "../src/client/composables/crud/crudLookupFieldLabelSupport.js";
|
|
8
|
-
|
|
9
|
-
test("resolveLookupItemLabel composes name + surname", () => {
|
|
10
|
-
assert.equal(
|
|
11
|
-
resolveLookupItemLabel(
|
|
12
|
-
{
|
|
13
|
-
name: "South",
|
|
14
|
-
surname: "Clinic"
|
|
15
|
-
},
|
|
16
|
-
"name"
|
|
17
|
-
),
|
|
18
|
-
"South Clinic"
|
|
19
|
-
);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("resolveLookupItemLabel composes firstName + surname", () => {
|
|
23
|
-
assert.equal(
|
|
24
|
-
resolveLookupItemLabel(
|
|
25
|
-
{
|
|
26
|
-
firstName: "Ana",
|
|
27
|
-
surname: "Marin"
|
|
28
|
-
},
|
|
29
|
-
"name"
|
|
30
|
-
),
|
|
31
|
-
"Ana Marin"
|
|
32
|
-
);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("resolveLookupItemLabel composes firstName + lastName", () => {
|
|
36
|
-
assert.equal(
|
|
37
|
-
resolveLookupItemLabel(
|
|
38
|
-
{
|
|
39
|
-
firstName: "Ana",
|
|
40
|
-
lastName: "Marin"
|
|
41
|
-
},
|
|
42
|
-
"name"
|
|
43
|
-
),
|
|
44
|
-
"Ana Marin"
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("resolveLookupItemLabel falls back to explicit labelKey", () => {
|
|
49
|
-
assert.equal(
|
|
50
|
-
resolveLookupItemLabel(
|
|
51
|
-
{
|
|
52
|
-
clinicName: "Harbor Vet"
|
|
53
|
-
},
|
|
54
|
-
"clinicName"
|
|
55
|
-
),
|
|
56
|
-
"Harbor Vet"
|
|
57
|
-
);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("resolveLookupItemLabel resolves name when surname is missing", () => {
|
|
61
|
-
assert.equal(
|
|
62
|
-
resolveLookupItemLabel(
|
|
63
|
-
{
|
|
64
|
-
name: "Harbor Vet"
|
|
65
|
-
},
|
|
66
|
-
""
|
|
67
|
-
),
|
|
68
|
-
"Harbor Vet"
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("resolveLookupItemLabel returns empty when no label fields match", () => {
|
|
73
|
-
assert.equal(resolveLookupItemLabel({ id: 42 }, "name"), "");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("resolveRecordTitle composes name-like fields before fallback key", () => {
|
|
77
|
-
assert.equal(
|
|
78
|
-
resolveRecordTitle(
|
|
79
|
-
{
|
|
80
|
-
firstName: "Ana",
|
|
81
|
-
lastName: "Marin",
|
|
82
|
-
title: "Ignored"
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
fallbackKey: "title",
|
|
86
|
-
defaultValue: "Record"
|
|
87
|
-
}
|
|
88
|
-
),
|
|
89
|
-
"Ana Marin"
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("resolveRecordTitle falls back to provided field key", () => {
|
|
94
|
-
assert.equal(
|
|
95
|
-
resolveRecordTitle(
|
|
96
|
-
{
|
|
97
|
-
title: "Harbor Visit"
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
fallbackKey: "title",
|
|
101
|
-
defaultValue: "Record"
|
|
102
|
-
}
|
|
103
|
-
),
|
|
104
|
-
"Harbor Visit"
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('resolveRecordTitle falls back to "-" when no title data exists', () => {
|
|
109
|
-
assert.equal(resolveRecordTitle({}, { fallbackKey: "title", defaultValue: "" }), "-");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("resolveLookupFieldDisplayValue returns hydrated label for lookup fields", () => {
|
|
113
|
-
assert.equal(
|
|
114
|
-
resolveLookupFieldDisplayValue(
|
|
115
|
-
{
|
|
116
|
-
vetId: 17,
|
|
117
|
-
lookups: {
|
|
118
|
-
vetId: {
|
|
119
|
-
id: 17,
|
|
120
|
-
name: "Harbor Vet"
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
key: "vetId",
|
|
126
|
-
relation: {
|
|
127
|
-
kind: "lookup",
|
|
128
|
-
valueKey: "id",
|
|
129
|
-
labelKey: "name"
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
),
|
|
133
|
-
"Harbor Vet"
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("resolveLookupFieldDisplayValue falls back to hydrated valueKey", () => {
|
|
138
|
-
assert.equal(
|
|
139
|
-
resolveLookupFieldDisplayValue(
|
|
140
|
-
{
|
|
141
|
-
vetId: 17,
|
|
142
|
-
lookups: {
|
|
143
|
-
vetId: {
|
|
144
|
-
id: 99
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
key: "vetId",
|
|
150
|
-
relation: {
|
|
151
|
-
kind: "lookup",
|
|
152
|
-
valueKey: "id",
|
|
153
|
-
labelKey: "name"
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
),
|
|
157
|
-
99
|
|
158
|
-
);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("resolveLookupFieldDisplayValue falls back to raw id when lookup is not hydrated", () => {
|
|
162
|
-
assert.equal(
|
|
163
|
-
resolveLookupFieldDisplayValue(
|
|
164
|
-
{
|
|
165
|
-
vetId: 17
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
key: "vetId",
|
|
169
|
-
relation: {
|
|
170
|
-
kind: "lookup",
|
|
171
|
-
valueKey: "id",
|
|
172
|
-
labelKey: "name"
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
),
|
|
176
|
-
17
|
|
177
|
-
);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("resolveLookupFieldDisplayValue supports custom lookup container key", () => {
|
|
181
|
-
assert.equal(
|
|
182
|
-
resolveLookupFieldDisplayValue(
|
|
183
|
-
{
|
|
184
|
-
vetId: 17,
|
|
185
|
-
lookupData: {
|
|
186
|
-
vetId: {
|
|
187
|
-
id: 17,
|
|
188
|
-
name: "Harbor Vet"
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
key: "vetId",
|
|
194
|
-
relation: {
|
|
195
|
-
kind: "lookup",
|
|
196
|
-
containerKey: "lookupData",
|
|
197
|
-
valueKey: "id",
|
|
198
|
-
labelKey: "name"
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
),
|
|
202
|
-
"Harbor Vet"
|
|
203
|
-
);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("resolveLookupFieldDisplayValue falls back to alias-keyed lookups for relationship-backed fields", () => {
|
|
207
|
-
assert.equal(
|
|
208
|
-
resolveLookupFieldDisplayValue(
|
|
209
|
-
{
|
|
210
|
-
vetId: "17",
|
|
211
|
-
lookups: {
|
|
212
|
-
vet: {
|
|
213
|
-
id: "17",
|
|
214
|
-
name: "Harbor Vet"
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
key: "vetId",
|
|
220
|
-
relation: {
|
|
221
|
-
kind: "lookup",
|
|
222
|
-
valueKey: "id",
|
|
223
|
-
labelKey: "name"
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
),
|
|
227
|
-
"Harbor Vet"
|
|
228
|
-
);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test("resolveLookupFieldDisplayValue returns raw value for non-lookup fields", () => {
|
|
232
|
-
assert.equal(
|
|
233
|
-
resolveLookupFieldDisplayValue(
|
|
234
|
-
{
|
|
235
|
-
firstName: "Ana"
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
key: "firstName"
|
|
239
|
-
}
|
|
240
|
-
),
|
|
241
|
-
"Ana"
|
|
242
|
-
);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("resolveLookupFieldDisplayValue supports scalar lookup descriptor arguments", () => {
|
|
246
|
-
assert.equal(
|
|
247
|
-
resolveLookupFieldDisplayValue(
|
|
248
|
-
{
|
|
249
|
-
vetId: 17,
|
|
250
|
-
lookups: {
|
|
251
|
-
vetId: {
|
|
252
|
-
id: 17,
|
|
253
|
-
name: "Harbor Vet"
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
"vetId",
|
|
258
|
-
"lookup",
|
|
259
|
-
"id",
|
|
260
|
-
"name"
|
|
261
|
-
),
|
|
262
|
-
"Harbor Vet"
|
|
263
|
-
);
|
|
264
|
-
});
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import test from "node:test";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
|
|
9
|
-
|
|
10
|
-
async function readComponent(name) {
|
|
11
|
-
return readFile(path.join(PACKAGE_DIR, "src", "client", "components", name), "utf8");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function readClientFile(...parts) {
|
|
15
|
-
return readFile(path.join(PACKAGE_DIR, "src", "client", ...parts), "utf8");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
test("CRUD screen components own generated list/view/form chrome centrally", async () => {
|
|
19
|
-
const listSource = await readComponent("CrudListScreen.vue");
|
|
20
|
-
const viewSource = await readComponent("CrudViewScreen.vue");
|
|
21
|
-
const addEditSource = await readComponent("CrudAddEditScreen.vue");
|
|
22
|
-
|
|
23
|
-
assert.match(listSource, /CrudListBulkActionSurface/);
|
|
24
|
-
assert.match(listSource, /CrudListFilterSurface/);
|
|
25
|
-
assert.match(listSource, /CrudListRecordActionMenu/);
|
|
26
|
-
assert.match(listSource, /class="ui-generator-list-cards"/);
|
|
27
|
-
assert.match(listSource, /class="ui-generator-list-table"/);
|
|
28
|
-
assert.match(listSource, /class="ui-generator-list-fab"/);
|
|
29
|
-
assert.doesNotMatch(listSource, /ui-generator-list-cards d-md-none/);
|
|
30
|
-
assert.doesNotMatch(listSource, /ui-generator-list-table d-none d-md-block/);
|
|
31
|
-
assert.doesNotMatch(listSource, /class="ui-generator-list-fab d-md-none"/);
|
|
32
|
-
assert.match(listSource, /\.ui-generator-list-table\s*\{\s*display:\s*none;/);
|
|
33
|
-
assert.match(
|
|
34
|
-
listSource,
|
|
35
|
-
/@media \(min-width: 960px\)[\s\S]*\.ui-generator-list-cards\s*\{\s*display:\s*none;[\s\S]*\.ui-generator-list-table\s*\{\s*display:\s*block;[\s\S]*\.ui-generator-list-fab\s*\{\s*display:\s*none;/
|
|
36
|
-
);
|
|
37
|
-
assert.match(listSource, /button-label="More"/);
|
|
38
|
-
assert.match(listSource, /selectableRows/);
|
|
39
|
-
assert.match(listSource, /min-height:\s*48px/);
|
|
40
|
-
assert.match(listSource, /<slot[\s\S]*name="card-fields"/);
|
|
41
|
-
assert.match(listSource, /<slot name="table-header"/);
|
|
42
|
-
assert.match(listSource, /<slot[\s\S]*name="table-row"/);
|
|
43
|
-
assert.match(listSource, /:row="row"/);
|
|
44
|
-
|
|
45
|
-
assert.match(viewSource, /generated-ui-screen generated-ui-screen--operator ui-generator-view-element/);
|
|
46
|
-
assert.match(viewSource, /ui-generator-view-panel/);
|
|
47
|
-
assert.match(viewSource, /@click="view\.refresh"/);
|
|
48
|
-
assert.match(viewSource, /<slot name="actions" :screen="screen" :view="view" \/>/);
|
|
49
|
-
assert.match(viewSource, /<slot name="before-fields"/);
|
|
50
|
-
assert.match(viewSource, /<slot name="fields"/);
|
|
51
|
-
assert.match(viewSource, /<slot name="after-fields"/);
|
|
52
|
-
assert.match(viewSource, /supporting-content/);
|
|
53
|
-
|
|
54
|
-
assert.match(addEditSource, /generated-ui-screen generated-ui-screen--operator ui-generator-add-edit-form/);
|
|
55
|
-
assert.match(addEditSource, /addEdit\.canRetryLoad/);
|
|
56
|
-
assert.match(addEditSource, /@click="addEdit\.refresh"/);
|
|
57
|
-
assert.match(addEditSource, /<slot[\s\S]*name="fields"/);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("CRUD screen composables expose generated page extension inputs", async () => {
|
|
61
|
-
const listSource = await readClientFile("composables", "useCrudListScreen.js");
|
|
62
|
-
const viewSource = await readClientFile("composables", "useCrudViewScreen.js");
|
|
63
|
-
|
|
64
|
-
assert.match(listSource, /listRowActions = \[\]/);
|
|
65
|
-
assert.match(listSource, /syntheticRows = null/);
|
|
66
|
-
assert.match(listSource, /readEnabled = true/);
|
|
67
|
-
assert.match(listSource, /useCrudListRowActions/);
|
|
68
|
-
assert.match(listSource, /selectableRows/);
|
|
69
|
-
assert.match(listSource, /readEnabled,\n\s+requestRecoveryLabel/);
|
|
70
|
-
|
|
71
|
-
assert.match(viewSource, /requestQueryParams = null/);
|
|
72
|
-
assert.match(viewSource, /readEnabled = true/);
|
|
73
|
-
assert.match(viewSource, /queryKeyFactory = null/);
|
|
74
|
-
assert.match(viewSource, /requestQueryParams/);
|
|
75
|
-
assert.match(viewSource, /readEnabled/);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("account settings load state exposes a local retry action", async () => {
|
|
79
|
-
const source = await readComponent("AccountSettingsClientElement.vue");
|
|
80
|
-
|
|
81
|
-
assert.match(source, /settingsLoadError/);
|
|
82
|
-
assert.match(source, /@click="runtime\.refreshSettings"/);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("CRUD screen composables are importable package APIs", async () => {
|
|
86
|
-
const [
|
|
87
|
-
listModule,
|
|
88
|
-
viewModule,
|
|
89
|
-
addEditModule,
|
|
90
|
-
rowActionsModule,
|
|
91
|
-
rowActionsRuntimeModule,
|
|
92
|
-
deleteActionModule
|
|
93
|
-
] = await Promise.all([
|
|
94
|
-
import("@jskit-ai/users-web/client/composables/useCrudListScreen"),
|
|
95
|
-
import("@jskit-ai/users-web/client/composables/useCrudViewScreen"),
|
|
96
|
-
import("@jskit-ai/users-web/client/composables/useCrudAddEditScreen"),
|
|
97
|
-
import("@jskit-ai/users-web/client/rowActions"),
|
|
98
|
-
import("@jskit-ai/users-web/client/composables/useCrudListRowActions"),
|
|
99
|
-
import("@jskit-ai/users-web/client/composables/useCrudDeleteAction")
|
|
100
|
-
]);
|
|
101
|
-
|
|
102
|
-
assert.equal(typeof listModule.useCrudListScreen, "function");
|
|
103
|
-
assert.equal(typeof viewModule.useCrudViewScreen, "function");
|
|
104
|
-
assert.equal(typeof addEditModule.useCrudAddEditScreen, "function");
|
|
105
|
-
assert.equal(typeof rowActionsModule.defineCrudListRowActions, "function");
|
|
106
|
-
assert.equal(typeof rowActionsRuntimeModule.useCrudListRowActions, "function");
|
|
107
|
-
assert.equal(typeof deleteActionModule.useCrudDeleteAction, "function");
|
|
108
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import test from "node:test";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
|
|
9
|
-
|
|
10
|
-
async function readClientSource(relativePath) {
|
|
11
|
-
return readFile(path.join(PACKAGE_DIR, "src", "client", relativePath), "utf8");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
test("users-web reports load errors as local resource-load intent by default", async () => {
|
|
15
|
-
const source = await readClientSource("composables/runtime/operationUiHelpers.js");
|
|
16
|
-
|
|
17
|
-
assert.match(source, /loadChannel = ""/);
|
|
18
|
-
assert.match(source, /notFoundChannel = ""/);
|
|
19
|
-
assert.match(source, /intent = "resource-load"/);
|
|
20
|
-
assert.match(source, /intent: "resource-load"/);
|
|
21
|
-
assert.doesNotMatch(source, /loadChannel = "banner"/);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("users-web action feedback lets shell policy choose snackbar presentation", async () => {
|
|
25
|
-
const source = await readClientSource("composables/runtime/useUiFeedback.js");
|
|
26
|
-
|
|
27
|
-
assert.match(source, /successChannel = ""/);
|
|
28
|
-
assert.match(source, /errorChannel = ""/);
|
|
29
|
-
assert.match(source, /intent: "action-feedback"/);
|
|
30
|
-
assert.doesNotMatch(source, /errorChannel = "banner"/);
|
|
31
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { toQueryErrorMessage, toUiErrorMessage } from "../src/client/composables/support/errorMessageHelpers.js";
|
|
4
|
-
|
|
5
|
-
test("toQueryErrorMessage returns empty when query has no error", () => {
|
|
6
|
-
assert.equal(toQueryErrorMessage(null, "Unable to load list."), "");
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
test("toQueryErrorMessage resolves specific runtime error messages before fallback", () => {
|
|
10
|
-
assert.equal(toQueryErrorMessage({ message: "Network timeout" }, "Unable to load list."), "Network timeout");
|
|
11
|
-
assert.equal(toQueryErrorMessage({}, "Unable to load list."), "Unable to load list.");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test("toQueryErrorMessage prefers fallback for generic transport messages", () => {
|
|
15
|
-
assert.equal(
|
|
16
|
-
toQueryErrorMessage({ status: 500, message: "Request failed with status 500." }, "Unable to load list."),
|
|
17
|
-
"Unable to load list."
|
|
18
|
-
);
|
|
19
|
-
assert.equal(
|
|
20
|
-
toQueryErrorMessage({ status: 0, message: "Network request failed." }, "Unable to load list."),
|
|
21
|
-
"Unable to load list."
|
|
22
|
-
);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("toUiErrorMessage prefers fallback copy before runtime error text", () => {
|
|
26
|
-
assert.equal(toUiErrorMessage({ message: "Server exploded" }, "Unable to save."), "Unable to save.");
|
|
27
|
-
assert.equal(toUiErrorMessage({ message: "Server exploded" }, ""), "Server exploded");
|
|
28
|
-
});
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { ref } from "vue";
|
|
4
|
-
import {
|
|
5
|
-
normalizeListSyncToRouteConfig,
|
|
6
|
-
resolveQueryParamDescriptors,
|
|
7
|
-
resolveActiveQueryParamEntries,
|
|
8
|
-
resolveWritableQueryParamBindings,
|
|
9
|
-
buildQueryParamEntriesToken,
|
|
10
|
-
parseRouteBindingValue,
|
|
11
|
-
areQueryParamBindingValuesEqual,
|
|
12
|
-
buildRouteQueryCompareToken,
|
|
13
|
-
mergeManagedQueryParamKeyHistory,
|
|
14
|
-
resolveRouteSyncManagedKeys
|
|
15
|
-
} from "../src/client/composables/support/listQueryParamSupport.js";
|
|
16
|
-
|
|
17
|
-
test("normalizeListSyncToRouteConfig defaults", () => {
|
|
18
|
-
assert.deepEqual(
|
|
19
|
-
normalizeListSyncToRouteConfig(false, {
|
|
20
|
-
defaultSearchParam: "search"
|
|
21
|
-
}),
|
|
22
|
-
{
|
|
23
|
-
enabled: false,
|
|
24
|
-
mode: "replace",
|
|
25
|
-
syncSearch: false,
|
|
26
|
-
syncQueryParams: false,
|
|
27
|
-
hydrateFromRoute: false,
|
|
28
|
-
searchParam: "search",
|
|
29
|
-
queryParamBlacklist: []
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
assert.deepEqual(
|
|
34
|
-
normalizeListSyncToRouteConfig(true),
|
|
35
|
-
{
|
|
36
|
-
enabled: true,
|
|
37
|
-
mode: "replace",
|
|
38
|
-
syncSearch: true,
|
|
39
|
-
syncQueryParams: true,
|
|
40
|
-
hydrateFromRoute: true,
|
|
41
|
-
searchParam: "q",
|
|
42
|
-
queryParamBlacklist: []
|
|
43
|
-
}
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
assert.deepEqual(
|
|
47
|
-
normalizeListSyncToRouteConfig({
|
|
48
|
-
enabled: true,
|
|
49
|
-
queryParamBlacklist: [" include ", "cursor", "include", ""]
|
|
50
|
-
}),
|
|
51
|
-
{
|
|
52
|
-
enabled: true,
|
|
53
|
-
mode: "replace",
|
|
54
|
-
syncSearch: true,
|
|
55
|
-
syncQueryParams: true,
|
|
56
|
-
hydrateFromRoute: true,
|
|
57
|
-
searchParam: "q",
|
|
58
|
-
queryParamBlacklist: ["include", "cursor"]
|
|
59
|
-
}
|
|
60
|
-
);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("resolveQueryParamDescriptors keeps declared keys and supports writable bindings", () => {
|
|
64
|
-
const source = {
|
|
65
|
-
" status ": ref("open"),
|
|
66
|
-
" count ": 2,
|
|
67
|
-
includeArchived: ref(false),
|
|
68
|
-
empty: ""
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const descriptors = resolveQueryParamDescriptors(source);
|
|
72
|
-
assert.deepEqual(
|
|
73
|
-
descriptors.map((descriptor) => descriptor.key),
|
|
74
|
-
["count", "empty", "includeArchived", "status"]
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
const activeEntries = resolveActiveQueryParamEntries(descriptors);
|
|
78
|
-
assert.deepEqual(
|
|
79
|
-
activeEntries,
|
|
80
|
-
[
|
|
81
|
-
{
|
|
82
|
-
key: "count",
|
|
83
|
-
values: ["2"]
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
key: "status",
|
|
87
|
-
values: ["open"]
|
|
88
|
-
}
|
|
89
|
-
]
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const bindings = resolveWritableQueryParamBindings(descriptors);
|
|
93
|
-
const countBinding = bindings.find((binding) => binding.key === "count");
|
|
94
|
-
assert.ok(countBinding);
|
|
95
|
-
countBinding.set(9);
|
|
96
|
-
assert.equal(source[" count "], 9);
|
|
97
|
-
assert.equal(source.count, undefined);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("buildQueryParamEntriesToken and compare token stay deterministic", () => {
|
|
101
|
-
assert.equal(
|
|
102
|
-
buildQueryParamEntriesToken([
|
|
103
|
-
{
|
|
104
|
-
key: "status",
|
|
105
|
-
values: ["open"]
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
key: "tags",
|
|
109
|
-
values: ["a", "b"]
|
|
110
|
-
}
|
|
111
|
-
]),
|
|
112
|
-
"status=open&tags=a,b"
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
assert.equal(
|
|
116
|
-
buildRouteQueryCompareToken({
|
|
117
|
-
b: "2",
|
|
118
|
-
a: ["1"]
|
|
119
|
-
}),
|
|
120
|
-
buildRouteQueryCompareToken({
|
|
121
|
-
a: "1",
|
|
122
|
-
b: ["2"]
|
|
123
|
-
})
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("parseRouteBindingValue handles boolean, number and array bindings", () => {
|
|
128
|
-
const booleanBinding = {
|
|
129
|
-
valueType: "boolean",
|
|
130
|
-
get: () => false
|
|
131
|
-
};
|
|
132
|
-
assert.equal(parseRouteBindingValue(booleanBinding, "1"), true);
|
|
133
|
-
assert.equal(parseRouteBindingValue(booleanBinding, undefined), false);
|
|
134
|
-
|
|
135
|
-
const numberBinding = {
|
|
136
|
-
valueType: "number",
|
|
137
|
-
get: () => 7
|
|
138
|
-
};
|
|
139
|
-
assert.equal(parseRouteBindingValue(numberBinding, "12"), 12);
|
|
140
|
-
assert.equal(parseRouteBindingValue(numberBinding, "bad"), 7);
|
|
141
|
-
assert.equal(parseRouteBindingValue(numberBinding, undefined), null);
|
|
142
|
-
|
|
143
|
-
const arrayBinding = {
|
|
144
|
-
valueType: "array",
|
|
145
|
-
arrayItemType: "number",
|
|
146
|
-
get: () => []
|
|
147
|
-
};
|
|
148
|
-
assert.deepEqual(
|
|
149
|
-
parseRouteBindingValue(arrayBinding, ["1", "bad", "3"]),
|
|
150
|
-
[1, 3]
|
|
151
|
-
);
|
|
152
|
-
});
|
|
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
|
-
|
|
187
|
-
test("route sync key helpers preserve declared key history for cleanup", () => {
|
|
188
|
-
const history = mergeManagedQueryParamKeyHistory(
|
|
189
|
-
["status"],
|
|
190
|
-
["assignee", "status"]
|
|
191
|
-
);
|
|
192
|
-
assert.deepEqual(history, ["assignee", "status"]);
|
|
193
|
-
|
|
194
|
-
assert.deepEqual(
|
|
195
|
-
resolveRouteSyncManagedKeys({
|
|
196
|
-
searchEnabled: true,
|
|
197
|
-
searchParam: "q",
|
|
198
|
-
syncSearch: true,
|
|
199
|
-
syncQueryParams: true,
|
|
200
|
-
declaredKeys: ["status"],
|
|
201
|
-
keyHistory: ["assignee"]
|
|
202
|
-
}),
|
|
203
|
-
["assignee", "q", "status"]
|
|
204
|
-
);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test("areQueryParamBindingValuesEqual handles arrays and dates", () => {
|
|
208
|
-
assert.equal(
|
|
209
|
-
areQueryParamBindingValuesEqual([1, 2], [1, 2]),
|
|
210
|
-
true
|
|
211
|
-
);
|
|
212
|
-
assert.equal(
|
|
213
|
-
areQueryParamBindingValuesEqual([1, 2], [2, 1]),
|
|
214
|
-
false
|
|
215
|
-
);
|
|
216
|
-
assert.equal(
|
|
217
|
-
areQueryParamBindingValuesEqual(
|
|
218
|
-
new Date("2026-01-01T00:00:00.000Z"),
|
|
219
|
-
new Date("2026-01-01T00:00:00.000Z")
|
|
220
|
-
),
|
|
221
|
-
true
|
|
222
|
-
);
|
|
223
|
-
});
|