@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.
|
|
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.
|
|
20
|
-
"@cosmicdrift/kumiko-headless": "0.105.
|
|
21
|
-
"@cosmicdrift/kumiko-renderer": "0.105.
|
|
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("
|
|
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 } =
|
|
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
|
+
});
|
package/src/primitives/index.tsx
CHANGED
|
@@ -1431,12 +1431,12 @@ function DefaultSection({ title, subtitle, children, actions, testId }: SectionP
|
|
|
1431
1431
|
</h3>
|
|
1432
1432
|
)}
|
|
1433
1433
|
{subtitle !== undefined && (
|
|
1434
|
-
<
|
|
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
|
-
</
|
|
1439
|
+
</div>
|
|
1440
1440
|
)}
|
|
1441
1441
|
</div>
|
|
1442
1442
|
) : null;
|