@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,239 +0,0 @@
|
|
|
1
|
-
// Finding: render-field-unsupported-types.test.tsx only proves jsonb/embedded
|
|
2
|
-
// (no embeddedListCells) fields render read-only (Banner, no Input wired up).
|
|
3
|
-
// It never proves the actual claim that matters: saving the form after
|
|
4
|
-
// editing an UNRELATED field does not corrupt or overwrite those fields'
|
|
5
|
-
// real data. Because RenderField never wires an onChange for them, their
|
|
6
|
-
// value in the form controller stays reference-identical to the record it
|
|
7
|
-
// was seeded from — the update payload's `changes` diff (reference-equality
|
|
8
|
-
// based, see form-controller.ts valuesDiff) must therefore omit them
|
|
9
|
-
// entirely, never re-include them as e.g. a stringified copy.
|
|
10
|
-
//
|
|
11
|
-
// This renders the real update path (KumikoScreen → EntityEditScreen →
|
|
12
|
-
// EntityEditUpdateBody → RenderEdit → RenderField) under a stub dispatcher
|
|
13
|
-
// and inspects the actual write() payload, not just the render tree.
|
|
14
|
-
|
|
15
|
-
import { describe, expect, test } from "bun:test";
|
|
16
|
-
import type {
|
|
17
|
-
EntityDefinition,
|
|
18
|
-
EntityEditScreenDefinition,
|
|
19
|
-
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
20
|
-
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
21
|
-
import { act, render, waitFor } from "@testing-library/react";
|
|
22
|
-
import type { ComponentType, FormEvent, ReactNode } from "react";
|
|
23
|
-
import { DispatcherProvider } from "../../context/dispatcher-context";
|
|
24
|
-
import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
|
|
25
|
-
import { kumikoDefaultTranslations } from "../../i18n-defaults";
|
|
26
|
-
import {
|
|
27
|
-
type CorePrimitives,
|
|
28
|
-
type FormProps,
|
|
29
|
-
type InputProps,
|
|
30
|
-
PrimitivesProvider,
|
|
31
|
-
} from "../../primitives";
|
|
32
|
-
import type { FeatureSchema } from "../feature-schema";
|
|
33
|
-
import { KumikoScreen } from "../kumiko-screen";
|
|
34
|
-
import { NavProvider } from "../nav";
|
|
35
|
-
|
|
36
|
-
const capturedInputs: Record<string, InputProps | undefined> = {};
|
|
37
|
-
const captureInput: ComponentType<InputProps> = (props) => {
|
|
38
|
-
capturedInputs[props.name] = props;
|
|
39
|
-
return null;
|
|
40
|
-
};
|
|
41
|
-
let capturedFormSubmit: ((e?: FormEvent) => void) | undefined;
|
|
42
|
-
const captureForm: ComponentType<FormProps> = (props) => {
|
|
43
|
-
capturedFormSubmit = props.onSubmit;
|
|
44
|
-
return <>{props.children}</>;
|
|
45
|
-
};
|
|
46
|
-
const noop = (): ReactNode => null;
|
|
47
|
-
const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
|
|
48
|
-
|
|
49
|
-
const testPrimitives: CorePrimitives = {
|
|
50
|
-
Button: noop,
|
|
51
|
-
Banner: passChildren,
|
|
52
|
-
Field: passChildren,
|
|
53
|
-
Input: captureInput,
|
|
54
|
-
DataTable: noop,
|
|
55
|
-
Form: captureForm,
|
|
56
|
-
Section: passChildren,
|
|
57
|
-
Card: passChildren,
|
|
58
|
-
Grid: passChildren,
|
|
59
|
-
GridCell: passChildren,
|
|
60
|
-
Text: passChildren,
|
|
61
|
-
Heading: noop,
|
|
62
|
-
Dialog: noop,
|
|
63
|
-
Modal: noop,
|
|
64
|
-
Lightbox: noop,
|
|
65
|
-
ConfigSourceBadge: noop,
|
|
66
|
-
ConfigCascadeView: noop,
|
|
67
|
-
Link: noop,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
let lastWrite: { type: string; payload: unknown } | undefined;
|
|
71
|
-
|
|
72
|
-
// The annotation resets TS's flow-narrowing, which only tracks the
|
|
73
|
-
// straight-line `lastWrite = undefined` reset, not the reassignment inside the dispatcher closure.
|
|
74
|
-
function requireLastWrite(): { type: string; payload: unknown } {
|
|
75
|
-
const write: { type: string; payload: unknown } | undefined = lastWrite;
|
|
76
|
-
if (!write) throw new Error("expected a write() call");
|
|
77
|
-
return write;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function stubDispatcher(record: Readonly<Record<string, unknown>>): Dispatcher {
|
|
81
|
-
return {
|
|
82
|
-
write: (async (type: string, payload: unknown) => {
|
|
83
|
-
lastWrite = { type, payload };
|
|
84
|
-
return { isSuccess: true, data: {} };
|
|
85
|
-
}) as unknown as Dispatcher["write"],
|
|
86
|
-
query: (async () => ({ isSuccess: true, data: record })) as unknown as Dispatcher["query"],
|
|
87
|
-
batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
|
|
88
|
-
statusStore: {
|
|
89
|
-
getState: () => "online",
|
|
90
|
-
subscribe: () => () => {},
|
|
91
|
-
} as unknown as Dispatcher["statusStore"],
|
|
92
|
-
async *stream() {},
|
|
93
|
-
pendingWrites: () => [],
|
|
94
|
-
pendingFiles: () => [],
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function buildSchema(): FeatureSchema {
|
|
99
|
-
const entity: EntityDefinition = {
|
|
100
|
-
fields: {
|
|
101
|
-
name: { type: "text", maxLength: 200, required: false, searchable: false, sortable: false },
|
|
102
|
-
extra: { type: "jsonb" },
|
|
103
|
-
meta: {
|
|
104
|
-
type: "embedded",
|
|
105
|
-
schema: { note: { type: "text" } },
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
const screen: EntityEditScreenDefinition = {
|
|
110
|
-
id: "widget-edit",
|
|
111
|
-
type: "entityEdit",
|
|
112
|
-
entity: "widget",
|
|
113
|
-
layout: { sections: [{ columns: 1, fields: ["name", "extra", "meta"] }] },
|
|
114
|
-
};
|
|
115
|
-
return {
|
|
116
|
-
featureName: "widgets",
|
|
117
|
-
entities: { widget: entity },
|
|
118
|
-
screens: [screen],
|
|
119
|
-
} as FeatureSchema;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
describe("EntityEditUpdateForm — untouched jsonb/embedded fields survive submit", () => {
|
|
123
|
-
test("changing an unrelated field and submitting keeps jsonb/embedded fields out of the diff (not stringified, not overwritten)", async () => {
|
|
124
|
-
lastWrite = undefined;
|
|
125
|
-
capturedFormSubmit = undefined;
|
|
126
|
-
for (const key of Object.keys(capturedInputs)) delete capturedInputs[key];
|
|
127
|
-
|
|
128
|
-
const record = {
|
|
129
|
-
id: "w1",
|
|
130
|
-
version: 3,
|
|
131
|
-
name: "Old name",
|
|
132
|
-
extra: { a: 1 },
|
|
133
|
-
meta: { note: "keep me" },
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
render(
|
|
137
|
-
<LocaleProvider
|
|
138
|
-
resolver={createStaticLocaleResolver({ locale: "de-DE" })}
|
|
139
|
-
fallbackBundles={[kumikoDefaultTranslations]}
|
|
140
|
-
>
|
|
141
|
-
<DispatcherProvider dispatcher={stubDispatcher(record)}>
|
|
142
|
-
<NavProvider
|
|
143
|
-
value={{
|
|
144
|
-
route: { screenId: "widgets:widget-edit" },
|
|
145
|
-
navigate: () => {},
|
|
146
|
-
replace: () => {},
|
|
147
|
-
hrefFor: () => "",
|
|
148
|
-
searchParams: {},
|
|
149
|
-
setSearchParams: () => {},
|
|
150
|
-
}}
|
|
151
|
-
>
|
|
152
|
-
<PrimitivesProvider value={testPrimitives}>
|
|
153
|
-
<KumikoScreen schema={buildSchema()} qn="widgets:screen:widget-edit" entityId="w1" />
|
|
154
|
-
</PrimitivesProvider>
|
|
155
|
-
</NavProvider>
|
|
156
|
-
</DispatcherProvider>
|
|
157
|
-
</LocaleProvider>,
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
await waitFor(() => {
|
|
161
|
-
expect(capturedInputs["name"]).toBeDefined();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const nameInput = capturedInputs["name"];
|
|
165
|
-
if (nameInput?.kind !== "text") throw new Error("expected a text input for the name field");
|
|
166
|
-
act(() => nameInput.onChange("New name"));
|
|
167
|
-
|
|
168
|
-
await waitFor(() => {
|
|
169
|
-
expect(capturedFormSubmit).toBeDefined();
|
|
170
|
-
});
|
|
171
|
-
act(() => capturedFormSubmit?.());
|
|
172
|
-
|
|
173
|
-
await waitFor(() => {
|
|
174
|
-
expect(lastWrite).toBeDefined();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const payload = requireLastWrite().payload as { changes: Record<string, unknown> };
|
|
178
|
-
expect(payload.changes["name"]).toBe("New name");
|
|
179
|
-
// The untouched jsonb/embedded fields must never resurface in the diff —
|
|
180
|
-
// if they did (the pre-fix bug this guards against), it would mean the
|
|
181
|
-
// update overwrites them, and the old code path could send a corrupted
|
|
182
|
-
// (e.g. stringified) copy instead of the real object.
|
|
183
|
-
expect(Object.hasOwn(payload.changes, "extra")).toBe(false);
|
|
184
|
-
expect(Object.hasOwn(payload.changes, "meta")).toBe(false);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test("touching the jsonb field itself keeps it a real object in the diff, never a string", async () => {
|
|
188
|
-
lastWrite = undefined;
|
|
189
|
-
capturedFormSubmit = undefined;
|
|
190
|
-
for (const key of Object.keys(capturedInputs)) delete capturedInputs[key];
|
|
191
|
-
|
|
192
|
-
const record = {
|
|
193
|
-
id: "w1",
|
|
194
|
-
version: 3,
|
|
195
|
-
name: "Old name",
|
|
196
|
-
extra: { a: 1 },
|
|
197
|
-
meta: { note: "keep me" },
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
render(
|
|
201
|
-
<LocaleProvider
|
|
202
|
-
resolver={createStaticLocaleResolver({ locale: "de-DE" })}
|
|
203
|
-
fallbackBundles={[kumikoDefaultTranslations]}
|
|
204
|
-
>
|
|
205
|
-
<DispatcherProvider dispatcher={stubDispatcher(record)}>
|
|
206
|
-
<NavProvider
|
|
207
|
-
value={{
|
|
208
|
-
route: { screenId: "widgets:widget-edit" },
|
|
209
|
-
navigate: () => {},
|
|
210
|
-
replace: () => {},
|
|
211
|
-
hrefFor: () => "",
|
|
212
|
-
searchParams: {},
|
|
213
|
-
setSearchParams: () => {},
|
|
214
|
-
}}
|
|
215
|
-
>
|
|
216
|
-
<PrimitivesProvider value={testPrimitives}>
|
|
217
|
-
<KumikoScreen schema={buildSchema()} qn="widgets:screen:widget-edit" entityId="w1" />
|
|
218
|
-
</PrimitivesProvider>
|
|
219
|
-
</NavProvider>
|
|
220
|
-
</DispatcherProvider>
|
|
221
|
-
</LocaleProvider>,
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
await waitFor(() => {
|
|
225
|
-
expect(capturedFormSubmit).toBeDefined();
|
|
226
|
-
});
|
|
227
|
-
act(() => capturedFormSubmit?.());
|
|
228
|
-
|
|
229
|
-
await waitFor(() => {
|
|
230
|
-
expect(lastWrite).toBeDefined();
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
const payload = requireLastWrite().payload as { changes: Record<string, unknown> };
|
|
234
|
-
// Nothing was touched at all — the diff must be empty, and specifically
|
|
235
|
-
// must not contain a stringified copy of extra/meta.
|
|
236
|
-
expect(Object.hasOwn(payload.changes, "extra")).toBe(false);
|
|
237
|
-
expect(Object.hasOwn(payload.changes, "meta")).toBe(false);
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
// Guard-evasion-adjacent bug: useNavigateToCreateFor picked the first
|
|
2
|
-
// entityEdit screen with allowCreate !== false as the "+ Neu" target,
|
|
3
|
-
// without excluding singleton:true screens. A singleton screen opened
|
|
4
|
-
// without an entityId resolves the existing record (EntityEditSingletonBody)
|
|
5
|
-
// instead of a blank create form — so "+ Neu" silently opened a prefilled
|
|
6
|
-
// update form. This test renders the real list path (KumikoScreen →
|
|
7
|
-
// EntityListScreen → EntityListBody → RenderList) for an entity whose only
|
|
8
|
-
// registered entityEdit screen is a singleton, and asserts no create button
|
|
9
|
-
// renders — not just the hook in isolation.
|
|
10
|
-
import { describe, expect, test } from "bun:test";
|
|
11
|
-
import type {
|
|
12
|
-
EntityDefinition,
|
|
13
|
-
EntityEditScreenDefinition,
|
|
14
|
-
EntityListScreenDefinition,
|
|
15
|
-
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
16
|
-
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
17
|
-
import { render } from "@testing-library/react";
|
|
18
|
-
import type { ComponentType, ReactNode } from "react";
|
|
19
|
-
import { DispatcherProvider } from "../../context/dispatcher-context";
|
|
20
|
-
import { createStaticLocaleResolver, LocaleProvider } from "../../i18n";
|
|
21
|
-
import { type ButtonProps, type CorePrimitives, PrimitivesProvider } from "../../primitives";
|
|
22
|
-
import type { FeatureSchema } from "../feature-schema";
|
|
23
|
-
import { KumikoScreen } from "../kumiko-screen";
|
|
24
|
-
import { NavProvider } from "../nav";
|
|
25
|
-
|
|
26
|
-
const capturedButtonTestIds: string[] = [];
|
|
27
|
-
const captureButton: ComponentType<ButtonProps> = (props) => {
|
|
28
|
-
if (props.testId !== undefined) capturedButtonTestIds.push(props.testId);
|
|
29
|
-
return null;
|
|
30
|
-
};
|
|
31
|
-
const noop = (): ReactNode => null;
|
|
32
|
-
const passChildren = ({ children }: { readonly children?: ReactNode }): ReactNode => children;
|
|
33
|
-
|
|
34
|
-
const testPrimitives: CorePrimitives = {
|
|
35
|
-
Button: captureButton,
|
|
36
|
-
Banner: passChildren,
|
|
37
|
-
Field: passChildren,
|
|
38
|
-
Input: noop,
|
|
39
|
-
DataTable: noop,
|
|
40
|
-
Form: passChildren,
|
|
41
|
-
Section: passChildren,
|
|
42
|
-
Card: passChildren,
|
|
43
|
-
Grid: passChildren,
|
|
44
|
-
GridCell: passChildren,
|
|
45
|
-
Text: passChildren,
|
|
46
|
-
Heading: noop,
|
|
47
|
-
Dialog: noop,
|
|
48
|
-
Modal: noop,
|
|
49
|
-
Lightbox: noop,
|
|
50
|
-
ConfigSourceBadge: noop,
|
|
51
|
-
ConfigCascadeView: noop,
|
|
52
|
-
Link: noop,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
function stubDispatcher(): Dispatcher {
|
|
56
|
-
return {
|
|
57
|
-
write: (async () => ({ isSuccess: true, data: {} })) as unknown as Dispatcher["write"],
|
|
58
|
-
query: (async () => ({
|
|
59
|
-
isSuccess: true,
|
|
60
|
-
data: { rows: [], total: 0 },
|
|
61
|
-
})) as unknown as Dispatcher["query"],
|
|
62
|
-
batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
|
|
63
|
-
statusStore: {
|
|
64
|
-
getState: () => "online",
|
|
65
|
-
subscribe: () => () => {},
|
|
66
|
-
} as unknown as Dispatcher["statusStore"],
|
|
67
|
-
async *stream() {},
|
|
68
|
-
pendingWrites: () => [],
|
|
69
|
-
pendingFiles: () => [],
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function buildSchema(): FeatureSchema {
|
|
74
|
-
const entity: EntityDefinition = {
|
|
75
|
-
fields: {
|
|
76
|
-
name: { type: "text", maxLength: 200, required: false, searchable: false, sortable: false },
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
const listScreen: EntityListScreenDefinition = {
|
|
80
|
-
id: "org-list",
|
|
81
|
-
type: "entityList",
|
|
82
|
-
entity: "org",
|
|
83
|
-
columns: ["name"],
|
|
84
|
-
};
|
|
85
|
-
// The only entityEdit screen for "org" is a singleton — there is
|
|
86
|
-
// deliberately no separate non-singleton entityEdit screen.
|
|
87
|
-
const editScreen: EntityEditScreenDefinition = {
|
|
88
|
-
id: "org-edit",
|
|
89
|
-
type: "entityEdit",
|
|
90
|
-
entity: "org",
|
|
91
|
-
singleton: true,
|
|
92
|
-
layout: { sections: [{ columns: 1, fields: ["name"] }] },
|
|
93
|
-
};
|
|
94
|
-
return {
|
|
95
|
-
featureName: "orgs",
|
|
96
|
-
entities: { org: entity },
|
|
97
|
-
screens: [listScreen, editScreen],
|
|
98
|
-
} as FeatureSchema;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
describe("entityList '+ Neu' target excludes singleton entityEdit screens", () => {
|
|
102
|
-
test("no create button renders when the only entityEdit screen is singleton:true", () => {
|
|
103
|
-
capturedButtonTestIds.length = 0;
|
|
104
|
-
render(
|
|
105
|
-
<LocaleProvider resolver={createStaticLocaleResolver({ locale: "de-DE" })}>
|
|
106
|
-
<DispatcherProvider dispatcher={stubDispatcher()}>
|
|
107
|
-
<NavProvider
|
|
108
|
-
value={{
|
|
109
|
-
route: { screenId: "orgs:org-list" },
|
|
110
|
-
navigate: () => {},
|
|
111
|
-
replace: () => {},
|
|
112
|
-
hrefFor: () => "",
|
|
113
|
-
searchParams: {},
|
|
114
|
-
setSearchParams: () => {},
|
|
115
|
-
}}
|
|
116
|
-
>
|
|
117
|
-
<PrimitivesProvider value={testPrimitives}>
|
|
118
|
-
<KumikoScreen schema={buildSchema()} qn="orgs:screen:org-list" />
|
|
119
|
-
</PrimitivesProvider>
|
|
120
|
-
</NavProvider>
|
|
121
|
-
</DispatcherProvider>
|
|
122
|
-
</LocaleProvider>,
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
expect(capturedButtonTestIds).not.toContain("render-list-create");
|
|
126
|
-
expect(capturedButtonTestIds).not.toContain("render-list-empty-create");
|
|
127
|
-
});
|
|
128
|
-
});
|