@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.0",
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 { DispatcherProvider, KumikoScreen } from "@cosmicdrift/kumiko-renderer";
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",