@cosmicdrift/kumiko-renderer-web 0.105.0 → 0.105.2

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.105.0",
3
+ "version": "0.105.2",
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>",
@@ -16,9 +16,9 @@
16
16
  "./styles.css": "./src/styles.css"
17
17
  },
18
18
  "dependencies": {
19
- "@cosmicdrift/kumiko-dispatcher-live": "0.105.0",
20
- "@cosmicdrift/kumiko-headless": "0.105.0",
21
- "@cosmicdrift/kumiko-renderer": "0.105.0",
19
+ "@cosmicdrift/kumiko-dispatcher-live": "0.105.2",
20
+ "@cosmicdrift/kumiko-headless": "0.105.2",
21
+ "@cosmicdrift/kumiko-renderer": "0.105.2",
22
22
  "@radix-ui/react-dialog": "^1.1.15",
23
23
  "@radix-ui/react-dropdown-menu": "^2.1.16",
24
24
  "@radix-ui/react-label": "^2.1.8",
@@ -82,13 +82,29 @@ describe("Input kind=locatedTimestamp", () => {
82
82
  expect(atSchema.safeParse(last.at).success).toBe(true);
83
83
  });
84
84
 
85
- test("leeres Datum + Uhrzeit ohne Zone undefined (Feld leer)", () => {
85
+ test("kein Input noch kein onChange (Sanity, Startwert bleibt Sentinel)", () => {
86
86
  let last: { at: string; tz: string } | undefined | "sentinel" = "sentinel";
87
87
  render(<ControlledLocated initial="" onEmit={(v) => (last = v)} />);
88
- // Kein Input → noch kein onChange. Sanity: Startwert blieb Sentinel.
89
88
  expect(last).toBe("sentinel");
90
89
  });
91
90
 
91
+ test("Datum + Uhrzeit wieder leeren (Zone nie gesetzt) → onChange(undefined)", () => {
92
+ const seen: Array<{ at: string; tz: string } | undefined> = [];
93
+ const view = render(<ControlledLocated initial="" onEmit={(v) => seen.push(v)} />);
94
+ const { date, time } = inputs(view.container);
95
+
96
+ fireEvent.change(date, { target: { value: "2026-04-03" } });
97
+ fireEvent.change(time, { target: { value: "10:00" } });
98
+ expect(seen.at(-1)?.at).toBe("2026-04-03T10:00");
99
+
100
+ // Both fields cleared again — the emit() undefined-branch (`nextAt === ""
101
+ // && nextTz === ""`) has zero coverage otherwise; the test above only
102
+ // checks that NO change fires before any input, not the clear-after-fill path.
103
+ fireEvent.change(date, { target: { value: "" } });
104
+ fireEvent.change(time, { target: { value: "" } });
105
+ expect(seen.at(-1)).toBeUndefined();
106
+ });
107
+
92
108
  test("sichtbarer Zonen-Hinweis", () => {
93
109
  const view = render(<ControlledLocated initial="" onEmit={() => {}} />);
94
110
  expect(view.container.textContent ?? "").toMatch(/lokal|local/i);
@@ -12,7 +12,8 @@ import userEvent from "@testing-library/user-event";
12
12
  import { defaultPrimitives } from "../primitives";
13
13
  import { fireEvent, render, screen } from "./test-utils";
14
14
 
15
- const { Button, Banner, Field, Input, DataTable, Form, Text, Heading, Dialog } = defaultPrimitives;
15
+ const { Button, Banner, Field, Input, DataTable, Form, Text, Heading, Dialog, Card } =
16
+ defaultPrimitives;
16
17
 
17
18
  describe("Button", () => {
18
19
  test("disabled: attribute gesetzt + Tailwind-Klassen für pointer-events/opacity", () => {
@@ -970,3 +971,76 @@ describe("Text variants", () => {
970
971
  expect(el.hasAttribute("data-required")).toBe(true);
971
972
  });
972
973
  });
974
+
975
+ describe("Card", () => {
976
+ test("padded=true (default) adds body padding", () => {
977
+ render(
978
+ <Card testId="c">
979
+ <span>body</span>
980
+ </Card>,
981
+ );
982
+ expect(screen.getByTestId("c").innerHTML).toContain("p-6");
983
+ });
984
+
985
+ test("padded=false renders body without padding classes", () => {
986
+ render(
987
+ <Card testId="c" options={{ padded: false }}>
988
+ <span>body</span>
989
+ </Card>,
990
+ );
991
+ const bodyWrapper = screen.getByText("body").parentElement;
992
+ expect(bodyWrapper?.className.includes("p-6")).toBe(false);
993
+ expect(bodyWrapper?.className.includes("px-6")).toBe(false);
994
+ });
995
+
996
+ test("slots.title/subtitle render a default header", () => {
997
+ render(<Card testId="c" slots={{ title: "Title", subtitle: "Subtitle" }} />);
998
+ expect(screen.getByText("Title")).toBeTruthy();
999
+ expect(screen.getByText("Subtitle")).toBeTruthy();
1000
+ });
1001
+
1002
+ test("no header slots → no header row rendered", () => {
1003
+ render(
1004
+ <Card testId="c">
1005
+ <span>only body</span>
1006
+ </Card>,
1007
+ );
1008
+ // Header row carries "items-start justify-between" — absent means no header.
1009
+ expect(screen.getByTestId("c").innerHTML).not.toContain("justify-between");
1010
+ });
1011
+
1012
+ test("slots.footer renders bordered by default", () => {
1013
+ render(<Card testId="c" slots={{ footer: <span>Footer</span> }} />);
1014
+ const footer = screen.getByText("Footer").parentElement;
1015
+ expect(footer?.className.includes("border-t")).toBe(true);
1016
+ });
1017
+
1018
+ test("footerBordered=false drops the border", () => {
1019
+ render(
1020
+ <Card
1021
+ testId="c"
1022
+ slots={{ footer: <span>Footer</span> }}
1023
+ options={{ footerBordered: false }}
1024
+ />,
1025
+ );
1026
+ const footer = screen.getByText("Footer").parentElement;
1027
+ expect(footer?.className.includes("border-t")).toBe(false);
1028
+ });
1029
+
1030
+ test('radius="lg" uses rounded-lg instead of rounded-xl', () => {
1031
+ render(
1032
+ <Card testId="c" options={{ radius: "lg" }}>
1033
+ x
1034
+ </Card>,
1035
+ );
1036
+ const el = screen.getByTestId("c");
1037
+ expect(el.className.includes("rounded-lg")).toBe(true);
1038
+ expect(el.className.includes("rounded-xl")).toBe(false);
1039
+ });
1040
+
1041
+ test("children=undefined → no body wrapper rendered", () => {
1042
+ render(<Card testId="c" slots={{ title: "Only header" }} />);
1043
+ expect(screen.getByTestId("c").querySelectorAll("div").length).toBeGreaterThan(0);
1044
+ expect(screen.getByTestId("c").innerHTML).not.toContain("grow");
1045
+ });
1046
+ });
@@ -1431,12 +1431,12 @@ function DefaultSection({ title, subtitle, children, actions, testId }: SectionP
1431
1431
  </h3>
1432
1432
  )}
1433
1433
  {subtitle !== undefined && (
1434
- <p
1434
+ <div
1435
1435
  data-testid={testId !== undefined ? `${testId}-subtitle` : undefined}
1436
1436
  className="text-sm text-muted-foreground"
1437
1437
  >
1438
1438
  {subtitle}
1439
- </p>
1439
+ </div>
1440
1440
  )}
1441
1441
  </div>
1442
1442
  ) : null;