@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,227 +0,0 @@
1
- // Editable number-field unit-of-measure suffix (static or sibling-field
2
- // reference). Complements render-field-unit-format.test.tsx, which only
3
- // covers the pre-existing read-only `format: "unit"` FieldRenderer path —
4
- // this feature is display-only decoration on the editable Input widget,
5
- // never a value conversion.
6
-
7
- import { describe, expect, test } from "bun:test";
8
- import {
9
- buildAppSchema,
10
- createRegistry,
11
- defineFeature,
12
- } from "@cosmicdrift/kumiko-framework/engine";
13
- import type {
14
- EntityDefinition,
15
- EntityEditScreenDefinition,
16
- } from "@cosmicdrift/kumiko-framework/ui-types";
17
- import { computeEditViewModel, type EditFieldViewModel } from "@cosmicdrift/kumiko-headless";
18
- import { render } from "@testing-library/react";
19
- import type { ComponentType, ReactNode } from "react";
20
- import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
21
- import { type CorePrimitives, type InputProps, PrimitivesProvider } from "../../primitives";
22
- import { RenderField } from "../render-field";
23
-
24
- let captured: InputProps | undefined;
25
- const captureInput: ComponentType<InputProps> = (props) => {
26
- captured = props;
27
- return null;
28
- };
29
- const noop = (): ReactNode => null;
30
- const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
31
-
32
- const testPrimitives: CorePrimitives = {
33
- Button: noop,
34
- Banner: noop,
35
- Field: passChildren,
36
- Input: captureInput,
37
- DataTable: noop,
38
- Form: noop,
39
- Section: noop,
40
- Card: noop,
41
- Grid: noop,
42
- GridCell: noop,
43
- Text: noop,
44
- Heading: noop,
45
- Dialog: noop,
46
- Modal: noop,
47
- Lightbox: noop,
48
- ConfigSourceBadge: noop,
49
- ConfigCascadeView: noop,
50
- Link: noop,
51
- };
52
-
53
- function buildEntity(
54
- mileageUnit: string | { readonly field: string } | undefined,
55
- ): EntityDefinition {
56
- return {
57
- fields: {
58
- mileage: {
59
- type: "number",
60
- required: false,
61
- ...(mileageUnit !== undefined && { unit: mileageUnit }),
62
- },
63
- mileageUnit: { type: "text", required: false },
64
- },
65
- } as EntityDefinition;
66
- }
67
-
68
- function buildScreen(): EntityEditScreenDefinition {
69
- return {
70
- id: "vehicle-edit",
71
- type: "entityEdit",
72
- entity: "vehicle",
73
- layout: { sections: [{ columns: 1, fields: ["mileage", "mileageUnit"] }] },
74
- } as EntityEditScreenDefinition;
75
- }
76
-
77
- function mileageField(
78
- entity: EntityDefinition,
79
- values: Record<string, unknown>,
80
- ): EditFieldViewModel {
81
- const vm = computeEditViewModel({
82
- screen: buildScreen(),
83
- entity,
84
- values,
85
- translate: (key) => key,
86
- featureName: "fleet",
87
- });
88
- const section = vm.sections[0];
89
- if (section === undefined || section.kind !== "fields") {
90
- throw new Error("expected a fields section");
91
- }
92
- const field = section.fields.find((f) => f.field === "mileage");
93
- if (field === undefined) throw new Error("expected a mileage field");
94
- return field;
95
- }
96
-
97
- function currentInput(): InputProps {
98
- if (captured === undefined) throw new Error("expected an Input to be captured");
99
- return captured;
100
- }
101
-
102
- function renderMileageField(
103
- entity: EntityDefinition,
104
- values: Record<string, unknown>,
105
- row?: Record<string, unknown>,
106
- ): InputProps {
107
- captured = undefined;
108
- render(
109
- <LocaleProvider resolver={createStaticLocaleResolver({ locale: "en-US" })}>
110
- <PrimitivesProvider value={testPrimitives}>
111
- <RenderField
112
- field={mileageField(entity, values)}
113
- onChange={() => {}}
114
- {...(row !== undefined && { row })}
115
- />
116
- </PrimitivesProvider>
117
- </LocaleProvider>,
118
- );
119
- if (captured === undefined) throw new Error("mileage field did not render an Input");
120
- return captured;
121
- }
122
-
123
- describe("RenderField — editable number unit suffix", () => {
124
- test("static unit resolves onto Input.unit, value stays untouched", () => {
125
- const field = renderMileageField(buildEntity("km"), { mileage: 58 });
126
- expect(field.kind).toBe("number");
127
- if (field.kind !== "number") return;
128
- expect(field.unit).toBe("km");
129
- expect(field.value).toBe(58);
130
- });
131
-
132
- test("no unit configured: Input.unit is undefined", () => {
133
- const field = renderMileageField(buildEntity(undefined), { mileage: 58 });
134
- expect(field.kind).toBe("number");
135
- if (field.kind !== "number") return;
136
- expect(field.unit).toBeUndefined();
137
- });
138
-
139
- test("sibling-field unit resolves from the live row and updates when the sibling changes", () => {
140
- const entity = buildEntity({ field: "mileageUnit" });
141
- captured = undefined;
142
- const { rerender } = render(
143
- <LocaleProvider resolver={createStaticLocaleResolver({ locale: "en-US" })}>
144
- <PrimitivesProvider value={testPrimitives}>
145
- <RenderField
146
- field={mileageField(entity, { mileage: 58, mileageUnit: "mi" })}
147
- onChange={() => {}}
148
- row={{ mileage: 58, mileageUnit: "mi" }}
149
- />
150
- </PrimitivesProvider>
151
- </LocaleProvider>,
152
- );
153
- const first = currentInput();
154
- if (first.kind !== "number") throw new Error("expected a number Input");
155
- expect(first.unit).toBe("mi");
156
-
157
- captured = undefined;
158
- rerender(
159
- <LocaleProvider resolver={createStaticLocaleResolver({ locale: "en-US" })}>
160
- <PrimitivesProvider value={testPrimitives}>
161
- <RenderField
162
- field={mileageField(entity, { mileage: 58, mileageUnit: "km" })}
163
- onChange={() => {}}
164
- row={{ mileage: 58, mileageUnit: "km" }}
165
- />
166
- </PrimitivesProvider>
167
- </LocaleProvider>,
168
- );
169
- const second = currentInput();
170
- if (second.kind !== "number") throw new Error("expected a number Input");
171
- expect(second.unit).toBe("km");
172
- });
173
-
174
- test("missing sibling field: no suffix, no crash, no guessed default", () => {
175
- const entity = buildEntity({ field: "mileageUnit" });
176
- const field = renderMileageField(entity, { mileage: 58 }, { mileage: 58 });
177
- expect(field.kind).toBe("number");
178
- if (field.kind !== "number") return;
179
- expect(field.unit).toBeUndefined();
180
- });
181
-
182
- test("empty-string sibling value: no suffix, no guessed default", () => {
183
- const entity = buildEntity({ field: "mileageUnit" });
184
- const field = renderMileageField(
185
- entity,
186
- { mileage: 58, mileageUnit: "" },
187
- {
188
- mileage: 58,
189
- mileageUnit: "",
190
- },
191
- );
192
- expect(field.kind).toBe("number");
193
- if (field.kind !== "number") return;
194
- expect(field.unit).toBeUndefined();
195
- });
196
- });
197
-
198
- describe("RenderField — number unit through the shipped client schema", () => {
199
- function projectedVehicleEntity(
200
- mileageUnit: string | { readonly field: string },
201
- ): EntityDefinition {
202
- const feature = defineFeature("fleet", (r) => {
203
- r.entity("vehicle", buildEntity(mileageUnit));
204
- });
205
- const app = buildAppSchema(createRegistry([feature]));
206
- const entity = app.features[0]?.entities["vehicle"];
207
- if (entity === undefined) throw new Error("expected the projected vehicle entity");
208
- // Mirrors the browser-injection pipeline, which ships the schema as JSON.
209
- return JSON.parse(JSON.stringify(entity)) as EntityDefinition;
210
- }
211
-
212
- test("static unit survives buildAppSchema and reaches Input.unit", () => {
213
- const field = renderMileageField(projectedVehicleEntity("mi"), { mileage: 58 });
214
- if (field.kind !== "number") throw new Error("expected a number Input");
215
- expect(field.unit).toBe("mi");
216
- });
217
-
218
- test("sibling-field unit survives buildAppSchema and resolves from the row", () => {
219
- const field = renderMileageField(
220
- projectedVehicleEntity({ field: "mileageUnit" }),
221
- { mileage: 58, mileageUnit: "km" },
222
- { mileage: 58, mileageUnit: "km" },
223
- );
224
- if (field.kind !== "number") throw new Error("expected a number Input");
225
- expect(field.unit).toBe("km");
226
- });
227
- });
@@ -1,101 +0,0 @@
1
- // fw#2548: TextFieldDef.format "password" is a pure render hint — the
2
- // editable widget masks the input, and the read-only display never shows
3
- // the plaintext value.
4
-
5
- import { describe, expect, test } from "bun:test";
6
- import type { EditFieldViewModel } from "@cosmicdrift/kumiko-headless";
7
- import { render } from "@testing-library/react";
8
- import type { ComponentType, ReactNode } from "react";
9
- import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
10
- import {
11
- type CorePrimitives,
12
- type InputProps,
13
- PrimitivesProvider,
14
- type TextProps,
15
- } from "../../primitives";
16
- import { RenderField } from "../render-field";
17
-
18
- let capturedInput: InputProps | undefined;
19
- const captureInput: ComponentType<InputProps> = (props) => {
20
- capturedInput = props;
21
- return null;
22
- };
23
- let capturedText: TextProps | undefined;
24
- const captureText: ComponentType<TextProps> = (props) => {
25
- capturedText = props;
26
- return null;
27
- };
28
- const noop = (): ReactNode => null;
29
- const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
30
-
31
- const testPrimitives: CorePrimitives = {
32
- Button: noop,
33
- Banner: noop,
34
- Field: passChildren,
35
- Input: captureInput,
36
- DataTable: noop,
37
- Form: noop,
38
- Section: noop,
39
- Card: noop,
40
- Grid: noop,
41
- GridCell: noop,
42
- Text: captureText,
43
- Heading: noop,
44
- Dialog: noop,
45
- Modal: noop,
46
- Lightbox: noop,
47
- ConfigSourceBadge: noop,
48
- ConfigCascadeView: noop,
49
- Link: noop,
50
- };
51
-
52
- function textField(overrides: Partial<EditFieldViewModel> = {}): EditFieldViewModel {
53
- return {
54
- field: "apiToken",
55
- label: "API token",
56
- type: "text",
57
- value: "s3cr3t",
58
- visible: true,
59
- readOnly: false,
60
- required: false,
61
- ...overrides,
62
- };
63
- }
64
-
65
- function renderField(field: EditFieldViewModel, valueDisplay?: "form" | "text"): void {
66
- capturedInput = undefined;
67
- capturedText = undefined;
68
- render(
69
- <LocaleProvider resolver={createStaticLocaleResolver({ locale: "en-US" })}>
70
- <PrimitivesProvider value={testPrimitives}>
71
- <RenderField
72
- field={field}
73
- onChange={() => {}}
74
- {...(valueDisplay !== undefined && { valueDisplay })}
75
- />
76
- </PrimitivesProvider>
77
- </LocaleProvider>,
78
- );
79
- }
80
-
81
- describe("RenderField — format: 'password'", () => {
82
- test("editable password field renders Input kind=password", () => {
83
- renderField(textField({ format: "password" }));
84
- expect(capturedInput?.kind).toBe("password");
85
- });
86
-
87
- test("a plain text field (no format) still renders Input kind=text", () => {
88
- renderField(textField());
89
- expect(capturedInput?.kind).toBe("text");
90
- });
91
-
92
- test("read-only password field shows a fixed mask, never the plaintext", () => {
93
- renderField(textField({ format: "password", readOnly: true }), "text");
94
- expect(capturedText?.children).toBe("••••••••");
95
- });
96
-
97
- test("read-only password field with an empty value shows the empty placeholder, not the mask", () => {
98
- renderField(textField({ format: "password", readOnly: true, value: "" }), "text");
99
- expect(capturedText?.children).toBe("—");
100
- });
101
- });
@@ -1,134 +0,0 @@
1
- // SelectFieldDef.display travels the whole declarative chain (#2711):
2
- // EntityDefinition → computeEditViewModel → RenderField → InputProps. Without
3
- // it an app can only hope its labels fall under the renderer's heuristic.
4
-
5
- import { describe, expect, test } from "bun:test";
6
- import type {
7
- EntityDefinition,
8
- EntityEditScreenDefinition,
9
- } from "@cosmicdrift/kumiko-framework/ui-types";
10
- import { computeEditViewModel, type EditFieldViewModel } from "@cosmicdrift/kumiko-headless";
11
- import { render } from "@testing-library/react";
12
- import type { ComponentType, ReactNode } from "react";
13
- import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
14
- import { type CorePrimitives, type InputProps, PrimitivesProvider } from "../../primitives";
15
- import { RenderField } from "../render-field";
16
-
17
- let captured: InputProps | undefined;
18
- const captureInput: ComponentType<InputProps> = (props) => {
19
- captured = props;
20
- return null;
21
- };
22
- const noop = (): ReactNode => null;
23
- const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
24
-
25
- const testPrimitives: CorePrimitives = {
26
- Button: noop,
27
- Banner: noop,
28
- Field: passChildren,
29
- Input: captureInput,
30
- DataTable: noop,
31
- Form: noop,
32
- Section: noop,
33
- Card: noop,
34
- Grid: noop,
35
- GridCell: noop,
36
- Text: noop,
37
- Heading: noop,
38
- Dialog: noop,
39
- Modal: noop,
40
- Lightbox: noop,
41
- ConfigSourceBadge: noop,
42
- ConfigCascadeView: noop,
43
- Link: noop,
44
- };
45
-
46
- const AREAS = ["jobs", "mail", "search", "events", "tenants"] as const;
47
-
48
- function buildEntity(display: "radio" | "dropdown" | undefined): EntityDefinition {
49
- return {
50
- fields: {
51
- area: {
52
- type: "select",
53
- required: false,
54
- options: AREAS,
55
- ...(display !== undefined && { display }),
56
- },
57
- tags: {
58
- type: "multiSelect",
59
- required: false,
60
- options: AREAS,
61
- display: "checkboxes",
62
- },
63
- },
64
- } as EntityDefinition;
65
- }
66
-
67
- const editScreen: EntityEditScreenDefinition = {
68
- id: "ticket-edit",
69
- type: "entityEdit",
70
- entity: "ticket",
71
- layout: { sections: [{ columns: 1, fields: ["area", "tags"] }] },
72
- } as EntityEditScreenDefinition;
73
-
74
- function viewModelField(entity: EntityDefinition, fieldName: string): EditFieldViewModel {
75
- const vm = computeEditViewModel({
76
- screen: editScreen,
77
- entity,
78
- values: { area: "jobs", tags: [] },
79
- translate: (key) => key,
80
- featureName: "helpdesk",
81
- });
82
- const section = vm.sections[0];
83
- if (section === undefined || section.kind !== "fields") {
84
- throw new Error("expected a fields section");
85
- }
86
- const field = section.fields.find((f) => f.field === fieldName);
87
- if (field === undefined) throw new Error(`expected a ${fieldName} field`);
88
- return field;
89
- }
90
-
91
- function renderField(field: EditFieldViewModel): InputProps {
92
- captured = undefined;
93
- render(
94
- <LocaleProvider resolver={createStaticLocaleResolver({ locale: "en-US" })}>
95
- <PrimitivesProvider value={testPrimitives}>
96
- <RenderField field={field} onChange={() => {}} />
97
- </PrimitivesProvider>
98
- </LocaleProvider>,
99
- );
100
- if (captured === undefined) throw new Error("field did not render an Input");
101
- return captured;
102
- }
103
-
104
- describe("RenderField — select display request", () => {
105
- test('display: "radio" on the field definition reaches the Input', () => {
106
- const field = viewModelField(buildEntity("radio"), "area");
107
- expect(field.display).toBe("radio");
108
- const input = renderField(field);
109
- expect(input.kind).toBe("select");
110
- if (input.kind !== "select") return;
111
- expect(input.display).toBe("radio");
112
- });
113
-
114
- test('display: "dropdown" on the field definition reaches the Input', () => {
115
- const input = renderField(viewModelField(buildEntity("dropdown"), "area"));
116
- expect(input.kind).toBe("select");
117
- if (input.kind !== "select") return;
118
- expect(input.display).toBe("dropdown");
119
- });
120
-
121
- test("no display on the field definition leaves the Input to its own default", () => {
122
- const field = viewModelField(buildEntity(undefined), "area");
123
- expect(field.display).toBeUndefined();
124
- const input = renderField(field);
125
- expect(input.kind).toBe("select");
126
- if (input.kind !== "select") return;
127
- expect(input.display).toBeUndefined();
128
- });
129
-
130
- test('multiSelect keeps its own "checkboxes" display untouched', () => {
131
- const field = viewModelField(buildEntity("radio"), "tags");
132
- expect(field.display).toBe("checkboxes");
133
- });
134
- });
@@ -1,107 +0,0 @@
1
- // fw#2187: readOnly fields with an explicit `field.renderer: { format: "unit" }`
2
- // must render through applyFormatSpec's unit case, and default to the
3
- // LocaleProvider's App-Locale when the FormatSpec itself carries no `locale`
4
- // — same posture as render-field-app-locale.test.tsx for money/date inputs,
5
- // but for the FieldRendererOutput (readOnly + declared renderer) path, which
6
- // that file doesn't cover.
7
- //
8
- // Capture-Text instead of a real primitive, same pattern as
9
- // render-field-app-locale.test.tsx (Capture-Input there).
10
-
11
- import { describe, expect, test } from "bun:test";
12
- import type { EditFieldViewModel } from "@cosmicdrift/kumiko-headless";
13
- import { render } from "@testing-library/react";
14
- import type { ComponentType, ReactNode } from "react";
15
- import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
16
- import { type CorePrimitives, PrimitivesProvider, type TextProps } from "../../primitives";
17
- import { RenderField } from "../render-field";
18
-
19
- let captured: TextProps | undefined;
20
- const captureText: ComponentType<TextProps> = (props) => {
21
- captured = props;
22
- return null;
23
- };
24
- const noop = (): ReactNode => null;
25
- const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
26
-
27
- const testPrimitives: CorePrimitives = {
28
- Button: noop,
29
- Banner: noop,
30
- Field: passChildren,
31
- Input: noop,
32
- DataTable: noop,
33
- Form: noop,
34
- Section: noop,
35
- Card: noop,
36
- Grid: noop,
37
- GridCell: noop,
38
- Text: captureText,
39
- Heading: noop,
40
- Dialog: noop,
41
- Modal: noop,
42
- Lightbox: noop,
43
- ConfigSourceBadge: noop,
44
- ConfigCascadeView: noop,
45
- Link: noop,
46
- };
47
-
48
- function livingSpaceField(
49
- renderer: EditFieldViewModel["renderer"],
50
- value: unknown = 58,
51
- ): EditFieldViewModel {
52
- return {
53
- field: "livingSpace",
54
- label: "Wohnfläche",
55
- type: "decimal",
56
- value,
57
- visible: true,
58
- readOnly: true,
59
- required: false,
60
- renderer,
61
- };
62
- }
63
-
64
- function renderUnderLocale(locale: string, field: EditFieldViewModel): void {
65
- captured = undefined;
66
- render(
67
- <LocaleProvider resolver={createStaticLocaleResolver({ locale })}>
68
- <PrimitivesProvider value={testPrimitives}>
69
- <RenderField field={field} onChange={() => {}} />
70
- </PrimitivesProvider>
71
- </LocaleProvider>,
72
- );
73
- }
74
-
75
- describe("RenderField — unit-FormatSpec (fw#2187)", () => {
76
- test("m2 rendert Zahl + m²-Suffix, locale-formatiert über das App-Locale", () => {
77
- renderUnderLocale("de-DE", livingSpaceField({ format: "unit", unit: "m2" }));
78
- expect(captured?.children).toBe("58 m²");
79
- });
80
-
81
- test("ein anderes App-Locale ändert die Zahl-Formatierung (en-US: Punkt statt Komma)", () => {
82
- renderUnderLocale("en-US", livingSpaceField({ format: "unit", unit: "m2" }, 58.5));
83
- expect(captured?.children).toBe("58.5 m²");
84
- });
85
-
86
- test("CLDR-sanktionierte Unit (km) rendert über Intl.NumberFormat(style:'unit')", () => {
87
- renderUnderLocale("en-US", livingSpaceField({ format: "unit", unit: "km" }, 3));
88
- expect(captured?.children).toBe("3 km");
89
- });
90
-
91
- test("explizites renderer.locale gewinnt gegen das App-Locale", () => {
92
- renderUnderLocale("en-US", livingSpaceField({ format: "unit", unit: "m2", locale: "de-DE" }));
93
- expect(captured?.children).toBe("58 m²");
94
- });
95
- });
96
-
97
- describe("RenderField — App-Locale an FieldRendererOutput durchreichen (fw#2187)", () => {
98
- test("number-FormatSpec ohne eigenes locale bekommt das App-Locale (de-DE, Komma statt Punkt)", () => {
99
- renderUnderLocale("de-DE", livingSpaceField({ format: "number" }, 1234.5));
100
- expect(captured?.children).toBe("1.234,5");
101
- });
102
-
103
- test("explicit locale: undefined still falls back to App-Locale (#2332)", () => {
104
- renderUnderLocale("de-DE", livingSpaceField({ format: "number", locale: undefined }, 1234.5));
105
- expect(captured?.children).toBe("1.234,5");
106
- });
107
- });