@cosmicdrift/kumiko-renderer-web 0.34.0 → 0.34.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer-web",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.1",
|
|
4
4
|
"description": "Web-platform bindings for @cosmicdrift/kumiko-renderer. HTML default-primitives, browser history-based navigation, EventSource-backed live events, and a one-call createKumikoApp that mounts the whole stack via react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -7,7 +7,11 @@ import type {
|
|
|
7
7
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
8
8
|
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
9
9
|
import type { FeatureSchema } from "@cosmicdrift/kumiko-renderer";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
DispatcherProvider,
|
|
12
|
+
ExtensionSectionsProvider,
|
|
13
|
+
KumikoScreen,
|
|
14
|
+
} from "@cosmicdrift/kumiko-renderer";
|
|
11
15
|
import userEvent from "@testing-library/user-event";
|
|
12
16
|
import { createMockDispatcher, fireEvent, render, screen, waitFor } from "./test-utils";
|
|
13
17
|
|
|
@@ -160,6 +164,70 @@ describe("KumikoScreen", () => {
|
|
|
160
164
|
});
|
|
161
165
|
});
|
|
162
166
|
|
|
167
|
+
// Regression-Anker für den Set-Value-UI-Bug: die extension-section muss im
|
|
168
|
+
// Update-Mode die ECHTE entity-id bekommen — durch den vollen Flow
|
|
169
|
+
// (KumikoScreen → detail-load → EntityEditUpdateForm → RenderEdit → Mount).
|
|
170
|
+
// EntityEditUpdateForm lässt `id` bewusst aus den Form-values (id ist keine
|
|
171
|
+
// deklarierte Field), also reicht NUR der route-entityId-Durchgriff. Ohne
|
|
172
|
+
// ihn fiele die Section auf vm.id (=values["id"]=undefined) zurück und zeigte
|
|
173
|
+
// create-mode trotz Edit. Der alte render-edit-Test mockte `initial.id`
|
|
174
|
+
// manuell und war für genau diesen Flow blind — dieser Test rendert den
|
|
175
|
+
// realen detail-Pfad, der das in CI gefangen hätte.
|
|
176
|
+
test("entityEdit mit entityId → extension-section bekommt die echte entity-id (nicht create-mode)", async () => {
|
|
177
|
+
const editScreenWithExtension: EntityEditScreenDefinition = {
|
|
178
|
+
id: "task-edit-ext",
|
|
179
|
+
type: "entityEdit",
|
|
180
|
+
entity: "task",
|
|
181
|
+
layout: {
|
|
182
|
+
sections: [
|
|
183
|
+
{ title: "Basics", fields: ["title"] },
|
|
184
|
+
{
|
|
185
|
+
kind: "extension",
|
|
186
|
+
title: "Custom Fields",
|
|
187
|
+
component: { react: { __component: "TaskCustomFields" } },
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
const extSchema: FeatureSchema = {
|
|
193
|
+
featureName: "tasks",
|
|
194
|
+
entities: { task: taskEntity },
|
|
195
|
+
screens: [editScreenWithExtension],
|
|
196
|
+
};
|
|
197
|
+
const TaskCustomFields = ({
|
|
198
|
+
entityName,
|
|
199
|
+
entityId,
|
|
200
|
+
}: {
|
|
201
|
+
entityName: string;
|
|
202
|
+
entityId: string | null;
|
|
203
|
+
}) => (
|
|
204
|
+
<div data-testid="task-custom-fields">
|
|
205
|
+
{entityName}:{entityId ?? "(create)"}
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
const dispatcher = makeDispatcher({
|
|
209
|
+
// detail liefert die row MIT id — aber der Update-Form filtert id aus
|
|
210
|
+
// den Form-values; die Section darf trotzdem nicht in create-mode fallen.
|
|
211
|
+
query: (async () => ({
|
|
212
|
+
isSuccess: true,
|
|
213
|
+
data: { id: "task-1", version: 7, title: "loaded", count: 0, done: false },
|
|
214
|
+
})) as unknown as Dispatcher["query"],
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
render(
|
|
218
|
+
<DispatcherProvider dispatcher={dispatcher}>
|
|
219
|
+
<ExtensionSectionsProvider value={{ TaskCustomFields }}>
|
|
220
|
+
<KumikoScreen schema={extSchema} qn="tasks:screen:task-edit-ext" entityId="task-1" />
|
|
221
|
+
</ExtensionSectionsProvider>
|
|
222
|
+
</DispatcherProvider>,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
await waitFor(() => expect(screen.queryByTestId("kumiko-screen-loading")).toBeNull());
|
|
226
|
+
const section = screen.getByTestId("task-custom-fields");
|
|
227
|
+
// Der Anker: echte route-id, NICHT "(create)". Vor dem Fix: "task:(create)".
|
|
228
|
+
expect(section.textContent).toBe("task:task-1");
|
|
229
|
+
});
|
|
230
|
+
|
|
163
231
|
test("entityList onRowClick → Callback feuert mit Row-Viewmodel", async () => {
|
|
164
232
|
const clicks: { id: string }[] = [];
|
|
165
233
|
const dispatcher = makeDispatcher({
|
|
@@ -229,6 +229,59 @@ describe("RenderEdit", () => {
|
|
|
229
229
|
expect(mounted.textContent).toBe("order:row-42");
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
+
// Realer Update-Flow: EntityEditUpdateForm lässt `id` BEWUSST aus den
|
|
233
|
+
// Form-values (id ist keine deklarierte Field) und reicht die route-id
|
|
234
|
+
// stattdessen über die entityId-prop durch. Ohne den entityId-prop-Pfad
|
|
235
|
+
// fiele die Section auf vm.id (=values["id"]=undefined) zurück → create-
|
|
236
|
+
// mode trotz Edit (der Set-Value-UI-Bug, #187..fix).
|
|
237
|
+
test("extension section uses the entityId prop when id is absent from form values", () => {
|
|
238
|
+
const screenDef: EntityEditScreenDefinition = {
|
|
239
|
+
id: "orders:screen:order-edit",
|
|
240
|
+
type: "entityEdit",
|
|
241
|
+
entity: "order",
|
|
242
|
+
layout: {
|
|
243
|
+
sections: [
|
|
244
|
+
{ title: "Basics", columns: 2, fields: [{ field: "title", span: 2 }] },
|
|
245
|
+
{
|
|
246
|
+
kind: "extension",
|
|
247
|
+
title: "Custom Fields",
|
|
248
|
+
component: { react: { __component: "MyCustomFieldsForm" } },
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
const MyCustomFieldsForm = ({
|
|
254
|
+
entityName,
|
|
255
|
+
entityId,
|
|
256
|
+
}: {
|
|
257
|
+
entityName: string;
|
|
258
|
+
entityId: string | null;
|
|
259
|
+
}) => (
|
|
260
|
+
<div data-testid="my-custom-fields-form">
|
|
261
|
+
{entityName}:{entityId ?? "(create)"}
|
|
262
|
+
</div>
|
|
263
|
+
);
|
|
264
|
+
render(
|
|
265
|
+
<DispatcherProvider dispatcher={makeDispatcher()}>
|
|
266
|
+
<ExtensionSectionsProvider value={{ MyCustomFieldsForm }}>
|
|
267
|
+
<RenderEdit<TestValues>
|
|
268
|
+
screen={screenDef}
|
|
269
|
+
entity={orderEntity}
|
|
270
|
+
featureName="orders"
|
|
271
|
+
// KEIN id in den values (wie der echte Update-Form), aber
|
|
272
|
+
// entityId-prop trägt die route-id.
|
|
273
|
+
initial={{ title: "Existing", count: 0, isUrgent: false } as TestValues}
|
|
274
|
+
entityId="order-99"
|
|
275
|
+
writeCommand="order:update"
|
|
276
|
+
/>
|
|
277
|
+
</ExtensionSectionsProvider>
|
|
278
|
+
</DispatcherProvider>,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const mounted = screen.getByTestId("my-custom-fields-form");
|
|
282
|
+
expect(mounted.textContent).toBe("order:order-99");
|
|
283
|
+
});
|
|
284
|
+
|
|
232
285
|
test("extension section without registered component shows the placeholder banner", () => {
|
|
233
286
|
const screenDef: EntityEditScreenDefinition = {
|
|
234
287
|
id: "orders:screen:order-edit",
|