@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,128 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import type { ContentEditorFormat } from "@cosmicdrift/kumiko-types/nav";
|
|
3
|
-
import { render, screen } from "@testing-library/react";
|
|
4
|
-
import type { ComponentType, ReactNode } from "react";
|
|
5
|
-
import { type CorePrimitives, type InputProps, PrimitivesProvider } from "../../primitives";
|
|
6
|
-
import {
|
|
7
|
-
CONTENT_EDITOR_ELEMENT_ID,
|
|
8
|
-
type ContentEditorProps,
|
|
9
|
-
ContentEditorsProvider,
|
|
10
|
-
TextareaContentEditor,
|
|
11
|
-
useContentEditor,
|
|
12
|
-
} from "../content-editors";
|
|
13
|
-
|
|
14
|
-
const captureInput: ComponentType<InputProps> = (props) => {
|
|
15
|
-
if (props.kind !== "textarea") return null;
|
|
16
|
-
return (
|
|
17
|
-
<textarea
|
|
18
|
-
data-testid="ca-textarea"
|
|
19
|
-
id={props.id}
|
|
20
|
-
value={props.value}
|
|
21
|
-
disabled={props.disabled}
|
|
22
|
-
readOnly={props.readOnly}
|
|
23
|
-
onChange={() => {}}
|
|
24
|
-
/>
|
|
25
|
-
);
|
|
26
|
-
};
|
|
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: passChildren,
|
|
34
|
-
Field: passChildren,
|
|
35
|
-
Input: captureInput,
|
|
36
|
-
DataTable: noop,
|
|
37
|
-
Form: passChildren,
|
|
38
|
-
Section: passChildren,
|
|
39
|
-
Card: passChildren,
|
|
40
|
-
Grid: passChildren,
|
|
41
|
-
GridCell: passChildren,
|
|
42
|
-
Text: passChildren,
|
|
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 RichEditor({ value }: ContentEditorProps): ReactNode {
|
|
53
|
-
return <div data-testid="ca-rich">{value}</div>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function Wrapper({ children }: { readonly children: ReactNode }): ReactNode {
|
|
57
|
-
return <PrimitivesProvider value={testPrimitives}>{children}</PrimitivesProvider>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function Probe({ contentFormat }: { readonly contentFormat?: ContentEditorFormat }): ReactNode {
|
|
61
|
-
const Editor = useContentEditor(contentFormat);
|
|
62
|
-
return (
|
|
63
|
-
<Editor
|
|
64
|
-
id={CONTENT_EDITOR_ELEMENT_ID}
|
|
65
|
-
value="hello"
|
|
66
|
-
onChange={() => {}}
|
|
67
|
-
variables={[]}
|
|
68
|
-
readOnly={false}
|
|
69
|
-
/>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
describe("useContentEditor", () => {
|
|
74
|
-
test("no editor registered for the format → falls back to the textarea", () => {
|
|
75
|
-
render(<Probe contentFormat="plain" />, { wrapper: Wrapper });
|
|
76
|
-
expect(screen.getByTestId("ca-textarea")).toBeTruthy();
|
|
77
|
-
expect(screen.getByTestId("ca-textarea").id).toBe(CONTENT_EDITOR_ELEMENT_ID);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("undefined contentFormat behaves like plain", () => {
|
|
81
|
-
render(<Probe />, { wrapper: Wrapper });
|
|
82
|
-
expect(screen.getByTestId("ca-textarea")).toBeTruthy();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("an app-registered editor for the format wins over the textarea fallback", () => {
|
|
86
|
-
render(
|
|
87
|
-
<ContentEditorsProvider value={{ rich: RichEditor }}>
|
|
88
|
-
<Probe contentFormat="rich" />
|
|
89
|
-
</ContentEditorsProvider>,
|
|
90
|
-
{ wrapper: Wrapper },
|
|
91
|
-
);
|
|
92
|
-
expect(screen.getByTestId("ca-rich").textContent).toBe("hello");
|
|
93
|
-
expect(screen.queryByTestId("ca-textarea")).toBeNull();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("a registered editor only applies to its own format — other formats stay on the fallback", () => {
|
|
97
|
-
render(
|
|
98
|
-
<ContentEditorsProvider value={{ rich: RichEditor }}>
|
|
99
|
-
<Probe contentFormat="plain" />
|
|
100
|
-
</ContentEditorsProvider>,
|
|
101
|
-
{ wrapper: Wrapper },
|
|
102
|
-
);
|
|
103
|
-
expect(screen.getByTestId("ca-textarea")).toBeTruthy();
|
|
104
|
-
expect(screen.queryByTestId("ca-rich")).toBeNull();
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe("TextareaContentEditor", () => {
|
|
109
|
-
test("passes value + readOnly through to the primitives Input as real readOnly, not disabled", () => {
|
|
110
|
-
// A disabled textarea isn't focusable/selectable in most browsers — a
|
|
111
|
-
// non-admin viewing read-only content couldn't select/copy it. readOnly
|
|
112
|
-
// keeps the field focusable and selectable while blocking edits.
|
|
113
|
-
render(
|
|
114
|
-
<TextareaContentEditor
|
|
115
|
-
id={CONTENT_EDITOR_ELEMENT_ID}
|
|
116
|
-
value="draft"
|
|
117
|
-
onChange={() => {}}
|
|
118
|
-
variables={[]}
|
|
119
|
-
readOnly={true}
|
|
120
|
-
/>,
|
|
121
|
-
{ wrapper: Wrapper },
|
|
122
|
-
);
|
|
123
|
-
const el = screen.getByTestId("ca-textarea") as HTMLTextAreaElement;
|
|
124
|
-
expect(el.value).toBe("draft");
|
|
125
|
-
expect(el.readOnly).toBe(true);
|
|
126
|
-
expect(el.disabled).toBe(false);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
3
|
-
import type { ComponentType, ReactNode } from "react";
|
|
4
|
-
import { type CorePrimitives, type InputProps, PrimitivesProvider } from "../../primitives";
|
|
5
|
-
import { type ContentEditorProps, ContentEditorsProvider } from "../content-editors";
|
|
6
|
-
import { ContentPreview, substituteVariables } from "../content-preview";
|
|
7
|
-
|
|
8
|
-
const noop = (): ReactNode => null;
|
|
9
|
-
const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
|
|
10
|
-
|
|
11
|
-
const captureInput: ComponentType<InputProps> = (props) => {
|
|
12
|
-
if (props.kind !== "textarea") return null;
|
|
13
|
-
return (
|
|
14
|
-
<textarea data-testid="cp-textarea" value={props.value} disabled={props.disabled} readOnly />
|
|
15
|
-
);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const testPrimitives: CorePrimitives = {
|
|
19
|
-
Button: noop,
|
|
20
|
-
Banner: passChildren,
|
|
21
|
-
Field: passChildren,
|
|
22
|
-
Input: captureInput,
|
|
23
|
-
DataTable: noop,
|
|
24
|
-
Form: passChildren,
|
|
25
|
-
Section: passChildren,
|
|
26
|
-
Card: passChildren,
|
|
27
|
-
Grid: passChildren,
|
|
28
|
-
GridCell: passChildren,
|
|
29
|
-
Text: passChildren,
|
|
30
|
-
Heading: noop,
|
|
31
|
-
Dialog: noop,
|
|
32
|
-
Modal: noop,
|
|
33
|
-
Lightbox: noop,
|
|
34
|
-
ConfigSourceBadge: noop,
|
|
35
|
-
ConfigCascadeView: noop,
|
|
36
|
-
Link: noop,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
function Wrapper({ children }: { readonly children: ReactNode }): ReactNode {
|
|
40
|
-
return <PrimitivesProvider value={testPrimitives}>{children}</PrimitivesProvider>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe("substituteVariables", () => {
|
|
44
|
-
test("replaces every {{name}} with its example value", () => {
|
|
45
|
-
expect(
|
|
46
|
-
substituteVariables("Hi {{customerName}}, order {{orderId}} shipped.", {
|
|
47
|
-
customerName: "Max Mustermann",
|
|
48
|
-
orderId: "A-1042",
|
|
49
|
-
}),
|
|
50
|
-
).toBe("Hi Max Mustermann, order A-1042 shipped.");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("a name with no example value stays as the literal placeholder", () => {
|
|
54
|
-
expect(substituteVariables("Hi {{customerName}}", {})).toBe("Hi {{customerName}}");
|
|
55
|
-
expect(substituteVariables("Hi {{constructor}}", {})).toBe("Hi {{constructor}}");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("no variables in the content → content passes through unchanged", () => {
|
|
59
|
-
expect(substituteVariables("Just plain text.", { customerName: "Max" })).toBe(
|
|
60
|
-
"Just plain text.",
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe("ContentPreview", () => {
|
|
66
|
-
test("rich format escapes attribute-breaking quotes in substituted values", () => {
|
|
67
|
-
function RichEditor({ value }: ContentEditorProps): ReactNode {
|
|
68
|
-
// biome-ignore lint/security/noDangerouslySetInnerHtml: XSS regression fixture for escapeHtml
|
|
69
|
-
return <div data-testid="rich" dangerouslySetInnerHTML={{ __html: value }} />;
|
|
70
|
-
}
|
|
71
|
-
render(
|
|
72
|
-
<Wrapper>
|
|
73
|
-
<ContentEditorsProvider value={{ rich: RichEditor }}>
|
|
74
|
-
<ContentPreview
|
|
75
|
-
content={'<a href="{{url}}">x</a>'}
|
|
76
|
-
variables={{ url: '" onerror="x' }}
|
|
77
|
-
contentFormat="rich"
|
|
78
|
-
/>
|
|
79
|
-
</ContentEditorsProvider>
|
|
80
|
-
</Wrapper>,
|
|
81
|
-
);
|
|
82
|
-
const html = screen.getByTestId("rich").innerHTML;
|
|
83
|
-
expect(html).toContain(""");
|
|
84
|
-
expect(html).not.toContain('onerror="x');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("renders the format's registered editor read-only, with variables substituted", () => {
|
|
88
|
-
function RichEditor({ value, readOnly }: ContentEditorProps): ReactNode {
|
|
89
|
-
return (
|
|
90
|
-
<div data-testid="cp-rich" data-readonly={readOnly}>
|
|
91
|
-
{value}
|
|
92
|
-
</div>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
render(
|
|
97
|
-
<ContentEditorsProvider value={{ rich: RichEditor }}>
|
|
98
|
-
<ContentPreview
|
|
99
|
-
content="Hallo {{customerName}}"
|
|
100
|
-
variables={{ customerName: "Max Mustermann" }}
|
|
101
|
-
contentFormat="rich"
|
|
102
|
-
/>
|
|
103
|
-
</ContentEditorsProvider>,
|
|
104
|
-
{ wrapper: Wrapper },
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const el = screen.getByTestId("cp-rich");
|
|
108
|
-
expect(el.textContent).toBe("Hallo Max Mustermann");
|
|
109
|
-
expect(el.dataset["readonly"]).toBe("true");
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("no editor registered for the format → falls back to the textarea, still substituted", () => {
|
|
113
|
-
render(
|
|
114
|
-
<ContentPreview
|
|
115
|
-
content="Hi {{orderId}}"
|
|
116
|
-
variables={{ orderId: "A-1042" }}
|
|
117
|
-
contentFormat="plain"
|
|
118
|
-
/>,
|
|
119
|
-
{ wrapper: Wrapper },
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
const el = screen.getByTestId("cp-textarea") as HTMLTextAreaElement;
|
|
123
|
-
expect(el.value).toBe("Hi A-1042");
|
|
124
|
-
expect(el.readOnly).toBe(true);
|
|
125
|
-
expect(el.disabled).toBe(false);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("rich format: an example value with markup characters is escaped, not injected as HTML", () => {
|
|
129
|
-
function RichEditor({ value }: ContentEditorProps): ReactNode {
|
|
130
|
-
// biome-ignore lint/security/noDangerouslySetInnerHtml: proves the substituted value is escaped, not injected — the whole point of this test.
|
|
131
|
-
return <div data-testid="cp-rich" dangerouslySetInnerHTML={{ __html: value }} />;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
render(
|
|
135
|
-
<ContentEditorsProvider value={{ rich: RichEditor }}>
|
|
136
|
-
<ContentPreview
|
|
137
|
-
content="Preis: {{price}}"
|
|
138
|
-
variables={{ price: "<b>0</b> & up" }}
|
|
139
|
-
contentFormat="rich"
|
|
140
|
-
/>
|
|
141
|
-
</ContentEditorsProvider>,
|
|
142
|
-
{ wrapper: Wrapper },
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
const el = screen.getByTestId("cp-rich");
|
|
146
|
-
expect(el.querySelector("b")).toBeNull();
|
|
147
|
-
expect(el.textContent).toBe("Preis: <b>0</b> & up");
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// Known limitation: escaping is keyed off the *declared* format, not the
|
|
151
|
-
// actually-resolved editor. If "rich" is declared but no rich editor is
|
|
152
|
-
// registered, useContentEditor falls back to the plain textarea and the
|
|
153
|
-
// markup-safe escaping (only applied for contentFormat === "rich") never
|
|
154
|
-
// ran for the plain path — entities show up literal instead of rendered.
|
|
155
|
-
// Pinned here rather than fixed: fixing it needs a per-editor
|
|
156
|
-
// `rendersHtml` flag threaded through content-editors.tsx.
|
|
157
|
-
test("rich format with no registered editor → falls back to the textarea, entities shown literally", () => {
|
|
158
|
-
render(
|
|
159
|
-
<ContentPreview
|
|
160
|
-
content="Preis: {{price}}"
|
|
161
|
-
variables={{ price: "<b>0</b> & up" }}
|
|
162
|
-
contentFormat="rich"
|
|
163
|
-
/>,
|
|
164
|
-
{ wrapper: Wrapper },
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
const el = screen.getByTestId("cp-textarea") as HTMLTextAreaElement;
|
|
168
|
-
expect(el.value).toBe("Preis: <b>0</b> & up");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("plain format: an example value with markup characters passes through unescaped", () => {
|
|
172
|
-
render(
|
|
173
|
-
<ContentPreview
|
|
174
|
-
content="Preis: {{price}}"
|
|
175
|
-
variables={{ price: "<b>0</b>" }}
|
|
176
|
-
contentFormat="plain"
|
|
177
|
-
/>,
|
|
178
|
-
{ wrapper: Wrapper },
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const el = screen.getByTestId("cp-textarea") as HTMLTextAreaElement;
|
|
182
|
-
expect(el.value).toBe("Preis: <b>0</b>");
|
|
183
|
-
});
|
|
184
|
-
});
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
// Issue #1680: navigate params on entityEdit-create were documented as
|
|
2
|
-
// "will be read" but EntityEditCreateBody never consulted nav.searchParams —
|
|
3
|
-
// only buildInitialValues(entity.fields). A rowAction navigate with params
|
|
4
|
-
// to an entityEdit target therefore opened an empty form with no boot error.
|
|
5
|
-
// This test renders the real create path (KumikoScreen → EntityEditScreen →
|
|
6
|
-
// EntityEditCreateBody → RenderEdit → RenderField) under a NavProvider with
|
|
7
|
-
// searchParams set and asserts the input is prefilled — not just the helper
|
|
8
|
-
// in isolation.
|
|
9
|
-
import { describe, expect, test } from "bun:test";
|
|
10
|
-
import type {
|
|
11
|
-
EntityDefinition,
|
|
12
|
-
EntityEditScreenDefinition,
|
|
13
|
-
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
14
|
-
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
15
|
-
import { act, render } from "@testing-library/react";
|
|
16
|
-
import { type ComponentType, type ReactNode, useState } from "react";
|
|
17
|
-
import { DispatcherProvider } from "../../context/dispatcher-context";
|
|
18
|
-
import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
|
|
19
|
-
import { type CorePrimitives, type InputProps, PrimitivesProvider } from "../../primitives";
|
|
20
|
-
import type { FeatureSchema } from "../feature-schema";
|
|
21
|
-
import { KumikoScreen } from "../kumiko-screen";
|
|
22
|
-
import { type NavApi, NavProvider, type NavTarget, useNavigateWithInitialValues } from "../nav";
|
|
23
|
-
|
|
24
|
-
const captured: Record<string, InputProps | undefined> = {};
|
|
25
|
-
const captureInput: ComponentType<InputProps> = (props) => {
|
|
26
|
-
captured[props.name] = 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: passChildren,
|
|
35
|
-
Field: passChildren,
|
|
36
|
-
Input: captureInput,
|
|
37
|
-
DataTable: noop,
|
|
38
|
-
Form: passChildren,
|
|
39
|
-
Section: passChildren,
|
|
40
|
-
Card: passChildren,
|
|
41
|
-
Grid: passChildren,
|
|
42
|
-
GridCell: passChildren,
|
|
43
|
-
Text: passChildren,
|
|
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 stubDispatcher(): Dispatcher {
|
|
54
|
-
return {
|
|
55
|
-
write: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["write"],
|
|
56
|
-
query: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["query"],
|
|
57
|
-
batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
|
|
58
|
-
statusStore: {
|
|
59
|
-
getState: () => "online",
|
|
60
|
-
subscribe: () => () => {},
|
|
61
|
-
} as unknown as Dispatcher["statusStore"],
|
|
62
|
-
async *stream() {},
|
|
63
|
-
pendingWrites: () => [],
|
|
64
|
-
pendingFiles: () => [],
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function buildSchema(urlPrefillFields: readonly string[] | undefined): FeatureSchema {
|
|
69
|
-
const entity: EntityDefinition = {
|
|
70
|
-
fields: {
|
|
71
|
-
name: { type: "text", maxLength: 200, required: false, searchable: false, sortable: false },
|
|
72
|
-
floorCount: { type: "number", required: false, sortable: false },
|
|
73
|
-
ownerEmail: { type: "text", maxLength: 200, required: false, searchable: false },
|
|
74
|
-
accessCode: {
|
|
75
|
-
type: "text",
|
|
76
|
-
maxLength: 200,
|
|
77
|
-
required: false,
|
|
78
|
-
searchable: false,
|
|
79
|
-
sensitive: true,
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
const screen: EntityEditScreenDefinition = {
|
|
84
|
-
id: "unit-edit",
|
|
85
|
-
type: "entityEdit",
|
|
86
|
-
entity: "unit",
|
|
87
|
-
layout: {
|
|
88
|
-
sections: [{ columns: 1, fields: ["name", "floorCount", "ownerEmail", "accessCode"] }],
|
|
89
|
-
},
|
|
90
|
-
...(urlPrefillFields !== undefined && { urlPrefillFields }),
|
|
91
|
-
};
|
|
92
|
-
return {
|
|
93
|
-
featureName: "housing",
|
|
94
|
-
entities: { unit: entity },
|
|
95
|
-
screens: [screen],
|
|
96
|
-
} as FeatureSchema;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function staticNav(searchParams: Record<string, string>): NavApi {
|
|
100
|
-
return {
|
|
101
|
-
route: { screenId: "housing:unit-edit" },
|
|
102
|
-
navigate: () => {},
|
|
103
|
-
replace: () => {},
|
|
104
|
-
hrefFor: () => "",
|
|
105
|
-
searchParams,
|
|
106
|
-
setSearchParams: () => {},
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function renderWithNav(nav: NavApi, schema: FeatureSchema): void {
|
|
111
|
-
render(
|
|
112
|
-
<LocaleProvider resolver={createStaticLocaleResolver({ locale: "de-DE" })}>
|
|
113
|
-
<DispatcherProvider dispatcher={stubDispatcher()}>
|
|
114
|
-
<NavProvider value={nav}>
|
|
115
|
-
<PrimitivesProvider value={testPrimitives}>
|
|
116
|
-
<KumikoScreen schema={schema} qn="housing:screen:unit-edit" />
|
|
117
|
-
</PrimitivesProvider>
|
|
118
|
-
</NavProvider>
|
|
119
|
-
</DispatcherProvider>
|
|
120
|
-
</LocaleProvider>,
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function resetCaptured(): void {
|
|
125
|
-
for (const key of Object.keys(captured)) delete captured[key];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
describe("EntityEditCreateBody — navigate params as initial values (#1680)", () => {
|
|
129
|
-
test("URL searchParams from rowAction navigate prefill the create form", () => {
|
|
130
|
-
resetCaptured();
|
|
131
|
-
renderWithNav(
|
|
132
|
-
staticNav({ name: "Erdgeschoss", floorCount: "3" }),
|
|
133
|
-
buildSchema(["name", "floorCount"]),
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
expect(captured["name"]?.value).toBe("Erdgeschoss");
|
|
137
|
-
expect(captured["floorCount"]?.value).toBe(3);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("without a matching searchParam the field keeps its default (empty)", () => {
|
|
141
|
-
resetCaptured();
|
|
142
|
-
renderWithNav(staticNav({}), buildSchema(["name"]));
|
|
143
|
-
|
|
144
|
-
expect(captured["name"]?.value).toBe("");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("a crafted link cannot prefill a field outside urlPrefillFields", () => {
|
|
148
|
-
resetCaptured();
|
|
149
|
-
renderWithNav(
|
|
150
|
-
staticNav({ name: "Erdgeschoss", ownerEmail: "attacker@example.com" }),
|
|
151
|
-
buildSchema(["name"]),
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
expect(captured["name"]?.value).toBe("Erdgeschoss");
|
|
155
|
-
expect(captured["ownerEmail"]?.value).toBe("");
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("a screen without urlPrefillFields takes nothing from the URL", () => {
|
|
159
|
-
resetCaptured();
|
|
160
|
-
renderWithNav(staticNav({ name: "Erdgeschoss" }), buildSchema(undefined));
|
|
161
|
-
|
|
162
|
-
expect(captured["name"]?.value).toBe("");
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("a sensitive field stays empty even when urlPrefillFields names it", () => {
|
|
166
|
-
resetCaptured();
|
|
167
|
-
renderWithNav(staticNav({ accessCode: "1234" }), buildSchema(["accessCode"]));
|
|
168
|
-
|
|
169
|
-
expect(captured["accessCode"]?.value).toBe("");
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe("EntityEditCreateBody — useNavigateWithInitialValues", () => {
|
|
174
|
-
function HandoffApp({
|
|
175
|
-
schema,
|
|
176
|
-
initialValues,
|
|
177
|
-
}: {
|
|
178
|
-
readonly schema: FeatureSchema;
|
|
179
|
-
readonly initialValues: Readonly<Record<string, unknown>>;
|
|
180
|
-
}): ReactNode {
|
|
181
|
-
const [screenId, setScreenId] = useState("home");
|
|
182
|
-
const nav: NavApi = {
|
|
183
|
-
route: { screenId },
|
|
184
|
-
navigate: (target: NavTarget) => {
|
|
185
|
-
if ("screenId" in target) setScreenId(target.screenId);
|
|
186
|
-
},
|
|
187
|
-
replace: () => {},
|
|
188
|
-
hrefFor: () => "",
|
|
189
|
-
searchParams: {},
|
|
190
|
-
setSearchParams: () => {
|
|
191
|
-
throw new Error("the handoff must not write the query string");
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
|
-
return (
|
|
195
|
-
<LocaleProvider resolver={createStaticLocaleResolver({ locale: "de-DE" })}>
|
|
196
|
-
<DispatcherProvider dispatcher={stubDispatcher()}>
|
|
197
|
-
<NavProvider value={nav}>
|
|
198
|
-
<PrimitivesProvider value={testPrimitives}>
|
|
199
|
-
{screenId === "unit-edit" ? (
|
|
200
|
-
<KumikoScreen schema={schema} qn="housing:screen:unit-edit" />
|
|
201
|
-
) : (
|
|
202
|
-
<Opener initialValues={initialValues} />
|
|
203
|
-
)}
|
|
204
|
-
</PrimitivesProvider>
|
|
205
|
-
</NavProvider>
|
|
206
|
-
</DispatcherProvider>
|
|
207
|
-
</LocaleProvider>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
let openForm: (() => void) | undefined;
|
|
212
|
-
function Opener({
|
|
213
|
-
initialValues,
|
|
214
|
-
}: {
|
|
215
|
-
readonly initialValues: Readonly<Record<string, unknown>>;
|
|
216
|
-
}): ReactNode {
|
|
217
|
-
const navigateWithInitialValues = useNavigateWithInitialValues();
|
|
218
|
-
openForm = () => navigateWithInitialValues({ screenId: "unit-edit" }, initialValues);
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
test("handed-off values prefill fields outside urlPrefillFields, but never a sensitive one", () => {
|
|
223
|
-
resetCaptured();
|
|
224
|
-
render(
|
|
225
|
-
<HandoffApp
|
|
226
|
-
schema={buildSchema([])}
|
|
227
|
-
initialValues={{ ownerEmail: "owner@example.com", floorCount: "2", accessCode: "1234" }}
|
|
228
|
-
/>,
|
|
229
|
-
);
|
|
230
|
-
act(() => openForm?.());
|
|
231
|
-
|
|
232
|
-
expect(captured["ownerEmail"]?.value).toBe("owner@example.com");
|
|
233
|
-
expect(captured["floorCount"]?.value).toBe(2);
|
|
234
|
-
expect(captured["accessCode"]?.value).toBe("");
|
|
235
|
-
});
|
|
236
|
-
});
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
// fw#3381: an entityEdit screen pins its action bar to the shell height by
|
|
2
|
-
// default (Form fillHeight + stickyActions); `fillHeight: false` opts out.
|
|
3
|
-
import { describe, expect, test } from "bun:test";
|
|
4
|
-
import type {
|
|
5
|
-
EntityDefinition,
|
|
6
|
-
EntityEditScreenDefinition,
|
|
7
|
-
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
8
|
-
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
9
|
-
import { render } from "@testing-library/react";
|
|
10
|
-
import type { ComponentType, ReactNode } from "react";
|
|
11
|
-
import { DispatcherProvider } from "../../context/dispatcher-context";
|
|
12
|
-
import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
|
|
13
|
-
import { type CorePrimitives, type FormProps, PrimitivesProvider } from "../../primitives";
|
|
14
|
-
import type { FeatureSchema } from "../feature-schema";
|
|
15
|
-
import { KumikoScreen } from "../kumiko-screen";
|
|
16
|
-
import { NavProvider } from "../nav";
|
|
17
|
-
|
|
18
|
-
const noop = (): ReactNode => null;
|
|
19
|
-
const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
|
|
20
|
-
|
|
21
|
-
function stubDispatcher(): Dispatcher {
|
|
22
|
-
return {
|
|
23
|
-
write: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["write"],
|
|
24
|
-
query: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["query"],
|
|
25
|
-
batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
|
|
26
|
-
statusStore: {
|
|
27
|
-
getState: () => "online",
|
|
28
|
-
subscribe: () => () => {},
|
|
29
|
-
} as unknown as Dispatcher["statusStore"],
|
|
30
|
-
async *stream() {},
|
|
31
|
-
pendingWrites: () => [],
|
|
32
|
-
pendingFiles: () => [],
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function buildSchema(fillHeight?: boolean): FeatureSchema {
|
|
37
|
-
const entity: EntityDefinition = {
|
|
38
|
-
fields: {
|
|
39
|
-
name: { type: "text", maxLength: 200, required: false, searchable: false, sortable: false },
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
const screen: EntityEditScreenDefinition = {
|
|
43
|
-
id: "unit-edit",
|
|
44
|
-
type: "entityEdit",
|
|
45
|
-
entity: "unit",
|
|
46
|
-
layout: { sections: [{ columns: 1, fields: ["name"] }] },
|
|
47
|
-
...(fillHeight !== undefined && { fillHeight }),
|
|
48
|
-
};
|
|
49
|
-
return {
|
|
50
|
-
featureName: "housing",
|
|
51
|
-
entities: { unit: entity },
|
|
52
|
-
screens: [screen],
|
|
53
|
-
} as FeatureSchema;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function captureFormProps(fillHeight?: boolean): FormProps {
|
|
57
|
-
let captured: FormProps | undefined;
|
|
58
|
-
const capturingForm: ComponentType<FormProps> = (props) => {
|
|
59
|
-
captured = props;
|
|
60
|
-
return null;
|
|
61
|
-
};
|
|
62
|
-
const primitives: CorePrimitives = {
|
|
63
|
-
Button: noop,
|
|
64
|
-
Banner: passChildren,
|
|
65
|
-
Field: passChildren,
|
|
66
|
-
Input: noop,
|
|
67
|
-
DataTable: noop,
|
|
68
|
-
Form: capturingForm,
|
|
69
|
-
Section: passChildren,
|
|
70
|
-
Card: passChildren,
|
|
71
|
-
Grid: passChildren,
|
|
72
|
-
GridCell: passChildren,
|
|
73
|
-
Text: passChildren,
|
|
74
|
-
Heading: noop,
|
|
75
|
-
Dialog: noop,
|
|
76
|
-
Modal: noop,
|
|
77
|
-
Lightbox: noop,
|
|
78
|
-
ConfigSourceBadge: noop,
|
|
79
|
-
ConfigCascadeView: noop,
|
|
80
|
-
Link: noop,
|
|
81
|
-
};
|
|
82
|
-
render(
|
|
83
|
-
<LocaleProvider resolver={createStaticLocaleResolver({ locale: "de-DE" })}>
|
|
84
|
-
<DispatcherProvider dispatcher={stubDispatcher()}>
|
|
85
|
-
<NavProvider
|
|
86
|
-
value={{
|
|
87
|
-
route: { screenId: "housing:unit-edit" },
|
|
88
|
-
navigate: () => {},
|
|
89
|
-
replace: () => {},
|
|
90
|
-
hrefFor: () => "",
|
|
91
|
-
searchParams: {},
|
|
92
|
-
setSearchParams: () => {},
|
|
93
|
-
}}
|
|
94
|
-
>
|
|
95
|
-
<PrimitivesProvider value={primitives}>
|
|
96
|
-
<KumikoScreen schema={buildSchema(fillHeight)} qn="housing:screen:unit-edit" />
|
|
97
|
-
</PrimitivesProvider>
|
|
98
|
-
</NavProvider>
|
|
99
|
-
</DispatcherProvider>
|
|
100
|
-
</LocaleProvider>,
|
|
101
|
-
);
|
|
102
|
-
if (captured === undefined) throw new Error("Form was not rendered");
|
|
103
|
-
return captured;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
describe("entityEdit fillHeight (fw#3381)", () => {
|
|
107
|
-
test("default: form fills the height with a pinned action bar", () => {
|
|
108
|
-
const props = captureFormProps();
|
|
109
|
-
expect(props.fillHeight).toBe(true);
|
|
110
|
-
expect(props.stickyActions).toBe(true);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("fillHeight: false restores page scroll", () => {
|
|
114
|
-
const props = captureFormProps(false);
|
|
115
|
-
expect(props.fillHeight).toBeFalsy();
|
|
116
|
-
expect(props.stickyActions).toBeFalsy();
|
|
117
|
-
});
|
|
118
|
-
});
|