@cosmicdrift/kumiko-renderer-web 0.14.0 → 0.16.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.
Files changed (36) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/avatar.test.tsx +1 -2
  3. package/src/__tests__/combobox.test.tsx +1 -2
  4. package/src/__tests__/config-edit.test.tsx +5 -8
  5. package/src/__tests__/create-app.test.tsx +30 -10
  6. package/src/__tests__/date-input.test.tsx +2 -3
  7. package/src/__tests__/default-app-shell.test.tsx +1 -2
  8. package/src/__tests__/dispatcher-context.test.tsx +1 -2
  9. package/src/__tests__/dispatcher-status-wiring.test.tsx +5 -6
  10. package/src/__tests__/kumiko-screen.test.tsx +2 -3
  11. package/src/__tests__/language-switcher.test.tsx +2 -3
  12. package/src/__tests__/money-input.test.tsx +8 -9
  13. package/src/__tests__/nav-base-path.test.tsx +1 -2
  14. package/src/__tests__/nav-search-params.test.tsx +1 -2
  15. package/src/__tests__/nav-tree.test.tsx +1 -2
  16. package/src/__tests__/nav.test.tsx +16 -9
  17. package/src/__tests__/primitives.test.tsx +53 -54
  18. package/src/__tests__/render-edit.test.tsx +3 -4
  19. package/src/__tests__/render-list-column-renderer.test.tsx +3 -4
  20. package/src/__tests__/render-list-debounce.test.tsx +9 -10
  21. package/src/__tests__/render-list.test.tsx +3 -4
  22. package/src/__tests__/sidebar.test.tsx +1 -2
  23. package/src/__tests__/theme-toggle.test.tsx +4 -5
  24. package/src/__tests__/toast.test.tsx +1 -2
  25. package/src/__tests__/tokens.test.ts +8 -0
  26. package/src/__tests__/use-form.test.tsx +6 -7
  27. package/src/__tests__/use-query-live.test.tsx +1 -2
  28. package/src/__tests__/use-query.test.tsx +7 -8
  29. package/src/__tests__/use-store.test.tsx +2 -3
  30. package/src/__tests__/visual-tree-integration.test.tsx +1 -2
  31. package/src/__tests__/workspace-shell.test.tsx +2 -4
  32. package/src/app/create-app.tsx +5 -3
  33. package/src/layout/__tests__/target-url.test.ts +36 -0
  34. package/src/layout/__tests__/visual-tree.test.tsx +33 -30
  35. package/src/lib/__tests__/cn.test.ts +12 -0
  36. package/CHANGELOG.md +0 -485
@@ -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 = vi.fn();
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 = vi.fn();
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 = vi.fn();
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 = vi.fn();
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 = vi.fn();
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 = vi.fn();
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 = vi.fn();
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={vi.fn()} />);
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={vi.fn()} />);
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={vi.fn()}
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={vi.fn()}
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 = vi.fn();
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 = vi.fn();
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 = vi.fn();
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 = vi.fn();
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: vi.fn() }}
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: vi.fn() }}
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: vi.fn() }}
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 = vi.fn();
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 = vi.fn();
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: vi.fn() }}
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: vi.fn() }}
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: vi.fn() }}
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={vi.fn()}
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={vi.fn()}
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={vi.fn()}
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: vi.fn() }]}
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: vi.fn() },
546
- { id: "delete", label: "Delete", style: "danger", onTrigger: vi.fn() },
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: vi.fn() },
562
- { id: "b", label: "B", onTrigger: vi.fn() },
563
- { id: "c", label: "C", onTrigger: vi.fn() },
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: vi.fn() },
582
- { id: "b", label: "Duplicate", onTrigger: vi.fn() },
583
- { id: "c", label: "Export", onTrigger: vi.fn() },
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 = vi.fn();
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: vi.fn() },
604
- { id: "c", label: "Export", onTrigger: vi.fn() },
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 = vi.fn();
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: vi.fn() },
625
- { id: "b", label: "Duplicate", onTrigger: vi.fn() },
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: vi.fn(),
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 = vi.fn();
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 = vi.fn();
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: vi.fn(),
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 = vi.fn();
720
- const onTrigger = vi.fn();
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 = vi.fn();
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 = vi.fn();
856
- const onOpenChange = vi.fn();
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 = vi.fn();
887
- const onOpenChange = vi.fn();
885
+ const onConfirm = mock();
886
+ const onOpenChange = mock();
888
887
  render(
889
888
  <Dialog
890
889
  open
@@ -1,11 +1,10 @@
1
- // @vitest-environment jsdom
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 = vi.fn(async () => ({ isSuccess: true, data: { id: "42" } }) as never);
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).toHaveBeenCalledOnce();
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
- // @vitest-environment jsdom
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: MockInstance<typeof console.warn>;
52
+ let warnSpy: Mock<typeof console.warn>;
54
53
  beforeEach(() => {
55
- warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
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
- vi.useFakeTimers({ shouldAdvanceTime: true });
58
+ jest.useFakeTimers();
60
59
  });
61
60
 
62
61
  afterEach(() => {
63
- vi.useRealTimers();
62
+ jest.useRealTimers();
64
63
  });
65
64
 
66
65
  test("Tippen unter 300ms feuert NICHT mehrfach onSearchChange", () => {
67
- const onSearchChange = vi.fn();
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
- vi.advanceTimersByTime(299);
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
- vi.advanceTimersByTime(1);
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 = vi.fn();
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 = vi.fn();
120
+ const onSearchChange = mock();
122
121
  renderRL({ searchValue: "x", onSearchChange });
123
122
  act(() => {
124
- vi.advanceTimersByTime(500);
123
+ jest.advanceTimersByTime(500);
125
124
  });
126
125
  expect(onSearchChange).not.toHaveBeenCalled();
127
126
  });
@@ -1,10 +1,9 @@
1
- // @vitest-environment jsdom
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 = vi.fn();
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).toHaveBeenCalledOnce();
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 "vitest";
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 vi.fn> } {
23
+ ): StubApi & { toggleMode: ReturnType<typeof mock> } {
25
24
  const stub = {
26
25
  mode: initial,
27
- toggleMode: vi.fn(),
28
- } as StubApi & { toggleMode: ReturnType<typeof vi.fn> };
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
@@ -0,0 +1,8 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { applyTokensToCssVars } from "../tokens";
3
+
4
+ describe("applyTokensToCssVars", () => {
5
+ test("is a documented no-op kept for backward compatibility", () => {
6
+ expect(() => applyTokensToCssVars({} as never)).not.toThrow();
7
+ });
8
+ });