@cosmicdrift/kumiko-renderer-web 0.13.0 → 0.15.0
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 +5 -5
- package/src/__tests__/avatar.test.tsx +1 -2
- package/src/__tests__/combobox.test.tsx +1 -2
- package/src/__tests__/config-edit.test.tsx +5 -8
- package/src/__tests__/create-app.test.tsx +30 -10
- package/src/__tests__/date-input.test.tsx +2 -3
- package/src/__tests__/default-app-shell.test.tsx +1 -2
- package/src/__tests__/dispatcher-context.test.tsx +1 -2
- package/src/__tests__/dispatcher-status-wiring.test.tsx +5 -6
- package/src/__tests__/kumiko-screen.test.tsx +2 -3
- package/src/__tests__/language-switcher.test.tsx +2 -3
- package/src/__tests__/money-input.test.tsx +8 -9
- package/src/__tests__/nav-base-path.test.tsx +1 -2
- package/src/__tests__/nav-search-params.test.tsx +1 -2
- package/src/__tests__/nav-tree.test.tsx +1 -2
- package/src/__tests__/nav.test.tsx +16 -9
- package/src/__tests__/primitives.test.tsx +53 -54
- package/src/__tests__/render-edit.test.tsx +3 -4
- package/src/__tests__/render-list-column-renderer.test.tsx +3 -4
- package/src/__tests__/render-list-debounce.test.tsx +9 -10
- package/src/__tests__/render-list.test.tsx +3 -4
- package/src/__tests__/sidebar.test.tsx +1 -2
- package/src/__tests__/theme-toggle.test.tsx +4 -5
- package/src/__tests__/toast.test.tsx +1 -2
- package/src/__tests__/use-form.test.tsx +6 -7
- package/src/__tests__/use-query-live.test.tsx +1 -2
- package/src/__tests__/use-query.test.tsx +7 -8
- package/src/__tests__/use-store.test.tsx +2 -3
- package/src/__tests__/visual-tree-integration.test.tsx +1 -2
- package/src/__tests__/workspace-shell.test.tsx +2 -4
- package/src/app/create-app.tsx +5 -3
- package/src/layout/__tests__/visual-tree.test.tsx +33 -30
- package/CHANGELOG.md +0 -477
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
1
|
//
|
|
3
2
|
// Default-Primitives für Web-Renderer. Tests pinnen den Vertrag, den die
|
|
4
3
|
// Renderer-Komponenten (RenderEdit, RenderList, KumikoScreen) an die
|
|
@@ -8,8 +7,8 @@
|
|
|
8
7
|
// ChangeEvent) und das testId-Forwarding, von dem die E2E-Tests
|
|
9
8
|
// abhängen werden.
|
|
10
9
|
|
|
10
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
11
11
|
import userEvent from "@testing-library/user-event";
|
|
12
|
-
import { describe, expect, test, vi } from "vitest";
|
|
13
12
|
import { defaultPrimitives } from "../primitives";
|
|
14
13
|
import { fireEvent, render, screen } from "./test-utils";
|
|
15
14
|
|
|
@@ -30,7 +29,7 @@ describe("Button", () => {
|
|
|
30
29
|
});
|
|
31
30
|
|
|
32
31
|
test("onClick fires on click", () => {
|
|
33
|
-
const onClick =
|
|
32
|
+
const onClick = mock();
|
|
34
33
|
render(
|
|
35
34
|
<Button onClick={onClick} testId="btn">
|
|
36
35
|
Go
|
|
@@ -41,7 +40,7 @@ describe("Button", () => {
|
|
|
41
40
|
});
|
|
42
41
|
|
|
43
42
|
test("loading: rendert Spinner statt Children + ist disabled", () => {
|
|
44
|
-
const onClick =
|
|
43
|
+
const onClick = mock();
|
|
45
44
|
render(
|
|
46
45
|
<Button loading onClick={onClick} testId="btn">
|
|
47
46
|
Save
|
|
@@ -126,14 +125,14 @@ describe("Field", () => {
|
|
|
126
125
|
|
|
127
126
|
describe("Input kind mapping", () => {
|
|
128
127
|
test('kind="text": onChange receives string', () => {
|
|
129
|
-
const onChange =
|
|
128
|
+
const onChange = mock();
|
|
130
129
|
render(<Input id="i" name="i" kind="text" value="" onChange={onChange} />);
|
|
131
130
|
fireEvent.change(screen.getByRole("textbox"), { target: { value: "hello" } });
|
|
132
131
|
expect(onChange).toHaveBeenCalledWith("hello");
|
|
133
132
|
});
|
|
134
133
|
|
|
135
134
|
test('kind="number": "" → undefined, numeric → number', () => {
|
|
136
|
-
const onChange =
|
|
135
|
+
const onChange = mock();
|
|
137
136
|
render(<Input id="i" name="i" kind="number" value={0} onChange={onChange} />);
|
|
138
137
|
const input = screen.getByRole("spinbutton");
|
|
139
138
|
fireEvent.change(input, { target: { value: "42" } });
|
|
@@ -143,7 +142,7 @@ describe("Input kind mapping", () => {
|
|
|
143
142
|
});
|
|
144
143
|
|
|
145
144
|
test('kind="boolean": onChange receives checked', () => {
|
|
146
|
-
const onChange =
|
|
145
|
+
const onChange = mock();
|
|
147
146
|
render(<Input id="i" name="i" kind="boolean" value={false} onChange={onChange} />);
|
|
148
147
|
fireEvent.click(screen.getByRole("checkbox"));
|
|
149
148
|
expect(onChange).toHaveBeenCalledWith(true);
|
|
@@ -153,7 +152,7 @@ describe("Input kind mapping", () => {
|
|
|
153
152
|
// Default-DateInput nutzt Radix-Popover + DayPicker statt native
|
|
154
153
|
// <input type="date">. Trigger ist ein Button mit dem formatierten
|
|
155
154
|
// Datum als sichtbarem Text.
|
|
156
|
-
const onChange =
|
|
155
|
+
const onChange = mock();
|
|
157
156
|
render(
|
|
158
157
|
<Input id="i" name="i" kind="date" value="2026-04-23" onChange={onChange} locale="de-DE" />,
|
|
159
158
|
);
|
|
@@ -198,7 +197,7 @@ describe("DataTable", () => {
|
|
|
198
197
|
});
|
|
199
198
|
|
|
200
199
|
test("onRowClick fires with the clicked row", () => {
|
|
201
|
-
const onRowClick =
|
|
200
|
+
const onRowClick = mock();
|
|
202
201
|
const row = { id: "r1", values: { name: "Alice" } };
|
|
203
202
|
render(
|
|
204
203
|
<DataTable
|
|
@@ -233,7 +232,7 @@ describe("DataTable", () => {
|
|
|
233
232
|
});
|
|
234
233
|
|
|
235
234
|
test("mit onSortChange: sortable-Column rendert Button + ArrowUpDown-Icon", () => {
|
|
236
|
-
render(<DataTable columns={sortableCols} rows={oneRow} onSortChange={
|
|
235
|
+
render(<DataTable columns={sortableCols} rows={oneRow} onSortChange={mock()} />);
|
|
237
236
|
const header = screen.getByTestId("column-name");
|
|
238
237
|
expect(header.querySelector("button")).not.toBeNull();
|
|
239
238
|
// Default-Icon (kein active sort) ist ArrowUpDown — Lucide rendert
|
|
@@ -242,7 +241,7 @@ describe("DataTable", () => {
|
|
|
242
241
|
});
|
|
243
242
|
|
|
244
243
|
test("non-sortable Column rendert KEINEN Button (auch mit onSortChange)", () => {
|
|
245
|
-
render(<DataTable columns={sortableCols} rows={oneRow} onSortChange={
|
|
244
|
+
render(<DataTable columns={sortableCols} rows={oneRow} onSortChange={mock()} />);
|
|
246
245
|
expect(screen.getByTestId("column-id").querySelector("button")).toBeNull();
|
|
247
246
|
});
|
|
248
247
|
|
|
@@ -252,7 +251,7 @@ describe("DataTable", () => {
|
|
|
252
251
|
columns={sortableCols}
|
|
253
252
|
rows={oneRow}
|
|
254
253
|
sort={{ field: "name", dir: "asc" }}
|
|
255
|
-
onSortChange={
|
|
254
|
+
onSortChange={mock()}
|
|
256
255
|
/>,
|
|
257
256
|
);
|
|
258
257
|
expect(screen.getByTestId("column-name").getAttribute("aria-sort")).toBe("ascending");
|
|
@@ -265,21 +264,21 @@ describe("DataTable", () => {
|
|
|
265
264
|
columns={sortableCols}
|
|
266
265
|
rows={oneRow}
|
|
267
266
|
sort={{ field: "name", dir: "desc" }}
|
|
268
|
-
onSortChange={
|
|
267
|
+
onSortChange={mock()}
|
|
269
268
|
/>,
|
|
270
269
|
);
|
|
271
270
|
expect(screen.getByTestId("column-name").getAttribute("aria-sort")).toBe("descending");
|
|
272
271
|
});
|
|
273
272
|
|
|
274
273
|
test("Click ohne aktiven Sort: onSortChange({field, dir:'asc'})", () => {
|
|
275
|
-
const onSortChange =
|
|
274
|
+
const onSortChange = mock();
|
|
276
275
|
render(<DataTable columns={sortableCols} rows={oneRow} onSortChange={onSortChange} />);
|
|
277
276
|
fireEvent.click(screen.getByTestId("column-name").querySelector("button") as HTMLElement);
|
|
278
277
|
expect(onSortChange).toHaveBeenCalledWith({ field: "name", dir: "asc" });
|
|
279
278
|
});
|
|
280
279
|
|
|
281
280
|
test("Click mit aktivem asc: onSortChange({field, dir:'desc'})", () => {
|
|
282
|
-
const onSortChange =
|
|
281
|
+
const onSortChange = mock();
|
|
283
282
|
render(
|
|
284
283
|
<DataTable
|
|
285
284
|
columns={sortableCols}
|
|
@@ -293,7 +292,7 @@ describe("DataTable", () => {
|
|
|
293
292
|
});
|
|
294
293
|
|
|
295
294
|
test("Click mit aktivem desc: onSortChange(null) (3-State zurück zu unsorted)", () => {
|
|
296
|
-
const onSortChange =
|
|
295
|
+
const onSortChange = mock();
|
|
297
296
|
render(
|
|
298
297
|
<DataTable
|
|
299
298
|
columns={sortableCols}
|
|
@@ -307,7 +306,7 @@ describe("DataTable", () => {
|
|
|
307
306
|
});
|
|
308
307
|
|
|
309
308
|
test("Click auf andere Spalte (sort=null für die): startet bei asc", () => {
|
|
310
|
-
const onSortChange =
|
|
309
|
+
const onSortChange = mock();
|
|
311
310
|
render(
|
|
312
311
|
<DataTable
|
|
313
312
|
columns={sortableCols}
|
|
@@ -342,7 +341,7 @@ describe("DataTable", () => {
|
|
|
342
341
|
columns={cols}
|
|
343
342
|
rows={[]}
|
|
344
343
|
testId="dt"
|
|
345
|
-
pager={{ page: 1, limit: 50, total: 0, onPageChange:
|
|
344
|
+
pager={{ page: 1, limit: 50, total: 0, onPageChange: mock() }}
|
|
346
345
|
/>,
|
|
347
346
|
);
|
|
348
347
|
expect(screen.queryByTestId("dt-pager")).toBeNull();
|
|
@@ -354,7 +353,7 @@ describe("DataTable", () => {
|
|
|
354
353
|
columns={cols}
|
|
355
354
|
rows={oneRow}
|
|
356
355
|
testId="dt"
|
|
357
|
-
pager={{ page: 1, limit: 50, total: 3000, onPageChange:
|
|
356
|
+
pager={{ page: 1, limit: 50, total: 3000, onPageChange: mock() }}
|
|
358
357
|
/>,
|
|
359
358
|
);
|
|
360
359
|
expect((screen.getByTestId("dt-pager-prev") as HTMLButtonElement).disabled).toBe(true);
|
|
@@ -367,14 +366,14 @@ describe("DataTable", () => {
|
|
|
367
366
|
columns={cols}
|
|
368
367
|
rows={oneRow}
|
|
369
368
|
testId="dt"
|
|
370
|
-
pager={{ page: 60, limit: 50, total: 3000, onPageChange:
|
|
369
|
+
pager={{ page: 60, limit: 50, total: 3000, onPageChange: mock() }}
|
|
371
370
|
/>,
|
|
372
371
|
);
|
|
373
372
|
expect((screen.getByTestId("dt-pager-next") as HTMLButtonElement).disabled).toBe(true);
|
|
374
373
|
});
|
|
375
374
|
|
|
376
375
|
test("Click auf Page-Button: onPageChange feuert mit der Seite", () => {
|
|
377
|
-
const onPageChange =
|
|
376
|
+
const onPageChange = mock();
|
|
378
377
|
render(
|
|
379
378
|
<DataTable
|
|
380
379
|
columns={cols}
|
|
@@ -388,7 +387,7 @@ describe("DataTable", () => {
|
|
|
388
387
|
});
|
|
389
388
|
|
|
390
389
|
test("Click auf Prev von page=3: onPageChange(2)", () => {
|
|
391
|
-
const onPageChange =
|
|
390
|
+
const onPageChange = mock();
|
|
392
391
|
render(
|
|
393
392
|
<DataTable
|
|
394
393
|
columns={cols}
|
|
@@ -407,7 +406,7 @@ describe("DataTable", () => {
|
|
|
407
406
|
columns={cols}
|
|
408
407
|
rows={oneRow}
|
|
409
408
|
testId="dt"
|
|
410
|
-
pager={{ page: 5, limit: 50, total: 3000, onPageChange:
|
|
409
|
+
pager={{ page: 5, limit: 50, total: 3000, onPageChange: mock() }}
|
|
411
410
|
/>,
|
|
412
411
|
);
|
|
413
412
|
expect(screen.getByTestId("dt-pager-page-5").getAttribute("aria-current")).toBe("page");
|
|
@@ -419,7 +418,7 @@ describe("DataTable", () => {
|
|
|
419
418
|
columns={cols}
|
|
420
419
|
rows={oneRow}
|
|
421
420
|
testId="dt"
|
|
422
|
-
pager={{ page: 1, limit: 50, total: 200, onPageChange:
|
|
421
|
+
pager={{ page: 1, limit: 50, total: 200, onPageChange: mock() }}
|
|
423
422
|
/>,
|
|
424
423
|
);
|
|
425
424
|
// total=200, limit=50 → 4 Seiten, kein Window
|
|
@@ -435,7 +434,7 @@ describe("DataTable", () => {
|
|
|
435
434
|
columns={cols}
|
|
436
435
|
rows={oneRow}
|
|
437
436
|
testId="dt"
|
|
438
|
-
pager={{ page: 30, limit: 50, total: 3000, onPageChange:
|
|
437
|
+
pager={{ page: 30, limit: 50, total: 3000, onPageChange: mock() }}
|
|
439
438
|
/>,
|
|
440
439
|
);
|
|
441
440
|
// Window: 1 ... 28 29 [30] 31 32 ... 60
|
|
@@ -465,7 +464,7 @@ describe("DataTable", () => {
|
|
|
465
464
|
columns={cols}
|
|
466
465
|
rows={oneRow}
|
|
467
466
|
testId="dt"
|
|
468
|
-
onReachEnd={
|
|
467
|
+
onReachEnd={mock()}
|
|
469
468
|
loadingMore={false}
|
|
470
469
|
hasMore={true}
|
|
471
470
|
/>,
|
|
@@ -483,7 +482,7 @@ describe("DataTable", () => {
|
|
|
483
482
|
columns={cols}
|
|
484
483
|
rows={oneRow}
|
|
485
484
|
testId="dt"
|
|
486
|
-
onReachEnd={
|
|
485
|
+
onReachEnd={mock()}
|
|
487
486
|
loadingMore={true}
|
|
488
487
|
hasMore={true}
|
|
489
488
|
/>,
|
|
@@ -497,7 +496,7 @@ describe("DataTable", () => {
|
|
|
497
496
|
columns={cols}
|
|
498
497
|
rows={oneRow}
|
|
499
498
|
testId="dt"
|
|
500
|
-
onReachEnd={
|
|
499
|
+
onReachEnd={mock()}
|
|
501
500
|
loadingMore={false}
|
|
502
501
|
hasMore={false}
|
|
503
502
|
/>,
|
|
@@ -528,7 +527,7 @@ describe("DataTable", () => {
|
|
|
528
527
|
columns={cols}
|
|
529
528
|
rows={rows}
|
|
530
529
|
testId="dt"
|
|
531
|
-
rowActions={[{ id: "edit", label: "Edit", onTrigger:
|
|
530
|
+
rowActions={[{ id: "edit", label: "Edit", onTrigger: mock() }]}
|
|
532
531
|
/>,
|
|
533
532
|
);
|
|
534
533
|
expect(screen.queryByTestId("column-actions")).not.toBeNull();
|
|
@@ -542,8 +541,8 @@ describe("DataTable", () => {
|
|
|
542
541
|
rows={rows}
|
|
543
542
|
testId="dt"
|
|
544
543
|
rowActions={[
|
|
545
|
-
{ id: "edit", label: "Edit", onTrigger:
|
|
546
|
-
{ id: "delete", label: "Delete", style: "danger", onTrigger:
|
|
544
|
+
{ id: "edit", label: "Edit", onTrigger: mock() },
|
|
545
|
+
{ id: "delete", label: "Delete", style: "danger", onTrigger: mock() },
|
|
547
546
|
]}
|
|
548
547
|
/>,
|
|
549
548
|
);
|
|
@@ -558,9 +557,9 @@ describe("DataTable", () => {
|
|
|
558
557
|
rows={rows}
|
|
559
558
|
testId="dt"
|
|
560
559
|
rowActions={[
|
|
561
|
-
{ id: "a", label: "A", onTrigger:
|
|
562
|
-
{ id: "b", label: "B", onTrigger:
|
|
563
|
-
{ id: "c", label: "C", onTrigger:
|
|
560
|
+
{ id: "a", label: "A", onTrigger: mock() },
|
|
561
|
+
{ id: "b", label: "B", onTrigger: mock() },
|
|
562
|
+
{ id: "c", label: "C", onTrigger: mock() },
|
|
564
563
|
]}
|
|
565
564
|
/>,
|
|
566
565
|
);
|
|
@@ -578,9 +577,9 @@ describe("DataTable", () => {
|
|
|
578
577
|
rows={rows}
|
|
579
578
|
testId="dt"
|
|
580
579
|
rowActions={[
|
|
581
|
-
{ id: "a", label: "Archive", onTrigger:
|
|
582
|
-
{ id: "b", label: "Duplicate", onTrigger:
|
|
583
|
-
{ id: "c", label: "Export", onTrigger:
|
|
580
|
+
{ id: "a", label: "Archive", onTrigger: mock() },
|
|
581
|
+
{ id: "b", label: "Duplicate", onTrigger: mock() },
|
|
582
|
+
{ id: "c", label: "Export", onTrigger: mock() },
|
|
584
583
|
]}
|
|
585
584
|
/>,
|
|
586
585
|
);
|
|
@@ -592,7 +591,7 @@ describe("DataTable", () => {
|
|
|
592
591
|
|
|
593
592
|
test("Kebab: Click auf Item ohne confirm → onTrigger feuert direkt", async () => {
|
|
594
593
|
const user = userEvent.setup();
|
|
595
|
-
const onTrigger =
|
|
594
|
+
const onTrigger = mock();
|
|
596
595
|
render(
|
|
597
596
|
<DataTable
|
|
598
597
|
columns={cols}
|
|
@@ -600,8 +599,8 @@ describe("DataTable", () => {
|
|
|
600
599
|
testId="dt"
|
|
601
600
|
rowActions={[
|
|
602
601
|
{ id: "a", label: "Archive", onTrigger },
|
|
603
|
-
{ id: "b", label: "Duplicate", onTrigger:
|
|
604
|
-
{ id: "c", label: "Export", onTrigger:
|
|
602
|
+
{ id: "b", label: "Duplicate", onTrigger: mock() },
|
|
603
|
+
{ id: "c", label: "Export", onTrigger: mock() },
|
|
605
604
|
]}
|
|
606
605
|
/>,
|
|
607
606
|
);
|
|
@@ -614,15 +613,15 @@ describe("DataTable", () => {
|
|
|
614
613
|
|
|
615
614
|
test("Kebab: Click auf Danger-Item → Confirm-Dialog statt direkt-Trigger", async () => {
|
|
616
615
|
const user = userEvent.setup();
|
|
617
|
-
const onTrigger =
|
|
616
|
+
const onTrigger = mock();
|
|
618
617
|
render(
|
|
619
618
|
<DataTable
|
|
620
619
|
columns={cols}
|
|
621
620
|
rows={rows}
|
|
622
621
|
testId="dt"
|
|
623
622
|
rowActions={[
|
|
624
|
-
{ id: "a", label: "Archive", onTrigger:
|
|
625
|
-
{ id: "b", label: "Duplicate", onTrigger:
|
|
623
|
+
{ id: "a", label: "Archive", onTrigger: mock() },
|
|
624
|
+
{ id: "b", label: "Duplicate", onTrigger: mock() },
|
|
626
625
|
{ id: "delete", label: "Delete", style: "danger", onTrigger },
|
|
627
626
|
]}
|
|
628
627
|
/>,
|
|
@@ -648,7 +647,7 @@ describe("DataTable", () => {
|
|
|
648
647
|
style: "danger",
|
|
649
648
|
confirmLabel: "Cancel Subscription",
|
|
650
649
|
confirm: "This is permanent.",
|
|
651
|
-
onTrigger:
|
|
650
|
+
onTrigger: mock(),
|
|
652
651
|
},
|
|
653
652
|
]}
|
|
654
653
|
/>,
|
|
@@ -663,7 +662,7 @@ describe("DataTable", () => {
|
|
|
663
662
|
|
|
664
663
|
test("Click auf Action ohne confirm: onTrigger wird mit Row gerufen", async () => {
|
|
665
664
|
const user = userEvent.setup();
|
|
666
|
-
const onTrigger =
|
|
665
|
+
const onTrigger = mock();
|
|
667
666
|
render(
|
|
668
667
|
<DataTable
|
|
669
668
|
columns={cols}
|
|
@@ -677,7 +676,7 @@ describe("DataTable", () => {
|
|
|
677
676
|
});
|
|
678
677
|
|
|
679
678
|
test("style=danger: erzwingt Confirm-Dialog vor onTrigger", async () => {
|
|
680
|
-
const onTrigger =
|
|
679
|
+
const onTrigger = mock();
|
|
681
680
|
render(
|
|
682
681
|
<DataTable
|
|
683
682
|
columns={cols}
|
|
@@ -703,7 +702,7 @@ describe("DataTable", () => {
|
|
|
703
702
|
{
|
|
704
703
|
id: "archive",
|
|
705
704
|
label: "Archive",
|
|
706
|
-
onTrigger:
|
|
705
|
+
onTrigger: mock(),
|
|
707
706
|
// Nur für r1 sichtbar
|
|
708
707
|
isVisible: (row) => row.id === "r1",
|
|
709
708
|
},
|
|
@@ -716,8 +715,8 @@ describe("DataTable", () => {
|
|
|
716
715
|
|
|
717
716
|
test("Click auf Action-Cell propagiert NICHT auf onRowClick", async () => {
|
|
718
717
|
const user = userEvent.setup();
|
|
719
|
-
const onRowClick =
|
|
720
|
-
const onTrigger =
|
|
718
|
+
const onRowClick = mock();
|
|
719
|
+
const onTrigger = mock();
|
|
721
720
|
render(
|
|
722
721
|
<DataTable
|
|
723
722
|
columns={cols}
|
|
@@ -737,7 +736,7 @@ describe("DataTable", () => {
|
|
|
737
736
|
|
|
738
737
|
describe("Form", () => {
|
|
739
738
|
test("submit calls onSubmit and prevents default navigation", () => {
|
|
740
|
-
const onSubmit =
|
|
739
|
+
const onSubmit = mock();
|
|
741
740
|
render(
|
|
742
741
|
<Form onSubmit={onSubmit} testId="form">
|
|
743
742
|
<button type="submit">Go</button>
|
|
@@ -852,8 +851,8 @@ describe("DataTable toolbar slots", () => {
|
|
|
852
851
|
|
|
853
852
|
describe("Dialog", () => {
|
|
854
853
|
test("open=true rendert Dialog mit Title und Confirm/Cancel Buttons", () => {
|
|
855
|
-
const onConfirm =
|
|
856
|
-
const onOpenChange =
|
|
854
|
+
const onConfirm = mock();
|
|
855
|
+
const onOpenChange = mock();
|
|
857
856
|
render(
|
|
858
857
|
<Dialog
|
|
859
858
|
open
|
|
@@ -883,8 +882,8 @@ describe("Dialog", () => {
|
|
|
883
882
|
|
|
884
883
|
test("Confirm-Button feuert onConfirm und schließt den Dialog", async () => {
|
|
885
884
|
const user = userEvent.setup();
|
|
886
|
-
const onConfirm =
|
|
887
|
-
const onOpenChange =
|
|
885
|
+
const onConfirm = mock();
|
|
886
|
+
const onOpenChange = mock();
|
|
888
887
|
render(
|
|
889
888
|
<Dialog
|
|
890
889
|
open
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
2
|
import type {
|
|
3
3
|
EntityDefinition,
|
|
4
4
|
EntityEditScreenDefinition,
|
|
5
5
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
6
6
|
import type { Dispatcher, SubmitResult } from "@cosmicdrift/kumiko-headless";
|
|
7
7
|
import { DispatcherProvider, RenderEdit } from "@cosmicdrift/kumiko-renderer";
|
|
8
|
-
import { describe, expect, test, vi } from "vitest";
|
|
9
8
|
import { act, createMockDispatcher, fireEvent, render, screen } from "./test-utils";
|
|
10
9
|
|
|
11
10
|
const orderEntity = {
|
|
@@ -120,7 +119,7 @@ describe("RenderEdit", () => {
|
|
|
120
119
|
});
|
|
121
120
|
|
|
122
121
|
test("submit fires dispatcher.write with the current values; onSubmit receives the result", async () => {
|
|
123
|
-
const write =
|
|
122
|
+
const write = mock(async () => ({ isSuccess: true, data: { id: "42" } }) as never);
|
|
124
123
|
const dispatcher = makeDispatcher(write);
|
|
125
124
|
const seenResults: SubmitResult<unknown>[] = [];
|
|
126
125
|
|
|
@@ -150,7 +149,7 @@ describe("RenderEdit", () => {
|
|
|
150
149
|
await Promise.resolve();
|
|
151
150
|
});
|
|
152
151
|
|
|
153
|
-
expect(write).
|
|
152
|
+
expect(write).toHaveBeenCalledTimes(1);
|
|
154
153
|
expect(write).toHaveBeenCalledWith("order:create", expect.anything());
|
|
155
154
|
expect(seenResults).toHaveLength(1);
|
|
156
155
|
expect(seenResults[0]?.isSuccess).toBe(true);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { afterAll, beforeEach, describe, expect, type Mock, spyOn, test } from "bun:test";
|
|
2
2
|
import type {
|
|
3
3
|
EntityDefinition,
|
|
4
4
|
EntityListScreenDefinition,
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
RenderList,
|
|
10
10
|
} from "@cosmicdrift/kumiko-renderer";
|
|
11
11
|
import type { ReactElement, ReactNode } from "react";
|
|
12
|
-
import { afterAll, beforeEach, describe, expect, type MockInstance, test, vi } from "vitest";
|
|
13
12
|
import { render, screen } from "./test-utils";
|
|
14
13
|
|
|
15
14
|
// Tests für die JSX-Renderer-Form von ListColumn-Spalten:
|
|
@@ -50,9 +49,9 @@ describe("RenderList — column-renderer registry", () => {
|
|
|
50
49
|
// Spy lokal pro Test installieren + global zurückbauen, damit die
|
|
51
50
|
// Mock-Implementation nicht in andere Test-Dateien leakt (Console-Spy
|
|
52
51
|
// auf File-Level würde den ganzen Vitest-Worker betreffen).
|
|
53
|
-
let warnSpy:
|
|
52
|
+
let warnSpy: Mock<typeof console.warn>;
|
|
54
53
|
beforeEach(() => {
|
|
55
|
-
warnSpy =
|
|
54
|
+
warnSpy = spyOn(console, "warn").mockImplementation(() => {});
|
|
56
55
|
});
|
|
57
56
|
afterAll(() => {
|
|
58
57
|
warnSpy.mockRestore();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
1
|
//
|
|
3
2
|
// RenderList puffert Tipps im Search-Input lokal und schickt
|
|
4
3
|
// onSearchChange erst nach 300ms ohne weitere Tasten. Vor dieser Suite
|
|
@@ -6,6 +5,7 @@
|
|
|
6
5
|
// die Race-Condition (Sync-Effect auf searchValue + Debounce-Effect)
|
|
7
6
|
// wahrscheinlich kaputt gegangen ohne dass eine CI das fängt.
|
|
8
7
|
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, jest, mock, test } from "bun:test";
|
|
9
9
|
import type {
|
|
10
10
|
EntityDefinition,
|
|
11
11
|
EntityListScreenDefinition,
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
RenderList,
|
|
18
18
|
} from "@cosmicdrift/kumiko-renderer";
|
|
19
19
|
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
20
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
21
20
|
import { defaultPrimitives } from "../primitives";
|
|
22
21
|
|
|
23
22
|
// Minimal-Entity damit RenderList nicht über fehlende Felder stolpert.
|
|
@@ -56,15 +55,15 @@ function renderRL(props: {
|
|
|
56
55
|
|
|
57
56
|
describe("RenderList — Search-Debounce", () => {
|
|
58
57
|
beforeEach(() => {
|
|
59
|
-
|
|
58
|
+
jest.useFakeTimers();
|
|
60
59
|
});
|
|
61
60
|
|
|
62
61
|
afterEach(() => {
|
|
63
|
-
|
|
62
|
+
jest.useRealTimers();
|
|
64
63
|
});
|
|
65
64
|
|
|
66
65
|
test("Tippen unter 300ms feuert NICHT mehrfach onSearchChange", () => {
|
|
67
|
-
const onSearchChange =
|
|
66
|
+
const onSearchChange = mock();
|
|
68
67
|
renderRL({ searchValue: "", onSearchChange });
|
|
69
68
|
|
|
70
69
|
const input = screen.getByPlaceholderText(/kumiko\.list\.search-placeholder|suchen/i);
|
|
@@ -76,20 +75,20 @@ describe("RenderList — Search-Debounce", () => {
|
|
|
76
75
|
// Vor Debounce-Ablauf: kein call (jeder Keypress hat den Timer
|
|
77
76
|
// resettet).
|
|
78
77
|
act(() => {
|
|
79
|
-
|
|
78
|
+
jest.advanceTimersByTime(299);
|
|
80
79
|
});
|
|
81
80
|
expect(onSearchChange).not.toHaveBeenCalled();
|
|
82
81
|
|
|
83
82
|
// 300ms: jetzt feuert es exakt einmal mit dem letzten Wert.
|
|
84
83
|
act(() => {
|
|
85
|
-
|
|
84
|
+
jest.advanceTimersByTime(1);
|
|
86
85
|
});
|
|
87
86
|
expect(onSearchChange).toHaveBeenCalledTimes(1);
|
|
88
87
|
expect(onSearchChange).toHaveBeenCalledWith("acme");
|
|
89
88
|
});
|
|
90
89
|
|
|
91
90
|
test("searchValue-Update von außen syncs lokalen Buffer (Browser-Back)", () => {
|
|
92
|
-
const onSearchChange =
|
|
91
|
+
const onSearchChange = mock();
|
|
93
92
|
const { rerender } = renderRL({ searchValue: "first", onSearchChange });
|
|
94
93
|
const input = screen.getByDisplayValue("first") as HTMLInputElement;
|
|
95
94
|
expect(input.value).toBe("first");
|
|
@@ -118,10 +117,10 @@ describe("RenderList — Search-Debounce", () => {
|
|
|
118
117
|
// Wenn der Parent searchValue auf "x" setzt UND der lokale Buffer
|
|
119
118
|
// schon "x" ist (z.B. Cleanup-Timing), darf RenderList nicht den
|
|
120
119
|
// Wert nochmal zurückrufen — sonst wäre's eine Loop.
|
|
121
|
-
const onSearchChange =
|
|
120
|
+
const onSearchChange = mock();
|
|
122
121
|
renderRL({ searchValue: "x", onSearchChange });
|
|
123
122
|
act(() => {
|
|
124
|
-
|
|
123
|
+
jest.advanceTimersByTime(500);
|
|
125
124
|
});
|
|
126
125
|
expect(onSearchChange).not.toHaveBeenCalled();
|
|
127
126
|
});
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
2
|
import type {
|
|
3
3
|
EntityDefinition,
|
|
4
4
|
EntityListScreenDefinition,
|
|
5
5
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
6
6
|
import { RenderList } from "@cosmicdrift/kumiko-renderer";
|
|
7
|
-
import { describe, expect, test, vi } from "vitest";
|
|
8
7
|
import { fireEvent, render, screen } from "./test-utils";
|
|
9
8
|
|
|
10
9
|
const taskEntity = {
|
|
@@ -113,7 +112,7 @@ describe("RenderList", () => {
|
|
|
113
112
|
});
|
|
114
113
|
|
|
115
114
|
test("onRowClick fires with the ListRowViewModel when present; no-op without", () => {
|
|
116
|
-
const onClick =
|
|
115
|
+
const onClick = mock();
|
|
117
116
|
render(
|
|
118
117
|
<RenderList
|
|
119
118
|
screen={listScreen}
|
|
@@ -124,7 +123,7 @@ describe("RenderList", () => {
|
|
|
124
123
|
/>,
|
|
125
124
|
);
|
|
126
125
|
fireEvent.click(screen.getByTestId("row-r1"));
|
|
127
|
-
expect(onClick).
|
|
126
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
128
127
|
const arg = onClick.mock.lastCall?.[0] as { id: string; values: Record<string, unknown> };
|
|
129
128
|
expect(arg.id).toBe("r1");
|
|
130
129
|
expect(arg.values["title"]).toBe("A");
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
1
|
//
|
|
3
2
|
// Sidebar: 4-Slot-Layout (header, actions, children, footer). Pinnt
|
|
4
3
|
// dass die Sektionen conditional rendern UND in der richtigen
|
|
5
4
|
// Reihenfolge stehen — header → actions → nav → footer.
|
|
6
5
|
|
|
7
|
-
import { describe, expect, test } from "
|
|
6
|
+
import { describe, expect, test } from "bun:test";
|
|
8
7
|
import { Sidebar } from "../layout/sidebar";
|
|
9
8
|
import { render, screen } from "./test-utils";
|
|
10
9
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
1
|
//
|
|
3
2
|
// Tests pinnen den ThemeToggle-Vertrag: Click ruft toggleMode, Icon-
|
|
4
3
|
// Slot defaultet auf Unicode, Custom-Slots werden durchgereicht, das
|
|
@@ -8,10 +7,10 @@
|
|
|
8
7
|
// `mode` + `toggleMode`, der Rest (tokens, setMode) wird vom Toggle
|
|
9
8
|
// nicht angefasst.
|
|
10
9
|
|
|
10
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
11
11
|
import { TokensProvider } from "@cosmicdrift/kumiko-renderer";
|
|
12
12
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
13
13
|
import type { ReactNode } from "react";
|
|
14
|
-
import { describe, expect, test, vi } from "vitest";
|
|
15
14
|
import { ThemeToggle } from "../layout/theme-toggle";
|
|
16
15
|
|
|
17
16
|
type StubApi = {
|
|
@@ -21,11 +20,11 @@ type StubApi = {
|
|
|
21
20
|
|
|
22
21
|
function makeStub(
|
|
23
22
|
initial: "light" | "dark" = "light",
|
|
24
|
-
): StubApi & { toggleMode: ReturnType<typeof
|
|
23
|
+
): StubApi & { toggleMode: ReturnType<typeof mock> } {
|
|
25
24
|
const stub = {
|
|
26
25
|
mode: initial,
|
|
27
|
-
toggleMode:
|
|
28
|
-
} as StubApi & { toggleMode: ReturnType<typeof
|
|
26
|
+
toggleMode: mock(),
|
|
27
|
+
} as StubApi & { toggleMode: ReturnType<typeof mock> };
|
|
29
28
|
return stub;
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
1
|
//
|
|
3
2
|
// ToastProvider + useToast pinnt: toast() rendert Title+Description in
|
|
4
3
|
// einem Radix-Toast; mehrere toasts stapeln; Variant=destructive setzt
|
|
@@ -6,9 +5,9 @@
|
|
|
6
5
|
// (kein crash); IDs sind kollisionsfrei auch bei zwei Calls im selben
|
|
7
6
|
// Tick (Counter-Race-Bug).
|
|
8
7
|
|
|
8
|
+
import { describe, expect, test } from "bun:test";
|
|
9
9
|
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
10
10
|
import { type ReactNode, useEffect } from "react";
|
|
11
|
-
import { describe, expect, test } from "vitest";
|
|
12
11
|
import { type ToastOptions, ToastProvider, useToast } from "../primitives/toast";
|
|
13
12
|
|
|
14
13
|
// Trigger-Component die im Mount toast() aufruft. So testen wir den
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
2
|
import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
|
|
3
3
|
import { DispatcherProvider, useForm } from "@cosmicdrift/kumiko-renderer";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
|
-
import { describe, expect, test, vi } from "vitest";
|
|
6
5
|
import { z } from "zod";
|
|
7
6
|
import { act, createMockDispatcher, renderHook } from "./test-utils";
|
|
8
7
|
|
|
@@ -40,7 +39,7 @@ describe("useForm", () => {
|
|
|
40
39
|
});
|
|
41
40
|
|
|
42
41
|
test("submit dispatches to the context dispatcher when no explicit one is passed", async () => {
|
|
43
|
-
const write =
|
|
42
|
+
const write = mock(async () => ({ isSuccess: true, data: { id: "123" } }) as never);
|
|
44
43
|
const dispatcher = makeDispatcher(write);
|
|
45
44
|
const { result } = renderHook(
|
|
46
45
|
() =>
|
|
@@ -57,13 +56,13 @@ describe("useForm", () => {
|
|
|
57
56
|
submitResult = await result.current.controller.submit();
|
|
58
57
|
});
|
|
59
58
|
|
|
60
|
-
expect(write).
|
|
59
|
+
expect(write).toHaveBeenCalledTimes(1);
|
|
61
60
|
expect(write).toHaveBeenCalledWith("x:create", expect.anything());
|
|
62
61
|
expect((submitResult as { isSuccess: boolean }).isSuccess).toBe(true);
|
|
63
62
|
});
|
|
64
63
|
|
|
65
64
|
test("zod schema failure blocks submit; no network call fires", async () => {
|
|
66
|
-
const write =
|
|
65
|
+
const write = mock();
|
|
67
66
|
const dispatcher = makeDispatcher(write as unknown as Dispatcher["write"]);
|
|
68
67
|
const schema = z.object({ title: z.string().min(1), count: z.number().optional() });
|
|
69
68
|
const { result } = renderHook(
|
|
@@ -87,8 +86,8 @@ describe("useForm", () => {
|
|
|
87
86
|
});
|
|
88
87
|
|
|
89
88
|
test("explicit dispatcher on submit wins over context dispatcher", async () => {
|
|
90
|
-
const contextWrite =
|
|
91
|
-
const overrideWrite =
|
|
89
|
+
const contextWrite = mock(async () => ({ isSuccess: true, data: {} }) as never);
|
|
90
|
+
const overrideWrite = mock(async () => ({ isSuccess: true, data: {} }) as never);
|
|
92
91
|
const contextDispatcher = makeDispatcher(contextWrite);
|
|
93
92
|
const overrideDispatcher = makeDispatcher(overrideWrite);
|
|
94
93
|
|