@cosmicdrift/kumiko-renderer 0.327.0 → 0.328.1
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/dist/app/action-form-shim.d.ts +14 -0
- package/{src/app/action-form-shim.ts → dist/app/action-form-shim.js} +26 -44
- package/dist/app/app-features-context.d.ts +8 -0
- package/dist/app/app-features-context.js +16 -0
- package/dist/app/column-renderers.d.ts +30 -0
- package/dist/app/column-renderers.js +34 -0
- package/dist/app/config-edit-shim.d.ts +11 -0
- package/{src/app/config-edit-shim.ts → dist/app/config-edit-shim.js} +12 -25
- package/dist/app/content-editors.d.ts +38 -0
- package/dist/app/content-editors.js +26 -0
- package/dist/app/content-preview.d.ts +11 -0
- package/dist/app/content-preview.js +30 -0
- package/dist/app/custom-screens.d.ts +11 -0
- package/dist/app/custom-screens.js +20 -0
- package/dist/app/dashboard-body.d.ts +16 -0
- package/dist/app/dashboard-body.js +11 -0
- package/dist/app/extension-form-submit.d.ts +32 -0
- package/dist/app/extension-form-submit.js +73 -0
- package/dist/app/extension-sections.d.ts +72 -0
- package/dist/app/extension-sections.js +34 -0
- package/dist/app/feature-schema.d.ts +4 -0
- package/{src/app/feature-schema.ts → dist/app/feature-schema.js} +12 -22
- package/dist/app/form-schema.d.ts +4 -0
- package/dist/app/form-schema.js +85 -0
- package/dist/app/kumiko-screen.d.ts +43 -0
- package/dist/app/kumiko-screen.js +2206 -0
- package/dist/app/layout-fields.d.ts +3 -0
- package/dist/app/layout-fields.js +20 -0
- package/dist/app/list-facets.d.ts +48 -0
- package/dist/app/list-facets.js +185 -0
- package/dist/app/nav.d.ts +61 -0
- package/dist/app/nav.js +139 -0
- package/dist/app/projection-detail-shim.d.ts +8 -0
- package/dist/app/projection-detail-shim.js +71 -0
- package/dist/app/projection-list-shim.d.ts +13 -0
- package/{src/app/projection-list-shim.ts → dist/app/projection-list-shim.js} +26 -38
- package/dist/app/qn.d.ts +7 -0
- package/{src/app/qn.ts → dist/app/qn.js} +9 -11
- package/dist/app/qualify-screen-id.d.ts +2 -0
- package/{src/app/qualify-screen-id.ts → dist/app/qualify-screen-id.js} +5 -6
- package/dist/app/reference-facet-bridge.d.ts +10 -0
- package/dist/app/reference-facet-bridge.js +22 -0
- package/dist/app/return-to.d.ts +31 -0
- package/dist/app/return-to.js +188 -0
- package/dist/app/row-actions.d.ts +77 -0
- package/dist/app/row-actions.js +416 -0
- package/dist/app/screen-access.d.ts +8 -0
- package/dist/app/screen-access.js +48 -0
- package/dist/app/secret-mint-body.d.ts +10 -0
- package/dist/app/secret-mint-body.js +140 -0
- package/dist/app/secrets-edit-body.d.ts +8 -0
- package/dist/app/secrets-edit-body.js +76 -0
- package/dist/app/use-embedded-screen.d.ts +6 -0
- package/dist/app/use-embedded-screen.js +19 -0
- package/dist/app/variable-chips.d.ts +7 -0
- package/dist/app/variable-chips.js +16 -0
- package/dist/app/write-failed-error.d.ts +6 -0
- package/dist/app/write-failed-error.js +21 -0
- package/dist/components/embedded-list-field.d.ts +10 -0
- package/dist/components/embedded-list-field.js +259 -0
- package/dist/components/grid-cell-for-field.d.ts +25 -0
- package/dist/components/grid-cell-for-field.js +31 -0
- package/dist/components/multi-select-checkboxes.d.ts +14 -0
- package/dist/components/multi-select-checkboxes.js +36 -0
- package/dist/components/reference-create-dialog.d.ts +13 -0
- package/dist/components/reference-create-dialog.js +46 -0
- package/dist/components/related-list-section.d.ts +25 -0
- package/dist/components/related-list-section.js +273 -0
- package/dist/components/render-edit-action-button.d.ts +22 -0
- package/dist/components/render-edit-action-button.js +43 -0
- package/dist/components/render-edit-logic.d.ts +6 -0
- package/dist/components/render-edit-logic.js +63 -0
- package/dist/components/render-edit-types.d.ts +209 -0
- package/dist/components/render-edit-types.js +1 -0
- package/dist/components/render-edit.d.ts +6 -0
- package/dist/components/render-edit.js +1169 -0
- package/dist/components/render-field.d.ts +38 -0
- package/dist/components/render-field.js +544 -0
- package/dist/components/render-list.d.ts +117 -0
- package/dist/components/render-list.js +232 -0
- package/dist/components/write-form-section.d.ts +17 -0
- package/dist/components/write-form-section.js +104 -0
- package/dist/context/dispatcher-context.d.ts +10 -0
- package/{src/context/dispatcher-context.tsx → dist/context/dispatcher-context.js} +16 -29
- package/dist/context/draft-storage-context.d.ts +12 -0
- package/dist/context/draft-storage-context.js +18 -0
- package/dist/context/user-roles-context.d.ts +7 -0
- package/{src/context/user-roles-context.tsx → dist/context/user-roles-context.js} +7 -15
- package/dist/format-when.d.ts +1 -0
- package/{src/format-when.ts → dist/format-when.js} +7 -7
- package/dist/hooks/reference-limits.d.ts +3 -0
- package/{src/hooks/reference-limits.ts → dist/hooks/reference-limits.js} +0 -1
- package/dist/hooks/use-ai-text.d.ts +52 -0
- package/dist/hooks/use-ai-text.js +90 -0
- package/dist/hooks/use-disclosure.d.ts +7 -0
- package/dist/hooks/use-disclosure.js +8 -0
- package/dist/hooks/use-form.d.ts +7 -0
- package/dist/hooks/use-form.js +36 -0
- package/dist/hooks/use-list-url-state.d.ts +43 -0
- package/dist/hooks/use-list-url-state.js +131 -0
- package/dist/hooks/use-mutation.d.ts +9 -0
- package/dist/hooks/use-mutation.js +37 -0
- package/dist/hooks/use-query.d.ts +14 -0
- package/dist/hooks/use-query.js +118 -0
- package/dist/hooks/use-reference-lookup.d.ts +14 -0
- package/dist/hooks/use-reference-lookup.js +63 -0
- package/dist/hooks/use-store.d.ts +3 -0
- package/dist/hooks/use-store.js +38 -0
- package/dist/hooks/use-stream-handler.d.ts +14 -0
- package/dist/hooks/use-stream-handler.js +93 -0
- package/dist/i18n-defaults.d.ts +2 -0
- package/dist/i18n-defaults.js +201 -0
- package/dist/i18n.d.ts +64 -0
- package/dist/i18n.js +166 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +55 -0
- package/dist/inside-drawer.d.ts +2 -0
- package/dist/inside-drawer.js +9 -0
- package/dist/page-header-slot.d.ts +2 -0
- package/dist/page-header-slot.js +9 -0
- package/dist/primitives.d.ts +1351 -0
- package/dist/primitives.js +73 -0
- package/dist/screen-fills-height.d.ts +3 -0
- package/dist/screen-fills-height.js +3 -0
- package/dist/share-links.d.ts +4 -0
- package/dist/share-links.js +11 -0
- package/dist/sort-by-accessor.d.ts +5 -0
- package/dist/sort-by-accessor.js +16 -0
- package/dist/sse/live-events.d.ts +23 -0
- package/dist/sse/live-events.js +28 -0
- package/dist/tokens.d.ts +83 -0
- package/dist/tokens.js +80 -0
- package/package.json +16 -9
- package/src/changes.json +52 -0
- package/src/__tests__/error-i18n-defaults.test.ts +0 -82
- package/src/__tests__/format-when.test.ts +0 -12
- package/src/__tests__/i18n.test.tsx +0 -283
- package/src/__tests__/merge-search-params-into-initial.test.ts +0 -525
- package/src/__tests__/qn.test.ts +0 -83
- package/src/__tests__/share-links.test.ts +0 -26
- package/src/__tests__/sort-by-accessor.test.ts +0 -48
- package/src/__tests__/stringify-nav-params.test.ts +0 -33
- package/src/__tests__/use-list-url-state.test.tsx +0 -250
- package/src/app/__tests__/action-form-field-labels.test.tsx +0 -92
- package/src/app/__tests__/action-form-shim.test.ts +0 -137
- package/src/app/__tests__/build-list-query-payload.test.ts +0 -107
- package/src/app/__tests__/config-edit-shim.test.ts +0 -57
- package/src/app/__tests__/content-editors.test.tsx +0 -128
- package/src/app/__tests__/content-preview.test.tsx +0 -184
- package/src/app/__tests__/entity-edit-create-search-params.test.tsx +0 -236
- package/src/app/__tests__/entity-edit-jsonb-embedded-submit.test.tsx +0 -239
- package/src/app/__tests__/entity-list-create-singleton.test.tsx +0 -128
- package/src/app/__tests__/entity-list-row-action-entity-target.test.tsx +0 -424
- package/src/app/__tests__/entity-list-row-action-icon.test.tsx +0 -248
- package/src/app/__tests__/entity-list-row-action-kumiko-actions-view.test.tsx +0 -162
- package/src/app/__tests__/entity-list-row-action-refetch.test.tsx +0 -401
- package/src/app/__tests__/entity-list-screen-padding.test.tsx +0 -111
- package/src/app/__tests__/feature-schema.test.ts +0 -40
- package/src/app/__tests__/form-schema.test.ts +0 -247
- package/src/app/__tests__/list-daterange-facet.test.ts +0 -165
- package/src/app/__tests__/list-filter-facets.test.tsx +0 -521
- package/src/app/__tests__/nav.test.ts +0 -198
- package/src/app/__tests__/projection-detail-actions.test.tsx +0 -719
- package/src/app/__tests__/projection-detail-reference-field.test.tsx +0 -165
- package/src/app/__tests__/projection-detail-shim.test.ts +0 -188
- package/src/app/__tests__/projection-detail-singleton.test.tsx +0 -191
- package/src/app/__tests__/projection-list-bad-shape.test.tsx +0 -152
- package/src/app/__tests__/projection-list-reference-column.test.tsx +0 -219
- package/src/app/__tests__/projection-list-search-sort.test.tsx +0 -212
- package/src/app/__tests__/qualified-names.test.ts +0 -37
- package/src/app/__tests__/return-to.test.ts +0 -396
- package/src/app/__tests__/row-actions-navigate-tab.test.ts +0 -223
- package/src/app/__tests__/row-default-edit-action.test.tsx +0 -324
- package/src/app/__tests__/screen-access-allows.test.ts +0 -34
- package/src/app/__tests__/secret-mint-body.test.tsx +0 -404
- package/src/app/__tests__/variable-chips.test.tsx +0 -70
- package/src/app/app-features-context.tsx +0 -25
- package/src/app/column-renderers.tsx +0 -68
- package/src/app/content-editors.tsx +0 -85
- package/src/app/content-preview.tsx +0 -67
- package/src/app/custom-screens.tsx +0 -29
- package/src/app/dashboard-body.tsx +0 -33
- package/src/app/extension-form-submit.tsx +0 -123
- package/src/app/extension-sections.tsx +0 -111
- package/src/app/form-schema.ts +0 -91
- package/src/app/kumiko-screen.tsx +0 -3614
- package/src/app/layout-fields.ts +0 -31
- package/src/app/list-facets.ts +0 -259
- package/src/app/nav.tsx +0 -258
- package/src/app/projection-detail-shim.ts +0 -85
- package/src/app/reference-facet-bridge.tsx +0 -58
- package/src/app/return-to.tsx +0 -234
- package/src/app/row-actions.ts +0 -639
- package/src/app/screen-access.ts +0 -63
- package/src/app/secret-mint-body.tsx +0 -259
- package/src/app/secrets-edit-body.tsx +0 -194
- package/src/app/use-embedded-screen.ts +0 -27
- package/src/app/variable-chips.tsx +0 -39
- package/src/app/write-failed-error.ts +0 -27
- package/src/components/__tests__/embedded-list-field.test.tsx +0 -531
- package/src/components/__tests__/multi-select-checkboxes.test.tsx +0 -162
- package/src/components/__tests__/related-list-section-empty-state.test.tsx +0 -143
- package/src/components/__tests__/related-list-section.test.tsx +0 -1669
- package/src/components/__tests__/render-edit-action-button.test.tsx +0 -205
- package/src/components/__tests__/render-edit-hidden-section.test.tsx +0 -128
- package/src/components/__tests__/render-edit-logic.test.ts +0 -314
- package/src/components/__tests__/render-edit-screen-description.test.tsx +0 -152
- package/src/components/__tests__/render-edit-submit-actions.test.tsx +0 -460
- package/src/components/__tests__/render-field-app-locale.test.tsx +0 -142
- package/src/components/__tests__/render-field-enum-option.test.tsx +0 -99
- package/src/components/__tests__/render-field-icon-derivation.test.tsx +0 -119
- package/src/components/__tests__/render-field-json-format.test.tsx +0 -164
- package/src/components/__tests__/render-field-money-embedded-list-consistency.test.tsx +0 -212
- package/src/components/__tests__/render-field-money-roundtrip.test.tsx +0 -217
- package/src/components/__tests__/render-field-number-grouping.test.tsx +0 -76
- package/src/components/__tests__/render-field-number-unit.test.tsx +0 -227
- package/src/components/__tests__/render-field-password-format.test.tsx +0 -101
- package/src/components/__tests__/render-field-select-display.test.tsx +0 -134
- package/src/components/__tests__/render-field-unit-format.test.tsx +0 -107
- package/src/components/__tests__/render-field-unsupported-types.test.tsx +0 -195
- package/src/components/__tests__/render-list-toolbar-icon-only.test.tsx +0 -146
- package/src/components/__tests__/write-form-section.test.tsx +0 -375
- package/src/components/embedded-list-field.tsx +0 -354
- package/src/components/grid-cell-for-field.tsx +0 -60
- package/src/components/multi-select-checkboxes.tsx +0 -93
- package/src/components/reference-create-dialog.tsx +0 -91
- package/src/components/related-list-section.tsx +0 -467
- package/src/components/render-edit-action-button.tsx +0 -106
- package/src/components/render-edit-logic.ts +0 -85
- package/src/components/render-edit-types.ts +0 -216
- package/src/components/render-edit.tsx +0 -1700
- package/src/components/render-field.tsx +0 -942
- package/src/components/render-list.tsx +0 -561
- package/src/components/write-form-section.tsx +0 -199
- package/src/context/draft-storage-context.tsx +0 -39
- package/src/hooks/__tests__/use-ai-text.test.tsx +0 -177
- package/src/hooks/__tests__/use-disclosure.test.tsx +0 -25
- package/src/hooks/__tests__/use-form.test.tsx +0 -65
- package/src/hooks/__tests__/use-mutation.test.tsx +0 -77
- package/src/hooks/__tests__/use-stream-handler.test.tsx +0 -107
- package/src/hooks/use-ai-text.ts +0 -172
- package/src/hooks/use-disclosure.ts +0 -20
- package/src/hooks/use-form.ts +0 -78
- package/src/hooks/use-list-url-state.ts +0 -171
- package/src/hooks/use-mutation.ts +0 -61
- package/src/hooks/use-query.ts +0 -132
- package/src/hooks/use-reference-lookup.ts +0 -76
- package/src/hooks/use-store.ts +0 -48
- package/src/hooks/use-stream-handler.ts +0 -134
- package/src/i18n-defaults.ts +0 -220
- package/src/i18n.tsx +0 -286
- package/src/index.ts +0 -272
- package/src/primitives.tsx +0 -1446
- package/src/share-links.ts +0 -12
- package/src/sort-by-accessor.ts +0 -20
- package/src/sse/live-events.tsx +0 -56
- package/src/tokens.tsx +0 -170
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { stringifyNavParams } from "../app/row-actions";
|
|
3
|
-
|
|
4
|
-
describe("stringifyNavParams", () => {
|
|
5
|
-
test("plain object encodes as JSON (fw#2763)", () => {
|
|
6
|
-
const result = stringifyNavParams({ price: { amount: 12.5, currency: "EUR" } });
|
|
7
|
-
expect(result["price"]).toBe(JSON.stringify({ amount: 12.5, currency: "EUR" }));
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test("array stays JSON", () => {
|
|
11
|
-
const result = stringifyNavParams({ roles: ["Admin", "User"] });
|
|
12
|
-
expect(result["roles"]).toBe(JSON.stringify(["Admin", "User"]));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test("string, number and boolean stringify via String()", () => {
|
|
16
|
-
const result = stringifyNavParams({ name: "Alice", age: 42, active: true });
|
|
17
|
-
expect(result["name"]).toBe("Alice");
|
|
18
|
-
expect(result["age"]).toBe("42");
|
|
19
|
-
expect(result["active"]).toBe("true");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("null and undefined become null", () => {
|
|
23
|
-
const result = stringifyNavParams({ a: null, b: undefined });
|
|
24
|
-
expect(result["a"]).toBeNull();
|
|
25
|
-
expect(result["b"]).toBeNull();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("a Date instance stays String(date), not JSON", () => {
|
|
29
|
-
const date = new Date("2026-01-01T00:00:00.000Z");
|
|
30
|
-
const result = stringifyNavParams({ createdAt: date });
|
|
31
|
-
expect(result["createdAt"]).toBe(String(date));
|
|
32
|
-
});
|
|
33
|
-
});
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// useListUrlState pinnt den URL-State-Vertrag pro Screen-ID-Namespace:
|
|
3
|
-
// `?<screenId>.sort=…&<screenId>.dir=…&<screenId>.q=…&<screenId>.page=…`.
|
|
4
|
-
// Zwei Listen auf der Route teilen sich die URL ohne Param-Konflikt.
|
|
5
|
-
// Page wird bei Sort/Filter-Wechsel reseted; Sort bleibt bei Search.
|
|
6
|
-
|
|
7
|
-
import { describe, expect, mock, test } from "bun:test";
|
|
8
|
-
import { act, renderHook } from "@testing-library/react";
|
|
9
|
-
import type { ReactNode } from "react";
|
|
10
|
-
import type { NavApi } from "../app/nav";
|
|
11
|
-
import { NavProvider } from "../app/nav";
|
|
12
|
-
import { useListUrlState } from "../hooks/use-list-url-state";
|
|
13
|
-
|
|
14
|
-
function makeNav(initial: Record<string, string> = {}): NavApi & {
|
|
15
|
-
readonly current: { params: Record<string, string> };
|
|
16
|
-
readonly captures: Array<Record<string, string | null>>;
|
|
17
|
-
} {
|
|
18
|
-
const params: Record<string, string> = { ...initial };
|
|
19
|
-
const captures: Array<Record<string, string | null>> = [];
|
|
20
|
-
const api: NavApi & {
|
|
21
|
-
current: { params: Record<string, string> };
|
|
22
|
-
captures: Array<Record<string, string | null>>;
|
|
23
|
-
} = {
|
|
24
|
-
route: undefined,
|
|
25
|
-
navigate: mock(),
|
|
26
|
-
replace: mock(),
|
|
27
|
-
hrefFor: () => "",
|
|
28
|
-
get searchParams() {
|
|
29
|
-
return params;
|
|
30
|
-
},
|
|
31
|
-
setSearchParams: (updates) => {
|
|
32
|
-
captures.push(updates);
|
|
33
|
-
for (const [k, v] of Object.entries(updates)) {
|
|
34
|
-
if (v === null) delete params[k];
|
|
35
|
-
else params[k] = v;
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
current: { params },
|
|
39
|
-
captures,
|
|
40
|
-
};
|
|
41
|
-
return api;
|
|
42
|
-
}
|
|
43
|
-
function wrapper(nav: NavApi): (props: { children: ReactNode }) => ReactNode {
|
|
44
|
-
return ({ children }) => <NavProvider value={nav}>{children}</NavProvider>;
|
|
45
|
-
}
|
|
46
|
-
describe("useListUrlState", () => {
|
|
47
|
-
test("Default-State: kein URL-Param → sort=null, q='', page=1", () => {
|
|
48
|
-
const nav = makeNav();
|
|
49
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
50
|
-
expect(result.current.sort).toBeNull();
|
|
51
|
-
expect(result.current.q).toBe("");
|
|
52
|
-
expect(result.current.page).toBe(1);
|
|
53
|
-
});
|
|
54
|
-
test("liest sort+dir aus URL-Params (mit screenId-Prefix)", () => {
|
|
55
|
-
const nav = makeNav({ "orders.sort": "createdAt", "orders.dir": "desc" });
|
|
56
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
57
|
-
expect(result.current.sort).toEqual({ field: "createdAt", dir: "desc" });
|
|
58
|
-
});
|
|
59
|
-
test("ignoriert sort einer ANDEREN Liste (Namespacing)", () => {
|
|
60
|
-
const nav = makeNav({ "incidents.sort": "severity", "incidents.dir": "asc" });
|
|
61
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
62
|
-
expect(result.current.sort).toBeNull();
|
|
63
|
-
});
|
|
64
|
-
test("invalid dir (z.B. 'foo') → sort=null (defensive parse)", () => {
|
|
65
|
-
const nav = makeNav({ "orders.sort": "name", "orders.dir": "foo" });
|
|
66
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
67
|
-
expect(result.current.sort).toBeNull();
|
|
68
|
-
});
|
|
69
|
-
test("setSort schreibt sort+dir, resettet page (atomic update)", () => {
|
|
70
|
-
const nav = makeNav({ "orders.page": "5" });
|
|
71
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
72
|
-
act(() => {
|
|
73
|
-
result.current.setSort({ field: "name", dir: "asc" });
|
|
74
|
-
});
|
|
75
|
-
expect(nav.captures).toHaveLength(1);
|
|
76
|
-
expect(nav.captures[0]).toEqual({
|
|
77
|
-
"orders.sort": "name",
|
|
78
|
-
"orders.dir": "asc",
|
|
79
|
-
"orders.page": null, // page-reset bei sort-change
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
test("setSort(null) löscht sort+dir+page", () => {
|
|
83
|
-
const nav = makeNav({
|
|
84
|
-
"orders.sort": "name",
|
|
85
|
-
"orders.dir": "asc",
|
|
86
|
-
"orders.page": "3",
|
|
87
|
-
});
|
|
88
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
89
|
-
act(() => {
|
|
90
|
-
result.current.setSort(null);
|
|
91
|
-
});
|
|
92
|
-
expect(nav.captures[0]).toEqual({
|
|
93
|
-
"orders.sort": null,
|
|
94
|
-
"orders.dir": null,
|
|
95
|
-
"orders.page": null,
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
test("setQ schreibt q + resettet page (Sort bleibt unangetastet)", () => {
|
|
99
|
-
const nav = makeNav({
|
|
100
|
-
"orders.sort": "name",
|
|
101
|
-
"orders.dir": "asc",
|
|
102
|
-
"orders.page": "3",
|
|
103
|
-
});
|
|
104
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
105
|
-
act(() => {
|
|
106
|
-
result.current.setQ("acme");
|
|
107
|
-
});
|
|
108
|
-
expect(nav.captures[0]).toEqual({
|
|
109
|
-
"orders.q": "acme",
|
|
110
|
-
"orders.page": null,
|
|
111
|
-
});
|
|
112
|
-
// Sort darf NICHT in den captures sein — search-change zerlegt
|
|
113
|
-
// die Sortierung nicht.
|
|
114
|
-
expect(nav.captures[0]).not.toHaveProperty("orders.sort");
|
|
115
|
-
});
|
|
116
|
-
test("setQ('') löscht den q-Key", () => {
|
|
117
|
-
const nav = makeNav({ "orders.q": "acme" });
|
|
118
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
119
|
-
act(() => {
|
|
120
|
-
result.current.setQ("");
|
|
121
|
-
});
|
|
122
|
-
expect(nav.captures[0]).toEqual({
|
|
123
|
-
"orders.q": null,
|
|
124
|
-
"orders.page": null,
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
test("setPage(1) löscht den Key (Default-Page = unprefixed URL)", () => {
|
|
128
|
-
const nav = makeNav({ "orders.page": "5" });
|
|
129
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
130
|
-
act(() => {
|
|
131
|
-
result.current.setPage(1);
|
|
132
|
-
});
|
|
133
|
-
expect(nav.captures[0]).toEqual({ "orders.page": null });
|
|
134
|
-
});
|
|
135
|
-
test("setPage(N>1) speichert die Page als String", () => {
|
|
136
|
-
const nav = makeNav();
|
|
137
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
138
|
-
act(() => {
|
|
139
|
-
result.current.setPage(7);
|
|
140
|
-
});
|
|
141
|
-
expect(nav.captures[0]).toEqual({ "orders.page": "7" });
|
|
142
|
-
});
|
|
143
|
-
test("invalid page (negativ, NaN, 0) → fallback auf 1", () => {
|
|
144
|
-
const nav = makeNav({ "orders.page": "-3" });
|
|
145
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
146
|
-
expect(result.current.page).toBe(1);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("liest Faceted-Filter aus .f.<field>-Keys (comma-split), namespaced", () => {
|
|
150
|
-
const nav = makeNav({
|
|
151
|
-
"orders.f.status": "draft,published",
|
|
152
|
-
"orders.f.active": "true",
|
|
153
|
-
"incidents.f.status": "open", // andere Liste → ignoriert
|
|
154
|
-
});
|
|
155
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
156
|
-
expect(result.current.filters).toEqual({
|
|
157
|
-
status: ["draft", "published"],
|
|
158
|
-
active: ["true"],
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test("setFilter schreibt comma-joined Werte + resettet page", () => {
|
|
163
|
-
const nav = makeNav({ "orders.page": "4" });
|
|
164
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
165
|
-
act(() => {
|
|
166
|
-
result.current.setFilter("status", ["draft", "review"]);
|
|
167
|
-
});
|
|
168
|
-
expect(nav.captures[0]).toEqual({
|
|
169
|
-
"orders.f.status": "draft,review",
|
|
170
|
-
"orders.page": null,
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("setFilter mit leerem Array löscht den Facet-Key", () => {
|
|
175
|
-
const nav = makeNav({ "orders.f.status": "draft" });
|
|
176
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
177
|
-
act(() => {
|
|
178
|
-
result.current.setFilter("status", []);
|
|
179
|
-
});
|
|
180
|
-
expect(nav.captures[0]).toEqual({
|
|
181
|
-
"orders.f.status": null,
|
|
182
|
-
"orders.page": null,
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// fw#3104: the date-range facet lives in the same .f. namespace so
|
|
187
|
-
// clearFilters covers it — but both bounds have to land in ONE params
|
|
188
|
-
// update, or the second call overwrites the first.
|
|
189
|
-
test("setDateRange schreibt beide Grenzen atomar + resettet page", () => {
|
|
190
|
-
const nav = makeNav({ "orders.page": "4" });
|
|
191
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
192
|
-
act(() => {
|
|
193
|
-
result.current.setDateRange("createdAt", { from: "2020-06-10", to: "2020-06-14" });
|
|
194
|
-
});
|
|
195
|
-
expect(nav.captures).toHaveLength(1);
|
|
196
|
-
expect(nav.captures[0]).toEqual({
|
|
197
|
-
"orders.f.createdAt.from": "2020-06-10",
|
|
198
|
-
"orders.f.createdAt.to": "2020-06-14",
|
|
199
|
-
"orders.page": null,
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("setDateRange mit leerer Grenze löscht nur diesen Key", () => {
|
|
204
|
-
const nav = makeNav({
|
|
205
|
-
"orders.f.createdAt.from": "2020-06-10",
|
|
206
|
-
"orders.f.createdAt.to": "2020-06-14",
|
|
207
|
-
});
|
|
208
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
209
|
-
act(() => {
|
|
210
|
-
result.current.setDateRange("createdAt", { from: "", to: "2020-06-14" });
|
|
211
|
-
});
|
|
212
|
-
expect(nav.captures[0]).toEqual({
|
|
213
|
-
"orders.f.createdAt.from": null,
|
|
214
|
-
"orders.f.createdAt.to": "2020-06-14",
|
|
215
|
-
"orders.page": null,
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("clearFilters löscht auch die Zeitraum-Grenzen", () => {
|
|
220
|
-
const nav = makeNav({
|
|
221
|
-
"orders.f.createdAt.from": "2020-06-10",
|
|
222
|
-
"orders.f.createdAt.to": "2020-06-14",
|
|
223
|
-
});
|
|
224
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
225
|
-
act(() => {
|
|
226
|
-
result.current.clearFilters();
|
|
227
|
-
});
|
|
228
|
-
expect(nav.captures[0]).toEqual({
|
|
229
|
-
"orders.page": null,
|
|
230
|
-
"orders.f.createdAt.from": null,
|
|
231
|
-
"orders.f.createdAt.to": null,
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test("clearFilters löscht alle aktiven .f.<field>-Keys", () => {
|
|
236
|
-
const nav = makeNav({
|
|
237
|
-
"orders.f.status": "draft",
|
|
238
|
-
"orders.f.active": "true",
|
|
239
|
-
});
|
|
240
|
-
const { result } = renderHook(() => useListUrlState("orders"), { wrapper: wrapper(nav) });
|
|
241
|
-
act(() => {
|
|
242
|
-
result.current.clearFilters();
|
|
243
|
-
});
|
|
244
|
-
expect(nav.captures[0]).toEqual({
|
|
245
|
-
"orders.page": null,
|
|
246
|
-
"orders.f.status": null,
|
|
247
|
-
"orders.f.active": null,
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
});
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// fieldLabels overrides a field's label per screen, via the same
|
|
2
|
-
// label-resolution pipeline as entityEdit (see action-form-shim.ts).
|
|
3
|
-
|
|
4
|
-
import { describe, expect, test } from "bun:test";
|
|
5
|
-
import type { ActionFormScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
6
|
-
import { render } from "@testing-library/react";
|
|
7
|
-
import type { ReactNode } from "react";
|
|
8
|
-
import { RenderEdit } from "../../components/render-edit";
|
|
9
|
-
import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
|
|
10
|
-
import { kumikoDefaultTranslations } from "../../i18n-defaults";
|
|
11
|
-
import { type CorePrimitives, type FieldProps, PrimitivesProvider } from "../../primitives";
|
|
12
|
-
import { synthesizeActionFormEntity, synthesizeActionFormScreen } from "../action-form-shim";
|
|
13
|
-
|
|
14
|
-
const noop = (): ReactNode => null;
|
|
15
|
-
const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
|
|
16
|
-
|
|
17
|
-
function renderActionFormCapturingFieldLabels(screen: ActionFormScreenDefinition): {
|
|
18
|
-
labels: Record<string, string>;
|
|
19
|
-
} {
|
|
20
|
-
const labels: Record<string, string> = {};
|
|
21
|
-
const capturingField = (props: FieldProps): ReactNode => {
|
|
22
|
-
if (props.testId !== undefined) labels[props.testId] = props.label;
|
|
23
|
-
return props.children;
|
|
24
|
-
};
|
|
25
|
-
const primitives: CorePrimitives = {
|
|
26
|
-
Button: noop,
|
|
27
|
-
Banner: noop,
|
|
28
|
-
Field: capturingField,
|
|
29
|
-
Input: noop,
|
|
30
|
-
DataTable: noop,
|
|
31
|
-
Form: passChildren,
|
|
32
|
-
Section: passChildren,
|
|
33
|
-
Card: passChildren,
|
|
34
|
-
Grid: passChildren,
|
|
35
|
-
GridCell: passChildren,
|
|
36
|
-
Text: passChildren,
|
|
37
|
-
Heading: noop,
|
|
38
|
-
Dialog: noop,
|
|
39
|
-
Modal: noop,
|
|
40
|
-
Lightbox: noop,
|
|
41
|
-
ConfigSourceBadge: noop,
|
|
42
|
-
ConfigCascadeView: noop,
|
|
43
|
-
Link: noop,
|
|
44
|
-
};
|
|
45
|
-
render(
|
|
46
|
-
<LocaleProvider
|
|
47
|
-
resolver={createStaticLocaleResolver({ locale: "en-US" })}
|
|
48
|
-
fallbackBundles={[kumikoDefaultTranslations]}
|
|
49
|
-
>
|
|
50
|
-
<PrimitivesProvider value={primitives}>
|
|
51
|
-
<RenderEdit
|
|
52
|
-
screen={synthesizeActionFormScreen(screen)}
|
|
53
|
-
entity={synthesizeActionFormEntity(screen.fields)}
|
|
54
|
-
featureName="shop"
|
|
55
|
-
initial={{ dueAt: "" }}
|
|
56
|
-
/>
|
|
57
|
-
</PrimitivesProvider>
|
|
58
|
-
</LocaleProvider>,
|
|
59
|
-
);
|
|
60
|
-
return { labels };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
describe("RenderEdit — ActionFormScreenDefinition.fieldLabels", () => {
|
|
64
|
-
test("a field named in fieldLabels renders the screen's own label", () => {
|
|
65
|
-
const screen: ActionFormScreenDefinition = {
|
|
66
|
-
id: "reschedule",
|
|
67
|
-
type: "actionForm",
|
|
68
|
-
handler: "shop:write:reschedule",
|
|
69
|
-
fields: { dueAt: { type: "date" } },
|
|
70
|
-
fieldLabels: { dueAt: "shop:reschedule.dueAt" },
|
|
71
|
-
layout: { sections: [{ fields: ["dueAt"] }] },
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const { labels } = renderActionFormCapturingFieldLabels(screen);
|
|
75
|
-
|
|
76
|
-
expect(labels["field-dueAt"]).toBe("shop:reschedule.dueAt");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("without fieldLabels, the field renders the default __action-form__ convention label", () => {
|
|
80
|
-
const screen: ActionFormScreenDefinition = {
|
|
81
|
-
id: "reschedule",
|
|
82
|
-
type: "actionForm",
|
|
83
|
-
handler: "shop:write:reschedule",
|
|
84
|
-
fields: { dueAt: { type: "date" } },
|
|
85
|
-
layout: { sections: [{ fields: ["dueAt"] }] },
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const { labels } = renderActionFormCapturingFieldLabels(screen);
|
|
89
|
-
|
|
90
|
-
expect(labels["field-dueAt"]).toBe("shop:entity:__action-form__:field:dueAt");
|
|
91
|
-
});
|
|
92
|
-
});
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
synthesizeActionFormEntity,
|
|
4
|
-
synthesizeActionFormScreen,
|
|
5
|
-
synthesizeSecretMintConfirmScreen,
|
|
6
|
-
} from "../action-form-shim";
|
|
7
|
-
|
|
8
|
-
describe("synthesizeActionFormEntity", () => {
|
|
9
|
-
test("wraps inline fields as minimal EntityDefinition", () => {
|
|
10
|
-
const entity = synthesizeActionFormEntity({
|
|
11
|
-
title: { type: "text" },
|
|
12
|
-
});
|
|
13
|
-
expect(entity.fields["title"]).toEqual({ type: "text" });
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe("synthesizeActionFormScreen", () => {
|
|
18
|
-
test("maps actionForm screen to entityEdit shape", () => {
|
|
19
|
-
const screen = synthesizeActionFormScreen({
|
|
20
|
-
id: "invite-user",
|
|
21
|
-
type: "actionForm",
|
|
22
|
-
handler: "users:write:invite-user",
|
|
23
|
-
layout: { sections: [{ title: "Invite", fields: ["email"] }] },
|
|
24
|
-
fields: { email: { type: "text" } },
|
|
25
|
-
});
|
|
26
|
-
expect(screen.type).toBe("entityEdit");
|
|
27
|
-
expect(screen.entity).toBe("__action-form__");
|
|
28
|
-
expect(screen.layout).toEqual({ sections: [{ title: "Invite", fields: ["email"] }] });
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("carries description through so RenderEdit can render it as the form subtitle (fw#2723)", () => {
|
|
32
|
-
const withDescription = synthesizeActionFormScreen({
|
|
33
|
-
id: "invite-user",
|
|
34
|
-
type: "actionForm",
|
|
35
|
-
description: "Invite a new team member by email.",
|
|
36
|
-
handler: "users:write:invite-user",
|
|
37
|
-
layout: { sections: [{ title: "Invite", fields: ["email"] }] },
|
|
38
|
-
fields: { email: { type: "text" } },
|
|
39
|
-
});
|
|
40
|
-
expect(withDescription.description).toBe("Invite a new team member by email.");
|
|
41
|
-
|
|
42
|
-
const withoutDescription = synthesizeActionFormScreen({
|
|
43
|
-
id: "invite-user",
|
|
44
|
-
type: "actionForm",
|
|
45
|
-
handler: "users:write:invite-user",
|
|
46
|
-
layout: { sections: [{ title: "Invite", fields: ["email"] }] },
|
|
47
|
-
fields: { email: { type: "text" } },
|
|
48
|
-
});
|
|
49
|
-
expect("description" in withoutDescription).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("carries slots through so RenderEdit can mount header/footer slots on an actionForm", () => {
|
|
53
|
-
const withSlots = synthesizeActionFormScreen({
|
|
54
|
-
id: "invite-user",
|
|
55
|
-
type: "actionForm",
|
|
56
|
-
handler: "users:write:invite-user",
|
|
57
|
-
layout: { sections: [{ title: "Invite", fields: ["email"] }] },
|
|
58
|
-
fields: { email: { type: "text" } },
|
|
59
|
-
slots: { footer: { react: { __component: "f" } } },
|
|
60
|
-
});
|
|
61
|
-
expect(withSlots.slots).toEqual({ footer: { react: { __component: "f" } } });
|
|
62
|
-
|
|
63
|
-
const withoutSlots = synthesizeActionFormScreen({
|
|
64
|
-
id: "invite-user",
|
|
65
|
-
type: "actionForm",
|
|
66
|
-
handler: "users:write:invite-user",
|
|
67
|
-
layout: { sections: [{ title: "Invite", fields: ["email"] }] },
|
|
68
|
-
fields: { email: { type: "text" } },
|
|
69
|
-
});
|
|
70
|
-
expect("slots" in withoutSlots).toBe(false);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("carries fieldLabels through so RenderEdit resolves the screen's own label override", () => {
|
|
74
|
-
const withFieldLabels = synthesizeActionFormScreen({
|
|
75
|
-
id: "reschedule",
|
|
76
|
-
type: "actionForm",
|
|
77
|
-
handler: "shop:write:reschedule",
|
|
78
|
-
layout: { sections: [{ title: "Reschedule", fields: ["dueAt"] }] },
|
|
79
|
-
fields: { dueAt: { type: "date" } },
|
|
80
|
-
fieldLabels: { dueAt: "shop:reschedule.dueAt" },
|
|
81
|
-
});
|
|
82
|
-
expect(withFieldLabels.fieldLabels).toEqual({ dueAt: "shop:reschedule.dueAt" });
|
|
83
|
-
|
|
84
|
-
const withoutFieldLabels = synthesizeActionFormScreen({
|
|
85
|
-
id: "reschedule",
|
|
86
|
-
type: "actionForm",
|
|
87
|
-
handler: "shop:write:reschedule",
|
|
88
|
-
layout: { sections: [{ title: "Reschedule", fields: ["dueAt"] }] },
|
|
89
|
-
fields: { dueAt: { type: "date" } },
|
|
90
|
-
});
|
|
91
|
-
expect("fieldLabels" in withoutFieldLabels).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe("synthesizeSecretMintConfirmScreen (fw#2838)", () => {
|
|
96
|
-
test("maps a secretMint's confirm step to the entityEdit shape RenderEdit expects, with a distinct id", () => {
|
|
97
|
-
const screen = synthesizeSecretMintConfirmScreen(
|
|
98
|
-
{
|
|
99
|
-
id: "mint-token",
|
|
100
|
-
type: "secretMint",
|
|
101
|
-
handler: "shop:write:token:mint",
|
|
102
|
-
fields: { label: { type: "text" } },
|
|
103
|
-
layout: { sections: [{ title: "Mint", fields: ["label"] }] },
|
|
104
|
-
reveal: { fields: [{ field: "token", label: "Token" }] },
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
handler: "shop:write:token:confirm",
|
|
108
|
-
fields: { code: { type: "text" } },
|
|
109
|
-
layout: { sections: [{ title: "Confirm", fields: ["code"] }] },
|
|
110
|
-
},
|
|
111
|
-
);
|
|
112
|
-
expect(screen.id).toBe("mint-token:confirm");
|
|
113
|
-
expect(screen.type).toBe("entityEdit");
|
|
114
|
-
expect(screen.entity).toBe("__action-form__");
|
|
115
|
-
expect(screen.layout).toEqual({ sections: [{ title: "Confirm", fields: ["code"] }] });
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("carries the mint screen's access rule through to the confirm form", () => {
|
|
119
|
-
const screen = synthesizeSecretMintConfirmScreen(
|
|
120
|
-
{
|
|
121
|
-
id: "mint-token",
|
|
122
|
-
type: "secretMint",
|
|
123
|
-
handler: "shop:write:token:mint",
|
|
124
|
-
fields: { label: { type: "text" } },
|
|
125
|
-
layout: { sections: [{ title: "Mint", fields: ["label"] }] },
|
|
126
|
-
reveal: { fields: [{ field: "token", label: "Token" }] },
|
|
127
|
-
access: { roles: ["admin"] },
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
handler: "shop:write:token:confirm",
|
|
131
|
-
fields: { code: { type: "text" } },
|
|
132
|
-
layout: { sections: [{ title: "Confirm", fields: ["code"] }] },
|
|
133
|
-
},
|
|
134
|
-
);
|
|
135
|
-
expect(screen.access).toEqual({ roles: ["admin"] });
|
|
136
|
-
});
|
|
137
|
-
});
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
// fw#2165: buildListQueryPayload was extracted out of EntityListBody's
|
|
2
|
-
// queryPayload useMemo so ProjectionListBody could reuse it without
|
|
3
|
-
// duplicating the branch logic. This pins the extracted function against
|
|
4
|
-
// exactly what EntityListBody built before the extraction — the regression
|
|
5
|
-
// floor both list types now sit on.
|
|
6
|
-
import { describe, expect, test } from "bun:test";
|
|
7
|
-
import { buildListQueryPayload } from "../kumiko-screen";
|
|
8
|
-
|
|
9
|
-
const base = {
|
|
10
|
-
limit: 50,
|
|
11
|
-
search: "",
|
|
12
|
-
sort: null,
|
|
13
|
-
usePager: false,
|
|
14
|
-
page: 1,
|
|
15
|
-
useInfinite: false,
|
|
16
|
-
cursor: undefined,
|
|
17
|
-
} as const;
|
|
18
|
-
|
|
19
|
-
describe("buildListQueryPayload", () => {
|
|
20
|
-
test("bare state: only limit, no search/sort/pager/infinite keys", () => {
|
|
21
|
-
expect(buildListQueryPayload(base)).toEqual({ limit: 50 });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("empty search term is omitted, not sent as an empty string", () => {
|
|
25
|
-
expect(buildListQueryPayload({ ...base, search: "" })).toEqual({ limit: 50 });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("non-empty search lands in the payload", () => {
|
|
29
|
-
expect(buildListQueryPayload({ ...base, search: "acme" })).toEqual({
|
|
30
|
-
limit: 50,
|
|
31
|
-
search: "acme",
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("no sort: neither sort nor sortDirection appear", () => {
|
|
36
|
-
expect(buildListQueryPayload({ ...base, sort: null })).toEqual({ limit: 50 });
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("sort carries both field and direction", () => {
|
|
40
|
-
expect(buildListQueryPayload({ ...base, sort: { field: "createdAt", dir: "desc" } })).toEqual({
|
|
41
|
-
limit: 50,
|
|
42
|
-
sort: "createdAt",
|
|
43
|
-
sortDirection: "desc",
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("pager mode, page 1: totalCount is sent, offset is omitted (not offset: 0)", () => {
|
|
48
|
-
expect(buildListQueryPayload({ ...base, usePager: true, page: 1 })).toEqual({
|
|
49
|
-
limit: 50,
|
|
50
|
-
totalCount: true,
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("pager mode, page 3: offset is (page - 1) * limit", () => {
|
|
55
|
-
expect(buildListQueryPayload({ ...base, usePager: true, page: 3 })).toEqual({
|
|
56
|
-
limit: 50,
|
|
57
|
-
offset: 100,
|
|
58
|
-
totalCount: true,
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("infinite mode without a cursor yet: no cursor key", () => {
|
|
63
|
-
expect(buildListQueryPayload({ ...base, useInfinite: true, cursor: undefined })).toEqual({
|
|
64
|
-
limit: 50,
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("infinite mode with a cursor: cursor lands in the payload", () => {
|
|
69
|
-
expect(buildListQueryPayload({ ...base, useInfinite: true, cursor: "row-42" })).toEqual({
|
|
70
|
-
limit: 50,
|
|
71
|
-
cursor: "row-42",
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("pager wins over infinite when both flags are true (usePager gates first)", () => {
|
|
76
|
-
expect(
|
|
77
|
-
buildListQueryPayload({ ...base, usePager: true, useInfinite: true, cursor: "row-42" }),
|
|
78
|
-
).toEqual({ limit: 50, totalCount: true });
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("pagination=false (neither pager nor infinite): no pagination keys at all", () => {
|
|
82
|
-
expect(
|
|
83
|
-
buildListQueryPayload({ ...base, usePager: false, useInfinite: false, cursor: "row-42" }),
|
|
84
|
-
).toEqual({ limit: 50 });
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("full combination: search + sort + pager", () => {
|
|
88
|
-
expect(
|
|
89
|
-
buildListQueryPayload({
|
|
90
|
-
limit: 25,
|
|
91
|
-
search: "acme",
|
|
92
|
-
sort: { field: "name", dir: "asc" },
|
|
93
|
-
usePager: true,
|
|
94
|
-
page: 2,
|
|
95
|
-
useInfinite: false,
|
|
96
|
-
cursor: undefined,
|
|
97
|
-
}),
|
|
98
|
-
).toEqual({
|
|
99
|
-
limit: 25,
|
|
100
|
-
search: "acme",
|
|
101
|
-
sort: "name",
|
|
102
|
-
sortDirection: "asc",
|
|
103
|
-
offset: 25,
|
|
104
|
-
totalCount: true,
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import type { ConfigEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
3
|
-
import { synthesizeConfigEditEntity, synthesizeConfigEditScreen } from "../config-edit-shim";
|
|
4
|
-
|
|
5
|
-
describe("synthesizeConfigEditEntity", () => {
|
|
6
|
-
test("wraps the inline fields as an EntityDefinition", () => {
|
|
7
|
-
const fields = { apiKey: { type: "text" } } as unknown as ConfigEditScreenDefinition["fields"];
|
|
8
|
-
expect(synthesizeConfigEditEntity(fields)).toEqual({ fields });
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
describe("synthesizeConfigEditScreen", () => {
|
|
13
|
-
test("stamps type: entityEdit and a pseudo entity name", () => {
|
|
14
|
-
const screen = {
|
|
15
|
-
id: "settings",
|
|
16
|
-
layout: { sections: [] },
|
|
17
|
-
} as unknown as ConfigEditScreenDefinition;
|
|
18
|
-
|
|
19
|
-
const result = synthesizeConfigEditScreen(screen);
|
|
20
|
-
expect(result.id).toBe("settings");
|
|
21
|
-
expect(result.type).toBe("entityEdit");
|
|
22
|
-
expect(result.entity).toBe("__config-edit__");
|
|
23
|
-
expect(result.layout).toBe(screen.layout);
|
|
24
|
-
expect(result).not.toHaveProperty("fieldLabels");
|
|
25
|
-
expect(result).not.toHaveProperty("access");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("carries fieldLabels and access through when present", () => {
|
|
29
|
-
const screen = {
|
|
30
|
-
id: "settings",
|
|
31
|
-
layout: { sections: [] },
|
|
32
|
-
fieldLabels: { apiKey: "config.apiKey" },
|
|
33
|
-
access: { roles: ["Admin"] },
|
|
34
|
-
} as unknown as ConfigEditScreenDefinition;
|
|
35
|
-
|
|
36
|
-
const result = synthesizeConfigEditScreen(screen);
|
|
37
|
-
expect(result.fieldLabels).toEqual({ apiKey: "config.apiKey" });
|
|
38
|
-
expect(result.access).toEqual({ roles: ["Admin"] });
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("carries description through so RenderEdit can render it as the form subtitle", () => {
|
|
42
|
-
const withDescription = {
|
|
43
|
-
id: "settings",
|
|
44
|
-
layout: { sections: [] },
|
|
45
|
-
description: "These settings apply to every project in the workspace.",
|
|
46
|
-
} as unknown as ConfigEditScreenDefinition;
|
|
47
|
-
expect(synthesizeConfigEditScreen(withDescription).description).toBe(
|
|
48
|
-
"These settings apply to every project in the workspace.",
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const withoutDescription = {
|
|
52
|
-
id: "settings",
|
|
53
|
-
layout: { sections: [] },
|
|
54
|
-
} as unknown as ConfigEditScreenDefinition;
|
|
55
|
-
expect(synthesizeConfigEditScreen(withoutDescription)).not.toHaveProperty("description");
|
|
56
|
-
});
|
|
57
|
-
});
|