@cosmicdrift/kumiko-renderer-web 0.32.0 → 0.32.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.32.0",
3
+ "version": "0.32.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>",
@@ -547,6 +547,127 @@ describe("KumikoScreen", () => {
547
547
  expect(searchParamUpdates).toEqual([]);
548
548
  });
549
549
 
550
+ // JSON-Schema-Fall (window.__KUMIKO_SCHEMA__): Function-Props wie
551
+ // action.entityId werden beim JSON-Roundtrip silent gedroppt. Zielt
552
+ // die navigate-Action auf einen entityEdit-Screen, MUSS row.id als
553
+ // deklarativer Default greifen — sonst öffnet der Edit im Create-Mode
554
+ // (Prod-e2e-Befund 2026-06-07 nach F1).
555
+ test("entityList rowActions kind=navigate auf entityEdit-Ziel: row.id ist der entityId-Default (JSON-Schema-sicher)", async () => {
556
+ const navigateCalls: { screenId: string; entityId?: string }[] = [];
557
+ const memoryNav = {
558
+ route: { screenId: "task-list" },
559
+ navigate: (target: { screenId: string; entityId?: string }) => navigateCalls.push(target),
560
+ replace: () => undefined,
561
+ hrefFor: (t: { screenId: string }) => `/${t.screenId}`,
562
+ searchParams: {},
563
+ setSearchParams: () => undefined,
564
+ };
565
+ const dispatcher = makeDispatcher({
566
+ query: (async () => ({
567
+ isSuccess: true,
568
+ data: {
569
+ rows: [{ id: "r1", title: "Alpha", count: 1, done: false }],
570
+ nextCursor: null,
571
+ },
572
+ })) as unknown as Dispatcher["query"],
573
+ });
574
+
575
+ const screenWithEdit: EntityListScreenDefinition = {
576
+ id: "task-list",
577
+ type: "entityList",
578
+ entity: "task",
579
+ columns: ["title"],
580
+ rowActions: [
581
+ {
582
+ kind: "navigate",
583
+ id: "edit",
584
+ label: "actions.edit",
585
+ screen: "task-edit",
586
+ // entityId-Function ABSICHTLICH gesetzt und dann per
587
+ // JSON-Roundtrip gedroppt — exakt was buildAppSchema +
588
+ // JSON.stringify mit dem Schema im Browser machen.
589
+ entityId: (row) => String(row["id"] ?? ""),
590
+ },
591
+ ],
592
+ };
593
+ const jsonSchema = JSON.parse(
594
+ JSON.stringify({ ...schema, screens: [screenWithEdit, editScreen] }),
595
+ ) as FeatureSchema;
596
+
597
+ const { NavProvider } = await import("@cosmicdrift/kumiko-renderer");
598
+ const user = userEvent.setup();
599
+ render(
600
+ <NavProvider value={memoryNav}>
601
+ <DispatcherProvider dispatcher={dispatcher}>
602
+ <KumikoScreen schema={jsonSchema} qn="tasks:screen:task-list" />
603
+ </DispatcherProvider>
604
+ </NavProvider>,
605
+ );
606
+ await waitFor(() => expect(screen.queryByTestId("kumiko-screen-loading")).toBeNull());
607
+
608
+ await user.click(screen.getByTestId("row-r1-action-edit"));
609
+ await waitFor(() => expect(navigateCalls.length).toBe(1));
610
+ expect(navigateCalls[0]).toEqual({ screenId: "task-edit", entityId: "r1" });
611
+ });
612
+
613
+ test("entityList rowActions kind=navigate auf NICHT-entityEdit-Ziel: kein entityId-Default", async () => {
614
+ const navigateCalls: { screenId: string; entityId?: string }[] = [];
615
+ const memoryNav = {
616
+ route: { screenId: "task-list" },
617
+ navigate: (target: { screenId: string; entityId?: string }) => navigateCalls.push(target),
618
+ replace: () => undefined,
619
+ hrefFor: (t: { screenId: string }) => `/${t.screenId}`,
620
+ searchParams: {},
621
+ setSearchParams: () => undefined,
622
+ };
623
+ const dispatcher = makeDispatcher({
624
+ query: (async () => ({
625
+ isSuccess: true,
626
+ data: {
627
+ rows: [{ id: "r1", title: "Alpha", count: 1, done: false }],
628
+ nextCursor: null,
629
+ },
630
+ })) as unknown as Dispatcher["query"],
631
+ });
632
+
633
+ const actionScreen: ActionFormScreenDefinition = {
634
+ id: "task-approve",
635
+ type: "actionForm",
636
+ handler: "tasks:write:task:approve",
637
+ fields: { note: { type: "text" } } as ActionFormScreenDefinition["fields"],
638
+ layout: { sections: [{ title: "x", fields: ["note"] }] },
639
+ };
640
+ const screenWithNav: EntityListScreenDefinition = {
641
+ id: "task-list",
642
+ type: "entityList",
643
+ entity: "task",
644
+ columns: ["title"],
645
+ rowActions: [
646
+ { kind: "navigate", id: "approve", label: "actions.approve", screen: "task-approve" },
647
+ ],
648
+ };
649
+
650
+ const { NavProvider } = await import("@cosmicdrift/kumiko-renderer");
651
+ const user = userEvent.setup();
652
+ render(
653
+ <NavProvider value={memoryNav}>
654
+ <DispatcherProvider dispatcher={dispatcher}>
655
+ <KumikoScreen
656
+ schema={{ ...schema, screens: [screenWithNav, actionScreen] }}
657
+ qn="tasks:screen:task-list"
658
+ />
659
+ </DispatcherProvider>
660
+ </NavProvider>,
661
+ );
662
+ await waitFor(() => expect(screen.queryByTestId("kumiko-screen-loading")).toBeNull());
663
+
664
+ await user.click(screen.getByTestId("row-r1-action-approve"));
665
+ await waitFor(() => expect(navigateCalls.length).toBe(1));
666
+ // actionForm-Ziel: row-Kontext kommt via params/searchParams, NICHT
667
+ // als Pfad-Segment — kein entityId-Default.
668
+ expect(navigateCalls[0]).toEqual({ screenId: "task-approve" });
669
+ });
670
+
550
671
  test("entityList rowActions kind=navigate ohne params: setSearchParams wird NICHT gerufen", async () => {
551
672
  const navigateCalls: { screenId: string }[] = [];
552
673
  const searchParamUpdates: Record<string, string | null>[] = [];