@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.
Files changed (259) hide show
  1. package/dist/app/action-form-shim.d.ts +14 -0
  2. package/{src/app/action-form-shim.ts → dist/app/action-form-shim.js} +26 -46
  3. package/dist/app/app-features-context.d.ts +8 -0
  4. package/dist/app/app-features-context.js +16 -0
  5. package/dist/app/column-renderers.d.ts +30 -0
  6. package/dist/app/column-renderers.js +34 -0
  7. package/dist/app/config-edit-shim.d.ts +11 -0
  8. package/{src/app/config-edit-shim.ts → dist/app/config-edit-shim.js} +12 -25
  9. package/dist/app/content-editors.d.ts +38 -0
  10. package/dist/app/content-editors.js +26 -0
  11. package/dist/app/content-preview.d.ts +11 -0
  12. package/dist/app/content-preview.js +30 -0
  13. package/dist/app/custom-screens.d.ts +11 -0
  14. package/dist/app/custom-screens.js +20 -0
  15. package/dist/app/dashboard-body.d.ts +16 -0
  16. package/dist/app/dashboard-body.js +11 -0
  17. package/dist/app/extension-form-submit.d.ts +32 -0
  18. package/dist/app/extension-form-submit.js +73 -0
  19. package/dist/app/extension-sections.d.ts +72 -0
  20. package/dist/app/extension-sections.js +34 -0
  21. package/dist/app/feature-schema.d.ts +4 -0
  22. package/{src/app/feature-schema.ts → dist/app/feature-schema.js} +12 -22
  23. package/dist/app/form-schema.d.ts +4 -0
  24. package/dist/app/form-schema.js +85 -0
  25. package/dist/app/kumiko-screen.d.ts +43 -0
  26. package/dist/app/kumiko-screen.js +2206 -0
  27. package/dist/app/layout-fields.d.ts +3 -0
  28. package/dist/app/layout-fields.js +20 -0
  29. package/dist/app/list-facets.d.ts +48 -0
  30. package/dist/app/list-facets.js +185 -0
  31. package/dist/app/nav.d.ts +61 -0
  32. package/dist/app/nav.js +139 -0
  33. package/dist/app/projection-detail-shim.d.ts +8 -0
  34. package/dist/app/projection-detail-shim.js +71 -0
  35. package/dist/app/projection-list-shim.d.ts +13 -0
  36. package/{src/app/projection-list-shim.ts → dist/app/projection-list-shim.js} +26 -41
  37. package/dist/app/qn.d.ts +7 -0
  38. package/{src/app/qn.ts → dist/app/qn.js} +9 -11
  39. package/dist/app/qualify-screen-id.d.ts +2 -0
  40. package/{src/app/qualify-screen-id.ts → dist/app/qualify-screen-id.js} +5 -6
  41. package/dist/app/reference-facet-bridge.d.ts +10 -0
  42. package/dist/app/reference-facet-bridge.js +22 -0
  43. package/dist/app/return-to.d.ts +31 -0
  44. package/dist/app/return-to.js +188 -0
  45. package/dist/app/row-actions.d.ts +77 -0
  46. package/dist/app/row-actions.js +416 -0
  47. package/dist/app/screen-access.d.ts +8 -0
  48. package/dist/app/screen-access.js +48 -0
  49. package/dist/app/secret-mint-body.d.ts +10 -0
  50. package/dist/app/secret-mint-body.js +140 -0
  51. package/dist/app/secrets-edit-body.d.ts +8 -0
  52. package/dist/app/secrets-edit-body.js +76 -0
  53. package/dist/app/use-embedded-screen.d.ts +6 -0
  54. package/dist/app/use-embedded-screen.js +19 -0
  55. package/dist/app/variable-chips.d.ts +7 -0
  56. package/dist/app/variable-chips.js +16 -0
  57. package/dist/app/write-failed-error.d.ts +6 -0
  58. package/dist/app/write-failed-error.js +21 -0
  59. package/dist/components/embedded-list-field.d.ts +10 -0
  60. package/dist/components/embedded-list-field.js +259 -0
  61. package/dist/components/grid-cell-for-field.d.ts +25 -0
  62. package/dist/components/grid-cell-for-field.js +31 -0
  63. package/dist/components/multi-select-checkboxes.d.ts +14 -0
  64. package/dist/components/multi-select-checkboxes.js +36 -0
  65. package/dist/components/reference-create-dialog.d.ts +13 -0
  66. package/dist/components/reference-create-dialog.js +46 -0
  67. package/dist/components/related-list-section.d.ts +25 -0
  68. package/dist/components/related-list-section.js +273 -0
  69. package/dist/components/render-edit-action-button.d.ts +22 -0
  70. package/dist/components/render-edit-action-button.js +43 -0
  71. package/dist/components/render-edit-logic.d.ts +6 -0
  72. package/dist/components/render-edit-logic.js +63 -0
  73. package/dist/components/render-edit-types.d.ts +209 -0
  74. package/dist/components/render-edit-types.js +1 -0
  75. package/dist/components/render-edit.d.ts +6 -0
  76. package/dist/components/render-edit.js +1169 -0
  77. package/dist/components/render-field.d.ts +38 -0
  78. package/dist/components/render-field.js +544 -0
  79. package/dist/components/render-list.d.ts +117 -0
  80. package/dist/components/render-list.js +232 -0
  81. package/dist/components/write-form-section.d.ts +17 -0
  82. package/dist/components/write-form-section.js +104 -0
  83. package/dist/context/dispatcher-context.d.ts +10 -0
  84. package/{src/context/dispatcher-context.tsx → dist/context/dispatcher-context.js} +16 -29
  85. package/dist/context/draft-storage-context.d.ts +12 -0
  86. package/dist/context/draft-storage-context.js +18 -0
  87. package/dist/context/user-roles-context.d.ts +7 -0
  88. package/{src/context/user-roles-context.tsx → dist/context/user-roles-context.js} +7 -15
  89. package/dist/format-when.d.ts +1 -0
  90. package/{src/format-when.ts → dist/format-when.js} +7 -7
  91. package/dist/hooks/reference-limits.d.ts +3 -0
  92. package/{src/hooks/reference-limits.ts → dist/hooks/reference-limits.js} +0 -1
  93. package/dist/hooks/use-ai-text.d.ts +52 -0
  94. package/dist/hooks/use-ai-text.js +90 -0
  95. package/dist/hooks/use-disclosure.d.ts +7 -0
  96. package/dist/hooks/use-disclosure.js +8 -0
  97. package/dist/hooks/use-form.d.ts +7 -0
  98. package/dist/hooks/use-form.js +36 -0
  99. package/dist/hooks/use-list-url-state.d.ts +43 -0
  100. package/dist/hooks/use-list-url-state.js +131 -0
  101. package/dist/hooks/use-mutation.d.ts +9 -0
  102. package/dist/hooks/use-mutation.js +37 -0
  103. package/dist/hooks/use-query.d.ts +14 -0
  104. package/dist/hooks/use-query.js +118 -0
  105. package/dist/hooks/use-reference-lookup.d.ts +14 -0
  106. package/dist/hooks/use-reference-lookup.js +63 -0
  107. package/dist/hooks/use-store.d.ts +3 -0
  108. package/dist/hooks/use-store.js +38 -0
  109. package/dist/hooks/use-stream-handler.d.ts +14 -0
  110. package/dist/hooks/use-stream-handler.js +93 -0
  111. package/dist/i18n-defaults.d.ts +2 -0
  112. package/dist/i18n-defaults.js +201 -0
  113. package/dist/i18n.d.ts +64 -0
  114. package/dist/i18n.js +166 -0
  115. package/dist/index.d.ts +77 -0
  116. package/dist/index.js +55 -0
  117. package/dist/inside-drawer.d.ts +2 -0
  118. package/{src/inside-drawer.ts → dist/inside-drawer.js} +2 -5
  119. package/dist/page-header-slot.d.ts +2 -0
  120. package/{src/page-header-slot.ts → dist/page-header-slot.js} +2 -5
  121. package/dist/primitives.d.ts +1351 -0
  122. package/dist/primitives.js +73 -0
  123. package/dist/screen-fills-height.d.ts +3 -0
  124. package/dist/screen-fills-height.js +3 -0
  125. package/dist/share-links.d.ts +4 -0
  126. package/dist/share-links.js +11 -0
  127. package/dist/sort-by-accessor.d.ts +5 -0
  128. package/dist/sort-by-accessor.js +16 -0
  129. package/dist/sse/live-events.d.ts +23 -0
  130. package/dist/sse/live-events.js +28 -0
  131. package/dist/tokens.d.ts +83 -0
  132. package/dist/tokens.js +80 -0
  133. package/package.json +16 -9
  134. package/src/__tests__/error-i18n-defaults.test.ts +0 -82
  135. package/src/__tests__/format-when.test.ts +0 -12
  136. package/src/__tests__/i18n.test.tsx +0 -283
  137. package/src/__tests__/merge-search-params-into-initial.test.ts +0 -525
  138. package/src/__tests__/qn.test.ts +0 -83
  139. package/src/__tests__/share-links.test.ts +0 -26
  140. package/src/__tests__/sort-by-accessor.test.ts +0 -48
  141. package/src/__tests__/stringify-nav-params.test.ts +0 -33
  142. package/src/__tests__/use-list-url-state.test.tsx +0 -250
  143. package/src/app/__tests__/action-form-field-labels.test.tsx +0 -92
  144. package/src/app/__tests__/action-form-shim.test.ts +0 -137
  145. package/src/app/__tests__/build-list-query-payload.test.ts +0 -107
  146. package/src/app/__tests__/config-edit-shim.test.ts +0 -57
  147. package/src/app/__tests__/content-editors.test.tsx +0 -128
  148. package/src/app/__tests__/content-preview.test.tsx +0 -184
  149. package/src/app/__tests__/entity-edit-create-search-params.test.tsx +0 -236
  150. package/src/app/__tests__/entity-edit-fill-height.test.tsx +0 -118
  151. package/src/app/__tests__/entity-edit-jsonb-embedded-submit.test.tsx +0 -239
  152. package/src/app/__tests__/entity-list-create-singleton.test.tsx +0 -128
  153. package/src/app/__tests__/entity-list-row-action-entity-target.test.tsx +0 -424
  154. package/src/app/__tests__/entity-list-row-action-icon.test.tsx +0 -248
  155. package/src/app/__tests__/entity-list-row-action-kumiko-actions-view.test.tsx +0 -162
  156. package/src/app/__tests__/entity-list-row-action-refetch.test.tsx +0 -401
  157. package/src/app/__tests__/entity-list-screen-padding.test.tsx +0 -128
  158. package/src/app/__tests__/feature-schema.test.ts +0 -40
  159. package/src/app/__tests__/form-schema.test.ts +0 -247
  160. package/src/app/__tests__/list-daterange-facet.test.ts +0 -165
  161. package/src/app/__tests__/list-filter-facets.test.tsx +0 -521
  162. package/src/app/__tests__/nav.test.ts +0 -198
  163. package/src/app/__tests__/projection-detail-actions.test.tsx +0 -719
  164. package/src/app/__tests__/projection-detail-reference-field.test.tsx +0 -165
  165. package/src/app/__tests__/projection-detail-shim.test.ts +0 -188
  166. package/src/app/__tests__/projection-detail-singleton.test.tsx +0 -191
  167. package/src/app/__tests__/projection-list-bad-shape.test.tsx +0 -152
  168. package/src/app/__tests__/projection-list-reference-column.test.tsx +0 -219
  169. package/src/app/__tests__/projection-list-search-sort.test.tsx +0 -212
  170. package/src/app/__tests__/qualified-names.test.ts +0 -37
  171. package/src/app/__tests__/return-to.test.ts +0 -396
  172. package/src/app/__tests__/row-actions-navigate-tab.test.ts +0 -223
  173. package/src/app/__tests__/row-default-edit-action.test.tsx +0 -324
  174. package/src/app/__tests__/screen-access-allows.test.ts +0 -34
  175. package/src/app/__tests__/secret-mint-body.test.tsx +0 -404
  176. package/src/app/__tests__/variable-chips.test.tsx +0 -70
  177. package/src/app/app-features-context.tsx +0 -25
  178. package/src/app/column-renderers.tsx +0 -68
  179. package/src/app/content-editors.tsx +0 -85
  180. package/src/app/content-preview.tsx +0 -67
  181. package/src/app/custom-screens.tsx +0 -29
  182. package/src/app/dashboard-body.tsx +0 -33
  183. package/src/app/extension-form-submit.tsx +0 -123
  184. package/src/app/extension-sections.tsx +0 -111
  185. package/src/app/form-schema.ts +0 -91
  186. package/src/app/kumiko-screen.tsx +0 -3801
  187. package/src/app/layout-fields.ts +0 -31
  188. package/src/app/list-facets.ts +0 -259
  189. package/src/app/nav.tsx +0 -258
  190. package/src/app/projection-detail-shim.ts +0 -86
  191. package/src/app/reference-facet-bridge.tsx +0 -58
  192. package/src/app/return-to.tsx +0 -234
  193. package/src/app/row-actions.ts +0 -640
  194. package/src/app/screen-access.ts +0 -63
  195. package/src/app/secret-mint-body.tsx +0 -259
  196. package/src/app/secrets-edit-body.tsx +0 -194
  197. package/src/app/use-embedded-screen.ts +0 -27
  198. package/src/app/variable-chips.tsx +0 -39
  199. package/src/app/write-failed-error.ts +0 -27
  200. package/src/components/__tests__/embedded-list-field.test.tsx +0 -531
  201. package/src/components/__tests__/multi-select-checkboxes.test.tsx +0 -162
  202. package/src/components/__tests__/related-list-section-empty-state.test.tsx +0 -143
  203. package/src/components/__tests__/related-list-section.test.tsx +0 -1733
  204. package/src/components/__tests__/render-edit-action-button.test.tsx +0 -205
  205. package/src/components/__tests__/render-edit-hidden-section.test.tsx +0 -128
  206. package/src/components/__tests__/render-edit-logic.test.ts +0 -314
  207. package/src/components/__tests__/render-edit-screen-description.test.tsx +0 -152
  208. package/src/components/__tests__/render-edit-submit-actions.test.tsx +0 -460
  209. package/src/components/__tests__/render-field-app-locale.test.tsx +0 -142
  210. package/src/components/__tests__/render-field-enum-option.test.tsx +0 -99
  211. package/src/components/__tests__/render-field-icon-derivation.test.tsx +0 -119
  212. package/src/components/__tests__/render-field-json-format.test.tsx +0 -164
  213. package/src/components/__tests__/render-field-money-embedded-list-consistency.test.tsx +0 -212
  214. package/src/components/__tests__/render-field-money-roundtrip.test.tsx +0 -217
  215. package/src/components/__tests__/render-field-number-grouping.test.tsx +0 -76
  216. package/src/components/__tests__/render-field-number-unit.test.tsx +0 -227
  217. package/src/components/__tests__/render-field-password-format.test.tsx +0 -101
  218. package/src/components/__tests__/render-field-select-display.test.tsx +0 -134
  219. package/src/components/__tests__/render-field-unit-format.test.tsx +0 -107
  220. package/src/components/__tests__/render-field-unsupported-types.test.tsx +0 -195
  221. package/src/components/__tests__/render-list-toolbar-icon-only.test.tsx +0 -146
  222. package/src/components/__tests__/write-form-section.test.tsx +0 -375
  223. package/src/components/embedded-list-field.tsx +0 -354
  224. package/src/components/grid-cell-for-field.tsx +0 -88
  225. package/src/components/multi-select-checkboxes.tsx +0 -93
  226. package/src/components/reference-create-dialog.tsx +0 -91
  227. package/src/components/related-list-section.tsx +0 -491
  228. package/src/components/render-edit-action-button.tsx +0 -109
  229. package/src/components/render-edit-logic.ts +0 -85
  230. package/src/components/render-edit-types.ts +0 -226
  231. package/src/components/render-edit.tsx +0 -1871
  232. package/src/components/render-field.tsx +0 -951
  233. package/src/components/render-list.tsx +0 -593
  234. package/src/components/write-form-section.tsx +0 -198
  235. package/src/context/draft-storage-context.tsx +0 -39
  236. package/src/hooks/__tests__/use-ai-text.test.tsx +0 -177
  237. package/src/hooks/__tests__/use-disclosure.test.tsx +0 -25
  238. package/src/hooks/__tests__/use-form.test.tsx +0 -65
  239. package/src/hooks/__tests__/use-mutation.test.tsx +0 -77
  240. package/src/hooks/__tests__/use-query.test.tsx +0 -149
  241. package/src/hooks/__tests__/use-stream-handler.test.tsx +0 -107
  242. package/src/hooks/use-ai-text.ts +0 -172
  243. package/src/hooks/use-disclosure.ts +0 -20
  244. package/src/hooks/use-form.ts +0 -78
  245. package/src/hooks/use-list-url-state.ts +0 -207
  246. package/src/hooks/use-mutation.ts +0 -61
  247. package/src/hooks/use-query.ts +0 -166
  248. package/src/hooks/use-reference-lookup.ts +0 -76
  249. package/src/hooks/use-store.ts +0 -48
  250. package/src/hooks/use-stream-handler.ts +0 -134
  251. package/src/i18n-defaults.ts +0 -240
  252. package/src/i18n.tsx +0 -286
  253. package/src/index.ts +0 -279
  254. package/src/primitives.tsx +0 -1567
  255. package/src/screen-fills-height.ts +0 -3
  256. package/src/share-links.ts +0 -12
  257. package/src/sort-by-accessor.ts +0 -20
  258. package/src/sse/live-events.tsx +0 -56
  259. package/src/tokens.tsx +0 -170
@@ -1,521 +0,0 @@
1
- // fw#2224: ProjectionListBody had neither `filter` nor faceted filters —
2
- // only EntityListBody did (see kumiko-screen.tsx's buildFilterPayload/
3
- // buildFilterFacets doc). This proves both directions of the fix: entityList
4
- // keeps its exact pre-refactor payload/facet behavior after the shared
5
- // function extraction, and projectionList gains the same capability —
6
- // screen.filter and screen.facets reach payload.filter/payload.filters,
7
- // with boolean facets coerced from URL-state strings to real booleans.
8
-
9
- import { describe, expect, test } from "bun:test";
10
- import {
11
- type EntityDefinition,
12
- type EntityListScreenDefinition,
13
- type ProjectionListScreenDefinition,
14
- translationValueOtherText,
15
- } from "@cosmicdrift/kumiko-framework/ui-types";
16
- import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
17
- import { act, render, waitFor } from "@testing-library/react";
18
- import { type ComponentType, type ReactNode, useState } from "react";
19
- import { DispatcherProvider } from "../../context/dispatcher-context";
20
- import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
21
- import { kumikoDefaultTranslations } from "../../i18n-defaults";
22
- import { type CorePrimitives, type DataTableProps, PrimitivesProvider } from "../../primitives";
23
- import type { FeatureSchema } from "../feature-schema";
24
- import { KumikoScreen } from "../kumiko-screen";
25
- import type { NavApi } from "../nav";
26
- import { NavProvider } from "../nav";
27
-
28
- let capturedProps: DataTableProps | undefined;
29
- const captureDataTable: ComponentType<DataTableProps> = (props) => {
30
- capturedProps = props;
31
- return null;
32
- };
33
- // Indirection defeats TS narrowing `capturedProps` to `undefined` at read
34
- // sites — the compiler can't see that `captureDataTable` (a React render
35
- // callback) reassigns it between the reset and the read.
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
- let queryCalls: Array<{ readonly type: string; readonly payload: unknown }> = [];
62
-
63
- function stubDispatcher(): Dispatcher {
64
- return {
65
- write: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["write"],
66
- query: (async (type: string, payload: unknown) => {
67
- queryCalls.push({ type, payload });
68
- return { isSuccess: true, data: { rows: [], nextCursor: null } };
69
- }) as unknown as Dispatcher["query"],
70
- batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
71
- statusStore: {
72
- getState: () => "online",
73
- subscribe: () => () => {},
74
- } as unknown as Dispatcher["statusStore"],
75
- async *stream() {},
76
- pendingWrites: () => [],
77
- pendingFiles: () => [],
78
- };
79
- }
80
-
81
- // Stateful nav so setSearchParams (called by useListUrlState.setFilter)
82
- // actually re-renders the tree — a plain mock object wouldn't trigger React,
83
- // and toggling a facet would never reach a second query() call.
84
- function StatefulNav({ children }: { readonly children: ReactNode }): ReactNode {
85
- const [params, setParams] = useState<Record<string, string>>({});
86
- const value: NavApi = {
87
- route: { screenId: "unused" },
88
- navigate: () => {},
89
- replace: () => {},
90
- hrefFor: () => "",
91
- searchParams: params,
92
- setSearchParams: (updates) => {
93
- setParams((prev) => {
94
- const next = { ...prev };
95
- for (const [k, v] of Object.entries(updates)) {
96
- if (v === null) delete next[k];
97
- else next[k] = v;
98
- }
99
- return next;
100
- });
101
- },
102
- };
103
- return <NavProvider value={value}>{children}</NavProvider>;
104
- }
105
-
106
- function renderScreen(
107
- schema: FeatureSchema,
108
- qn: string,
109
- dispatcher: Dispatcher = stubDispatcher(),
110
- timeZone = "UTC",
111
- ): void {
112
- render(
113
- <LocaleProvider
114
- resolver={createStaticLocaleResolver({ locale: "de-DE", timeZone })}
115
- fallbackBundles={[kumikoDefaultTranslations]}
116
- >
117
- <DispatcherProvider dispatcher={dispatcher}>
118
- <StatefulNav>
119
- <PrimitivesProvider value={testPrimitives}>
120
- <KumikoScreen schema={schema} qn={qn} />
121
- </PrimitivesProvider>
122
- </StatefulNav>
123
- </DispatcherProvider>
124
- </LocaleProvider>,
125
- );
126
- }
127
-
128
- function stubDispatcherByType(
129
- responses: Readonly<Record<string, { rows: readonly Record<string, unknown>[] }>>,
130
- ): Dispatcher {
131
- return {
132
- write: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["write"],
133
- query: (async (type: string, payload: unknown) => {
134
- queryCalls.push({ type, payload });
135
- const rows = responses[type]?.rows ?? [];
136
- return { isSuccess: true, data: { rows, nextCursor: null } };
137
- }) as unknown as Dispatcher["query"],
138
- batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
139
- statusStore: {
140
- getState: () => "online",
141
- subscribe: () => () => {},
142
- } as unknown as Dispatcher["statusStore"],
143
- async *stream() {},
144
- pendingWrites: () => [],
145
- pendingFiles: () => [],
146
- };
147
- }
148
-
149
- describe("entityList facets — unchanged after the shared buildFilterFacets/buildFilterPayload extraction (fw#2224)", () => {
150
- test("a filterable boolean field renders as a facet, and toggling it sends a coerced boolean in payload.filters", async () => {
151
- queryCalls = [];
152
- capturedProps = undefined;
153
- const entity: EntityDefinition = {
154
- fields: {
155
- active: { type: "boolean", filterable: true, required: false },
156
- },
157
- };
158
- const screen: EntityListScreenDefinition = {
159
- id: "unit-list",
160
- type: "entityList",
161
- entity: "unit",
162
- columns: ["active"],
163
- };
164
- const schema: FeatureSchema = {
165
- featureName: "units",
166
- entities: { unit: entity },
167
- screens: [screen],
168
- } as FeatureSchema;
169
-
170
- renderScreen(schema, "units:screen:unit-list");
171
-
172
- await waitFor(() => expect(capturedProps).toBeDefined());
173
- const props = getCapturedProps();
174
- if (props === undefined) throw new Error("DataTable was not rendered");
175
- expect(props.filterFacets).toHaveLength(1);
176
- expect(props.filterFacets?.[0]?.field).toBe("active");
177
- expect(props.filterFacets?.[0]?.options).toEqual([
178
- { value: "true", label: expect.any(String) },
179
- { value: "false", label: expect.any(String) },
180
- ]);
181
-
182
- const countBeforeToggle = queryCalls.length;
183
- await act(async () => {
184
- props.onFilterChange?.("active", ["true"]);
185
- });
186
- await waitFor(() => expect(queryCalls.length).toBeGreaterThan(countBeforeToggle));
187
- const lastPayload = queryCalls[queryCalls.length - 1]?.payload;
188
- expect(lastPayload).toMatchObject({
189
- filters: [{ field: "active", op: "in", value: [true] }],
190
- });
191
- });
192
- });
193
-
194
- describe("projectionList filter + facets (fw#2224)", () => {
195
- test("screen.filter is sent verbatim in the query payload", async () => {
196
- queryCalls = [];
197
- const screen: ProjectionListScreenDefinition = {
198
- id: "member-list",
199
- type: "projectionList",
200
- query: "ledger:query:member:list",
201
- columns: ["status"],
202
- filter: { field: "tier", op: "eq", value: "gold" },
203
- };
204
- const schema: FeatureSchema = {
205
- featureName: "ledger",
206
- entities: {},
207
- screens: [screen],
208
- } as FeatureSchema;
209
-
210
- renderScreen(schema, "ledger:screen:member-list");
211
-
212
- await waitFor(() => expect(queryCalls.length).toBeGreaterThan(0));
213
- expect(queryCalls[0]?.payload).toMatchObject({
214
- filter: { field: "tier", op: "eq", value: "gold" },
215
- });
216
- });
217
-
218
- test("a boolean facet renders with the screen's explicit labels, and toggling it sends a coerced boolean in payload.filters", async () => {
219
- queryCalls = [];
220
- capturedProps = undefined;
221
- const screen: ProjectionListScreenDefinition = {
222
- id: "member-list",
223
- type: "projectionList",
224
- query: "ledger:query:member:list",
225
- columns: ["active"],
226
- facets: [
227
- {
228
- field: "active",
229
- type: "boolean",
230
- label: "Active",
231
- trueLabel: "Active",
232
- falseLabel: "Inactive",
233
- },
234
- ],
235
- };
236
- const schema: FeatureSchema = {
237
- featureName: "ledger",
238
- entities: {},
239
- screens: [screen],
240
- } as FeatureSchema;
241
-
242
- renderScreen(schema, "ledger:screen:member-list");
243
-
244
- await waitFor(() => expect(capturedProps).toBeDefined());
245
- const props = getCapturedProps();
246
- if (props === undefined) throw new Error("DataTable was not rendered");
247
- expect(props.filterFacets).toEqual([
248
- {
249
- field: "active",
250
- label: "Active",
251
- options: [
252
- { value: "true", label: "Active" },
253
- { value: "false", label: "Inactive" },
254
- ],
255
- },
256
- ]);
257
-
258
- const countBeforeToggle = queryCalls.length;
259
- await act(async () => {
260
- props.onFilterChange?.("active", ["true"]);
261
- });
262
- await waitFor(() => expect(queryCalls.length).toBeGreaterThan(countBeforeToggle));
263
- const lastPayload = queryCalls[queryCalls.length - 1]?.payload;
264
- expect(lastPayload).toMatchObject({
265
- filters: [{ field: "active", op: "in", value: [true] }],
266
- });
267
- });
268
-
269
- // fw#2373: labels without a translation key must pass through unchanged —
270
- // otherwise shipping translate() alone regresses Members filters to raw keys
271
- // when locale packs are missing.
272
- test("projection facet labels without a translation key pass through unchanged", async () => {
273
- queryCalls = [];
274
- capturedProps = undefined;
275
- const screen: ProjectionListScreenDefinition = {
276
- id: "member-list",
277
- type: "projectionList",
278
- query: "ledger:query:member:list",
279
- columns: ["tier", "active"],
280
- facets: [
281
- {
282
- field: "tier",
283
- type: "select",
284
- label: "ledger.member.tier",
285
- options: [
286
- { value: "gold", label: "ledger.member.tier.gold" },
287
- { value: "silver", label: "ledger.member.tier.silver" },
288
- ],
289
- },
290
- {
291
- field: "active",
292
- type: "boolean",
293
- label: "ledger.member.active",
294
- trueLabel: "ledger.member.active.true",
295
- falseLabel: "ledger.member.active.false",
296
- },
297
- ],
298
- };
299
- const schema: FeatureSchema = {
300
- featureName: "ledger",
301
- entities: {},
302
- screens: [screen],
303
- } as FeatureSchema;
304
-
305
- renderScreen(schema, "ledger:screen:member-list");
306
-
307
- await waitFor(() => expect(capturedProps).toBeDefined());
308
- const props = getCapturedProps();
309
- if (props === undefined) throw new Error("DataTable was not rendered");
310
- expect(props.filterFacets).toEqual([
311
- {
312
- field: "tier",
313
- label: "ledger.member.tier",
314
- options: [
315
- { value: "gold", label: "ledger.member.tier.gold" },
316
- { value: "silver", label: "ledger.member.tier.silver" },
317
- ],
318
- },
319
- {
320
- field: "active",
321
- label: "ledger.member.active",
322
- options: [
323
- { value: "true", label: "ledger.member.active.true" },
324
- { value: "false", label: "ledger.member.active.false" },
325
- ],
326
- },
327
- ]);
328
- });
329
-
330
- test("projection facet labels resolve when a translation key exists", async () => {
331
- queryCalls = [];
332
- capturedProps = undefined;
333
- const screen: ProjectionListScreenDefinition = {
334
- id: "member-list",
335
- type: "projectionList",
336
- query: "ledger:query:member:list",
337
- columns: ["tier"],
338
- facets: [
339
- {
340
- field: "tier",
341
- type: "select",
342
- label: "kumiko.actions.save",
343
- options: [{ value: "gold", label: "kumiko.actions.cancel" }],
344
- },
345
- ],
346
- };
347
- const schema: FeatureSchema = {
348
- featureName: "ledger",
349
- entities: {},
350
- screens: [screen],
351
- } as FeatureSchema;
352
-
353
- renderScreen(schema, "ledger:screen:member-list");
354
-
355
- await waitFor(() => expect(capturedProps).toBeDefined());
356
- const props = getCapturedProps();
357
- if (props === undefined) throw new Error("DataTable was not rendered");
358
- const facet = props.filterFacets?.[0];
359
- const de = kumikoDefaultTranslations["de"];
360
- const en = kumikoDefaultTranslations["en"];
361
- if (en === undefined) throw new Error("missing en default translations");
362
- const save = de?.["kumiko.actions.save"] ?? en["kumiko.actions.save"];
363
- const cancel = de?.["kumiko.actions.cancel"] ?? en["kumiko.actions.cancel"];
364
- expect(facet?.label).toBe(save === undefined ? undefined : translationValueOtherText(save));
365
- expect(facet?.options?.[0]?.label).toBe(
366
- cancel === undefined ? undefined : translationValueOtherText(cancel),
367
- );
368
- });
369
-
370
- test("a reference facet loads its options from the referenced entity's list query, and selecting one sends an id filter", async () => {
371
- queryCalls = [];
372
- capturedProps = undefined;
373
- const dispatcher = stubDispatcherByType({
374
- "ledger:query:member:list": { rows: [] },
375
- "ledger:query:tenant:list": {
376
- rows: [
377
- { id: "t1", name: "Acme" },
378
- { id: "t2", name: "Globex" },
379
- ],
380
- },
381
- });
382
- const screen: ProjectionListScreenDefinition = {
383
- id: "member-list",
384
- type: "projectionList",
385
- query: "ledger:query:member:list",
386
- columns: ["tenantId"],
387
- facets: [
388
- {
389
- field: "tenantId",
390
- type: "reference",
391
- label: "Tenant",
392
- entity: "tenant",
393
- labelField: "name",
394
- },
395
- ],
396
- };
397
- const schema: FeatureSchema = {
398
- featureName: "ledger",
399
- entities: {},
400
- screens: [screen],
401
- } as FeatureSchema;
402
-
403
- renderScreen(schema, "ledger:screen:member-list", dispatcher);
404
-
405
- await waitFor(() =>
406
- expect(queryCalls.some((c) => c.type === "ledger:query:tenant:list")).toBe(true),
407
- );
408
- await waitFor(() => expect(getCapturedProps()?.filterFacets?.[0]?.options).toHaveLength(2));
409
- const props = getCapturedProps();
410
- if (props === undefined) throw new Error("DataTable was not rendered");
411
- expect(props.filterFacets).toEqual([
412
- {
413
- field: "tenantId",
414
- label: "Tenant",
415
- options: [
416
- { value: "t1", label: "Acme" },
417
- { value: "t2", label: "Globex" },
418
- ],
419
- },
420
- ]);
421
-
422
- const countBeforeSelect = queryCalls.length;
423
- await act(async () => {
424
- props.onFilterChange?.("tenantId", ["t1"]);
425
- });
426
- await waitFor(() => expect(queryCalls.length).toBeGreaterThan(countBeforeSelect));
427
- const lastPayload = queryCalls[queryCalls.length - 1]?.payload;
428
- expect(lastPayload).toMatchObject({
429
- filters: [{ field: "tenantId", op: "in", value: ["t1"] }],
430
- });
431
- });
432
- });
433
-
434
- // fw#3104: a dateRange facet is the first facet that does NOT travel in
435
- // payload.filters — its two bounds go out as the top-level params the screen
436
- // declares. A screen may declare it as its only facet, so the wiring must not
437
- // hang off the option-facet path.
438
- describe("projectionList dateRange facet (fw#3104)", () => {
439
- const dateRangeScreen: ProjectionListScreenDefinition = {
440
- id: "event-list",
441
- type: "projectionList",
442
- query: "ledger:query:event:list",
443
- columns: ["createdAt"],
444
- facets: [
445
- {
446
- field: "createdAt",
447
- type: "dateRange",
448
- label: "When",
449
- params: { from: "from", to: "to" },
450
- },
451
- ],
452
- };
453
- const schema: FeatureSchema = {
454
- featureName: "ledger",
455
- entities: {},
456
- screens: [dateRangeScreen],
457
- } as FeatureSchema;
458
-
459
- test("reaches the DataTable as a dateRange facet, not as a filter dropdown", async () => {
460
- queryCalls = [];
461
- capturedProps = undefined;
462
- renderScreen(schema, "ledger:screen:event-list");
463
-
464
- await waitFor(() => expect(capturedProps).toBeDefined());
465
- const props = getCapturedProps();
466
- if (props === undefined) throw new Error("DataTable was not rendered");
467
- expect(props.dateRangeFacets).toEqual([
468
- { field: "createdAt", label: "When", from: "", to: "" },
469
- ]);
470
- expect(props.filterFacets).toBeUndefined();
471
- // Reset still has to be reachable on a dateRange-only screen.
472
- expect(props.onFilterReset).toBeDefined();
473
- expect(queryCalls[0]?.payload).not.toHaveProperty("from");
474
- });
475
-
476
- test("picking a bound sends it as the declared query param, in the viewer's zone", async () => {
477
- queryCalls = [];
478
- capturedProps = undefined;
479
- renderScreen(schema, "ledger:screen:event-list", stubDispatcher(), "Europe/Vienna");
480
-
481
- await waitFor(() => expect(capturedProps).toBeDefined());
482
- const before = queryCalls.length;
483
- await act(async () => {
484
- getCapturedProps()?.onDateRangeChange?.("createdAt", "to", "2020-06-14");
485
- });
486
- await waitFor(() => expect(queryCalls.length).toBeGreaterThan(before));
487
- const payload = queryCalls[queryCalls.length - 1]?.payload;
488
- // Open interval: only the bound the user picked travels.
489
- expect(payload).toMatchObject({ to: "2020-06-14T21:59:59.999999999Z" });
490
- expect(payload).not.toHaveProperty("from");
491
- expect(payload).not.toHaveProperty("filters");
492
- expect(getCapturedProps()?.dateRangeFacets).toEqual([
493
- { field: "createdAt", label: "When", from: "", to: "2020-06-14" },
494
- ]);
495
- });
496
-
497
- test("a `from` past the current `to` is clamped before it can reach the handler's refine", async () => {
498
- queryCalls = [];
499
- capturedProps = undefined;
500
- renderScreen(schema, "ledger:screen:event-list", stubDispatcher(), "Europe/Vienna");
501
-
502
- await waitFor(() => expect(capturedProps).toBeDefined());
503
- await act(async () => {
504
- getCapturedProps()?.onDateRangeChange?.("createdAt", "to", "2020-06-14");
505
- });
506
- await act(async () => {
507
- getCapturedProps()?.onDateRangeChange?.("createdAt", "from", "2020-06-20");
508
- });
509
- await waitFor(() =>
510
- expect(getCapturedProps()?.dateRangeFacets).toEqual([
511
- { field: "createdAt", label: "When", from: "2020-06-20", to: "2020-06-20" },
512
- ]),
513
- );
514
- const payload = queryCalls[queryCalls.length - 1]?.payload as Record<string, string>;
515
- const from = payload["from"];
516
- const to = payload["to"];
517
- expect(from).toBeString();
518
- expect(to).toBeString();
519
- expect((from ?? "") <= (to ?? "")).toBe(true);
520
- });
521
- });