@cosmicdrift/kumiko-renderer 0.328.0 → 0.328.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/action-form-shim.d.ts +14 -0
- package/{src/app/action-form-shim.ts → dist/app/action-form-shim.js} +26 -46
- package/dist/app/app-features-context.d.ts +8 -0
- package/dist/app/app-features-context.js +16 -0
- package/dist/app/column-renderers.d.ts +30 -0
- package/dist/app/column-renderers.js +34 -0
- package/dist/app/config-edit-shim.d.ts +11 -0
- package/{src/app/config-edit-shim.ts → dist/app/config-edit-shim.js} +12 -25
- package/dist/app/content-editors.d.ts +38 -0
- package/dist/app/content-editors.js +26 -0
- package/dist/app/content-preview.d.ts +11 -0
- package/dist/app/content-preview.js +30 -0
- package/dist/app/custom-screens.d.ts +11 -0
- package/dist/app/custom-screens.js +20 -0
- package/dist/app/dashboard-body.d.ts +16 -0
- package/dist/app/dashboard-body.js +11 -0
- package/dist/app/extension-form-submit.d.ts +32 -0
- package/dist/app/extension-form-submit.js +73 -0
- package/dist/app/extension-sections.d.ts +72 -0
- package/dist/app/extension-sections.js +34 -0
- package/dist/app/feature-schema.d.ts +4 -0
- package/{src/app/feature-schema.ts → dist/app/feature-schema.js} +12 -22
- package/dist/app/form-schema.d.ts +4 -0
- package/dist/app/form-schema.js +85 -0
- package/dist/app/kumiko-screen.d.ts +43 -0
- package/dist/app/kumiko-screen.js +2206 -0
- package/dist/app/layout-fields.d.ts +3 -0
- package/dist/app/layout-fields.js +20 -0
- package/dist/app/list-facets.d.ts +48 -0
- package/dist/app/list-facets.js +185 -0
- package/dist/app/nav.d.ts +61 -0
- package/dist/app/nav.js +139 -0
- package/dist/app/projection-detail-shim.d.ts +8 -0
- package/dist/app/projection-detail-shim.js +71 -0
- package/dist/app/projection-list-shim.d.ts +13 -0
- package/{src/app/projection-list-shim.ts → dist/app/projection-list-shim.js} +26 -41
- package/dist/app/qn.d.ts +7 -0
- package/{src/app/qn.ts → dist/app/qn.js} +9 -11
- package/dist/app/qualify-screen-id.d.ts +2 -0
- package/{src/app/qualify-screen-id.ts → dist/app/qualify-screen-id.js} +5 -6
- package/dist/app/reference-facet-bridge.d.ts +10 -0
- package/dist/app/reference-facet-bridge.js +22 -0
- package/dist/app/return-to.d.ts +31 -0
- package/dist/app/return-to.js +188 -0
- package/dist/app/row-actions.d.ts +77 -0
- package/dist/app/row-actions.js +416 -0
- package/dist/app/screen-access.d.ts +8 -0
- package/dist/app/screen-access.js +48 -0
- package/dist/app/secret-mint-body.d.ts +10 -0
- package/dist/app/secret-mint-body.js +140 -0
- package/dist/app/secrets-edit-body.d.ts +8 -0
- package/dist/app/secrets-edit-body.js +76 -0
- package/dist/app/use-embedded-screen.d.ts +6 -0
- package/dist/app/use-embedded-screen.js +19 -0
- package/dist/app/variable-chips.d.ts +7 -0
- package/dist/app/variable-chips.js +16 -0
- package/dist/app/write-failed-error.d.ts +6 -0
- package/dist/app/write-failed-error.js +21 -0
- package/dist/components/embedded-list-field.d.ts +10 -0
- package/dist/components/embedded-list-field.js +259 -0
- package/dist/components/grid-cell-for-field.d.ts +25 -0
- package/dist/components/grid-cell-for-field.js +31 -0
- package/dist/components/multi-select-checkboxes.d.ts +14 -0
- package/dist/components/multi-select-checkboxes.js +36 -0
- package/dist/components/reference-create-dialog.d.ts +13 -0
- package/dist/components/reference-create-dialog.js +46 -0
- package/dist/components/related-list-section.d.ts +25 -0
- package/dist/components/related-list-section.js +273 -0
- package/dist/components/render-edit-action-button.d.ts +22 -0
- package/dist/components/render-edit-action-button.js +43 -0
- package/dist/components/render-edit-logic.d.ts +6 -0
- package/dist/components/render-edit-logic.js +63 -0
- package/dist/components/render-edit-types.d.ts +209 -0
- package/dist/components/render-edit-types.js +1 -0
- package/dist/components/render-edit.d.ts +6 -0
- package/dist/components/render-edit.js +1169 -0
- package/dist/components/render-field.d.ts +38 -0
- package/dist/components/render-field.js +544 -0
- package/dist/components/render-list.d.ts +117 -0
- package/dist/components/render-list.js +232 -0
- package/dist/components/write-form-section.d.ts +17 -0
- package/dist/components/write-form-section.js +104 -0
- package/dist/context/dispatcher-context.d.ts +10 -0
- package/{src/context/dispatcher-context.tsx → dist/context/dispatcher-context.js} +16 -29
- package/dist/context/draft-storage-context.d.ts +12 -0
- package/dist/context/draft-storage-context.js +18 -0
- package/dist/context/user-roles-context.d.ts +7 -0
- package/{src/context/user-roles-context.tsx → dist/context/user-roles-context.js} +7 -15
- package/dist/format-when.d.ts +1 -0
- package/{src/format-when.ts → dist/format-when.js} +7 -7
- package/dist/hooks/reference-limits.d.ts +3 -0
- package/{src/hooks/reference-limits.ts → dist/hooks/reference-limits.js} +0 -1
- package/dist/hooks/use-ai-text.d.ts +52 -0
- package/dist/hooks/use-ai-text.js +90 -0
- package/dist/hooks/use-disclosure.d.ts +7 -0
- package/dist/hooks/use-disclosure.js +8 -0
- package/dist/hooks/use-form.d.ts +7 -0
- package/dist/hooks/use-form.js +36 -0
- package/dist/hooks/use-list-url-state.d.ts +43 -0
- package/dist/hooks/use-list-url-state.js +131 -0
- package/dist/hooks/use-mutation.d.ts +9 -0
- package/dist/hooks/use-mutation.js +37 -0
- package/dist/hooks/use-query.d.ts +14 -0
- package/dist/hooks/use-query.js +118 -0
- package/dist/hooks/use-reference-lookup.d.ts +14 -0
- package/dist/hooks/use-reference-lookup.js +63 -0
- package/dist/hooks/use-store.d.ts +3 -0
- package/dist/hooks/use-store.js +38 -0
- package/dist/hooks/use-stream-handler.d.ts +14 -0
- package/dist/hooks/use-stream-handler.js +93 -0
- package/dist/i18n-defaults.d.ts +2 -0
- package/dist/i18n-defaults.js +201 -0
- package/dist/i18n.d.ts +64 -0
- package/dist/i18n.js +166 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +55 -0
- package/dist/inside-drawer.d.ts +2 -0
- package/{src/inside-drawer.ts → dist/inside-drawer.js} +2 -5
- package/dist/page-header-slot.d.ts +2 -0
- package/{src/page-header-slot.ts → dist/page-header-slot.js} +2 -5
- package/dist/primitives.d.ts +1351 -0
- package/dist/primitives.js +73 -0
- package/dist/screen-fills-height.d.ts +3 -0
- package/dist/screen-fills-height.js +3 -0
- package/dist/share-links.d.ts +4 -0
- package/dist/share-links.js +11 -0
- package/dist/sort-by-accessor.d.ts +5 -0
- package/dist/sort-by-accessor.js +16 -0
- package/dist/sse/live-events.d.ts +23 -0
- package/dist/sse/live-events.js +28 -0
- package/dist/tokens.d.ts +83 -0
- package/dist/tokens.js +80 -0
- package/package.json +16 -9
- package/src/__tests__/error-i18n-defaults.test.ts +0 -82
- package/src/__tests__/format-when.test.ts +0 -12
- package/src/__tests__/i18n.test.tsx +0 -283
- package/src/__tests__/merge-search-params-into-initial.test.ts +0 -525
- package/src/__tests__/qn.test.ts +0 -83
- package/src/__tests__/share-links.test.ts +0 -26
- package/src/__tests__/sort-by-accessor.test.ts +0 -48
- package/src/__tests__/stringify-nav-params.test.ts +0 -33
- package/src/__tests__/use-list-url-state.test.tsx +0 -250
- package/src/app/__tests__/action-form-field-labels.test.tsx +0 -92
- package/src/app/__tests__/action-form-shim.test.ts +0 -137
- package/src/app/__tests__/build-list-query-payload.test.ts +0 -107
- package/src/app/__tests__/config-edit-shim.test.ts +0 -57
- package/src/app/__tests__/content-editors.test.tsx +0 -128
- package/src/app/__tests__/content-preview.test.tsx +0 -184
- package/src/app/__tests__/entity-edit-create-search-params.test.tsx +0 -236
- package/src/app/__tests__/entity-edit-fill-height.test.tsx +0 -118
- package/src/app/__tests__/entity-edit-jsonb-embedded-submit.test.tsx +0 -239
- package/src/app/__tests__/entity-list-create-singleton.test.tsx +0 -128
- package/src/app/__tests__/entity-list-row-action-entity-target.test.tsx +0 -424
- package/src/app/__tests__/entity-list-row-action-icon.test.tsx +0 -248
- package/src/app/__tests__/entity-list-row-action-kumiko-actions-view.test.tsx +0 -162
- package/src/app/__tests__/entity-list-row-action-refetch.test.tsx +0 -401
- package/src/app/__tests__/entity-list-screen-padding.test.tsx +0 -128
- package/src/app/__tests__/feature-schema.test.ts +0 -40
- package/src/app/__tests__/form-schema.test.ts +0 -247
- package/src/app/__tests__/list-daterange-facet.test.ts +0 -165
- package/src/app/__tests__/list-filter-facets.test.tsx +0 -521
- package/src/app/__tests__/nav.test.ts +0 -198
- package/src/app/__tests__/projection-detail-actions.test.tsx +0 -719
- package/src/app/__tests__/projection-detail-reference-field.test.tsx +0 -165
- package/src/app/__tests__/projection-detail-shim.test.ts +0 -188
- package/src/app/__tests__/projection-detail-singleton.test.tsx +0 -191
- package/src/app/__tests__/projection-list-bad-shape.test.tsx +0 -152
- package/src/app/__tests__/projection-list-reference-column.test.tsx +0 -219
- package/src/app/__tests__/projection-list-search-sort.test.tsx +0 -212
- package/src/app/__tests__/qualified-names.test.ts +0 -37
- package/src/app/__tests__/return-to.test.ts +0 -396
- package/src/app/__tests__/row-actions-navigate-tab.test.ts +0 -223
- package/src/app/__tests__/row-default-edit-action.test.tsx +0 -324
- package/src/app/__tests__/screen-access-allows.test.ts +0 -34
- package/src/app/__tests__/secret-mint-body.test.tsx +0 -404
- package/src/app/__tests__/variable-chips.test.tsx +0 -70
- package/src/app/app-features-context.tsx +0 -25
- package/src/app/column-renderers.tsx +0 -68
- package/src/app/content-editors.tsx +0 -85
- package/src/app/content-preview.tsx +0 -67
- package/src/app/custom-screens.tsx +0 -29
- package/src/app/dashboard-body.tsx +0 -33
- package/src/app/extension-form-submit.tsx +0 -123
- package/src/app/extension-sections.tsx +0 -111
- package/src/app/form-schema.ts +0 -91
- package/src/app/kumiko-screen.tsx +0 -3801
- package/src/app/layout-fields.ts +0 -31
- package/src/app/list-facets.ts +0 -259
- package/src/app/nav.tsx +0 -258
- package/src/app/projection-detail-shim.ts +0 -86
- package/src/app/reference-facet-bridge.tsx +0 -58
- package/src/app/return-to.tsx +0 -234
- package/src/app/row-actions.ts +0 -640
- package/src/app/screen-access.ts +0 -63
- package/src/app/secret-mint-body.tsx +0 -259
- package/src/app/secrets-edit-body.tsx +0 -194
- package/src/app/use-embedded-screen.ts +0 -27
- package/src/app/variable-chips.tsx +0 -39
- package/src/app/write-failed-error.ts +0 -27
- package/src/components/__tests__/embedded-list-field.test.tsx +0 -531
- package/src/components/__tests__/multi-select-checkboxes.test.tsx +0 -162
- package/src/components/__tests__/related-list-section-empty-state.test.tsx +0 -143
- package/src/components/__tests__/related-list-section.test.tsx +0 -1733
- package/src/components/__tests__/render-edit-action-button.test.tsx +0 -205
- package/src/components/__tests__/render-edit-hidden-section.test.tsx +0 -128
- package/src/components/__tests__/render-edit-logic.test.ts +0 -314
- package/src/components/__tests__/render-edit-screen-description.test.tsx +0 -152
- package/src/components/__tests__/render-edit-submit-actions.test.tsx +0 -460
- package/src/components/__tests__/render-field-app-locale.test.tsx +0 -142
- package/src/components/__tests__/render-field-enum-option.test.tsx +0 -99
- package/src/components/__tests__/render-field-icon-derivation.test.tsx +0 -119
- package/src/components/__tests__/render-field-json-format.test.tsx +0 -164
- package/src/components/__tests__/render-field-money-embedded-list-consistency.test.tsx +0 -212
- package/src/components/__tests__/render-field-money-roundtrip.test.tsx +0 -217
- package/src/components/__tests__/render-field-number-grouping.test.tsx +0 -76
- package/src/components/__tests__/render-field-number-unit.test.tsx +0 -227
- package/src/components/__tests__/render-field-password-format.test.tsx +0 -101
- package/src/components/__tests__/render-field-select-display.test.tsx +0 -134
- package/src/components/__tests__/render-field-unit-format.test.tsx +0 -107
- package/src/components/__tests__/render-field-unsupported-types.test.tsx +0 -195
- package/src/components/__tests__/render-list-toolbar-icon-only.test.tsx +0 -146
- package/src/components/__tests__/write-form-section.test.tsx +0 -375
- package/src/components/embedded-list-field.tsx +0 -354
- package/src/components/grid-cell-for-field.tsx +0 -88
- package/src/components/multi-select-checkboxes.tsx +0 -93
- package/src/components/reference-create-dialog.tsx +0 -91
- package/src/components/related-list-section.tsx +0 -491
- package/src/components/render-edit-action-button.tsx +0 -109
- package/src/components/render-edit-logic.ts +0 -85
- package/src/components/render-edit-types.ts +0 -226
- package/src/components/render-edit.tsx +0 -1871
- package/src/components/render-field.tsx +0 -951
- package/src/components/render-list.tsx +0 -593
- package/src/components/write-form-section.tsx +0 -198
- package/src/context/draft-storage-context.tsx +0 -39
- package/src/hooks/__tests__/use-ai-text.test.tsx +0 -177
- package/src/hooks/__tests__/use-disclosure.test.tsx +0 -25
- package/src/hooks/__tests__/use-form.test.tsx +0 -65
- package/src/hooks/__tests__/use-mutation.test.tsx +0 -77
- package/src/hooks/__tests__/use-query.test.tsx +0 -149
- package/src/hooks/__tests__/use-stream-handler.test.tsx +0 -107
- package/src/hooks/use-ai-text.ts +0 -172
- package/src/hooks/use-disclosure.ts +0 -20
- package/src/hooks/use-form.ts +0 -78
- package/src/hooks/use-list-url-state.ts +0 -207
- package/src/hooks/use-mutation.ts +0 -61
- package/src/hooks/use-query.ts +0 -166
- package/src/hooks/use-reference-lookup.ts +0 -76
- package/src/hooks/use-store.ts +0 -48
- package/src/hooks/use-stream-handler.ts +0 -134
- package/src/i18n-defaults.ts +0 -240
- package/src/i18n.tsx +0 -286
- package/src/index.ts +0 -279
- package/src/primitives.tsx +0 -1567
- package/src/screen-fills-height.ts +0 -3
- package/src/share-links.ts +0 -12
- package/src/sort-by-accessor.ts +0 -20
- package/src/sse/live-events.tsx +0 -56
- package/src/tokens.tsx +0 -170
|
@@ -1,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
|
-
});
|