@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.
Files changed (257) 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 -44
  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 -38
  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/dist/inside-drawer.js +9 -0
  119. package/dist/page-header-slot.d.ts +2 -0
  120. package/dist/page-header-slot.js +9 -0
  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/changes.json +52 -0
  135. package/src/__tests__/error-i18n-defaults.test.ts +0 -82
  136. package/src/__tests__/format-when.test.ts +0 -12
  137. package/src/__tests__/i18n.test.tsx +0 -283
  138. package/src/__tests__/merge-search-params-into-initial.test.ts +0 -525
  139. package/src/__tests__/qn.test.ts +0 -83
  140. package/src/__tests__/share-links.test.ts +0 -26
  141. package/src/__tests__/sort-by-accessor.test.ts +0 -48
  142. package/src/__tests__/stringify-nav-params.test.ts +0 -33
  143. package/src/__tests__/use-list-url-state.test.tsx +0 -250
  144. package/src/app/__tests__/action-form-field-labels.test.tsx +0 -92
  145. package/src/app/__tests__/action-form-shim.test.ts +0 -137
  146. package/src/app/__tests__/build-list-query-payload.test.ts +0 -107
  147. package/src/app/__tests__/config-edit-shim.test.ts +0 -57
  148. package/src/app/__tests__/content-editors.test.tsx +0 -128
  149. package/src/app/__tests__/content-preview.test.tsx +0 -184
  150. package/src/app/__tests__/entity-edit-create-search-params.test.tsx +0 -236
  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 -111
  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 -3614
  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 -85
  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 -639
  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 -1669
  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 -60
  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 -467
  228. package/src/components/render-edit-action-button.tsx +0 -106
  229. package/src/components/render-edit-logic.ts +0 -85
  230. package/src/components/render-edit-types.ts +0 -216
  231. package/src/components/render-edit.tsx +0 -1700
  232. package/src/components/render-field.tsx +0 -942
  233. package/src/components/render-list.tsx +0 -561
  234. package/src/components/write-form-section.tsx +0 -199
  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-stream-handler.test.tsx +0 -107
  241. package/src/hooks/use-ai-text.ts +0 -172
  242. package/src/hooks/use-disclosure.ts +0 -20
  243. package/src/hooks/use-form.ts +0 -78
  244. package/src/hooks/use-list-url-state.ts +0 -171
  245. package/src/hooks/use-mutation.ts +0 -61
  246. package/src/hooks/use-query.ts +0 -132
  247. package/src/hooks/use-reference-lookup.ts +0 -76
  248. package/src/hooks/use-store.ts +0 -48
  249. package/src/hooks/use-stream-handler.ts +0 -134
  250. package/src/i18n-defaults.ts +0 -220
  251. package/src/i18n.tsx +0 -286
  252. package/src/index.ts +0 -272
  253. package/src/primitives.tsx +0 -1446
  254. package/src/share-links.ts +0 -12
  255. package/src/sort-by-accessor.ts +0 -20
  256. package/src/sse/live-events.tsx +0 -56
  257. 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
- });