@cosmicdrift/kumiko-renderer 0.328.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 -46
- 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 -41
- 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/{src/inside-drawer.ts → dist/inside-drawer.js} +2 -5
- package/dist/page-header-slot.d.ts +2 -0
- package/{src/page-header-slot.ts → dist/page-header-slot.js} +2 -5
- 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/__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-fill-height.test.tsx +0 -118
- 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 -128
- 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 -3801
- 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 -86
- package/src/app/reference-facet-bridge.tsx +0 -58
- package/src/app/return-to.tsx +0 -234
- package/src/app/row-actions.ts +0 -640
- 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 -1733
- 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 -88
- 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 -491
- package/src/components/render-edit-action-button.tsx +0 -109
- package/src/components/render-edit-logic.ts +0 -85
- package/src/components/render-edit-types.ts +0 -226
- package/src/components/render-edit.tsx +0 -1871
- package/src/components/render-field.tsx +0 -951
- package/src/components/render-list.tsx +0 -593
- package/src/components/write-form-section.tsx +0 -198
- 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-query.test.tsx +0 -149
- 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 -207
- package/src/hooks/use-mutation.ts +0 -61
- package/src/hooks/use-query.ts +0 -166
- 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 -240
- package/src/i18n.tsx +0 -286
- package/src/index.ts +0 -279
- package/src/primitives.tsx +0 -1567
- package/src/screen-fills-height.ts +0 -3
- 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,152 +0,0 @@
|
|
|
1
|
-
// fw#2216: a projectionList query handler that doesn't honor the PagedRows
|
|
2
|
-
// contract ({ rows, nextCursor }) used to fall through `rowsQuery.data?.rows
|
|
3
|
-
// ?? []` and silently render an empty table with HTTP 200 (prod bug on
|
|
4
|
-
// /session-list). This renders the real path (KumikoScreen →
|
|
5
|
-
// ProjectionListBody → RenderList) under a stub dispatcher returning a bare
|
|
6
|
-
// array, and proves the renderer-level shape guard surfaces an error banner
|
|
7
|
-
// instead of an empty list — plus that a correctly-shaped response still
|
|
8
|
-
// renders rows.
|
|
9
|
-
|
|
10
|
-
import { describe, expect, test } from "bun:test";
|
|
11
|
-
import type { ProjectionListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
12
|
-
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
13
|
-
import { render, waitFor } from "@testing-library/react";
|
|
14
|
-
import type { ComponentType, ReactNode } from "react";
|
|
15
|
-
import { DispatcherProvider } from "../../context/dispatcher-context";
|
|
16
|
-
import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
|
|
17
|
-
import { kumikoDefaultTranslations } from "../../i18n-defaults";
|
|
18
|
-
import {
|
|
19
|
-
type BannerProps,
|
|
20
|
-
type CorePrimitives,
|
|
21
|
-
type DataTableProps,
|
|
22
|
-
PrimitivesProvider,
|
|
23
|
-
} from "../../primitives";
|
|
24
|
-
import type { FeatureSchema } from "../feature-schema";
|
|
25
|
-
import { KumikoScreen } from "../kumiko-screen";
|
|
26
|
-
import type { NavApi } from "../nav";
|
|
27
|
-
import { NavProvider } from "../nav";
|
|
28
|
-
|
|
29
|
-
let capturedProps: DataTableProps | undefined;
|
|
30
|
-
const captureDataTable: ComponentType<DataTableProps> = (props) => {
|
|
31
|
-
capturedProps = props;
|
|
32
|
-
return null;
|
|
33
|
-
};
|
|
34
|
-
const getCapturedProps = (): DataTableProps | undefined => capturedProps;
|
|
35
|
-
const noop = (): ReactNode => null;
|
|
36
|
-
const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
|
|
37
|
-
|
|
38
|
-
// passChildren would drop testId — the bad-shape test asserts on which
|
|
39
|
-
// banner rendered, so it needs a real (if minimal) wrapper element.
|
|
40
|
-
const TestBanner: ComponentType<BannerProps> = ({ children, testId }) => (
|
|
41
|
-
<div data-testid={testId}>{children}</div>
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const testPrimitives: CorePrimitives = {
|
|
45
|
-
Button: noop,
|
|
46
|
-
Banner: TestBanner,
|
|
47
|
-
Field: passChildren,
|
|
48
|
-
Input: noop,
|
|
49
|
-
DataTable: captureDataTable,
|
|
50
|
-
Form: passChildren,
|
|
51
|
-
Section: passChildren,
|
|
52
|
-
Card: passChildren,
|
|
53
|
-
Grid: passChildren,
|
|
54
|
-
GridCell: passChildren,
|
|
55
|
-
Text: passChildren,
|
|
56
|
-
Heading: noop,
|
|
57
|
-
Dialog: noop,
|
|
58
|
-
Modal: noop,
|
|
59
|
-
Lightbox: noop,
|
|
60
|
-
ConfigSourceBadge: noop,
|
|
61
|
-
ConfigCascadeView: noop,
|
|
62
|
-
Link: noop,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
function stubDispatcher(queryResult: unknown): Dispatcher {
|
|
66
|
-
return {
|
|
67
|
-
write: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["write"],
|
|
68
|
-
query: (async () => ({ isSuccess: true, data: queryResult })) as unknown as Dispatcher["query"],
|
|
69
|
-
batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
|
|
70
|
-
statusStore: {
|
|
71
|
-
getState: () => "online",
|
|
72
|
-
subscribe: () => () => {},
|
|
73
|
-
} as unknown as Dispatcher["statusStore"],
|
|
74
|
-
async *stream() {},
|
|
75
|
-
pendingWrites: () => [],
|
|
76
|
-
pendingFiles: () => [],
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function buildSchema(screen: ProjectionListScreenDefinition): FeatureSchema {
|
|
81
|
-
return {
|
|
82
|
-
featureName: "ledger",
|
|
83
|
-
entities: {},
|
|
84
|
-
screens: [screen],
|
|
85
|
-
} as FeatureSchema;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const nav: NavApi = {
|
|
89
|
-
route: { screenId: "ledger:screen:schedule-list" },
|
|
90
|
-
navigate: () => {},
|
|
91
|
-
replace: () => {},
|
|
92
|
-
hrefFor: () => "",
|
|
93
|
-
searchParams: {},
|
|
94
|
-
setSearchParams: () => {},
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
function renderProjectionList(
|
|
98
|
-
screen: ProjectionListScreenDefinition,
|
|
99
|
-
queryResult: unknown,
|
|
100
|
-
): ReturnType<typeof render> {
|
|
101
|
-
return render(
|
|
102
|
-
<LocaleProvider
|
|
103
|
-
resolver={createStaticLocaleResolver({ locale: "de-DE" })}
|
|
104
|
-
fallbackBundles={[kumikoDefaultTranslations]}
|
|
105
|
-
>
|
|
106
|
-
<DispatcherProvider dispatcher={stubDispatcher(queryResult)}>
|
|
107
|
-
<NavProvider value={nav}>
|
|
108
|
-
<PrimitivesProvider value={testPrimitives}>
|
|
109
|
-
<KumikoScreen schema={buildSchema(screen)} qn="ledger:screen:schedule-list" />
|
|
110
|
-
</PrimitivesProvider>
|
|
111
|
-
</NavProvider>
|
|
112
|
-
</DispatcherProvider>
|
|
113
|
-
</LocaleProvider>,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const screen: ProjectionListScreenDefinition = {
|
|
118
|
-
id: "schedule-list",
|
|
119
|
-
type: "projectionList",
|
|
120
|
-
query: "ledger:query:schedule:list",
|
|
121
|
-
columns: ["description"],
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
describe("ProjectionListBody — PagedRows shape guard (fw#2216)", () => {
|
|
125
|
-
test("a bare-array query response renders the bad-shape error banner, not an empty table", async () => {
|
|
126
|
-
capturedProps = undefined;
|
|
127
|
-
const { container } = renderProjectionList(screen, [{ id: "1", description: "acme" }]);
|
|
128
|
-
|
|
129
|
-
const banner = await waitFor(() => {
|
|
130
|
-
const el = container.querySelector('[data-testid="kumiko-screen-projection-list-bad-shape"]');
|
|
131
|
-
if (el === null) throw new Error("bad-shape banner not rendered");
|
|
132
|
-
return el;
|
|
133
|
-
});
|
|
134
|
-
expect(banner.textContent).toContain("schedule-list");
|
|
135
|
-
expect(banner.textContent).toContain("ledger:query:schedule:list");
|
|
136
|
-
expect(getCapturedProps()).toBeUndefined();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test("a correct { rows, nextCursor } response renders the rows, no error banner", async () => {
|
|
140
|
-
capturedProps = undefined;
|
|
141
|
-
const { container } = renderProjectionList(screen, {
|
|
142
|
-
rows: [{ id: "1", description: "acme" }],
|
|
143
|
-
nextCursor: null,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
await waitFor(() => expect(getCapturedProps()).toBeDefined());
|
|
147
|
-
expect(getCapturedProps()?.rows).toHaveLength(1);
|
|
148
|
-
expect(
|
|
149
|
-
container.querySelector('[data-testid="kumiko-screen-projection-list-bad-shape"]'),
|
|
150
|
-
).toBeNull();
|
|
151
|
-
});
|
|
152
|
-
});
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
// fw#2662: projectionList columns have no EntityDefinition to carry a real
|
|
2
|
-
// "reference" field type (the shim hardcodes every field as "text"), so a
|
|
3
|
-
// declared reference column used to render the raw GUID. This renders the
|
|
4
|
-
// real path (KumikoScreen -> ProjectionListScreen -> RenderList ->
|
|
5
|
-
// useReferenceLookup) under a stub dispatcher, and reads the resolved cell
|
|
6
|
-
// text back off the reference column's injected runtime renderer.
|
|
7
|
-
|
|
8
|
-
import { describe, expect, test } from "bun:test";
|
|
9
|
-
import type {
|
|
10
|
-
ListColumnSpec,
|
|
11
|
-
ProjectionListScreenDefinition,
|
|
12
|
-
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
13
|
-
import type { Dispatcher, RuntimeRenderer } from "@cosmicdrift/kumiko-headless";
|
|
14
|
-
import { render, waitFor } from "@testing-library/react";
|
|
15
|
-
import type { ComponentType, ReactNode } from "react";
|
|
16
|
-
import { DispatcherProvider } from "../../context/dispatcher-context";
|
|
17
|
-
import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
|
|
18
|
-
import { kumikoDefaultTranslations } from "../../i18n-defaults";
|
|
19
|
-
import { type CorePrimitives, type DataTableProps, PrimitivesProvider } from "../../primitives";
|
|
20
|
-
import type { FeatureSchema } from "../feature-schema";
|
|
21
|
-
import { KumikoScreen } from "../kumiko-screen";
|
|
22
|
-
import type { NavApi } from "../nav";
|
|
23
|
-
import { NavProvider } from "../nav";
|
|
24
|
-
|
|
25
|
-
const SYSTEM_TENANT_ID = "00000000-0000-4000-8000-000000000000";
|
|
26
|
-
const REAL_TENANT_ID = "11111111-1111-4111-8111-111111111111";
|
|
27
|
-
const SYSTEM_USER_ID = "00000000-0000-0000-0000-000000000000";
|
|
28
|
-
const REAL_USER_ID = "22222222-2222-4222-8222-222222222222";
|
|
29
|
-
const DELETED_USER_ID = "33333333-3333-4333-8333-333333333333";
|
|
30
|
-
|
|
31
|
-
let capturedProps: DataTableProps | undefined;
|
|
32
|
-
const captureDataTable: ComponentType<DataTableProps> = (props) => {
|
|
33
|
-
capturedProps = props;
|
|
34
|
-
return null;
|
|
35
|
-
};
|
|
36
|
-
const getCapturedProps = (): DataTableProps | undefined => capturedProps;
|
|
37
|
-
const noop = (): ReactNode => null;
|
|
38
|
-
const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
|
|
39
|
-
|
|
40
|
-
const testPrimitives: CorePrimitives = {
|
|
41
|
-
Button: noop,
|
|
42
|
-
Banner: passChildren,
|
|
43
|
-
Field: passChildren,
|
|
44
|
-
Input: noop,
|
|
45
|
-
DataTable: captureDataTable,
|
|
46
|
-
Form: passChildren,
|
|
47
|
-
Section: passChildren,
|
|
48
|
-
Card: passChildren,
|
|
49
|
-
Grid: passChildren,
|
|
50
|
-
GridCell: passChildren,
|
|
51
|
-
Text: passChildren,
|
|
52
|
-
Heading: noop,
|
|
53
|
-
Dialog: noop,
|
|
54
|
-
Modal: noop,
|
|
55
|
-
Lightbox: noop,
|
|
56
|
-
ConfigSourceBadge: noop,
|
|
57
|
-
ConfigCascadeView: noop,
|
|
58
|
-
Link: noop,
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
function stubDispatcher(): Dispatcher {
|
|
62
|
-
return {
|
|
63
|
-
write: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["write"],
|
|
64
|
-
query: (async (type: string) => {
|
|
65
|
-
if (type === "delivery:query:log:list") {
|
|
66
|
-
return {
|
|
67
|
-
isSuccess: true,
|
|
68
|
-
data: {
|
|
69
|
-
rows: [
|
|
70
|
-
{ id: "row-1", tenantId: REAL_TENANT_ID, createdBy: REAL_USER_ID },
|
|
71
|
-
{ id: "row-2", tenantId: SYSTEM_TENANT_ID, createdBy: SYSTEM_USER_ID },
|
|
72
|
-
{ id: "row-3", tenantId: REAL_TENANT_ID, createdBy: DELETED_USER_ID },
|
|
73
|
-
],
|
|
74
|
-
nextCursor: null,
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
// tenant:tenant resolves through the tenant directory, not the
|
|
79
|
-
// SystemAdmin-only tenant:query:tenant:list a TenantAdmin can't call
|
|
80
|
-
// (fw#3142).
|
|
81
|
-
if (type === "tenant:query:tenant-directory") {
|
|
82
|
-
return {
|
|
83
|
-
isSuccess: true,
|
|
84
|
-
data: { rows: [{ id: REAL_TENANT_ID, label: "Acme Inc" }], nextCursor: null },
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
// user:user resolves through the tenant-scoped member directory, not the
|
|
88
|
-
// SystemAdmin-only user:query:user:list a TenantAdmin can't call (fw#3107).
|
|
89
|
-
if (type === "tenant:query:member-directory") {
|
|
90
|
-
return {
|
|
91
|
-
isSuccess: true,
|
|
92
|
-
data: { rows: [{ id: REAL_USER_ID, label: "Ada Lovelace" }], nextCursor: null },
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return { isSuccess: true, data: { rows: [], nextCursor: null } };
|
|
96
|
-
}) as unknown as Dispatcher["query"],
|
|
97
|
-
batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
|
|
98
|
-
statusStore: {
|
|
99
|
-
getState: () => "online",
|
|
100
|
-
subscribe: () => () => {},
|
|
101
|
-
} as unknown as Dispatcher["statusStore"],
|
|
102
|
-
async *stream() {},
|
|
103
|
-
pendingWrites: () => [],
|
|
104
|
-
pendingFiles: () => [],
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function buildSchema(screen: ProjectionListScreenDefinition): FeatureSchema {
|
|
109
|
-
return {
|
|
110
|
-
featureName: "delivery",
|
|
111
|
-
entities: {},
|
|
112
|
-
screens: [screen],
|
|
113
|
-
} as FeatureSchema;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const TENANT_COLUMN: ListColumnSpec = {
|
|
117
|
-
field: "tenantId",
|
|
118
|
-
label: "delivery.log.col.tenantId",
|
|
119
|
-
refEntity: "tenant:tenant",
|
|
120
|
-
refLabelField: "name",
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
// Mirrors the audit-log actor column (fw#3103).
|
|
124
|
-
const ACTOR_COLUMN: ListColumnSpec = {
|
|
125
|
-
field: "createdBy",
|
|
126
|
-
label: "audit.log.col.actor",
|
|
127
|
-
refEntity: "user:user",
|
|
128
|
-
refLabelField: "displayName",
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const staticNav: NavApi = {
|
|
132
|
-
route: { screenId: "delivery:screen:log" },
|
|
133
|
-
navigate: () => {},
|
|
134
|
-
replace: () => {},
|
|
135
|
-
hrefFor: () => "",
|
|
136
|
-
searchParams: {},
|
|
137
|
-
setSearchParams: () => {},
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
function renderLogScreen(column: ListColumnSpec = TENANT_COLUMN): void {
|
|
141
|
-
const screen: ProjectionListScreenDefinition = {
|
|
142
|
-
id: "log",
|
|
143
|
-
type: "projectionList",
|
|
144
|
-
query: "delivery:query:log:list",
|
|
145
|
-
columns: [column],
|
|
146
|
-
};
|
|
147
|
-
render(
|
|
148
|
-
<LocaleProvider
|
|
149
|
-
resolver={createStaticLocaleResolver({ locale: "en" })}
|
|
150
|
-
fallbackBundles={[kumikoDefaultTranslations]}
|
|
151
|
-
>
|
|
152
|
-
<DispatcherProvider dispatcher={stubDispatcher()}>
|
|
153
|
-
<NavProvider value={staticNav}>
|
|
154
|
-
<PrimitivesProvider value={testPrimitives}>
|
|
155
|
-
<KumikoScreen schema={buildSchema(screen)} qn="delivery:screen:log" />
|
|
156
|
-
</PrimitivesProvider>
|
|
157
|
-
</NavProvider>
|
|
158
|
-
</DispatcherProvider>
|
|
159
|
-
</LocaleProvider>,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function referenceRenderer(field = "tenantId"): RuntimeRenderer {
|
|
164
|
-
const col = getCapturedProps()?.columns.find((c) => c.field === field);
|
|
165
|
-
if (typeof col?.renderer !== "function") throw new Error("reference column has no renderer yet");
|
|
166
|
-
return col.renderer as RuntimeRenderer;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
describe("projectionList reference column resolves labels (fw#2662)", () => {
|
|
170
|
-
test("a real tenant id resolves to its name instead of the raw GUID", async () => {
|
|
171
|
-
capturedProps = undefined;
|
|
172
|
-
renderLogScreen();
|
|
173
|
-
|
|
174
|
-
await waitFor(() => {
|
|
175
|
-
expect(referenceRenderer()(REAL_TENANT_ID, { tenantId: REAL_TENANT_ID })).toBe("Acme Inc");
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test("SYSTEM_TENANT_ID resolves to the system label instead of the raw GUID", async () => {
|
|
180
|
-
capturedProps = undefined;
|
|
181
|
-
renderLogScreen();
|
|
182
|
-
|
|
183
|
-
await waitFor(() => {
|
|
184
|
-
expect(referenceRenderer()(SYSTEM_TENANT_ID, { tenantId: SYSTEM_TENANT_ID })).toBe("System");
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe("actor column resolves display names (fw#3103)", () => {
|
|
190
|
-
const actorCell = (id: string): unknown => referenceRenderer("createdBy")(id, { createdBy: id });
|
|
191
|
-
|
|
192
|
-
test("a real user id resolves to the display name", async () => {
|
|
193
|
-
capturedProps = undefined;
|
|
194
|
-
renderLogScreen(ACTOR_COLUMN);
|
|
195
|
-
|
|
196
|
-
await waitFor(() => {
|
|
197
|
-
expect(actorCell(REAL_USER_ID)).toBe("Ada Lovelace");
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("SYSTEM_USER_ID resolves to the system label", async () => {
|
|
202
|
-
capturedProps = undefined;
|
|
203
|
-
renderLogScreen(ACTOR_COLUMN);
|
|
204
|
-
|
|
205
|
-
await waitFor(() => {
|
|
206
|
-
expect(actorCell(SYSTEM_USER_ID)).toBe("System");
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test("an unresolvable actor (deleted user) falls back to the raw id without throwing", async () => {
|
|
211
|
-
capturedProps = undefined;
|
|
212
|
-
renderLogScreen(ACTOR_COLUMN);
|
|
213
|
-
|
|
214
|
-
await waitFor(() => {
|
|
215
|
-
expect(actorCell(REAL_USER_ID)).toBe("Ada Lovelace");
|
|
216
|
-
});
|
|
217
|
-
expect(actorCell(DELETED_USER_ID)).toBe(DELETED_USER_ID);
|
|
218
|
-
});
|
|
219
|
-
});
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
// fw#2165: ProjectionListBody used to fetch with a hardcoded empty payload —
|
|
2
|
-
// search/sort/pagination were rendered but had no effect. This renders the
|
|
3
|
-
// real path (KumikoScreen → ProjectionListScreen → ProjectionListBody →
|
|
4
|
-
// RenderList) under a stub dispatcher that records every query() call, and a
|
|
5
|
-
// stateful NavProvider so setSearchParams actually re-renders — proving the
|
|
6
|
-
// URL-state → payload wiring for the two capabilities buildAppSchema derives
|
|
7
|
-
// per-screen (searchable, sortable).
|
|
8
|
-
|
|
9
|
-
import { describe, expect, test } from "bun:test";
|
|
10
|
-
import type { ProjectionListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
11
|
-
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
12
|
-
import { act, render, waitFor } from "@testing-library/react";
|
|
13
|
-
import { type ComponentType, type ReactNode, useState } from "react";
|
|
14
|
-
import { DispatcherProvider } from "../../context/dispatcher-context";
|
|
15
|
-
import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
|
|
16
|
-
import { kumikoDefaultTranslations } from "../../i18n-defaults";
|
|
17
|
-
import { type CorePrimitives, type DataTableProps, PrimitivesProvider } from "../../primitives";
|
|
18
|
-
import type { FeatureSchema } from "../feature-schema";
|
|
19
|
-
import { KumikoScreen } from "../kumiko-screen";
|
|
20
|
-
import type { NavApi } from "../nav";
|
|
21
|
-
import { NavProvider } from "../nav";
|
|
22
|
-
|
|
23
|
-
let capturedProps: DataTableProps | undefined;
|
|
24
|
-
const captureDataTable: ComponentType<DataTableProps> = (props) => {
|
|
25
|
-
capturedProps = props;
|
|
26
|
-
return null;
|
|
27
|
-
};
|
|
28
|
-
// Indirection defeats TS narrowing `capturedProps` to `undefined` at read
|
|
29
|
-
// sites — the compiler can't see that `captureDataTable` (a React render
|
|
30
|
-
// callback) reassigns it between the reset and the read.
|
|
31
|
-
const getCapturedProps = (): DataTableProps | undefined => capturedProps;
|
|
32
|
-
const noop = (): ReactNode => null;
|
|
33
|
-
const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
|
|
34
|
-
|
|
35
|
-
const testPrimitives: CorePrimitives = {
|
|
36
|
-
Button: noop,
|
|
37
|
-
Banner: passChildren,
|
|
38
|
-
Field: passChildren,
|
|
39
|
-
Input: noop,
|
|
40
|
-
DataTable: captureDataTable,
|
|
41
|
-
Form: passChildren,
|
|
42
|
-
Section: passChildren,
|
|
43
|
-
Card: passChildren,
|
|
44
|
-
Grid: passChildren,
|
|
45
|
-
GridCell: passChildren,
|
|
46
|
-
Text: passChildren,
|
|
47
|
-
Heading: noop,
|
|
48
|
-
Dialog: noop,
|
|
49
|
-
Modal: noop,
|
|
50
|
-
Lightbox: noop,
|
|
51
|
-
ConfigSourceBadge: noop,
|
|
52
|
-
ConfigCascadeView: noop,
|
|
53
|
-
Link: noop,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
let queryCalls: Array<{ readonly type: string; readonly payload: unknown }> = [];
|
|
57
|
-
|
|
58
|
-
function stubDispatcher(): Dispatcher {
|
|
59
|
-
return {
|
|
60
|
-
write: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["write"],
|
|
61
|
-
query: (async (type: string, payload: unknown) => {
|
|
62
|
-
queryCalls.push({ type, payload });
|
|
63
|
-
return { isSuccess: true, data: { rows: [], nextCursor: null } };
|
|
64
|
-
}) as unknown as Dispatcher["query"],
|
|
65
|
-
batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
|
|
66
|
-
statusStore: {
|
|
67
|
-
getState: () => "online",
|
|
68
|
-
subscribe: () => () => {},
|
|
69
|
-
} as unknown as Dispatcher["statusStore"],
|
|
70
|
-
async *stream() {},
|
|
71
|
-
pendingWrites: () => [],
|
|
72
|
-
pendingFiles: () => [],
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function buildSchema(screen: ProjectionListScreenDefinition): FeatureSchema {
|
|
77
|
-
return {
|
|
78
|
-
featureName: "ledger",
|
|
79
|
-
entities: {},
|
|
80
|
-
screens: [screen],
|
|
81
|
-
} as FeatureSchema;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Stateful nav so setSearchParams (called by useListUrlState) actually
|
|
85
|
-
// re-renders the tree — a plain mock object wouldn't trigger React.
|
|
86
|
-
function StatefulNav({
|
|
87
|
-
initialParams,
|
|
88
|
-
children,
|
|
89
|
-
}: {
|
|
90
|
-
readonly initialParams: Record<string, string>;
|
|
91
|
-
readonly children: ReactNode;
|
|
92
|
-
}): ReactNode {
|
|
93
|
-
const [params, setParams] = useState<Record<string, string>>(initialParams);
|
|
94
|
-
const value: NavApi = {
|
|
95
|
-
route: { screenId: "ledger:screen:schedule-list" },
|
|
96
|
-
navigate: () => {},
|
|
97
|
-
replace: () => {},
|
|
98
|
-
hrefFor: () => "",
|
|
99
|
-
searchParams: params,
|
|
100
|
-
setSearchParams: (updates) => {
|
|
101
|
-
setParams((prev) => {
|
|
102
|
-
const next = { ...prev };
|
|
103
|
-
for (const [k, v] of Object.entries(updates)) {
|
|
104
|
-
if (v === null) delete next[k];
|
|
105
|
-
else next[k] = v;
|
|
106
|
-
}
|
|
107
|
-
return next;
|
|
108
|
-
});
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
return <NavProvider value={value}>{children}</NavProvider>;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function renderProjectionList(
|
|
115
|
-
screen: ProjectionListScreenDefinition,
|
|
116
|
-
initialParams: Record<string, string> = {},
|
|
117
|
-
): void {
|
|
118
|
-
render(
|
|
119
|
-
<LocaleProvider
|
|
120
|
-
resolver={createStaticLocaleResolver({ locale: "de-DE" })}
|
|
121
|
-
fallbackBundles={[kumikoDefaultTranslations]}
|
|
122
|
-
>
|
|
123
|
-
<DispatcherProvider dispatcher={stubDispatcher()}>
|
|
124
|
-
<StatefulNav initialParams={initialParams}>
|
|
125
|
-
<PrimitivesProvider value={testPrimitives}>
|
|
126
|
-
<KumikoScreen schema={buildSchema(screen)} qn="ledger:screen:schedule-list" />
|
|
127
|
-
</PrimitivesProvider>
|
|
128
|
-
</StatefulNav>
|
|
129
|
-
</DispatcherProvider>
|
|
130
|
-
</LocaleProvider>,
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
describe("ProjectionListBody — search/sort capability wiring (fw#2165)", () => {
|
|
135
|
-
test("search-capable screen: a URL search term lands in the query payload", async () => {
|
|
136
|
-
queryCalls = [];
|
|
137
|
-
renderProjectionList(
|
|
138
|
-
{
|
|
139
|
-
id: "schedule-list",
|
|
140
|
-
type: "projectionList",
|
|
141
|
-
query: "ledger:query:schedule:list",
|
|
142
|
-
columns: ["description"],
|
|
143
|
-
searchable: true,
|
|
144
|
-
},
|
|
145
|
-
{ "schedule-list.q": "acme" },
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
await waitFor(() => expect(queryCalls.length).toBeGreaterThan(0));
|
|
149
|
-
expect(queryCalls[0]?.payload).toMatchObject({ search: "acme" });
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("non-search-capable screen: the same URL search term is NOT sent (schema doesn't accept it)", async () => {
|
|
153
|
-
queryCalls = [];
|
|
154
|
-
renderProjectionList(
|
|
155
|
-
{
|
|
156
|
-
id: "schedule-list",
|
|
157
|
-
type: "projectionList",
|
|
158
|
-
query: "ledger:query:schedule:list",
|
|
159
|
-
columns: ["description"],
|
|
160
|
-
searchable: false,
|
|
161
|
-
},
|
|
162
|
-
{ "schedule-list.q": "acme" },
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
await waitFor(() => expect(queryCalls.length).toBeGreaterThan(0));
|
|
166
|
-
expect(queryCalls[0]?.payload).not.toHaveProperty("search");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("sort-capable screen: the column is rendered sortable, and a header click updates state and payload", async () => {
|
|
170
|
-
queryCalls = [];
|
|
171
|
-
capturedProps = undefined;
|
|
172
|
-
renderProjectionList({
|
|
173
|
-
id: "schedule-list",
|
|
174
|
-
type: "projectionList",
|
|
175
|
-
query: "ledger:query:schedule:list",
|
|
176
|
-
columns: ["description"],
|
|
177
|
-
sortable: true,
|
|
178
|
-
defaultSort: { field: "description", dir: "asc" },
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
await waitFor(() => expect(capturedProps).toBeDefined());
|
|
182
|
-
const sortableProps = getCapturedProps();
|
|
183
|
-
if (sortableProps === undefined) throw new Error("DataTable was not rendered");
|
|
184
|
-
// Column-click affordance is only wired when the query schema accepts
|
|
185
|
-
// sort — DefaultDataTable gates the header click on col.sortable.
|
|
186
|
-
expect(sortableProps.columns.find((c) => c.field === "description")?.sortable).toBe(true);
|
|
187
|
-
|
|
188
|
-
const countBeforeClick = queryCalls.length;
|
|
189
|
-
await act(async () => {
|
|
190
|
-
capturedProps?.onSortChange?.({ field: "description", dir: "desc" });
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
await waitFor(() => expect(queryCalls.length).toBeGreaterThan(countBeforeClick));
|
|
194
|
-
const lastPayload = queryCalls[queryCalls.length - 1]?.payload;
|
|
195
|
-
expect(lastPayload).toMatchObject({ sort: "description", sortDirection: "desc" });
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("non-sort-capable screen: the column is rendered NOT sortable", async () => {
|
|
199
|
-
capturedProps = undefined;
|
|
200
|
-
renderProjectionList({
|
|
201
|
-
id: "schedule-list",
|
|
202
|
-
type: "projectionList",
|
|
203
|
-
query: "ledger:query:schedule:list",
|
|
204
|
-
columns: ["description"],
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
await waitFor(() => expect(capturedProps).toBeDefined());
|
|
208
|
-
const notSortableProps = getCapturedProps();
|
|
209
|
-
if (notSortableProps === undefined) throw new Error("DataTable was not rendered");
|
|
210
|
-
expect(notSortableProps.columns.find((c) => c.field === "description")?.sortable).toBe(false);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// Qualified-Name Pure-Logik Tests (Phase 1, test-luecken-integration, Tier 1).
|
|
2
|
-
//
|
|
3
|
-
// lastSegment (qn.ts) ist die Inverse von qualifyScreenId/qualifyNavId
|
|
4
|
-
// (kumiko-screen.tsx) — Schema speichert QN-Form, der Renderer/die URL
|
|
5
|
-
// nutzt Short-Form. Roundtrip pinnt diese Symmetrie.
|
|
6
|
-
|
|
7
|
-
import { describe, expect, test } from "bun:test";
|
|
8
|
-
import { qualifyNavId, qualifyScreenId } from "../kumiko-screen";
|
|
9
|
-
import { lastSegment } from "../qn";
|
|
10
|
-
|
|
11
|
-
describe("lastSegment", () => {
|
|
12
|
-
test("nimmt den letzten ':'-getrennten Teil", () => {
|
|
13
|
-
expect(lastSegment("tasks:screen:task-list")).toBe("task-list");
|
|
14
|
-
expect(lastSegment("a:b")).toBe("b");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("String ohne ':' bleibt unverändert (Short-Form passt durch)", () => {
|
|
18
|
-
expect(lastSegment("task-list")).toBe("task-list");
|
|
19
|
-
expect(lastSegment("")).toBe("");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("trailing ':' → leerer Suffix", () => {
|
|
23
|
-
expect(lastSegment("a:")).toBe("");
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe("qualifyScreenId / qualifyNavId", () => {
|
|
28
|
-
test("baut featureName:screen:id bzw. featureName:nav:id", () => {
|
|
29
|
-
expect(qualifyScreenId("tasks", "task-list")).toBe("tasks:screen:task-list");
|
|
30
|
-
expect(qualifyNavId("tasks", "main")).toBe("tasks:nav:main");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("lastSegment ist die Inverse von qualify*", () => {
|
|
34
|
-
expect(lastSegment(qualifyScreenId("tasks", "task-list"))).toBe("task-list");
|
|
35
|
-
expect(lastSegment(qualifyNavId("tasks", "main"))).toBe("main");
|
|
36
|
-
});
|
|
37
|
-
});
|