@jskit-ai/users-web 0.1.72 → 0.1.74
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 +107 -19
- package/package.json +8 -8
- package/src/client/account-settings/sections.js +14 -10
- package/{templates/src/components/account/settings → src/client/components}/AccountSettingsClientElement.vue +14 -41
- package/src/client/composables/crud/crudJsonApiTransportSupport.js +137 -0
- package/src/client/composables/crud/crudLookupFieldLabelSupport.js +13 -1
- package/src/client/composables/crud/crudLookupFieldRuntime.js +6 -0
- package/src/client/composables/crud/crudSchemaFormHelpers.js +30 -0
- package/src/client/composables/internal/crudListParentTitleSupport.js +4 -0
- package/src/client/composables/records/useCrudAddEdit.js +11 -1
- package/src/client/composables/records/useCrudList.js +4 -0
- package/src/client/composables/records/useCrudView.js +7 -0
- package/src/client/composables/runtime/addEditUiRuntime.js +7 -6
- package/src/client/composables/runtime/useListCore.js +8 -0
- package/src/client/composables/useCrudListFilterLookups.js +5 -0
- package/src/client/composables/useCrudListParentTitle.js +21 -1
- package/src/client/index.js +1 -0
- package/templates/src/components/account/settings/vibe-coding-todo.todo +20 -0
- package/templates/src/pages/account/index.vue +1 -1
- package/test/accountSettingsSections.test.js +16 -5
- package/test/addEditUiRuntime.test.js +17 -0
- package/test/crudJsonApiTransportSupport.test.js +166 -0
- package/test/crudLookupFieldRuntime.test.js +25 -0
- package/test/exportsContract.test.js +1 -0
- package/test/requestTransportOptions.test.js +35 -0
- package/test/settingsPlacementContract.test.js +153 -1
- package/test/useCrudAddEdit.test.js +50 -0
- package/test/useCrudListParentTitle.test.js +106 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed, proxyRefs, reactive, watch } from "vue";
|
|
2
2
|
import { useRoute, useRouter } from "vue-router";
|
|
3
3
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
4
|
+
import { resolveCrudJsonApiTransport } from "../crud/crudJsonApiTransportSupport.js";
|
|
4
5
|
import { useAddEdit } from "./useAddEdit.js";
|
|
5
6
|
import {
|
|
6
7
|
resolveCrudBoundValues,
|
|
@@ -54,6 +55,15 @@ function useCrudAddEdit({
|
|
|
54
55
|
const route = useRoute();
|
|
55
56
|
const normalizedFields = normalizeCrudFormFields(formFields);
|
|
56
57
|
const normalizedAddEditOptions = asPlainObject(addEditOptions);
|
|
58
|
+
const resolvedResource = normalizedAddEditOptions.resource || resource;
|
|
59
|
+
const resolvedTransport = resolveCrudJsonApiTransport(
|
|
60
|
+
normalizedAddEditOptions.transport,
|
|
61
|
+
resolvedResource,
|
|
62
|
+
{
|
|
63
|
+
mode: "add-edit",
|
|
64
|
+
operationName
|
|
65
|
+
}
|
|
66
|
+
);
|
|
57
67
|
const saveSuccessOptions = normalizeSaveSuccessOptions(saveSuccess);
|
|
58
68
|
const defaultFieldErrorKeys = normalizedFields.map((field) => field.key);
|
|
59
69
|
const providedFieldErrorKeys = normalizeFieldErrorKeys(normalizedAddEditOptions.fieldErrorKeys);
|
|
@@ -101,7 +111,6 @@ function useCrudAddEdit({
|
|
|
101
111
|
? normalizedAddEditOptions.onSaveSuccess
|
|
102
112
|
: null;
|
|
103
113
|
const shouldApplyDefaultMapPayload = normalizedAddEditOptions.readEnabled !== false;
|
|
104
|
-
const resolvedResource = normalizedAddEditOptions.resource || resource;
|
|
105
114
|
const resolvedInput = inputOverride || resolvedResource?.operations?.[operationName]?.body || null;
|
|
106
115
|
|
|
107
116
|
function resolveBuildRawPayload(model = {}, context = {}) {
|
|
@@ -170,6 +179,7 @@ function useCrudAddEdit({
|
|
|
170
179
|
const addEdit = useAddEdit({
|
|
171
180
|
...normalizedAddEditOptions,
|
|
172
181
|
resource: resolvedResource,
|
|
182
|
+
transport: resolvedTransport,
|
|
173
183
|
model: form,
|
|
174
184
|
fieldErrorKeys,
|
|
175
185
|
input: resolvedInput,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { computed, unref } from "vue";
|
|
2
2
|
import { useRoute } from "vue-router";
|
|
3
|
+
import { resolveCrudJsonApiTransport } from "../crud/crudJsonApiTransportSupport.js";
|
|
3
4
|
import { resolveLookupFieldDisplayValue } from "../crud/crudLookupFieldLabelSupport.js";
|
|
4
5
|
import { resolveCrudBoundValues } from "../crud/crudBindingSupport.js";
|
|
5
6
|
import { resolveCrudListParentDescriptor } from "../internal/crudListParentTitleSupport.js";
|
|
@@ -65,6 +66,9 @@ function useCrudList({
|
|
|
65
66
|
});
|
|
66
67
|
const records = useList({
|
|
67
68
|
...listOptions,
|
|
69
|
+
transport: resolveCrudJsonApiTransport(listOptions.transport, resource, {
|
|
70
|
+
mode: "list"
|
|
71
|
+
}),
|
|
68
72
|
recordIdParam,
|
|
69
73
|
requestQueryParams(context = {}) {
|
|
70
74
|
const baseRequestQueryParams = resolveRequestQueryParamsInput(requestQueryParams, context);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useRoute } from "vue-router";
|
|
3
|
+
import {
|
|
4
|
+
resolveCrudJsonApiTransport
|
|
5
|
+
} from "../crud/crudJsonApiTransportSupport.js";
|
|
3
6
|
import {
|
|
4
7
|
resolveLookupFieldDisplayValue,
|
|
5
8
|
resolveRecordTitle
|
|
@@ -9,6 +12,7 @@ import { asPlainObject } from "../support/scopeHelpers.js";
|
|
|
9
12
|
import { useView } from "./useView.js";
|
|
10
13
|
|
|
11
14
|
function useCrudView({
|
|
15
|
+
resource = null,
|
|
12
16
|
paramBinding = null,
|
|
13
17
|
route = null,
|
|
14
18
|
...viewOptions
|
|
@@ -25,6 +29,9 @@ function useCrudView({
|
|
|
25
29
|
});
|
|
26
30
|
const view = useView({
|
|
27
31
|
...viewOptions,
|
|
32
|
+
transport: resolveCrudJsonApiTransport(viewOptions.transport, resource, {
|
|
33
|
+
mode: "view"
|
|
34
|
+
}),
|
|
28
35
|
routeParams: boundRouteParams
|
|
29
36
|
});
|
|
30
37
|
view.resolveFieldDisplay = resolveLookupFieldDisplayValue;
|
|
@@ -57,19 +57,20 @@ function createAddEditUiRuntime({
|
|
|
57
57
|
...currentRouteParams,
|
|
58
58
|
...asPlainObject(extraParams)
|
|
59
59
|
};
|
|
60
|
+
const currentRouteRecordId = toResolvedRecordId({
|
|
61
|
+
routeParams: currentRouteParams,
|
|
62
|
+
recordIdParam: normalizedRecordIdParam,
|
|
63
|
+
routeRecordId
|
|
64
|
+
});
|
|
60
65
|
const resolvedRecordId = toRouteParamValue(sourceParams[normalizedRecordIdParam]) ||
|
|
61
|
-
|
|
62
|
-
routeParams: currentRouteParams,
|
|
63
|
-
recordIdParam: normalizedRecordIdParam,
|
|
64
|
-
routeRecordId
|
|
65
|
-
});
|
|
66
|
+
currentRouteRecordId;
|
|
66
67
|
sourceParams[normalizedRecordIdParam] = resolvedRecordId;
|
|
67
68
|
const currentPathname = resolveScopedRoutePathname({
|
|
68
69
|
currentPathname: routePath,
|
|
69
70
|
params: currentRouteParams,
|
|
70
71
|
orderedParamNames: routeParamNames,
|
|
71
72
|
anchorParamName: normalizedRecordIdParam,
|
|
72
|
-
anchorParamValue:
|
|
73
|
+
anchorParamValue: currentRouteRecordId,
|
|
73
74
|
anchorMode: "after"
|
|
74
75
|
});
|
|
75
76
|
|
|
@@ -4,6 +4,8 @@ import { asPlainObject } from "../support/scopeHelpers.js";
|
|
|
4
4
|
import { resolveEnabledRef, resolveTextRef } from "../support/refValueHelpers.js";
|
|
5
5
|
import { usePagedCollection } from "../usePagedCollection.js";
|
|
6
6
|
|
|
7
|
+
const DEFAULT_LIST_LIMIT = 20;
|
|
8
|
+
|
|
7
9
|
function buildListRequestOptions({
|
|
8
10
|
requestOptions = null,
|
|
9
11
|
transport = null,
|
|
@@ -18,6 +20,12 @@ function buildListRequestOptions({
|
|
|
18
20
|
resolvedOptions.query && typeof resolvedOptions.query === "object" && !Array.isArray(resolvedOptions.query)
|
|
19
21
|
? { ...resolvedOptions.query }
|
|
20
22
|
: {};
|
|
23
|
+
if (
|
|
24
|
+
!Object.hasOwn(sourceQuery, "limit") &&
|
|
25
|
+
!Object.hasOwn(sourceQuery, "page[limit]")
|
|
26
|
+
) {
|
|
27
|
+
sourceQuery.limit = DEFAULT_LIST_LIMIT;
|
|
28
|
+
}
|
|
21
29
|
if (pageParam !== null && pageParam !== undefined && pageParam !== "") {
|
|
22
30
|
sourceQuery.cursor = String(pageParam);
|
|
23
31
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
mergeSelectedLookupOptions,
|
|
16
16
|
resolveLookupOptionLabel
|
|
17
17
|
} from "./internal/crudListFilterLookupSupport.js";
|
|
18
|
+
import { inferCrudLookupJsonApiTransport } from "./crud/crudJsonApiTransportSupport.js";
|
|
18
19
|
|
|
19
20
|
function useCrudListFilterLookups(
|
|
20
21
|
definitions = {},
|
|
@@ -46,10 +47,14 @@ function useCrudListFilterLookups(
|
|
|
46
47
|
if (!filter.lookup?.apiSuffix) {
|
|
47
48
|
continue;
|
|
48
49
|
}
|
|
50
|
+
const transport = inferCrudLookupJsonApiTransport({
|
|
51
|
+
apiPath: filter.lookup.apiSuffix
|
|
52
|
+
});
|
|
49
53
|
|
|
50
54
|
const runtime = useList({
|
|
51
55
|
adapter: adapter || undefined,
|
|
52
56
|
apiSuffix: filter.lookup.apiSuffix,
|
|
57
|
+
...(transport ? { transport } : {}),
|
|
53
58
|
queryKeyFactory: (surfaceId = "", scopeParamValue = "") => [
|
|
54
59
|
...normalizedQueryKeyPrefix,
|
|
55
60
|
filter.key,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed, proxyRefs } from "vue";
|
|
2
2
|
import { useRoute } from "vue-router";
|
|
3
3
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
+
import { inferCrudLookupJsonApiTransport } from "./crud/crudJsonApiTransportSupport.js";
|
|
4
5
|
import {
|
|
5
6
|
resolveRouteParamsSource,
|
|
6
7
|
toRouteParamValue
|
|
@@ -59,6 +60,19 @@ function useCrudListParentTitle({
|
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
const initialParentDescriptor = parentDescriptor.value || {};
|
|
63
|
+
const parentTransport = (() => {
|
|
64
|
+
const baseTransport = inferCrudLookupJsonApiTransport({
|
|
65
|
+
namespace: initialParentDescriptor.relationNamespace
|
|
66
|
+
});
|
|
67
|
+
if (!baseTransport) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
...baseTransport,
|
|
73
|
+
responseKind: "record"
|
|
74
|
+
});
|
|
75
|
+
})();
|
|
62
76
|
const normalizedQueryKeyPrefix = normalizeQueryKeyPrefix(queryKeyPrefix);
|
|
63
77
|
const shouldLoadParentRecord = computed(() => {
|
|
64
78
|
const descriptor = parentDescriptor.value;
|
|
@@ -70,12 +84,18 @@ function useCrudListParentTitle({
|
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
const items = Array.isArray(listRuntime?.items) ? listRuntime.items : [];
|
|
73
|
-
|
|
87
|
+
if (items.length < 1) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const listTitle = resolveCrudListParentTitleFromItems(items, descriptor);
|
|
92
|
+
return !listTitle;
|
|
74
93
|
});
|
|
75
94
|
|
|
76
95
|
const parentView = viewRuntimeFactory({
|
|
77
96
|
adapter,
|
|
78
97
|
apiUrlTemplate: normalizeText(initialParentDescriptor.apiUrlTemplate),
|
|
98
|
+
transport: parentTransport,
|
|
79
99
|
readEnabled: shouldLoadParentRecord,
|
|
80
100
|
recordIdParam: normalizeText(initialParentDescriptor.routeParamKey) || "recordId",
|
|
81
101
|
includeRecordIdInQueryKey: true,
|
package/src/client/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { UsersWebClientProvider } from "./providers/UsersWebClientProvider.js";
|
|
2
2
|
|
|
3
3
|
export { UsersWebClientProvider } from "./providers/UsersWebClientProvider.js";
|
|
4
|
+
export { default as AccountSettingsClientElement } from "./components/AccountSettingsClientElement.vue";
|
|
4
5
|
|
|
5
6
|
const clientProviders = Object.freeze([UsersWebClientProvider]);
|
|
6
7
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
This note was split.
|
|
2
|
+
|
|
3
|
+
Use these repo-level docs instead:
|
|
4
|
+
|
|
5
|
+
- `docs/jskit-product-lanes-rulebook.md`
|
|
6
|
+
- `docs/jskit-product-lanes-workboard.md`
|
|
7
|
+
|
|
8
|
+
Why:
|
|
9
|
+
|
|
10
|
+
- the previous file had become a giant mixed artifact
|
|
11
|
+
- the durable product direction now lives in the rulebook
|
|
12
|
+
- the prioritized execution plan now lives in the workboard
|
|
13
|
+
- this template-path file is intentionally reduced to a pointer so the catch-all handoff does not keep living inside a shipped template location
|
|
14
|
+
|
|
15
|
+
If a future agent needs to continue the work:
|
|
16
|
+
|
|
17
|
+
1. read `AGENTS.md`
|
|
18
|
+
2. read the JSKIT pattern/workflow files named in the rulebook
|
|
19
|
+
3. use the rulebook for architecture and product direction
|
|
20
|
+
4. use the workboard for prioritization and execution planning
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<script setup>
|
|
16
|
-
import AccountSettingsClientElement from "
|
|
16
|
+
import AccountSettingsClientElement from "@jskit-ai/users-web/client/components/AccountSettingsClientElement";
|
|
17
17
|
</script>
|
|
@@ -16,12 +16,12 @@ test("resolveAccountSettingsSections normalizes, deduplicates, and sorts placeme
|
|
|
16
16
|
|
|
17
17
|
const resolved = resolveAccountSettingsSections([
|
|
18
18
|
{
|
|
19
|
-
id: "users.
|
|
19
|
+
id: "users.profile.duplicate",
|
|
20
20
|
order: 999,
|
|
21
21
|
component: SectionA,
|
|
22
22
|
props: {
|
|
23
|
-
value: "
|
|
24
|
-
title: "
|
|
23
|
+
value: "profile",
|
|
24
|
+
title: "Profile duplicate"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
{
|
|
@@ -37,10 +37,20 @@ test("resolveAccountSettingsSections normalizes, deduplicates, and sorts placeme
|
|
|
37
37
|
id: "invalid.missing-component",
|
|
38
38
|
order: 250,
|
|
39
39
|
props: {
|
|
40
|
-
value: "
|
|
40
|
+
value: "broken",
|
|
41
41
|
title: "Broken missing component"
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
|
+
{
|
|
45
|
+
id: "users.profile",
|
|
46
|
+
order: 100,
|
|
47
|
+
component: SectionB,
|
|
48
|
+
props: {
|
|
49
|
+
value: "profile",
|
|
50
|
+
title: "Profile",
|
|
51
|
+
usesSharedRuntime: true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
44
54
|
{
|
|
45
55
|
id: "security.section",
|
|
46
56
|
order: 350,
|
|
@@ -70,8 +80,9 @@ test("resolveAccountSettingsSections normalizes, deduplicates, and sorts placeme
|
|
|
70
80
|
usesSharedRuntime: entry.usesSharedRuntime
|
|
71
81
|
})),
|
|
72
82
|
[
|
|
83
|
+
{ value: "invites", title: "Second duplicate", order: 100, usesSharedRuntime: false },
|
|
84
|
+
{ value: "profile", title: "Profile", order: 100, usesSharedRuntime: true },
|
|
73
85
|
{ value: "security", title: "Security", order: 350, usesSharedRuntime: true },
|
|
74
|
-
{ value: "invites", title: "Invites", order: 400, usesSharedRuntime: false }
|
|
75
86
|
]
|
|
76
87
|
);
|
|
77
88
|
});
|
|
@@ -35,6 +35,23 @@ test("createAddEditUiRuntime resolves view urls for saved payload ids with neste
|
|
|
35
35
|
assert.equal(runtime.resolveSavedViewUrl({ id: 99 }), "/contacts/7/addresses/99");
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
test("createAddEditUiRuntime keeps nested child routes stable when the saved child id matches a parent id", () => {
|
|
39
|
+
const runtime = createAddEditUiRuntime({
|
|
40
|
+
recordIdParam: "addressId",
|
|
41
|
+
routeParams: ref({
|
|
42
|
+
workspaceSlug: "tonymobily",
|
|
43
|
+
contactId: "1"
|
|
44
|
+
}),
|
|
45
|
+
routeParamNames: ref(["workspaceSlug", "contactId"]),
|
|
46
|
+
routePath: ref("/w/tonymobily/admin/contacts/1/addresses/new"),
|
|
47
|
+
viewUrlTemplate: "../:addressId",
|
|
48
|
+
listUrlTemplate: ".."
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
assert.equal(runtime.listUrl.value, "/w/tonymobily/admin/contacts/1/addresses");
|
|
52
|
+
assert.equal(runtime.resolveSavedViewUrl({ id: 1 }), "/w/tonymobily/admin/contacts/1/addresses/1");
|
|
53
|
+
});
|
|
54
|
+
|
|
38
55
|
test("createAddEditUiRuntime resolves edit-page relative list and cancel links", () => {
|
|
39
56
|
const runtime = createAddEditUiRuntime({
|
|
40
57
|
recordIdParam: "addressId",
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createSchema } from "json-rest-schema";
|
|
4
|
+
import {
|
|
5
|
+
inferCrudJsonApiTransport,
|
|
6
|
+
inferCrudLookupJsonApiTransport,
|
|
7
|
+
resolveCrudJsonApiTransport,
|
|
8
|
+
resolveLookupFieldMap
|
|
9
|
+
} from "../src/client/composables/crud/crudJsonApiTransportSupport.js";
|
|
10
|
+
|
|
11
|
+
const resource = {
|
|
12
|
+
namespace: "pets",
|
|
13
|
+
contract: {
|
|
14
|
+
lookup: {
|
|
15
|
+
containerKey: "lookups"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
operations: {
|
|
19
|
+
view: {
|
|
20
|
+
output: {
|
|
21
|
+
schema: createSchema({
|
|
22
|
+
id: {
|
|
23
|
+
type: "string",
|
|
24
|
+
required: true
|
|
25
|
+
},
|
|
26
|
+
contactId: {
|
|
27
|
+
type: "string",
|
|
28
|
+
required: true,
|
|
29
|
+
belongsTo: "contacts",
|
|
30
|
+
as: "contact"
|
|
31
|
+
},
|
|
32
|
+
breedId: {
|
|
33
|
+
type: "string",
|
|
34
|
+
nullable: true,
|
|
35
|
+
belongsTo: "breeds",
|
|
36
|
+
as: "breed"
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
test("inferCrudJsonApiTransport infers collection transport for CRUD lists", () => {
|
|
45
|
+
assert.deepEqual(
|
|
46
|
+
inferCrudJsonApiTransport(resource, {
|
|
47
|
+
mode: "list"
|
|
48
|
+
}),
|
|
49
|
+
{
|
|
50
|
+
kind: "jsonapi-resource",
|
|
51
|
+
responseType: "pets",
|
|
52
|
+
responseKind: "collection"
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("inferCrudJsonApiTransport infers record request/response transport for CRUD add/edit", () => {
|
|
58
|
+
assert.deepEqual(
|
|
59
|
+
inferCrudJsonApiTransport(resource, {
|
|
60
|
+
mode: "add-edit",
|
|
61
|
+
operationName: "patch"
|
|
62
|
+
}),
|
|
63
|
+
{
|
|
64
|
+
kind: "jsonapi-resource",
|
|
65
|
+
requestType: "pets",
|
|
66
|
+
responseType: "pets",
|
|
67
|
+
responseKind: "record"
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("inferCrudLookupJsonApiTransport infers collection transport from lookup namespace", () => {
|
|
73
|
+
assert.deepEqual(
|
|
74
|
+
inferCrudLookupJsonApiTransport({
|
|
75
|
+
namespace: "services"
|
|
76
|
+
}),
|
|
77
|
+
{
|
|
78
|
+
kind: "jsonapi-resource",
|
|
79
|
+
responseType: "services",
|
|
80
|
+
responseKind: "collection"
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("inferCrudLookupJsonApiTransport infers collection transport from lookup apiPath", () => {
|
|
86
|
+
assert.deepEqual(
|
|
87
|
+
inferCrudLookupJsonApiTransport({
|
|
88
|
+
apiPath: "/contact-roles"
|
|
89
|
+
}),
|
|
90
|
+
{
|
|
91
|
+
kind: "jsonapi-resource",
|
|
92
|
+
responseType: "contact-roles",
|
|
93
|
+
responseKind: "collection"
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("inferCrudLookupJsonApiTransport returns null without a lookup namespace", () => {
|
|
99
|
+
assert.equal(
|
|
100
|
+
inferCrudLookupJsonApiTransport({}),
|
|
101
|
+
null
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("resolveLookupFieldMap derives lookup aliases from the shared CRUD resource", () => {
|
|
106
|
+
assert.deepEqual(resolveLookupFieldMap(resource), {
|
|
107
|
+
breed: "breedId",
|
|
108
|
+
contact: "contactId"
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("resolveCrudJsonApiTransport infers and enriches JSON:API transport from the resource", () => {
|
|
113
|
+
assert.deepEqual(
|
|
114
|
+
resolveCrudJsonApiTransport(undefined, resource, {
|
|
115
|
+
mode: "list"
|
|
116
|
+
}),
|
|
117
|
+
{
|
|
118
|
+
kind: "jsonapi-resource",
|
|
119
|
+
responseType: "pets",
|
|
120
|
+
responseKind: "collection",
|
|
121
|
+
lookupContainerKey: "lookups",
|
|
122
|
+
lookupFieldMap: {
|
|
123
|
+
breed: "breedId",
|
|
124
|
+
contact: "contactId"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("resolveCrudJsonApiTransport infers record request/response transport for CRUD add/edit", () => {
|
|
131
|
+
assert.deepEqual(
|
|
132
|
+
resolveCrudJsonApiTransport(undefined, resource, {
|
|
133
|
+
mode: "add-edit",
|
|
134
|
+
operationName: "patch"
|
|
135
|
+
}),
|
|
136
|
+
{
|
|
137
|
+
kind: "jsonapi-resource",
|
|
138
|
+
requestType: "pets",
|
|
139
|
+
responseType: "pets",
|
|
140
|
+
responseKind: "record",
|
|
141
|
+
lookupContainerKey: "lookups",
|
|
142
|
+
lookupFieldMap: {
|
|
143
|
+
breed: "breedId",
|
|
144
|
+
contact: "contactId"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("resolveCrudJsonApiTransport rejects explicit CRUD transport overrides", () => {
|
|
151
|
+
assert.throws(
|
|
152
|
+
() =>
|
|
153
|
+
resolveCrudJsonApiTransport(
|
|
154
|
+
{
|
|
155
|
+
kind: "jsonapi-resource",
|
|
156
|
+
responseType: "pets",
|
|
157
|
+
responseKind: "record"
|
|
158
|
+
},
|
|
159
|
+
resource,
|
|
160
|
+
{
|
|
161
|
+
mode: "view"
|
|
162
|
+
}
|
|
163
|
+
),
|
|
164
|
+
/no longer accept explicit transport/
|
|
165
|
+
);
|
|
166
|
+
});
|
|
@@ -203,6 +203,31 @@ test("resolveLookupFieldDisplayValue supports custom lookup container key", () =
|
|
|
203
203
|
);
|
|
204
204
|
});
|
|
205
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
|
+
|
|
206
231
|
test("resolveLookupFieldDisplayValue returns raw value for non-lookup fields", () => {
|
|
207
232
|
assert.equal(
|
|
208
233
|
resolveLookupFieldDisplayValue(
|
|
@@ -15,6 +15,7 @@ test("users-web exports are explicit and aligned with production/template usage"
|
|
|
15
15
|
packageId: "@jskit-ai/users-web",
|
|
16
16
|
requiredExports: [
|
|
17
17
|
"./client",
|
|
18
|
+
"./client/components/AccountSettingsClientElement",
|
|
18
19
|
"./client/account-settings/sections",
|
|
19
20
|
"./client/composables/useAddEdit",
|
|
20
21
|
"./client/composables/useCommand",
|
|
@@ -95,6 +95,7 @@ test("list request options stay GET and carry transport when provided", () => {
|
|
|
95
95
|
"x-demo": "1"
|
|
96
96
|
},
|
|
97
97
|
query: {
|
|
98
|
+
limit: 20,
|
|
98
99
|
cursor: "cursor_2"
|
|
99
100
|
},
|
|
100
101
|
transport: {
|
|
@@ -105,3 +106,37 @@ test("list request options stay GET and carry transport when provided", () => {
|
|
|
105
106
|
}
|
|
106
107
|
);
|
|
107
108
|
});
|
|
109
|
+
|
|
110
|
+
test("list request options preserve explicit limit values", () => {
|
|
111
|
+
assert.deepEqual(
|
|
112
|
+
buildListRequestOptions({
|
|
113
|
+
requestOptions: {
|
|
114
|
+
query: {
|
|
115
|
+
limit: 50
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
119
|
+
{
|
|
120
|
+
method: "GET",
|
|
121
|
+
query: {
|
|
122
|
+
limit: 50
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
assert.deepEqual(
|
|
128
|
+
buildListRequestOptions({
|
|
129
|
+
requestOptions: {
|
|
130
|
+
query: {
|
|
131
|
+
"page[limit]": "75"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}),
|
|
135
|
+
{
|
|
136
|
+
method: "GET",
|
|
137
|
+
query: {
|
|
138
|
+
"page[limit]": "75"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
});
|